1 package org.apache.fulcrum.cache.impl;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.ByteArrayOutputStream;
23 import java.io.IOException;
24 import java.io.ObjectOutputStream;
25 import java.io.Serializable;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Set;
29
30 import org.apache.avalon.framework.activity.Disposable;
31 import org.apache.avalon.framework.activity.Initializable;
32 import org.apache.avalon.framework.configuration.Configurable;
33 import org.apache.avalon.framework.configuration.Configuration;
34 import org.apache.avalon.framework.configuration.ConfigurationException;
35 import org.apache.avalon.framework.logger.AbstractLogEnabled;
36 import org.apache.avalon.framework.thread.ThreadSafe;
37 import org.apache.commons.jcs3.JCS;
38 import org.apache.commons.jcs3.access.GroupCacheAccess;
39 import org.apache.commons.jcs3.access.exception.CacheException;
40 import org.apache.commons.jcs3.engine.ElementAttributes;
41 import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
42 import org.apache.fulcrum.cache.CachedObject;
43 import org.apache.fulcrum.cache.GlobalCacheService;
44 import org.apache.fulcrum.cache.ObjectExpiredException;
45 import org.apache.fulcrum.cache.RefreshableCachedObject;
46
47
48
49
50
51
52
53 public class JCSCacheService extends AbstractLogEnabled implements
54 GlobalCacheService, Runnable, Configurable, Disposable, Initializable,
55 ThreadSafe
56 {
57
58
59
60
61 public static final long DEFAULT_CACHE_CHECK_FREQUENCY = 5000;
62
63
64
65
66 private long cacheCheckFrequency;
67
68
69
70
71 private GroupCacheAccess<String, CachedObject<?>> cacheManager;
72
73
74
75
76 private String region;
77
78
79
80
81 private String configFile;
82
83
84
85
86 private static String group = "default_group";
87
88
89 private Thread refreshing;
90
91
92 private boolean continueThread;
93
94 public JCSCacheService()
95 {
96
97 }
98
99
100
101
102
103
104 @Override
105 public void configure(Configuration config) throws ConfigurationException
106 {
107 this.cacheCheckFrequency = config.getChild("cacheCheckFrequency")
108 .getValueAsLong(DEFAULT_CACHE_CHECK_FREQUENCY);
109 this.region = config.getChild("region").getValue("fulcrum");
110 this.configFile = config.getChild("configurationFile").getValue(
111 "/cache.ccf");
112 }
113
114
115
116
117 @Override
118 public void initialize() throws Exception
119 {
120 JCS.setConfigFilename(this.configFile);
121 this.cacheManager = JCS.getGroupCacheInstance(this.region);
122
123
124 this.continueThread = true;
125 this.refreshing = new Thread(this);
126
127
128
129
130
131 this.refreshing.setDaemon(true);
132 this.refreshing.setName("JCSCacheService Refreshing");
133 this.refreshing.start();
134
135 getLogger().debug("JCSCacheService started.");
136 }
137
138
139
140
141 @Override
142 public void dispose()
143 {
144 this.continueThread = false;
145 this.refreshing.interrupt();
146
147 this.cacheManager.dispose();
148 this.cacheManager = null;
149 CompositeCacheManager.getInstance().shutDown();
150
151 getLogger().debug("JCSCacheService stopped.");
152 }
153
154
155
156
157 @Override
158 public <T> CachedObject<T> getObject(final String objectId) throws ObjectExpiredException
159 {
160 @SuppressWarnings("unchecked")
161 CachedObject<T> cachedObject = (CachedObject<T>)this.cacheManager.getFromGroup(objectId, group);
162
163 if (cachedObject == null)
164 {
165
166 throw new ObjectExpiredException();
167 }
168
169 if (cachedObject.isStale())
170 {
171 if (cachedObject instanceof RefreshableCachedObject)
172 {
173 RefreshableCachedObject<?> refreshableObject = (RefreshableCachedObject<?>) cachedObject;
174 if (refreshableObject.isUntouched())
175 {
176
177 removeObject(objectId);
178 throw new ObjectExpiredException();
179 }
180
181
182 refreshableObject.refresh();
183 if (refreshableObject.isStale())
184 {
185
186 removeObject(objectId);
187 throw new ObjectExpiredException();
188 }
189 }
190 else
191 {
192
193 removeObject(objectId);
194 throw new ObjectExpiredException();
195 }
196 }
197
198 if (cachedObject instanceof RefreshableCachedObject)
199 {
200
201 RefreshableCachedObject<?> refreshableCachedObject = (RefreshableCachedObject<?>) cachedObject;
202 refreshableCachedObject.touch();
203 }
204
205 return cachedObject;
206 }
207
208
209
210
211
212 @Override
213 public <T> void addObject(String objectId, CachedObject<T> cachedObject)
214 {
215 try
216 {
217 if (!(cachedObject.getContents() instanceof Serializable))
218 {
219 getLogger()
220 .warn(
221 "Object (contents) with id ["
222 + objectId
223 + "] is not serializable. Expect problems with auxiliary caches: " +
224 cachedObject.getContents().getClass().getSimpleName());
225 }
226
227 ElementAttributes attrib = (ElementAttributes) this.cacheManager.getDefaultElementAttributes();
228
229 if (cachedObject instanceof RefreshableCachedObject)
230 {
231 attrib.setIsEternal(true);
232 }
233 else
234 {
235 attrib.setIsEternal(false);
236
237 double tmp0 = ((double) (cachedObject.getExpires() + 500)) / 1000;
238 getLogger().debug( "setting maxlife seconds (minimum 1sec) from expiry + 0.5s: " + (int)tmp0 );
239 attrib.setMaxLife( tmp0 > 0 ? (int) Math.floor( tmp0 ) : 1 );
240 }
241
242 attrib.setLastAccessTimeNow();
243 attrib.setCreateTime();
244
245 this.cacheManager.putInGroup(objectId, group, cachedObject, attrib);
246 }
247 catch (CacheException e)
248 {
249 getLogger().error("Could not add object " + objectId + " to cache", e);
250 }
251 }
252
253
254
255
256 @Override
257 public void removeObject(String objectId)
258 {
259 this.cacheManager.removeFromGroup(objectId, group);
260 }
261
262
263
264
265 @Override
266 public List<String> getKeys()
267 {
268 ArrayList<String> keys = new ArrayList<>();
269 keys.addAll(this.cacheManager.getGroupKeys(group));
270 return keys;
271 }
272
273
274
275
276 @Override
277 public List<CachedObject<?>> getCachedObjects()
278 {
279 ArrayList<CachedObject<?>> values = new ArrayList<>();
280 for (String key : this.cacheManager.getGroupKeys(group))
281 {
282 CachedObject<?> cachedObject = this.cacheManager.getFromGroup(key, group);
283 if (cachedObject != null)
284 {
285 values.add(cachedObject);
286 }
287 }
288
289 return values;
290 }
291
292
293
294
295
296 @Override
297 public void run()
298 {
299 while (this.continueThread)
300 {
301
302
303 try
304 {
305 Thread.sleep(this.cacheCheckFrequency);
306 }
307 catch (InterruptedException exc)
308 {
309 if (!this.continueThread)
310 {
311 return;
312 }
313 }
314
315 for (String key : this.cacheManager.getGroupKeys(group))
316 {
317 CachedObject<?> cachedObject = this.cacheManager.getFromGroup(key, group);
318 if (cachedObject == null)
319 {
320 removeObject(key);
321 }
322 else
323 {
324 if (cachedObject instanceof RefreshableCachedObject)
325 {
326 RefreshableCachedObject<?> refreshableObject = (RefreshableCachedObject<?>) cachedObject;
327 if (refreshableObject.isUntouched())
328 {
329 this.cacheManager.removeFromGroup(key, group);
330 }
331 else if (refreshableObject.isStale())
332 {
333 refreshableObject.refresh();
334 }
335 }
336 }
337 }
338 }
339 }
340
341
342
343
344 @Override
345 public int getCacheSize() throws IOException
346 {
347
348 ByteArrayOutputStream baos = new ByteArrayOutputStream();
349 ObjectOutputStream out = new ObjectOutputStream(baos);
350 Set<String> keys = this.cacheManager.getGroupKeys(group);
351
352 for (String key : keys)
353 {
354 out.writeObject(this.cacheManager.getFromGroup(key, group));
355 }
356
357 out.flush();
358
359
360
361
362
363
364 return baos.toByteArray().length - 4 * keys.size();
365 }
366
367
368
369
370 @Override
371 public int getNumberOfObjects()
372 {
373 int count = 0;
374
375 for (String key : this.cacheManager.getGroupKeys(group))
376 {
377 if (this.cacheManager.getFromGroup(key, group) != null)
378 {
379 count++;
380 }
381 }
382
383 return count;
384 }
385
386
387
388
389 @Override
390 public void flushCache()
391 {
392 this.cacheManager.invalidateGroup(group);
393 }
394 }