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}