Coverage Report - org.kuali.rice.kew.useroptions.UserOptionsServiceCache
 
Classes in this File Line Coverage Branch Coverage Complexity
UserOptionsServiceCache
0%
0/35
0%
0/14
1.778
UserOptionsServiceCache$ClusterSafeMethodCache
0%
0/35
0%
0/14
1.778
 
 1  
 /*
 2  
  * Copyright 2007-2009 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
 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  
 package org.kuali.rice.kew.useroptions;
 17  
 
 18  
 import java.lang.ref.SoftReference;
 19  
 import java.util.Collection;
 20  
 import java.util.List;
 21  
 import java.util.Map;
 22  
 
 23  
 import org.kuali.rice.ksb.api.KsbApiServiceLocator;
 24  
 
 25  
 /**
 26  
  * This class decorates a UserOptionsService and provides distributed caching for findByOptionId calls, and 
 27  
  * intelligently clears cache entries on update/delete type calls.
 28  
  * 
 29  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 30  
  *
 31  
  */
 32  0
 public class UserOptionsServiceCache implements UserOptionsService {
 33  
         
 34  
         private UserOptionsService inner;
 35  
 
 36  0
         private final ClusterSafeMethodCache cache = new ClusterSafeMethodCache(UserOptionsService.class.getSimpleName()); 
 37  
         
 38  
         /**
 39  
          * clears any entry in the cache for this option id
 40  
          * @see org.kuali.rice.kew.useroptions.UserOptionsService#deleteUserOptions(org.kuali.rice.kew.useroptions.UserOptions)
 41  
          */
 42  
         public void deleteUserOptions(UserOptions userOptions) {
 43  0
                 if (userOptions != null && userOptions.getWorkflowId() != null) {
 44  0
                         cache.clearCacheEntry(userOptions.getWorkflowId(), "findByOptionId", userOptions.getOptionId(), userOptions.getWorkflowId());
 45  
                 }
 46  0
                 inner.deleteUserOptions(userOptions);
 47  0
         }
 48  
 
 49  
         /**
 50  
          * Checks the cache for results.  On cache miss, delegates the call and caches the result before returning.
 51  
          * @see org.kuali.rice.kew.useroptions.UserOptionsService#findByOptionId(java.lang.String, java.lang.String)
 52  
          */
 53  
         public UserOptions findByOptionId(String optionId, String principalId) {
 54  0
                 Object result = null;
 55  0
                 if (optionId != null && principalId != null) {
 56  0
                         String methodName = "findByOptionId";
 57  0
                         Object [] arguments = {optionId, principalId};
 58  
 
 59  0
                         result = cache.getFromCache(principalId, methodName, arguments);
 60  0
                         if (result == null) {
 61  0
                                 result = inner.findByOptionId(optionId, principalId);
 62  0
                                 cache.putInCache(principalId, methodName, (result != null) ? result : cache.NULL_OBJECT, arguments);
 63  
                         }
 64  0
                 } else {
 65  
                         // just delegate on bad inputs:
 66  0
                         result = inner.findByOptionId(optionId, principalId);
 67  
                 }
 68  
 
 69  0
             return (result == cache.NULL_OBJECT) ? null : (UserOptions) result;                
 70  
         }
 71  
 
 72  
         /**
 73  
          * This overridden method delegates the call.
 74  
          * @see org.kuali.rice.kew.useroptions.UserOptionsService#findByOptionValue(java.lang.String, java.lang.String)
 75  
          */
 76  
         public Collection<UserOptions> findByOptionValue(String optionId,
 77  
                         String optionValue) {
 78  
                 // no caching for this method since it doesn't discriminate by user
 79  0
                 return inner.findByOptionValue(optionId, optionValue);
 80  
         }
 81  
 
 82  
         /**
 83  
          * This overridden method delegates the call.
 84  
          * @see org.kuali.rice.kew.useroptions.UserOptionsService#findByUserQualified(java.lang.String, java.lang.String)
 85  
          */
 86  
         public List<UserOptions> findByUserQualified(String principalId,
 87  
                         String likeString) {
 88  
                 // no caching for this method since it could be pulling in saveRefreshUserOption generated UserOptions
 89  0
                 return inner.findByUserQualified(principalId, likeString);
 90  
         }
 91  
 
 92  
         /**
 93  
          * This overridden method delegates the call.
 94  
          * @see org.kuali.rice.kew.useroptions.UserOptionsService#findByWorkflowUser(java.lang.String)
 95  
          */
 96  
         public Collection<UserOptions> findByWorkflowUser(String principalId) {
 97  
                 // no caching for this method since it could be pulling in saveRefreshUserOption generated UserOptions
 98  0
                 return inner.findByWorkflowUser(principalId);
 99  
         }
 100  
 
 101  
         /**
 102  
          * This overridden method ...
 103  
          * @see org.kuali.rice.kew.useroptions.UserOptionsService#refreshActionList(java.lang.String)
 104  
          */
 105  
         public boolean refreshActionList(String principalId) {
 106  
         // ignore the cache for this user option, as these are generated with unique numbers and shouldn't be
 107  
             // retrieved more then once, as they are deleted once retrieved
 108  0
                 return inner.refreshActionList(principalId);
 109  
         }
 110  
 
 111  
         /**
 112  
          * This overridden method ...
 113  
          * 
 114  
          * @see org.kuali.rice.kew.useroptions.UserOptionsService#save(java.lang.String, java.util.Map)
 115  
          */
 116  
         public void save(String principalId, Map<String, String> optionsMap) {
 117  0
                 inner.save(principalId, optionsMap);
 118  0
                 cache.clearCacheGroup(principalId);
 119  0
         }
 120  
 
 121  
         /**
 122  
          * This overridden method ...
 123  
          * 
 124  
          * @see org.kuali.rice.kew.useroptions.UserOptionsService#save(java.lang.String, java.lang.String, java.lang.String)
 125  
          */
 126  
         public void save(String principalId, String optionId, String optionValue) {
 127  0
                 inner.save(principalId, optionId, optionValue);
 128  0
                 cache.clearCacheGroup(principalId);
 129  0
         }
 130  
 
 131  
         /**
 132  
          * This overridden method ...
 133  
          * 
 134  
          * @see org.kuali.rice.kew.useroptions.UserOptionsService#save(org.kuali.rice.kew.useroptions.UserOptions)
 135  
          */
 136  
         public void save(UserOptions userOptions) {
 137  0
                 inner.save(userOptions);
 138  0
                 cache.clearCacheGroup(userOptions.getWorkflowId());
 139  0
         }
 140  
 
 141  
         /**
 142  
          * This overridden method ...
 143  
          * 
 144  
          * @see org.kuali.rice.kew.useroptions.UserOptionsService#saveRefreshUserOption(java.lang.String)
 145  
          */
 146  
         public void saveRefreshUserOption(String principalId) {
 147  
                 // this shouldn't impact our simple cache since we're only caching single options queries, and this will 
 148  
                 // generate a key that hasn't been used previously
 149  0
                 inner.saveRefreshUserOption(principalId);
 150  0
         }
 151  
         
 152  
         /**
 153  
          * @param inner the decorated service
 154  
          */
 155  
         public void setInnerService(UserOptionsService inner) {
 156  0
                 this.inner = inner;
 157  0
         }
 158  
         
 159  
         /**
 160  
          * a helper class to encapsulate cluster safe cache functionality 
 161  
          * 
 162  
          * @author Kuali Rice Team (rice.collab@kuali.org)
 163  
          *
 164  
          */
 165  0
     private static class ClusterSafeMethodCache {
 166  
             
 167  0
             public static final Object NULL_OBJECT = new Object(); 
 168  
             public final String cachedServiceName;
 169  
             
 170  
             /**
 171  
                  * This constructs a ClusterSaveMethodCache
 172  
                  * 
 173  
                  * @param cachedServiceName a name for the service being cached
 174  
                  */
 175  0
                 public ClusterSafeMethodCache(String cachedServiceName) {
 176  0
                         this.cachedServiceName = cachedServiceName;
 177  0
                 }
 178  
             
 179  
             /**
 180  
              * build a cache key based on the user, method name and the parameters
 181  
              * 
 182  
              * @param methodName
 183  
              * @param args
 184  
              * @return
 185  
              */
 186  
             private String getCacheKey(String principalId, String methodName, Object ... args) {
 187  
                     
 188  0
                     StringBuilder sb = new StringBuilder(principalId);
 189  0
                     sb.append("/");
 190  0
                     sb.append(cachedServiceName);
 191  0
                     sb.append(".");
 192  0
                     sb.append(methodName);
 193  0
                     sb.append(":");
 194  0
                     boolean first = true;
 195  0
                     for (Object arg : args) {
 196  0
                             if (first) {
 197  0
                                     first = false;
 198  
                             } else {
 199  0
                                     sb.append(",");
 200  
                             }
 201  0
                             sb.append((arg == null) ? "null" : arg.toString());
 202  
                     }
 203  
                     
 204  0
                     return sb.toString();
 205  
             }
 206  
             
 207  
             /**
 208  
              * caches a method result
 209  
              * 
 210  
              * @param principalId the principal for whom the call is being cached
 211  
              * @param methodName the name of the method whose result is being cached
 212  
              * @param value 
 213  
              * @param keySource the parameters to the method call that is being cached.  These are used to build the cache key, so order is important. 
 214  
              */
 215  
             @SuppressWarnings("unchecked")
 216  
             public void putInCache(String principalId, String methodName, Object value, Object ... keySource) {
 217  0
                     if (value != null) {
 218  0
                             KsbApiServiceLocator.getCacheAdministrator().putInCache(getCacheKey(principalId, methodName, keySource), new SoftReference(value), getCacheGroup(principalId));
 219  
                     } else {
 220  0
                             KsbApiServiceLocator.getCacheAdministrator().putInCache(getCacheKey(principalId, methodName, keySource), NULL_OBJECT, getCacheGroup(principalId));
 221  
                     }
 222  0
             }
 223  
             
 224  
             /**
 225  
              * retrieves a method result from the cache.  If the static final NULL_OBJECT is returned then the cached result
 226  
              * was null.
 227  
              * 
 228  
              * @param principalId the principal for whom the call is being cached
 229  
              * @param methodName the name of the method whose result is being cached
 230  
              * @param keySource the parameters to the method call that is being cached.  These are used to build the cache key, so order is important. 
 231  
              * @return
 232  
              */
 233  
             @SuppressWarnings("unchecked")
 234  
             public Object getFromCache(String principalId, String methodName, Object ... keySource) {
 235  0
                     Object result = null;
 236  0
                     Object cacheReturned = KsbApiServiceLocator.getCacheAdministrator().getFromCache(getCacheKey(principalId, methodName, keySource));
 237  0
                     if (cacheReturned == NULL_OBJECT) {
 238  0
                             result = cacheReturned;
 239  
                     } else {
 240  0
                             SoftReference reference = (SoftReference)cacheReturned;
 241  0
                             if (reference != null) {
 242  0
                                     result = reference.get();
 243  
                             }
 244  
                     }
 245  0
                     return result;
 246  
             }
 247  
             
 248  
             /**
 249  
              * This method clears all cached calls for the given principal
 250  
              * 
 251  
              * @param principalId the principal for whom the cache is being cleared
 252  
              */
 253  
             public void clearCacheGroup(String principalId) {
 254  0
                     if (principalId != null) {
 255  0
                             KsbApiServiceLocator.getCacheAdministrator().flushGroup(getCacheGroup(principalId));
 256  
                     }
 257  0
             }
 258  
             
 259  
             /**
 260  
              * This method clears a cache entry for a given method call / arguments combination
 261  
              * 
 262  
              * @param principalId the principal for whom the cache entry is being cleared
 263  
              * @param methodName the name of the method whose result is being cached
 264  
              * @param keySource the parameters to the method call that is being cached.  These are used to build the cache key, so order is important.
 265  
              */
 266  
             public void clearCacheEntry(String principalId, String methodName, Object ... keySource) {
 267  0
                     KsbApiServiceLocator.getCacheAdministrator().flushEntry(getCacheKey(principalId, methodName, keySource));
 268  0
             }
 269  
             
 270  
             /**
 271  
              * This method gets the cache group name (an entire cache group can be cleared at once)
 272  
              * 
 273  
              * @param principalId the principal whose cache group name is being generated
 274  
              * @return the cache group name
 275  
              */
 276  
             private String getCacheGroup(String principalId) {
 277  0
                     return principalId + "/" + cachedServiceName;
 278  
             }
 279  
     }
 280  
         
 281  
 }