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.endpoint; 018 019import java.lang.reflect.Method; 020 021import javax.resource.ResourceException; 022import javax.resource.spi.ApplicationServerInternalException; 023import javax.resource.spi.UnavailableException; 024import javax.resource.spi.endpoint.MessageEndpoint; 025import javax.resource.spi.endpoint.MessageEndpointFactory; 026import javax.transaction.Transaction; 027import javax.transaction.TransactionManager; 028import javax.transaction.xa.XAResource; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032 033import org.springframework.beans.factory.BeanNameAware; 034import org.springframework.lang.Nullable; 035import org.springframework.transaction.jta.SimpleTransactionFactory; 036import org.springframework.transaction.jta.TransactionFactory; 037import org.springframework.util.Assert; 038 039/** 040 * Abstract base implementation of the JCA 1.7 041 * {@link javax.resource.spi.endpoint.MessageEndpointFactory} interface, 042 * providing transaction management capabilities as well as ClassLoader 043 * exposure for endpoint invocations. 044 * 045 * @author Juergen Hoeller 046 * @since 2.5 047 * @see #setTransactionManager 048 */ 049public abstract class AbstractMessageEndpointFactory implements MessageEndpointFactory, BeanNameAware { 050 051 /** Logger available to subclasses. */ 052 protected final Log logger = LogFactory.getLog(getClass()); 053 054 @Nullable 055 private TransactionFactory transactionFactory; 056 057 @Nullable 058 private String transactionName; 059 060 private int transactionTimeout = -1; 061 062 @Nullable 063 private String beanName; 064 065 066 /** 067 * Set the XA transaction manager to use for wrapping endpoint 068 * invocations, enlisting the endpoint resource in each such transaction. 069 * <p>The passed-in object may be a transaction manager which implements 070 * Spring's {@link org.springframework.transaction.jta.TransactionFactory} 071 * interface, or a plain {@link javax.transaction.TransactionManager}. 072 * <p>If no transaction manager is specified, the endpoint invocation 073 * will simply not be wrapped in an XA transaction. Check out your 074 * resource provider's ActivationSpec documentation for local 075 * transaction options of your particular provider. 076 * @see #setTransactionName 077 * @see #setTransactionTimeout 078 */ 079 public void setTransactionManager(Object transactionManager) { 080 if (transactionManager instanceof TransactionFactory) { 081 this.transactionFactory = (TransactionFactory) transactionManager; 082 } 083 else if (transactionManager instanceof TransactionManager) { 084 this.transactionFactory = new SimpleTransactionFactory((TransactionManager) transactionManager); 085 } 086 else { 087 throw new IllegalArgumentException("Transaction manager [" + transactionManager + 088 "] is neither a [org.springframework.transaction.jta.TransactionFactory} nor a " + 089 "[javax.transaction.TransactionManager]"); 090 } 091 } 092 093 /** 094 * Set the Spring TransactionFactory to use for wrapping endpoint 095 * invocations, enlisting the endpoint resource in each such transaction. 096 * <p>Alternatively, specify an appropriate transaction manager through 097 * the {@link #setTransactionManager "transactionManager"} property. 098 * <p>If no transaction factory is specified, the endpoint invocation 099 * will simply not be wrapped in an XA transaction. Check out your 100 * resource provider's ActivationSpec documentation for local 101 * transaction options of your particular provider. 102 * @see #setTransactionName 103 * @see #setTransactionTimeout 104 */ 105 public void setTransactionFactory(TransactionFactory transactionFactory) { 106 this.transactionFactory = transactionFactory; 107 } 108 109 /** 110 * Specify the name of the transaction, if any. 111 * <p>Default is none. A specified name will be passed on to the transaction 112 * manager, allowing to identify the transaction in a transaction monitor. 113 */ 114 public void setTransactionName(String transactionName) { 115 this.transactionName = transactionName; 116 } 117 118 /** 119 * Specify the transaction timeout, if any. 120 * <p>Default is -1: rely on the transaction manager's default timeout. 121 * Specify a concrete timeout to restrict the maximum duration of each 122 * endpoint invocation. 123 */ 124 public void setTransactionTimeout(int transactionTimeout) { 125 this.transactionTimeout = transactionTimeout; 126 } 127 128 /** 129 * Set the name of this message endpoint. Populated with the bean name 130 * automatically when defined within Spring's bean factory. 131 */ 132 @Override 133 public void setBeanName(String beanName) { 134 this.beanName = beanName; 135 } 136 137 138 /** 139 * Implementation of the JCA 1.7 {@code #getActivationName()} method, 140 * returning the bean name as set on this MessageEndpointFactory. 141 * @see #setBeanName 142 */ 143 @Override 144 @Nullable 145 public String getActivationName() { 146 return this.beanName; 147 } 148 149 /** 150 * Implementation of the JCA 1.7 {@code #getEndpointClass()} method, 151 * returning {@code} null in order to indicate a synthetic endpoint type. 152 */ 153 @Override 154 @Nullable 155 public Class<?> getEndpointClass() { 156 return null; 157 } 158 159 /** 160 * This implementation returns {@code true} if a transaction manager 161 * has been specified; {@code false} otherwise. 162 * @see #setTransactionManager 163 * @see #setTransactionFactory 164 */ 165 @Override 166 public boolean isDeliveryTransacted(Method method) throws NoSuchMethodException { 167 return (this.transactionFactory != null); 168 } 169 170 /** 171 * The standard JCA 1.5 version of {@code createEndpoint}. 172 * <p>This implementation delegates to {@link #createEndpointInternal()}, 173 * initializing the endpoint's XAResource before the endpoint gets invoked. 174 */ 175 @Override 176 public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException { 177 AbstractMessageEndpoint endpoint = createEndpointInternal(); 178 endpoint.initXAResource(xaResource); 179 return endpoint; 180 } 181 182 /** 183 * The alternative JCA 1.6 version of {@code createEndpoint}. 184 * <p>This implementation delegates to {@link #createEndpointInternal()}, 185 * ignoring the specified timeout. It is only here for JCA 1.6 compliance. 186 */ 187 @Override 188 public MessageEndpoint createEndpoint(XAResource xaResource, long timeout) throws UnavailableException { 189 AbstractMessageEndpoint endpoint = createEndpointInternal(); 190 endpoint.initXAResource(xaResource); 191 return endpoint; 192 } 193 194 /** 195 * Create the actual endpoint instance, as a subclass of the 196 * {@link AbstractMessageEndpoint} inner class of this factory. 197 * @return the actual endpoint instance (never {@code null}) 198 * @throws UnavailableException if no endpoint is available at present 199 */ 200 protected abstract AbstractMessageEndpoint createEndpointInternal() throws UnavailableException; 201 202 203 /** 204 * Inner class for actual endpoint implementations, based on template 205 * method to allow for any kind of concrete endpoint implementation. 206 */ 207 protected abstract class AbstractMessageEndpoint implements MessageEndpoint { 208 209 @Nullable 210 private TransactionDelegate transactionDelegate; 211 212 private boolean beforeDeliveryCalled = false; 213 214 @Nullable 215 private ClassLoader previousContextClassLoader; 216 217 /** 218 * Initialize this endpoint's TransactionDelegate. 219 * @param xaResource the XAResource for this endpoint 220 */ 221 void initXAResource(XAResource xaResource) { 222 this.transactionDelegate = new TransactionDelegate(xaResource); 223 } 224 225 /** 226 * This {@code beforeDelivery} implementation starts a transaction, 227 * if necessary, and exposes the endpoint ClassLoader as current 228 * thread context ClassLoader. 229 * <p>Note that the JCA 1.7 specification does not require a ResourceAdapter 230 * to call this method before invoking the concrete endpoint. If this method 231 * has not been called (check {@link #hasBeforeDeliveryBeenCalled()}), the 232 * concrete endpoint method should call {@code beforeDelivery} and its 233 * sibling {@link #afterDelivery()} explicitly, as part of its own processing. 234 */ 235 @Override 236 public void beforeDelivery(@Nullable Method method) throws ResourceException { 237 this.beforeDeliveryCalled = true; 238 Assert.state(this.transactionDelegate != null, "Not initialized"); 239 try { 240 this.transactionDelegate.beginTransaction(); 241 } 242 catch (Throwable ex) { 243 throw new ApplicationServerInternalException("Failed to begin transaction", ex); 244 } 245 Thread currentThread = Thread.currentThread(); 246 this.previousContextClassLoader = currentThread.getContextClassLoader(); 247 currentThread.setContextClassLoader(getEndpointClassLoader()); 248 } 249 250 /** 251 * Template method for exposing the endpoint's ClassLoader 252 * (typically the ClassLoader that the message listener class 253 * has been loaded with). 254 * @return the endpoint ClassLoader (never {@code null}) 255 */ 256 protected abstract ClassLoader getEndpointClassLoader(); 257 258 /** 259 * Return whether the {@link #beforeDelivery} method of this endpoint 260 * has already been called. 261 */ 262 protected final boolean hasBeforeDeliveryBeenCalled() { 263 return this.beforeDeliveryCalled; 264 } 265 266 /** 267 * Callback method for notifying the endpoint base class 268 * that the concrete endpoint invocation led to an exception. 269 * <p>To be invoked by subclasses in case of the concrete 270 * endpoint throwing an exception. 271 * @param ex the exception thrown from the concrete endpoint 272 */ 273 protected void onEndpointException(Throwable ex) { 274 Assert.state(this.transactionDelegate != null, "Not initialized"); 275 this.transactionDelegate.setRollbackOnly(); 276 logger.debug("Transaction marked as rollback-only after endpoint exception", ex); 277 } 278 279 /** 280 * This {@code afterDelivery} implementation resets the thread context 281 * ClassLoader and completes the transaction, if any. 282 * <p>Note that the JCA 1.7 specification does not require a ResourceAdapter 283 * to call this method after invoking the concrete endpoint. See the 284 * explanation in {@link #beforeDelivery}'s javadoc. 285 */ 286 @Override 287 public void afterDelivery() throws ResourceException { 288 Assert.state(this.transactionDelegate != null, "Not initialized"); 289 this.beforeDeliveryCalled = false; 290 Thread.currentThread().setContextClassLoader(this.previousContextClassLoader); 291 this.previousContextClassLoader = null; 292 try { 293 this.transactionDelegate.endTransaction(); 294 } 295 catch (Throwable ex) { 296 logger.warn("Failed to complete transaction after endpoint delivery", ex); 297 throw new ApplicationServerInternalException("Failed to complete transaction", ex); 298 } 299 } 300 301 @Override 302 public void release() { 303 if (this.transactionDelegate != null) { 304 try { 305 this.transactionDelegate.setRollbackOnly(); 306 this.transactionDelegate.endTransaction(); 307 } 308 catch (Throwable ex) { 309 logger.warn("Could not complete unfinished transaction on endpoint release", ex); 310 } 311 } 312 } 313 } 314 315 316 /** 317 * Private inner class that performs the actual transaction handling, 318 * including enlistment of the endpoint's XAResource. 319 */ 320 private class TransactionDelegate { 321 322 @Nullable 323 private final XAResource xaResource; 324 325 @Nullable 326 private Transaction transaction; 327 328 private boolean rollbackOnly; 329 330 public TransactionDelegate(@Nullable XAResource xaResource) { 331 if (xaResource == null && transactionFactory != null && 332 !transactionFactory.supportsResourceAdapterManagedTransactions()) { 333 throw new IllegalStateException("ResourceAdapter-provided XAResource is required for " + 334 "transaction management. Check your ResourceAdapter's configuration."); 335 } 336 this.xaResource = xaResource; 337 } 338 339 public void beginTransaction() throws Exception { 340 if (transactionFactory != null && this.xaResource != null) { 341 this.transaction = transactionFactory.createTransaction(transactionName, transactionTimeout); 342 this.transaction.enlistResource(this.xaResource); 343 } 344 } 345 346 public void setRollbackOnly() { 347 if (this.transaction != null) { 348 this.rollbackOnly = true; 349 } 350 } 351 352 public void endTransaction() throws Exception { 353 if (this.transaction != null) { 354 try { 355 if (this.rollbackOnly) { 356 this.transaction.rollback(); 357 } 358 else { 359 this.transaction.commit(); 360 } 361 } 362 finally { 363 this.transaction = null; 364 this.rollbackOnly = false; 365 } 366 } 367 } 368 } 369 370}