001/* 002 * Copyright 2002-2013 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.io.Serializable; 020import java.lang.reflect.Method; 021import java.util.Enumeration; 022import java.util.HashMap; 023import java.util.Map; 024import java.util.Properties; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028 029import org.springframework.util.ClassUtils; 030import org.springframework.util.ObjectUtils; 031import org.springframework.util.PatternMatchUtils; 032 033/** 034 * Simple {@link TransactionAttributeSource} implementation that 035 * allows attributes to be matched by registered name. 036 * 037 * @author Juergen Hoeller 038 * @since 21.08.2003 039 * @see #isMatch 040 * @see MethodMapTransactionAttributeSource 041 */ 042@SuppressWarnings("serial") 043public class NameMatchTransactionAttributeSource implements TransactionAttributeSource, Serializable { 044 045 /** 046 * Logger available to subclasses. 047 * <p>Static for optimal serialization. 048 */ 049 protected static final Log logger = LogFactory.getLog(NameMatchTransactionAttributeSource.class); 050 051 /** Keys are method names; values are TransactionAttributes */ 052 private Map<String, TransactionAttribute> nameMap = new HashMap<String, TransactionAttribute>(); 053 054 055 /** 056 * Set a name/attribute map, consisting of method names 057 * (e.g. "myMethod") and TransactionAttribute instances 058 * (or Strings to be converted to TransactionAttribute instances). 059 * @see TransactionAttribute 060 * @see TransactionAttributeEditor 061 */ 062 public void setNameMap(Map<String, TransactionAttribute> nameMap) { 063 for (Map.Entry<String, TransactionAttribute> entry : nameMap.entrySet()) { 064 addTransactionalMethod(entry.getKey(), entry.getValue()); 065 } 066 } 067 068 /** 069 * Parses the given properties into a name/attribute map. 070 * Expects method names as keys and String attributes definitions as values, 071 * parsable into TransactionAttribute instances via TransactionAttributeEditor. 072 * @see #setNameMap 073 * @see TransactionAttributeEditor 074 */ 075 public void setProperties(Properties transactionAttributes) { 076 TransactionAttributeEditor tae = new TransactionAttributeEditor(); 077 Enumeration<?> propNames = transactionAttributes.propertyNames(); 078 while (propNames.hasMoreElements()) { 079 String methodName = (String) propNames.nextElement(); 080 String value = transactionAttributes.getProperty(methodName); 081 tae.setAsText(value); 082 TransactionAttribute attr = (TransactionAttribute) tae.getValue(); 083 addTransactionalMethod(methodName, attr); 084 } 085 } 086 087 /** 088 * Add an attribute for a transactional method. 089 * <p>Method names can be exact matches, or of the pattern "xxx*", 090 * "*xxx" or "*xxx*" for matching multiple methods. 091 * @param methodName the name of the method 092 * @param attr attribute associated with the method 093 */ 094 public void addTransactionalMethod(String methodName, TransactionAttribute attr) { 095 if (logger.isDebugEnabled()) { 096 logger.debug("Adding transactional method [" + methodName + "] with attribute [" + attr + "]"); 097 } 098 this.nameMap.put(methodName, attr); 099 } 100 101 102 @Override 103 public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) { 104 if (!ClassUtils.isUserLevelMethod(method)) { 105 return null; 106 } 107 108 // Look for direct name match. 109 String methodName = method.getName(); 110 TransactionAttribute attr = this.nameMap.get(methodName); 111 112 if (attr == null) { 113 // Look for most specific name match. 114 String bestNameMatch = null; 115 for (String mappedName : this.nameMap.keySet()) { 116 if (isMatch(methodName, mappedName) && 117 (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) { 118 attr = this.nameMap.get(mappedName); 119 bestNameMatch = mappedName; 120 } 121 } 122 } 123 124 return attr; 125 } 126 127 /** 128 * Return if the given method name matches the mapped name. 129 * <p>The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches, 130 * as well as direct equality. Can be overridden in subclasses. 131 * @param methodName the method name of the class 132 * @param mappedName the name in the descriptor 133 * @return if the names match 134 * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String) 135 */ 136 protected boolean isMatch(String methodName, String mappedName) { 137 return PatternMatchUtils.simpleMatch(mappedName, methodName); 138 } 139 140 141 @Override 142 public boolean equals(Object other) { 143 if (this == other) { 144 return true; 145 } 146 if (!(other instanceof NameMatchTransactionAttributeSource)) { 147 return false; 148 } 149 NameMatchTransactionAttributeSource otherTas = (NameMatchTransactionAttributeSource) other; 150 return ObjectUtils.nullSafeEquals(this.nameMap, otherTas.nameMap); 151 } 152 153 @Override 154 public int hashCode() { 155 return NameMatchTransactionAttributeSource.class.hashCode(); 156 } 157 158 @Override 159 public String toString() { 160 return getClass().getName() + ": " + this.nameMap; 161 } 162 163}