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.cache.interceptor; 018 019import java.io.Serializable; 020import java.lang.reflect.Method; 021import java.util.Collection; 022import java.util.LinkedHashMap; 023import java.util.Map; 024 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027 028import org.springframework.lang.Nullable; 029import org.springframework.util.ObjectUtils; 030import org.springframework.util.PatternMatchUtils; 031 032/** 033 * Simple {@link CacheOperationSource} implementation that allows attributes to be matched 034 * by registered name. 035 * 036 * @author Costin Leau 037 * @since 3.1 038 */ 039@SuppressWarnings("serial") 040public class NameMatchCacheOperationSource implements CacheOperationSource, Serializable { 041 042 /** 043 * Logger available to subclasses. 044 * <p>Static for optimal serialization. 045 */ 046 protected static final Log logger = LogFactory.getLog(NameMatchCacheOperationSource.class); 047 048 049 /** Keys are method names; values are TransactionAttributes. */ 050 private Map<String, Collection<CacheOperation>> nameMap = new LinkedHashMap<>(); 051 052 053 /** 054 * Set a name/attribute map, consisting of method names 055 * (e.g. "myMethod") and CacheOperation instances 056 * (or Strings to be converted to CacheOperation instances). 057 * @see CacheOperation 058 */ 059 public void setNameMap(Map<String, Collection<CacheOperation>> nameMap) { 060 nameMap.forEach(this::addCacheMethod); 061 } 062 063 /** 064 * Add an attribute for a cacheable method. 065 * <p>Method names can be exact matches, or of the pattern "xxx*", 066 * "*xxx" or "*xxx*" for matching multiple methods. 067 * @param methodName the name of the method 068 * @param ops operation associated with the method 069 */ 070 public void addCacheMethod(String methodName, Collection<CacheOperation> ops) { 071 if (logger.isDebugEnabled()) { 072 logger.debug("Adding method [" + methodName + "] with cache operations [" + ops + "]"); 073 } 074 this.nameMap.put(methodName, ops); 075 } 076 077 @Override 078 @Nullable 079 public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) { 080 // look for direct name match 081 String methodName = method.getName(); 082 Collection<CacheOperation> ops = this.nameMap.get(methodName); 083 084 if (ops == null) { 085 // Look for most specific name match. 086 String bestNameMatch = null; 087 for (String mappedName : this.nameMap.keySet()) { 088 if (isMatch(methodName, mappedName) 089 && (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) { 090 ops = this.nameMap.get(mappedName); 091 bestNameMatch = mappedName; 092 } 093 } 094 } 095 096 return ops; 097 } 098 099 /** 100 * Return if the given method name matches the mapped name. 101 * <p>The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches, 102 * as well as direct equality. Can be overridden in subclasses. 103 * @param methodName the method name of the class 104 * @param mappedName the name in the descriptor 105 * @return if the names match 106 * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String) 107 */ 108 protected boolean isMatch(String methodName, String mappedName) { 109 return PatternMatchUtils.simpleMatch(mappedName, methodName); 110 } 111 112 @Override 113 public boolean equals(@Nullable Object other) { 114 if (this == other) { 115 return true; 116 } 117 if (!(other instanceof NameMatchCacheOperationSource)) { 118 return false; 119 } 120 NameMatchCacheOperationSource otherTas = (NameMatchCacheOperationSource) other; 121 return ObjectUtils.nullSafeEquals(this.nameMap, otherTas.nameMap); 122 } 123 124 @Override 125 public int hashCode() { 126 return NameMatchCacheOperationSource.class.hashCode(); 127 } 128 129 @Override 130 public String toString() { 131 return getClass().getName() + ": " + this.nameMap; 132 } 133}