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