1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.transaction.jta;
18  
19  import java.util.List;
20  import javax.naming.NamingException;
21  
22  import com.ibm.websphere.uow.UOWSynchronizationRegistry;
23  import com.ibm.wsspi.uow.UOWAction;
24  import com.ibm.wsspi.uow.UOWActionException;
25  import com.ibm.wsspi.uow.UOWException;
26  import com.ibm.wsspi.uow.UOWManager;
27  import com.ibm.wsspi.uow.UOWManagerFactory;
28  
29  import org.springframework.transaction.IllegalTransactionStateException;
30  import org.springframework.transaction.InvalidTimeoutException;
31  import org.springframework.transaction.NestedTransactionNotSupportedException;
32  import org.springframework.transaction.TransactionDefinition;
33  import org.springframework.transaction.TransactionException;
34  import org.springframework.transaction.TransactionSystemException;
35  import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager;
36  import org.springframework.transaction.support.DefaultTransactionDefinition;
37  import org.springframework.transaction.support.DefaultTransactionStatus;
38  import org.springframework.transaction.support.SmartTransactionObject;
39  import org.springframework.transaction.support.TransactionCallback;
40  import org.springframework.transaction.support.TransactionSynchronization;
41  import org.springframework.transaction.support.TransactionSynchronizationManager;
42  import org.springframework.transaction.support.TransactionSynchronizationUtils;
43  import org.springframework.util.ReflectionUtils;
44  
45  /**
46   * WebSphere-specific PlatformTransactionManager implementation that delegates
47   * to a {@link com.ibm.wsspi.uow.UOWManager} instance, obtained from WebSphere's
48   * JNDI environment. This allows Spring to leverage the full power of the WebSphere
49   * transaction coordinator, including transaction suspension, in a manner that is
50   * perfectly compliant with officially supported WebSphere API.
51   *
52   * <p>The {@link CallbackPreferringPlatformTransactionManager} interface
53   * implemented by this class indicates that callers should preferably pass in
54   * a {@link TransactionCallback} through the {@link #execute} method, which
55   * will be handled through the callback-based WebSphere UOWManager API instead
56   * of through standard JTA API (UserTransaction / TransactionManager). This avoids
57   * the use of the non-public {@code javax.transaction.TransactionManager}
58   * API on WebSphere, staying within supported WebSphere API boundaries.
59   *
60   * <p>This transaction manager implementation derives from Spring's standard
61   * {@link JtaTransactionManager}, inheriting the capability to support programmatic
62   * transaction demarcation via {@code getTransaction} / {@code commit} /
63   * {@code rollback} calls through a JTA UserTransaction handle, for callers
64   * that do not use the TransactionCallback-based {@link #execute} method. However,
65   * transaction suspension is <i>not</i> supported in this {@code getTransaction}
66   * style (unless you explicitly specify a {@link #setTransactionManager} reference,
67   * despite the official WebSphere recommendations). Use the {@link #execute} style
68   * for any code that might require transaction suspension.
69   *
70   * <p>This transaction manager is compatible with WebSphere 6.1.0.9 and above.
71   * The default JNDI location for the UOWManager is "java:comp/websphere/UOWManager".
72   * If the location happens to differ according to your WebSphere documentation,
73   * simply specify the actual location through this transaction manager's
74   * "uowManagerName" bean property.
75   *
76   * <p><b>NOTE: This JtaTransactionManager is intended to refine specific transaction
77   * demarcation behavior on Spring's side. It will happily co-exist with independently
78   * configured WebSphere transaction strategies in your persistence provider, with no
79   * need to specifically connect those setups in any way.</b>
80   *
81   * @author Juergen Hoeller
82   * @since 2.5
83   * @see #setUowManager
84   * @see #setUowManagerName
85   * @see com.ibm.wsspi.uow.UOWManager
86   */
87  @SuppressWarnings("serial")
88  public class WebSphereUowTransactionManager extends JtaTransactionManager
89  		implements CallbackPreferringPlatformTransactionManager {
90  
91  	/**
92  	 * Default JNDI location for the WebSphere UOWManager.
93  	 * @see #setUowManagerName
94  	 */
95  	public static final String DEFAULT_UOW_MANAGER_NAME = "java:comp/websphere/UOWManager";
96  
97  
98  	private UOWManager uowManager;
99  
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 J2EE 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 || pb == TransactionDefinition.PROPAGATION_MANDATORY) {
250 				joinTx = true;
251 				newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
252 			}
253 			else if (pb == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
254 				uowType = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION;
255 				newSynch = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
256 			}
257 			else {
258 				newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
259 			}
260 		}
261 		else {
262 			if (pb == TransactionDefinition.PROPAGATION_MANDATORY) {
263 				throw new IllegalTransactionStateException(
264 						"Transaction propagation 'mandatory' but no existing transaction found");
265 			}
266 			if (pb == TransactionDefinition.PROPAGATION_SUPPORTS ||
267 					pb == TransactionDefinition.PROPAGATION_NOT_SUPPORTED || pb == TransactionDefinition.PROPAGATION_NEVER) {
268 				uowType = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION;
269 				newSynch = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
270 			}
271 			else {
272 				newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
273 			}
274 		}
275 
276 		boolean debug = logger.isDebugEnabled();
277 		if (debug) {
278 			logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
279 		}
280 		SuspendedResourcesHolder suspendedResources = (!joinTx ? suspend(null) : null);
281 		try {
282 			if (definition.getTimeout() > TransactionDefinition.TIMEOUT_DEFAULT) {
283 				this.uowManager.setUOWTimeout(uowType, definition.getTimeout());
284 			}
285 			if (debug) {
286 				logger.debug("Invoking WebSphere UOW action: type=" + uowType + ", join=" + joinTx);
287 			}
288 			UOWActionAdapter<T> action = new UOWActionAdapter<T>(
289 					definition, callback, (uowType == UOWManager.UOW_TYPE_GLOBAL_TRANSACTION), !joinTx, newSynch, debug);
290 			this.uowManager.runUnderUOW(uowType, joinTx, action);
291 			if (debug) {
292 				logger.debug("Returned from WebSphere UOW action: type=" + uowType + ", join=" + joinTx);
293 			}
294 			return action.getResult();
295 		}
296 		catch (UOWException ex) {
297 			throw new TransactionSystemException("UOWManager transaction processing failed", ex);
298 		}
299 		catch (UOWActionException ex) {
300 			throw new TransactionSystemException("UOWManager threw unexpected UOWActionException", ex);
301 		}
302 		finally {
303 			if (suspendedResources != null) {
304 				resume(null, suspendedResources);
305 			}
306 		}
307 	}
308 
309 
310 	/**
311 	 * Adapter that executes the given Spring transaction within the WebSphere UOWAction shape.
312 	 */
313 	private class UOWActionAdapter<T> implements UOWAction, SmartTransactionObject {
314 
315 		private final TransactionDefinition definition;
316 
317 		private final TransactionCallback<T> callback;
318 
319 		private final boolean actualTransaction;
320 
321 		private final boolean newTransaction;
322 
323 		private final boolean newSynchronization;
324 
325 		private boolean debug;
326 
327 		private T result;
328 
329 		private Throwable exception;
330 
331 		public UOWActionAdapter(TransactionDefinition definition, TransactionCallback<T> callback,
332 				boolean actualTransaction, boolean newTransaction, boolean newSynchronization, boolean debug) {
333 			this.definition = definition;
334 			this.callback = callback;
335 			this.actualTransaction = actualTransaction;
336 			this.newTransaction = newTransaction;
337 			this.newSynchronization = newSynchronization;
338 			this.debug = debug;
339 		}
340 
341 		@Override
342 		public void run() {
343 			DefaultTransactionStatus status = prepareTransactionStatus(
344 					this.definition, (this.actualTransaction ? this : null),
345 					this.newTransaction, this.newSynchronization, this.debug, null);
346 			try {
347 				this.result = this.callback.doInTransaction(status);
348 				triggerBeforeCommit(status);
349 			}
350 			catch (Throwable ex) {
351 				this.exception = ex;
352 				uowManager.setRollbackOnly();
353 			}
354 			finally {
355 				if (status.isLocalRollbackOnly()) {
356 					if (status.isDebug()) {
357 						logger.debug("Transactional code has requested rollback");
358 					}
359 					uowManager.setRollbackOnly();
360 				}
361 				triggerBeforeCompletion(status);
362 				if (status.isNewSynchronization()) {
363 					List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations();
364 					TransactionSynchronizationManager.clear();
365 					if (!synchronizations.isEmpty()) {
366 						uowManager.registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations));
367 					}
368 				}
369 			}
370 		}
371 
372 		public T getResult() {
373 			if (this.exception != null) {
374 				ReflectionUtils.rethrowRuntimeException(this.exception);
375 			}
376 			return this.result;
377 		}
378 
379 		@Override
380 		public boolean isRollbackOnly() {
381 			return uowManager.getRollbackOnly();
382 		}
383 
384 		@Override
385 		public void flush() {
386 			TransactionSynchronizationUtils.triggerFlush();
387 		}
388 	}
389 
390 }