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 }