001 /*
002 * Copyright 2007-2009 The Kuali Foundation
003 *
004 * Licensed under the Educational Community 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 * http://www.opensource.org/licenses/ecl2.php
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 package org.kuali.rice.kew.util;
017
018 import java.lang.ref.SoftReference;
019 import java.util.HashSet;
020 import java.util.Set;
021
022 import org.aopalliance.intercept.MethodInterceptor;
023 import org.aopalliance.intercept.MethodInvocation;
024 import org.apache.commons.logging.Log;
025 import org.apache.commons.logging.LogFactory;
026 import org.kuali.rice.kew.service.KEWServiceLocator;
027
028 /**
029 * <p>This is a generic caching proxy for KEW, using the cache administrator service to store results. It's inteded
030 * for caching query results for a DAO, but it likely has broader use.
031 * <p>It will cache the results of calls matching method names which are set in {@link #setCacheForMethods}.
032 * It will clear this "cache group" for calls with matching method names set in {@link #setClearCacheOnMethods}.
033 * <p>The cache group name (see {@link #setCacheGroupName(String)}) in most instances should unique for each class that
034 * is proxied.
035 * <p>Configuration should be done through Spring, probably using BeanNameAutoProxyCreator.
036 * <p>NOTE: One important assumption here is that the arguments for the methods being cached will all have
037 * {@link Object#toString()} implementations such that o1.equals(o2) iff o1.toString().equals(o2.toString()).
038 * @author Kuali Rice Team (rice.collab@kuali.org)
039 */
040 public class CachingInterceptor implements MethodInterceptor {
041
042 private static Log LOG = LogFactory.getLog(CachingInterceptor.class);
043
044 private Set<String> clearCacheOnMethods = new HashSet<String>(50);
045 private Set<String> cacheForMethods = new HashSet<String>(50);
046 private String cacheGroupName = "CachingInterceptor";
047
048 private static final Object NULL_OBJECT = new Object();
049
050 public void setClearCacheOnMethods(String [] methods) {
051 for (String method : methods) {
052 clearCacheOnMethods.add(method);
053 }
054 }
055
056 public void setCacheForMethods(String [] methods) {
057 for (String method : methods) {
058 cacheForMethods.add(method);
059 }
060 }
061
062 public void setCacheGroupName(String name) {
063 cacheGroupName = name;
064 }
065
066 /**
067 * This overridden method intercepts calls, and
068 * <ul>
069 * <li>if the intercepted method is in clearCacheOnMethods
070 * <ul><li>the cache is purged, then the method is called and any results returned.</ul>
071 * <li>if the intercepted method is in cacheForMethods
072 * <ul><li>the cache is checked. On a hit, cached results are returned. On a miss, the invocation
073 * occurrs and the results are cached.</ul>
074 * <li>otherwise
075 * <ul><li>the method is called and any results returned</ul>
076 * </ul>
077 * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
078 */
079 @SuppressWarnings("unchecked")
080 public Object invoke(MethodInvocation invocation) throws Throwable {
081
082 Object results = null;
083
084 String methodName = invocation.getMethod().getName();
085
086 if (clearCacheOnMethods.contains(methodName)) {
087 if (LOG.isTraceEnabled()) { LOG.trace("clearing cache group " +cacheGroupName+" on " + methodName); }
088 // clear the cache
089 KEWServiceLocator.getCacheAdministrator().flushGroup(cacheGroupName);
090 results = invocation.proceed();
091
092 } else if (cacheForMethods.contains(methodName)) {
093 boolean gotCachedResults = false;
094 // attempt to use the cache;
095 String cacheKey = getCacheKey(methodName, invocation.getArguments());
096 if (cacheKey != null) {
097 SoftReference<Object> resultRef =
098 (SoftReference<Object>) KEWServiceLocator.getCacheAdministrator().getFromCache(cacheKey);
099
100 if (resultRef != null) { results = resultRef.get(); }
101 if (null != results) {
102 if (LOG.isTraceEnabled()) { LOG.trace("cache hit in group " +cacheGroupName+" on " + methodName); }
103 gotCachedResults = true;
104 }
105 }
106
107 if (!gotCachedResults) {
108 if (LOG.isTraceEnabled()) { LOG.trace("cache miss in group " +cacheGroupName+" on " + methodName); }
109
110 results = invocation.proceed();
111 if (results == null) { results = NULL_OBJECT; /* we'll cache a null result too */ }
112
113 // add to the cache
114 if (cacheKey != null) {
115 KEWServiceLocator.getCacheAdministrator().
116 putInCache(cacheKey, new SoftReference<Object>(results), cacheGroupName);
117 }
118 }
119 } else {
120 if (LOG.isTraceEnabled()) { LOG.trace("no caching enabled in group " +cacheGroupName+" on " + methodName); }
121 results = invocation.proceed();
122 }
123
124 return (NULL_OBJECT == results) ? null : results;
125 }
126
127 /**
128 * build a cache key based on the method name and the parameters
129 *
130 * @param methodName
131 * @param args
132 * @return
133 */
134 private String getCacheKey(String methodName, Object [] args) {
135 StringBuilder sb = new StringBuilder(cacheGroupName);
136 sb.append(".");
137 sb.append(methodName);
138 sb.append(":");
139 boolean first = true;
140 for (Object arg : args) {
141 if (first) {
142 first = false;
143 } else {
144 sb.append(",");
145 }
146 sb.append((arg == null) ? "null" : arg.toString());
147 }
148
149 return sb.toString();
150 }
151 }