1. /*
  2. * Copyright 2002-2004 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. package org.springframework.transaction.interceptor;
  17. import java.lang.reflect.Method;
  18. import java.util.Collection;
  19. import java.util.HashMap;
  20. import java.util.Iterator;
  21. import java.util.LinkedList;
  22. import java.util.List;
  23. import org.apache.commons.logging.Log;
  24. import org.apache.commons.logging.LogFactory;
  25. import org.springframework.aop.support.AopUtils;
  26. import org.springframework.metadata.Attributes;
  27. /**
  28. * Implementation of TransactionAttributeSource that uses
  29. * attributes from an Attributes implementation.
  30. *
  31. * <p>Defaults to using class's transaction attribute if none is associated
  32. * with the target method. Any transaction attribute associated with the
  33. * target method completely overrides a class transaction attribute.
  34. *
  35. * <p>This implementation caches attributes by method after they are first used.
  36. * If it's ever desirable to allow dynamic changing of transaction attributes
  37. * (unlikely) caching could be made configurable. Caching is desirable because
  38. * of the cost of evaluating rollback rules.
  39. *
  40. * @author Rod Johnson
  41. * @version $Id: AttributesTransactionAttributeSource.java,v 1.8 2004/05/26 10:48:57 jhoeller Exp $
  42. * @see org.springframework.metadata.Attributes
  43. */
  44. public class AttributesTransactionAttributeSource implements TransactionAttributeSource {
  45. protected final Log logger = LogFactory.getLog(getClass());
  46. /**
  47. * Canonical value held in cache to indicate no transaction attribute was
  48. * found for this method, and we don't need to look again
  49. */
  50. private final static Object NULL_TX_ATTRIBUTE = new Object();
  51. /**
  52. * Underlying Attributes implementation we're using
  53. */
  54. private final Attributes attributes;
  55. /**
  56. * Cache of TransactionAttributes, keyed by Method and target class
  57. */
  58. private HashMap cache = new HashMap();
  59. public AttributesTransactionAttributeSource(Attributes attributes) {
  60. this.attributes = attributes;
  61. }
  62. /**
  63. * Return the transaction attribute for this method invocation.
  64. * Defaults to the class's transaction attribute if no method
  65. * attribute is found
  66. * @param method method for the current invocation. Can't be null
  67. * @param targetClass target class for this invocation. May be null.
  68. * @return TransactionAttribute for this method, or null if the method is non-transactional
  69. */
  70. public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) {
  71. // First, see if we have a cached value
  72. Object cacheKey = cacheKey(method, targetClass);
  73. Object cached = cache.get(cacheKey);
  74. if (cached != null) {
  75. // Value will either be canonical value indicating there is no transaction attribute,
  76. // or an actual transaction attribute
  77. if (cached == NULL_TX_ATTRIBUTE) {
  78. return null;
  79. }
  80. else {
  81. return (TransactionAttribute) cached;
  82. }
  83. }
  84. else {
  85. // We need to work it out
  86. TransactionAttribute txAtt = computeTransactionAttribute(method, targetClass);
  87. // Put it in the cache
  88. if (txAtt == null) {
  89. cache.put(cacheKey, NULL_TX_ATTRIBUTE);
  90. }
  91. else {
  92. cache.put(cacheKey, txAtt);
  93. }
  94. return txAtt;
  95. }
  96. }
  97. private Object cacheKey(Method method, Class targetClass) {
  98. // Class may be null, method can't
  99. return targetClass + "" + System.identityHashCode(method);
  100. }
  101. /**
  102. * Same return as getTransactionAttribute method, but doesn't cache the result.
  103. * getTransactionAttribute is a caching decorator for this method.
  104. */
  105. protected TransactionAttribute computeTransactionAttribute(Method method, Class targetClass) {
  106. // The method may be on an interface, but we need attributes from the target class.
  107. // The AopUtils class provides a convenience method for this. If the target class
  108. // is null, the method will be unchanged.
  109. Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
  110. // First try is the method in the target class
  111. TransactionAttribute txAtt = findTransactionAttribute(attributes.getAttributes(specificMethod));
  112. if (txAtt != null)
  113. return txAtt;
  114. // Second try is the transaction attribute on the target class
  115. txAtt = findTransactionAttribute(attributes.getAttributes(specificMethod.getDeclaringClass()));
  116. if (txAtt != null)
  117. return txAtt;
  118. if (specificMethod != method ) {
  119. // Fallback is to look at the original method
  120. txAtt = findTransactionAttribute(attributes.getAttributes(method));
  121. if (txAtt != null)
  122. return txAtt;
  123. // Last fallback is the class of the original method
  124. return findTransactionAttribute(attributes.getAttributes(method.getDeclaringClass()));
  125. }
  126. return null;
  127. }
  128. /**
  129. * Return the transaction attribute, given this set of attributes
  130. * attached to a method or class.
  131. * Protected rather than private as subclasses may want to customize
  132. * how this is done: for example, returning a TransactionAttribute
  133. * affected by the values of other attributes.
  134. * This implementation takes into account RollbackRuleAttributes, if
  135. * the TransactionAttribute is a RuleBasedTransactionAttribute.
  136. * Return null if it's not transactional.
  137. * @param atts attributes attached to a method or class. May
  138. * be null, in which case a null TransactionAttribute will be returned.
  139. * @return TransactionAttribute configured transaction attribute, or null
  140. * if none was found
  141. */
  142. protected TransactionAttribute findTransactionAttribute(Collection atts) {
  143. if (atts == null) {
  144. return null;
  145. }
  146. TransactionAttribute txAttribute = null;
  147. // Check there is a transaction attribute
  148. for (Iterator itr = atts.iterator(); itr.hasNext() && txAttribute == null; ) {
  149. Object att = itr.next();
  150. if (att instanceof TransactionAttribute) {
  151. txAttribute = (TransactionAttribute) att;
  152. }
  153. }
  154. if (txAttribute instanceof RuleBasedTransactionAttribute) {
  155. RuleBasedTransactionAttribute rbta = (RuleBasedTransactionAttribute) txAttribute;
  156. // We really want value: bit of a hack.
  157. List rollbackRules = new LinkedList();
  158. for (Iterator it = atts.iterator(); it.hasNext(); ) {
  159. Object att = it.next();
  160. if (att instanceof RollbackRuleAttribute) {
  161. if (logger.isDebugEnabled()) {
  162. logger.debug("Found RollbackRule: " + att);
  163. }
  164. rollbackRules.add(att);
  165. }
  166. }
  167. // Repeatedly setting this isn't elegant, but it works.
  168. rbta.setRollbackRules(rollbackRules);
  169. }
  170. return txAttribute;
  171. }
  172. }