Coverage Report - org.kuali.rice.kns.util.cache.MethodCacheNoCopyInterceptor
 
Classes in this File Line Coverage Branch Coverage Complexity
MethodCacheNoCopyInterceptor
0%
0/80
0%
0/40
4.375
 
 1  
 /*
 2  
  * Copyright 2002-2004 the original author or authors.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *      http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  * 
 16  
  * Code obtained from http://opensource.atlassian.com/confluence/spring/display/DISC/Caching+the+result+of+methods+using+Spring+and+EHCache
 17  
  * and modified for use within Kuali
 18  
  */
 19  
  
 20  
 // begin Kuali Foundation modification
 21  
 package org.kuali.rice.kns.util.cache;
 22  
 
 23  
 // Kuali Foundation modification: changed some imports
 24  
 import java.io.Serializable;
 25  
 import java.util.Collection;
 26  
 import java.util.Collections;
 27  
 import java.util.List;
 28  
 import java.util.Map;
 29  
 import java.util.Set;
 30  
 import java.util.SortedMap;
 31  
 import java.util.SortedSet;
 32  
 
 33  
 import org.aopalliance.intercept.MethodInterceptor;
 34  
 import org.aopalliance.intercept.MethodInvocation;
 35  
 import org.apache.log4j.Logger;
 36  
 import org.springframework.beans.factory.InitializingBean;
 37  
 import org.springframework.util.Assert;
 38  
 
 39  
 import com.opensymphony.oscache.base.Cache;
 40  
 import com.opensymphony.oscache.base.NeedsRefreshException;
 41  
 
 42  
 /**
 43  
  * begin Kuali Foundation modification
 44  
  * This class implements org.aopalliance.intercept.MethodInterceptor. This interceptor builds the cache key for the method and
 45  
  * checks if an earlier result was cached with that key. If so, the cached result is returned; otherwise, the intercepted method is
 46  
  * called and the result cached for future use.
 47  
  * end Kuali Foundation modification
 48  
  * 
 49  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 50  
  * @since 2004.10.07
 51  
  */
 52  0
 public class MethodCacheNoCopyInterceptor implements MethodInterceptor, InitializingBean {
 53  0
     private static final Logger LOG = Logger.getLogger(MethodCacheNoCopyInterceptor.class);
 54  
 
 55  
     private Cache cache;
 56  
     // begin Kuali Foundation modification
 57  0
     private int expirationTimeInSeconds = 1000;
 58  
     // end Kuali Foundation modification
 59  
 
 60  
     /**
 61  
      * begin Kuali Foundation modification
 62  
      * @param cache name of cache to be used
 63  
      * end Kuali Foundation modification
 64  
      */
 65  
     public void setCache(Cache cache) {
 66  0
         this.cache = cache;
 67  0
     }
 68  
 
 69  
     // begin Kuali Foundation modification
 70  
     /**
 71  
      * Entries older than this will have their contents replaced by the return value from a call to the appropriate method
 72  
      * 
 73  
      * @param expirationTimeInSeconds
 74  
      */
 75  
     public void setExpirationTimeInSeconds(int expirationTimeInSeconds) {
 76  0
         this.expirationTimeInSeconds = expirationTimeInSeconds;
 77  0
     }
 78  
 
 79  
     // end Kuali Foundation modification
 80  
 
 81  
 
 82  
     /**
 83  
      * Checks if required attributes are provided.
 84  
      * 
 85  
      * begin Kuali Foundation modification
 86  
      * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
 87  
      * end Kuali Foundation modification
 88  
      */
 89  
     public void afterPropertiesSet() throws Exception {
 90  0
         Assert.notNull(cache, "A cache is required. Use setCache(Cache) to provide one.");
 91  0
     }
 92  
 
 93  
     /**
 94  
      * begin Kuali Foundation modification
 95  
      * Caches method results, if possible.
 96  
      * <p>
 97  
      * Results must be Serializable to be cached. Method with unSerializable results will never have their results cached, and will
 98  
      * log error messages complaining about that fact.
 99  
      * 
 100  
      * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
 101  
      * end Kuali Foundation modification
 102  
      */
 103  
     @SuppressWarnings("unchecked")
 104  
         public Object invoke(MethodInvocation invocation) throws Throwable {
 105  
         // begin Kuali Foundation modification
 106  0
         boolean cancelUpdate = true;
 107  
 
 108  0
         Object methodResult = null;
 109  0
         String cacheKey = buildCacheKey(invocation);
 110  
 
 111  
         // lookup result in cache
 112  0
         if (LOG.isDebugEnabled()) {
 113  0
             LOG.debug("looking for method result for invocation '" + cacheKey + "'");
 114  
         }
 115  
         try {
 116  0
             methodResult = cache.getFromCache(cacheKey, expirationTimeInSeconds);
 117  0
             if (LOG.isDebugEnabled()) {
 118  0
                 LOG.debug("using cached result invocation '" + cacheKey + "'");
 119  
             }
 120  
 
 121  0
             cancelUpdate = false;
 122  0
         }
 123  0
         catch (NeedsRefreshException e) {
 124  
             // call intercepted method
 125  
             try {
 126  0
                 if (LOG.isDebugEnabled()) {
 127  0
                     LOG.debug("calling intercepted method for invocation '" + cacheKey + "'");
 128  
                 }
 129  0
                 methodResult = invocation.proceed();
 130  
             }
 131  0
             catch (Exception invocationException) {
 132  0
                 LOG.warn("unable to cache methodResult: caught exception invoking intercepted method: '" + invocationException);
 133  0
                 throw invocationException;
 134  0
             }
 135  
 
 136  
             // cache method result, if possible
 137  0
             if ( methodResult == null || Serializable.class.isAssignableFrom(methodResult.getClass() ) ) {
 138  
                 try {
 139  0
                     if (LOG.isDebugEnabled()) {
 140  0
                         LOG.debug("caching results for invocation '" + cacheKey + "'");
 141  
                     }
 142  
                     // ensure that items like lists/maps/collections are not modified once returned
 143  0
                     if ( methodResult != null ) {
 144  0
                             if ( methodResult instanceof SortedMap ) {
 145  0
                                     methodResult = Collections.unmodifiableSortedMap((SortedMap)methodResult);
 146  0
                             } else if ( methodResult instanceof Map ) {
 147  0
                                     methodResult = Collections.unmodifiableMap((Map)methodResult);
 148  0
                             } else if ( methodResult instanceof List ) {
 149  0
                                     methodResult = Collections.unmodifiableList((List)methodResult);
 150  0
                             } else if ( methodResult instanceof SortedSet ) {
 151  0
                                     methodResult = Collections.unmodifiableSortedSet((SortedSet)methodResult);
 152  0
                             } else if ( methodResult instanceof Set ) {
 153  0
                                     methodResult = Collections.unmodifiableSet((Set)methodResult);
 154  0
                             } else if ( methodResult instanceof Collection ) {
 155  0
                                     methodResult = Collections.unmodifiableCollection((Collection)methodResult);
 156  
                             }
 157  
                     }
 158  0
                     cache.putInCache(cacheKey, methodResult);
 159  
     
 160  
                     // adding, not updating
 161  0
                     cancelUpdate = false;
 162  0
                 } catch (Exception cacheException) {
 163  0
                     LOG.error("unable to cache methodResult: caught exception invoking putInCache: '" + cacheException);
 164  0
                     throw cacheException;
 165  0
                 }
 166  
             }
 167  0
         }
 168  
         finally {
 169  
             // it is imperative that you call cancelUpdate if you aren't going to update the cache entry
 170  0
             if (cancelUpdate) {
 171  0
                 cache.cancelUpdate(cacheKey);
 172  
             }
 173  0
         }
 174  
 
 175  
 
 176  0
         return methodResult;
 177  
         // end Kuali Foundation modification
 178  
     }
 179  
 
 180  
     // begin Kuali Foundation modification
 181  
     /**
 182  
      * @param invocation MethodInvocation being handled
 183  
      * @return cache key: className.methodName(paramClass=argValue[,paramClass=argValue...])
 184  
      */
 185  
     private String buildCacheKey(MethodInvocation invocation) {
 186  0
         return buildCacheKey(invocation.getStaticPart().toString(), invocation.getArguments());
 187  
     }
 188  
 
 189  
 
 190  
     /**
 191  
      * @param className
 192  
      * @param methodName
 193  
      * @param paramTypes
 194  
      * @param argValues
 195  
      * @return cache key: className.methodName(paramClass=argValue[,paramClass=argValue...])
 196  
      */
 197  
     public String buildCacheKey(String methodSignature, Object[] argValues) {
 198  0
         StringBuffer cacheKey = new StringBuffer(methodSignature);
 199  0
         cacheKey.append(": ");
 200  0
         if (argValues != null) {
 201  0
             for (int i = 0; i < argValues.length; i++) {
 202  0
                 if (i > 0) {
 203  0
                     cacheKey.append(",");
 204  
                 }
 205  
                 // handle weird cache bug:
 206  
                 // if you call a one-arg method foo with a null arg i.e. foo(null),
 207  
                 // and then call it with an argument whose toString evaluates to "null",
 208  
                 // OSCache gets stuck in an infinite wait() call because it somehow thinks
 209  
                 // another thread is already updating this cache entry
 210  
                 //
 211  
                 // workaround: change so that args which are actually null literal have
 212  
                 // some weird, unlikely-to-be-encountered String representation
 213  0
                 if (argValues[i] == null) {
 214  0
                     cacheKey.append("<literal null>");
 215  
                 }
 216  
                 else {
 217  0
                     cacheKey.append(argValues[i]);
 218  
                 }
 219  
             }
 220  
         }
 221  0
         return cacheKey.toString();
 222  
     }
 223  
 
 224  
 
 225  
     /**
 226  
      * @param key
 227  
      * @return true if the cache contains an entry with the given key
 228  
      */
 229  
     public boolean containsCacheKey(String key) {
 230  0
         boolean contains = false;
 231  
 
 232  
         try {
 233  0
             cache.getFromCache(key);
 234  0
             contains = true;
 235  
         }
 236  0
         catch (NeedsRefreshException e) {
 237  
             // it is imperative that you call cancelUpdate if you aren't going to update the cache entry that caused the
 238  
             // NeedsRefreshException above
 239  0
             cache.cancelUpdate(key);
 240  0
             contains = false;
 241  0
         }
 242  
 
 243  0
         return contains;
 244  
     }
 245  
     /** 
 246  
      * Removes a method cache if one exists for the given key.
 247  
      * @param cacheKey - key for method signature and parameters - see buildCacheKey
 248  
      */
 249  
     public void removeCacheKey(String cacheKey) {
 250  0
       if (!containsCacheKey(cacheKey)) {
 251  0
           return;
 252  
       }
 253  
       
 254  0
       if ( LOG.isDebugEnabled() ) {
 255  0
               LOG.debug("removing method cache for key: " + cacheKey);
 256  
       }
 257  0
       cache.cancelUpdate(cacheKey);
 258  0
       cache.flushEntry(cacheKey);
 259  0
     }
 260  
     
 261  
     // Kuali Foundation modification: removed getCacheKey(String, String, Object[])
 262  
     // end Kuali Foundation modification
 263  
 }