View Javadoc
1   package org.apache.fulcrum.cache.impl;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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   * Default implementation of JCSCacheService
49   *
50   * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
51   * @version $Id$
52   */
53  public class JCSCacheService extends AbstractLogEnabled implements
54          GlobalCacheService, Runnable, Configurable, Disposable, Initializable,
55          ThreadSafe
56  {
57      /**
58       * Cache check frequency in Millis (1000 Millis = 1 second). Value must be &gt;
59       * 0. Default = 5 seconds
60       */
61      public static final long DEFAULT_CACHE_CHECK_FREQUENCY = 5000; // 5 seconds
62  
63      /**
64       * cacheCheckFrequency (default - 5 seconds)
65       */
66      private long cacheCheckFrequency;
67  
68      /**
69       * Instance of the JCS cache
70       */
71      private GroupCacheAccess<String, CachedObject<?>> cacheManager;
72  
73      /**
74       * JCS region to use
75       */
76      private String region;
77  
78      /**
79       * Path name of the JCS configuration file
80       */
81      private String configFile;
82  
83      /**
84       * Constant value which provides a group name
85       */
86      private static String group = "default_group";
87  
88      /** thread for refreshing stale items in the cache */
89      private Thread refreshing;
90  
91      /** flag to stop the housekeeping thread when the component is disposed. */
92      private boolean continueThread;
93      
94      public JCSCacheService()
95      {
96          
97      }
98  
99      // ---------------- Avalon Lifecycle Methods ---------------------
100 
101     /**
102      * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
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      * @see org.apache.avalon.framework.activity.Initializable#initialize()
116      */
117     @Override
118 	public void initialize() throws Exception
119     {
120         JCS.setConfigFilename(this.configFile);
121         this.cacheManager = JCS.getGroupCacheInstance(this.region);
122 
123         // Start housekeeping thread.
124         this.continueThread = true;
125         this.refreshing = new Thread(this);
126 
127         // Indicate that this is a system thread. JVM will quit only when
128         // there are no more active user threads. Settings threads spawned
129         // internally by Turbine as daemons allows commandline applications
130         // using Turbine to terminate in an orderly manner.
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      * @see org.apache.avalon.framework.activity.Disposable#dispose()
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      * @see org.apache.fulcrum.cache.GlobalCacheService#getObject(java.lang.String)
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             // Not in the cache.
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                     // Do not refresh an object that has exceeded TimeToLive
177                     removeObject(objectId);
178                     throw new ObjectExpiredException();
179                 }
180 
181                 // Refresh Object
182                 refreshableObject.refresh();
183                 if (refreshableObject.isStale())
184                 {
185                     // Object is Expired, remove it from cache.
186                     removeObject(objectId);
187                     throw new ObjectExpiredException();
188                 }
189             }
190             else
191             {
192                 // Expired.
193                 removeObject(objectId);
194                 throw new ObjectExpiredException();
195             }
196         }
197 
198         if (cachedObject instanceof RefreshableCachedObject)
199         {
200             // notify it that it's being accessed.
201             RefreshableCachedObject<?> refreshableCachedObject = (RefreshableCachedObject<?>) cachedObject;
202             refreshableCachedObject.touch();
203         }
204 
205         return cachedObject;
206     }
207 
208     /**
209      * @see org.apache.fulcrum.cache.GlobalCacheService#addObject(java.lang.String,
210      *      org.apache.fulcrum.cache.CachedObject)
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                 // expires in millis, maxlife in seconds
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      * @see org.apache.fulcrum.cache.GlobalCacheService#removeObject(java.lang.String)
255      */
256     @Override
257 	public void removeObject(String objectId)
258     {
259         this.cacheManager.removeFromGroup(objectId, group);
260     }
261 
262     /**
263      * @see org.apache.fulcrum.cache.GlobalCacheService#getKeys()
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      * @see org.apache.fulcrum.cache.GlobalCacheService#getCachedObjects()
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      * Circle through the cache and refresh stale objects. Frequency is
294      * determined by the cacheCheckFrequency property.
295      */
296     @Override
297 	public void run()
298     {
299         while (this.continueThread)
300         {
301             // Sleep for amount of time set in cacheCheckFrequency -
302             // default = 5 seconds.
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      * @see org.apache.fulcrum.cache.GlobalCacheService#getCacheSize()
343      */
344     @Override
345 	public int getCacheSize() throws IOException
346     {
347         // This is evil!
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         // Subtract 4 bytes from the length, because the serialization
361         // magic number (2 bytes) and version number (2 bytes) are
362         // both written to the stream before the object
363         //
364         return baos.toByteArray().length - 4 * keys.size();
365     }
366 
367     /**
368      * @see org.apache.fulcrum.cache.GlobalCacheService#getNumberOfObjects()
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      * @see org.apache.fulcrum.cache.GlobalCacheService#flushCache()
388      */
389     @Override
390 	public void flushCache()
391     {
392         this.cacheManager.invalidateGroup(group);
393     }
394 }