001/* 002 * Copyright 2002-2012 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.transaction.interceptor; 018 019import java.lang.reflect.Method; 020import java.util.ArrayList; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027 028import org.springframework.beans.factory.BeanClassLoaderAware; 029import org.springframework.beans.factory.InitializingBean; 030import org.springframework.util.Assert; 031import org.springframework.util.ClassUtils; 032import org.springframework.util.ObjectUtils; 033import org.springframework.util.PatternMatchUtils; 034 035/** 036 * Simple {@link TransactionAttributeSource} implementation that 037 * allows attributes to be stored per method in a {@link Map}. 038 * 039 * @author Rod Johnson 040 * @author Juergen Hoeller 041 * @since 24.04.2003 042 * @see #isMatch 043 * @see NameMatchTransactionAttributeSource 044 */ 045public class MethodMapTransactionAttributeSource 046 implements TransactionAttributeSource, BeanClassLoaderAware, InitializingBean { 047 048 /** Logger available to subclasses */ 049 protected final Log logger = LogFactory.getLog(getClass()); 050 051 /** Map from method name to attribute value */ 052 private Map<String, TransactionAttribute> methodMap; 053 054 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 055 056 private boolean eagerlyInitialized = false; 057 058 private boolean initialized = false; 059 060 /** Map from Method to TransactionAttribute */ 061 private final Map<Method, TransactionAttribute> transactionAttributeMap = 062 new HashMap<Method, TransactionAttribute>(); 063 064 /** Map from Method to name pattern used for registration */ 065 private final Map<Method, String> methodNameMap = new HashMap<Method, String>(); 066 067 068 /** 069 * Set a name/attribute map, consisting of "FQCN.method" method names 070 * (e.g. "com.mycompany.mycode.MyClass.myMethod") and 071 * {@link TransactionAttribute} instances (or Strings to be converted 072 * to {@code TransactionAttribute} instances). 073 * <p>Intended for configuration via setter injection, typically within 074 * a Spring bean factory. Relies on {@link #afterPropertiesSet()} 075 * being called afterwards. 076 * @param methodMap said {@link Map} from method name to attribute value 077 * @see TransactionAttribute 078 * @see TransactionAttributeEditor 079 */ 080 public void setMethodMap(Map<String, TransactionAttribute> methodMap) { 081 this.methodMap = methodMap; 082 } 083 084 @Override 085 public void setBeanClassLoader(ClassLoader beanClassLoader) { 086 this.beanClassLoader = beanClassLoader; 087 } 088 089 090 /** 091 * Eagerly initializes the specified 092 * {@link #setMethodMap(java.util.Map) "methodMap"}, if any. 093 * @see #initMethodMap(java.util.Map) 094 */ 095 @Override 096 public void afterPropertiesSet() { 097 initMethodMap(this.methodMap); 098 this.eagerlyInitialized = true; 099 this.initialized = true; 100 } 101 102 /** 103 * Initialize the specified {@link #setMethodMap(java.util.Map) "methodMap"}, if any. 104 * @param methodMap Map from method names to {@code TransactionAttribute} instances 105 * @see #setMethodMap 106 */ 107 protected void initMethodMap(Map<String, TransactionAttribute> methodMap) { 108 if (methodMap != null) { 109 for (Map.Entry<String, TransactionAttribute> entry : methodMap.entrySet()) { 110 addTransactionalMethod(entry.getKey(), entry.getValue()); 111 } 112 } 113 } 114 115 116 /** 117 * Add an attribute for a transactional method. 118 * <p>Method names can end or start with "*" for matching multiple methods. 119 * @param name class and method name, separated by a dot 120 * @param attr attribute associated with the method 121 * @throws IllegalArgumentException in case of an invalid name 122 */ 123 public void addTransactionalMethod(String name, TransactionAttribute attr) { 124 Assert.notNull(name, "Name must not be null"); 125 int lastDotIndex = name.lastIndexOf('.'); 126 if (lastDotIndex == -1) { 127 throw new IllegalArgumentException("'" + name + "' is not a valid method name: format is FQN.methodName"); 128 } 129 String className = name.substring(0, lastDotIndex); 130 String methodName = name.substring(lastDotIndex + 1); 131 Class<?> clazz = ClassUtils.resolveClassName(className, this.beanClassLoader); 132 addTransactionalMethod(clazz, methodName, attr); 133 } 134 135 /** 136 * Add an attribute for a transactional method. 137 * Method names can end or start with "*" for matching multiple methods. 138 * @param clazz target interface or class 139 * @param mappedName mapped method name 140 * @param attr attribute associated with the method 141 */ 142 public void addTransactionalMethod(Class<?> clazz, String mappedName, TransactionAttribute attr) { 143 Assert.notNull(clazz, "Class must not be null"); 144 Assert.notNull(mappedName, "Mapped name must not be null"); 145 String name = clazz.getName() + '.' + mappedName; 146 147 Method[] methods = clazz.getDeclaredMethods(); 148 List<Method> matchingMethods = new ArrayList<Method>(); 149 for (Method method : methods) { 150 if (isMatch(method.getName(), mappedName)) { 151 matchingMethods.add(method); 152 } 153 } 154 if (matchingMethods.isEmpty()) { 155 throw new IllegalArgumentException( 156 "Couldn't find method '" + mappedName + "' on class [" + clazz.getName() + "]"); 157 } 158 159 // register all matching methods 160 for (Method method : matchingMethods) { 161 String regMethodName = this.methodNameMap.get(method); 162 if (regMethodName == null || (!regMethodName.equals(name) && regMethodName.length() <= name.length())) { 163 // No already registered method name, or more specific 164 // method name specification now -> (re-)register method. 165 if (logger.isDebugEnabled() && regMethodName != null) { 166 logger.debug("Replacing attribute for transactional method [" + method + "]: current name '" + 167 name + "' is more specific than '" + regMethodName + "'"); 168 } 169 this.methodNameMap.put(method, name); 170 addTransactionalMethod(method, attr); 171 } 172 else { 173 if (logger.isDebugEnabled()) { 174 logger.debug("Keeping attribute for transactional method [" + method + "]: current name '" + 175 name + "' is not more specific than '" + regMethodName + "'"); 176 } 177 } 178 } 179 } 180 181 /** 182 * Add an attribute for a transactional method. 183 * @param method the method 184 * @param attr attribute associated with the method 185 */ 186 public void addTransactionalMethod(Method method, TransactionAttribute attr) { 187 Assert.notNull(method, "Method must not be null"); 188 Assert.notNull(attr, "TransactionAttribute must not be null"); 189 if (logger.isDebugEnabled()) { 190 logger.debug("Adding transactional method [" + method + "] with attribute [" + attr + "]"); 191 } 192 this.transactionAttributeMap.put(method, attr); 193 } 194 195 /** 196 * Return if the given method name matches the mapped name. 197 * <p>The default implementation checks for "xxx*", "*xxx" and "*xxx*" 198 * matches, as well as direct equality. 199 * @param methodName the method name of the class 200 * @param mappedName the name in the descriptor 201 * @return if the names match 202 * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String) 203 */ 204 protected boolean isMatch(String methodName, String mappedName) { 205 return PatternMatchUtils.simpleMatch(mappedName, methodName); 206 } 207 208 209 @Override 210 public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) { 211 if (this.eagerlyInitialized) { 212 return this.transactionAttributeMap.get(method); 213 } 214 else { 215 synchronized (this.transactionAttributeMap) { 216 if (!this.initialized) { 217 initMethodMap(this.methodMap); 218 this.initialized = true; 219 } 220 return this.transactionAttributeMap.get(method); 221 } 222 } 223 } 224 225 226 @Override 227 public boolean equals(Object other) { 228 if (this == other) { 229 return true; 230 } 231 if (!(other instanceof MethodMapTransactionAttributeSource)) { 232 return false; 233 } 234 MethodMapTransactionAttributeSource otherTas = (MethodMapTransactionAttributeSource) other; 235 return ObjectUtils.nullSafeEquals(this.methodMap, otherTas.methodMap); 236 } 237 238 @Override 239 public int hashCode() { 240 return MethodMapTransactionAttributeSource.class.hashCode(); 241 } 242 243 @Override 244 public String toString() { 245 return getClass().getName() + ": " + this.methodMap; 246 } 247 248}