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.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.lang.Nullable; 030import org.springframework.util.ClassUtils; 031import org.springframework.util.ObjectUtils; 032import org.springframework.util.PatternMatchUtils; 033 034/** 035 * Simple {@link TransactionAttributeSource} implementation that 036 * allows attributes to be matched by registered name. 037 * 038 * @author Juergen Hoeller 039 * @since 21.08.2003 040 * @see #isMatch 041 * @see MethodMapTransactionAttributeSource 042 */ 043@SuppressWarnings("serial") 044public class NameMatchTransactionAttributeSource implements TransactionAttributeSource, Serializable { 045 046 /** 047 * Logger available to subclasses. 048 * <p>Static for optimal serialization. 049 */ 050 protected static final Log logger = LogFactory.getLog(NameMatchTransactionAttributeSource.class); 051 052 /** Keys are method names; values are TransactionAttributes. */ 053 private Map<String, TransactionAttribute> nameMap = new HashMap<>(); 054 055 056 /** 057 * Set a name/attribute map, consisting of method names 058 * (e.g. "myMethod") and TransactionAttribute instances 059 * (or Strings to be converted to TransactionAttribute instances). 060 * @see TransactionAttribute 061 * @see TransactionAttributeEditor 062 */ 063 public void setNameMap(Map<String, TransactionAttribute> nameMap) { 064 nameMap.forEach(this::addTransactionalMethod); 065 } 066 067 /** 068 * Parses the given properties into a name/attribute map. 069 * Expects method names as keys and String attributes definitions as values, 070 * parsable into TransactionAttribute instances via TransactionAttributeEditor. 071 * @see #setNameMap 072 * @see TransactionAttributeEditor 073 */ 074 public void setProperties(Properties transactionAttributes) { 075 TransactionAttributeEditor tae = new TransactionAttributeEditor(); 076 Enumeration<?> propNames = transactionAttributes.propertyNames(); 077 while (propNames.hasMoreElements()) { 078 String methodName = (String) propNames.nextElement(); 079 String value = transactionAttributes.getProperty(methodName); 080 tae.setAsText(value); 081 TransactionAttribute attr = (TransactionAttribute) tae.getValue(); 082 addTransactionalMethod(methodName, attr); 083 } 084 } 085 086 /** 087 * Add an attribute for a transactional method. 088 * <p>Method names can be exact matches, or of the pattern "xxx*", 089 * "*xxx" or "*xxx*" for matching multiple methods. 090 * @param methodName the name of the method 091 * @param attr attribute associated with the method 092 */ 093 public void addTransactionalMethod(String methodName, TransactionAttribute attr) { 094 if (logger.isDebugEnabled()) { 095 logger.debug("Adding transactional method [" + methodName + "] with attribute [" + attr + "]"); 096 } 097 this.nameMap.put(methodName, attr); 098 } 099 100 101 @Override 102 @Nullable 103 public TransactionAttribute getTransactionAttribute(Method method, @Nullable 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(@Nullable 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}