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.ArrayList;
  19. import java.util.HashMap;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. import java.util.Map;
  23. import org.apache.commons.logging.Log;
  24. import org.apache.commons.logging.LogFactory;
  25. import org.springframework.transaction.TransactionUsageException;
  26. /**
  27. * Simple implementation of TransactionAttributeSource that
  28. * allows attributes to be stored per method in a map.
  29. * @since 24-Apr-2003
  30. * @version $Id: MethodMapTransactionAttributeSource.java,v 1.8 2004/03/18 02:46:05 trisberg Exp $
  31. * @author Rod Johnson
  32. * @author Juergen Hoeller
  33. * @see #isMatch
  34. */
  35. public class MethodMapTransactionAttributeSource implements TransactionAttributeSource {
  36. protected final Log logger = LogFactory.getLog(getClass());
  37. /** Map from Method to TransactionAttribute */
  38. private Map methodMap = new HashMap();
  39. /** Map from Method to name pattern used for registration */
  40. private Map nameMap = new HashMap();
  41. /**
  42. * Set a name/attribute map, consisting of "FQCN.method" method names
  43. * (e.g. "com.mycompany.mycode.MyClass.myMethod") and TransactionAttribute
  44. * instances.
  45. */
  46. public void setMethodMap(Map methodMap) {
  47. Iterator it = methodMap.keySet().iterator();
  48. while (it.hasNext()) {
  49. String name = (String) it.next();
  50. TransactionAttribute attr = (TransactionAttribute) methodMap.get(name);
  51. addTransactionalMethod(name, attr);
  52. }
  53. }
  54. /**
  55. * Add an attribute for a transactional method.
  56. * Method names can end or start with "*" for matching multiple methods.
  57. * @param name class and method name, separated by a dot
  58. * @param attr attribute associated with the method
  59. */
  60. public void addTransactionalMethod(String name, TransactionAttribute attr) {
  61. int lastDotIndex = name.lastIndexOf(".");
  62. if (lastDotIndex == -1) {
  63. throw new TransactionUsageException("'" + name + "' is not a valid method name: format is FQN.methodName");
  64. }
  65. String className = name.substring(0, lastDotIndex);
  66. String methodName = name.substring(lastDotIndex + 1);
  67. try {
  68. Class clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
  69. addTransactionalMethod(clazz, methodName, attr);
  70. }
  71. catch (ClassNotFoundException ex) {
  72. throw new TransactionUsageException("Class '" + className + "' not found");
  73. }
  74. }
  75. /**
  76. * Add an attribute for a transactional method.
  77. * Method names can end or start with "*" for matching multiple methods.
  78. * @param clazz target interface or class
  79. * @param mappedName mapped method name
  80. * @param attr attribute associated with the method
  81. */
  82. public void addTransactionalMethod(Class clazz, String mappedName, TransactionAttribute attr) {
  83. String name = clazz.getName() + '.' + mappedName;
  84. logger.debug("Adding transactional method [" + name + "] with attribute [" + attr + "]");
  85. // TODO address method overloading? At present this will
  86. // simply match all methods that have the given name.
  87. // Consider EJB syntax (int, String) etc.?
  88. Method[] methods = clazz.getDeclaredMethods();
  89. List matchingMethods = new ArrayList();
  90. for (int i = 0; i < methods.length; i++) {
  91. if (methods[i].getName().equals(mappedName) || isMatch(methods[i].getName(), mappedName)) {
  92. matchingMethods.add(methods[i]);
  93. }
  94. }
  95. if (matchingMethods.isEmpty()) {
  96. throw new TransactionUsageException("Couldn't find method '" + mappedName +
  97. "' on class [" + clazz.getName() + "]");
  98. }
  99. // register all matching methods
  100. for (Iterator it = matchingMethods.iterator(); it.hasNext();) {
  101. Method method = (Method) it.next();
  102. String regMethodName = (String) this.nameMap.get(method);
  103. if (regMethodName == null || (!regMethodName.equals(name) && regMethodName.length() <= name.length())) {
  104. // no already registered method name, or more specific
  105. // method name specification now -> (re-)register method
  106. if (logger.isDebugEnabled() && regMethodName != null) {
  107. logger.debug("Replacing attribute for transactional method [" + method + "]: current name '" +
  108. name + "' is more specific than '" + regMethodName + "'");
  109. }
  110. this.nameMap.put(method, name);
  111. addTransactionalMethod(method, attr);
  112. }
  113. else {
  114. if (logger.isDebugEnabled() && regMethodName != null) {
  115. logger.debug("Keeping attribute for transactional method [" + method + "]: current name '" +
  116. name + "' is not more specific than '" + regMethodName + "'");
  117. }
  118. }
  119. }
  120. }
  121. /**
  122. * Add an attribute for a transactional method.
  123. * @param method the method
  124. * @param attr attribute associated with the method
  125. */
  126. public void addTransactionalMethod(Method method, TransactionAttribute attr) {
  127. logger.info("Adding transactional method [" + method + "] with attribute [" + attr + "]");
  128. this.methodMap.put(method, attr);
  129. }
  130. /**
  131. * Return if the given method name matches the mapped name.
  132. * The default implementation checks for "xxx*" and "*xxx" matches.
  133. * Can be overridden in subclasses.
  134. * @param methodName the method name of the class
  135. * @param mappedName the name in the descriptor
  136. * @return if the names match
  137. */
  138. protected boolean isMatch(String methodName, String mappedName) {
  139. return (mappedName.endsWith("*") && methodName.startsWith(mappedName.substring(0, mappedName.length() - 1))) ||
  140. (mappedName.startsWith("*") && methodName.endsWith(mappedName.substring(1, mappedName.length())));
  141. }
  142. public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) {
  143. return (TransactionAttribute) this.methodMap.get(method);
  144. }
  145. }