Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
UserOptionsServiceCache |
|
| 1.7777777777777777;1.778 | ||||
UserOptionsServiceCache$ClusterSafeMethodCache |
|
| 1.7777777777777777;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 | } |