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.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.util.ObjectUtils;
029import org.springframework.util.PatternMatchUtils;
030
031/**
032 * Simple {@link CacheOperationSource} implementation that allows attributes to be matched
033 * by registered name.
034 *
035 * @author Costin Leau
036 * @since 3.1
037 */
038@SuppressWarnings("serial")
039public class NameMatchCacheOperationSource implements CacheOperationSource, Serializable {
040
041        /**
042         * Logger available to subclasses.
043         * <p>Static for optimal serialization.
044         */
045        protected static final Log logger = LogFactory.getLog(NameMatchCacheOperationSource.class);
046
047
048        /** Keys are method names; values are TransactionAttributes */
049        private Map<String, Collection<CacheOperation>> nameMap = new LinkedHashMap<String, Collection<CacheOperation>>();
050
051
052        /**
053         * Set a name/attribute map, consisting of method names
054         * (e.g. "myMethod") and CacheOperation instances
055         * (or Strings to be converted to CacheOperation instances).
056         * @see CacheOperation
057         */
058        public void setNameMap(Map<String, Collection<CacheOperation>> nameMap) {
059                for (Map.Entry<String, Collection<CacheOperation>> entry : nameMap.entrySet()) {
060                        addCacheMethod(entry.getKey(), entry.getValue());
061                }
062        }
063
064        /**
065         * Add an attribute for a cacheable method.
066         * <p>Method names can be exact matches, or of the pattern "xxx*",
067         * "*xxx" or "*xxx*" for matching multiple methods.
068         * @param methodName the name of the method
069         * @param ops operation associated with the method
070         */
071        public void addCacheMethod(String methodName, Collection<CacheOperation> ops) {
072                if (logger.isDebugEnabled()) {
073                        logger.debug("Adding method [" + methodName + "] with cache operations [" + ops + "]");
074                }
075                this.nameMap.put(methodName, ops);
076        }
077
078        @Override
079        public Collection<CacheOperation> getCacheOperations(Method method, 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(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}