001    package org.apache.fulcrum.cache.impl;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.io.IOException;
023    import java.util.ArrayList;
024    import java.util.Iterator;
025    import java.util.List;
026    
027    import net.sf.ehcache.Cache;
028    import net.sf.ehcache.CacheManager;
029    import net.sf.ehcache.Element;
030    
031    import org.apache.avalon.framework.activity.Disposable;
032    import org.apache.avalon.framework.activity.Initializable;
033    import org.apache.avalon.framework.configuration.Configurable;
034    import org.apache.avalon.framework.configuration.Configuration;
035    import org.apache.avalon.framework.configuration.ConfigurationException;
036    import org.apache.avalon.framework.logger.AbstractLogEnabled;
037    import org.apache.avalon.framework.thread.ThreadSafe;
038    import org.apache.fulcrum.cache.CachedObject;
039    import org.apache.fulcrum.cache.GlobalCacheService;
040    import org.apache.fulcrum.cache.ObjectExpiredException;
041    import org.apache.fulcrum.cache.RefreshableCachedObject;
042    
043    /**
044     * Default implementation of EHCacheService
045     * 
046     * @author <a href="mailto:epughNOSPAM@opensourceconnections.com">Eric Pugh</a>
047     * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
048     * 
049     */
050    public class EHCacheService extends AbstractLogEnabled implements
051            GlobalCacheService, Runnable, Configurable, Disposable, Initializable, ThreadSafe
052    {
053        /**
054         * Cache check frequency in Millis (1000 Millis = 1 second). Value must be >
055         * 0. Default = 5 seconds
056         */
057        public static final long DEFAULT_CACHE_CHECK_FREQUENCY = 5000; // 5 seconds
058    
059        /**
060         * cacheCheckFrequency (default - 5 seconds)
061         */
062        private long cacheCheckFrequency;
063    
064        /**
065         * Path name of the JCS configuration file
066         */
067        private String configFile;
068    
069        /**
070         * Constant value which provides a cache name
071         */
072        private static final String DEFAULT_CACHE_NAME = "fulcrum";
073    
074        /**
075         * A cache name
076         */
077        private String cacheName;
078    
079        /** thread for refreshing stale items in the cache */
080        private Thread refreshing;
081    
082        /** flag to stop the housekeeping thread when the component is disposed. */
083        private boolean continueThread;
084    
085        /** The EHCache manager instance */
086        private CacheManager cacheManager;
087    
088        /** A cache instance */
089        private Cache cache;
090    
091        // ---------------- Avalon Lifecycle Methods ---------------------
092    
093        /**
094         * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
095         */
096        public void configure(Configuration config) throws ConfigurationException
097        {
098            this.cacheCheckFrequency = config.getChild("cacheCheckFrequency")
099                    .getValueAsLong(DEFAULT_CACHE_CHECK_FREQUENCY);
100            this.cacheName = config.getChild("cacheName").getValue(DEFAULT_CACHE_NAME);
101            this.configFile = config.getChild("configurationFile").getValue(null);
102        }
103    
104        /**
105         * @see org.apache.avalon.framework.activity.Initializable#initialize()
106         */
107        public void initialize() throws Exception
108        {
109            if (this.configFile == null)
110            {
111                this.cacheManager = new CacheManager();
112                this.cacheManager.addCache(this.cacheName);
113            }
114            else
115            {
116                this.cacheManager = new CacheManager(this.configFile);
117            }
118            
119            this.cache = this.cacheManager.getCache(this.cacheName);
120            
121            // Start housekeeping thread.
122            this.continueThread = true;
123            this.refreshing = new Thread(this);
124    
125            // Indicate that this is a system thread. JVM will quit only when
126            // there are no more active user threads. Settings threads spawned
127            // internally by Turbine as daemons allows commandline applications
128            // using Turbine to terminate in an orderly manner.
129            this.refreshing.setDaemon(true);
130            this.refreshing.setName("EHCacheService Refreshing");
131            this.refreshing.start();
132            
133            getLogger().debug("EHCacheService started!");
134        }
135    
136        /**
137         * @see org.apache.avalon.framework.activity.Disposable#dispose()
138         */
139        public void dispose()
140        {
141            this.continueThread = false;
142            this.refreshing.interrupt();
143    
144            this.cacheManager.shutdown();
145            this.cacheManager = null;
146            this.cache = null;
147            getLogger().debug("EHCacheService stopped!");
148        }
149        
150        /**
151         * @see org.apache.fulcrum.cache.GlobalCacheService#addObject(java.lang.String, org.apache.fulcrum.cache.CachedObject)
152         */
153        public void addObject(String id, CachedObject o)
154        {
155            Element cacheElement = new Element(id, o);
156    
157            if (o instanceof RefreshableCachedObject)
158            {
159                cacheElement.setEternal(true);
160            }
161            else
162            {
163                cacheElement.setEternal(false);
164                cacheElement.setTimeToLive((int)(o.getExpires() + 500) / 1000);
165            }
166    
167            cacheElement.setCreateTime();
168    
169            this.cache.put(cacheElement);
170        }
171    
172        /**
173         * @see org.apache.fulcrum.cache.GlobalCacheService#flushCache()
174         */
175        public void flushCache()
176        {
177            this.cache.removeAll();
178        }
179    
180        /**
181         * @see org.apache.fulcrum.cache.GlobalCacheService#getCachedObjects()
182         */
183        public List getCachedObjects()
184        {
185            ArrayList values = new ArrayList();
186    
187            for (Iterator i = getKeys().iterator(); i.hasNext();)
188            {
189                Element cachedElement = this.cache.get(i.next());
190                
191                if (cachedElement != null)
192                {
193                    values.add(cachedElement.getObjectValue());
194                }
195            }
196    
197            return values;
198        }
199    
200        /**
201         * @see org.apache.fulcrum.cache.GlobalCacheService#getCacheSize()
202         */
203        public int getCacheSize() throws IOException
204        {
205            return (int)this.cache.calculateInMemorySize();
206        }
207    
208        /**
209         * @see org.apache.fulcrum.cache.GlobalCacheService#getKeys()
210         */
211        public List getKeys()
212        {
213            return this.cache.getKeysWithExpiryCheck();
214        }
215    
216        /**
217         * @see org.apache.fulcrum.cache.GlobalCacheService#getNumberOfObjects()
218         */
219        public int getNumberOfObjects()
220        {
221            return getKeys().size();
222        }
223    
224        /**
225         * @see org.apache.fulcrum.cache.GlobalCacheService#getObject(java.lang.String)
226         */
227        public CachedObject getObject(String id) throws ObjectExpiredException
228        {
229            Element cachedElement = this.cache.get(id);
230            
231            if (cachedElement == null)
232            {
233                // Not in the cache.
234                throw new ObjectExpiredException();
235            }
236    
237            CachedObject obj = (CachedObject)cachedElement.getObjectValue();
238            
239            if (obj.isStale())
240            {
241                if (obj instanceof RefreshableCachedObject)
242                {
243                    RefreshableCachedObject rco = (RefreshableCachedObject) obj;
244                    if (rco.isUntouched())
245                    {
246                        // Do not refresh an object that has exceeded TimeToLive
247                        removeObject(id);
248                        throw new ObjectExpiredException();
249                    }
250    
251                    // Refresh Object
252                    rco.refresh();
253                    if (rco.isStale())
254                    {
255                        // Object is Expired, remove it from cache.
256                        removeObject(id);
257                        throw new ObjectExpiredException();
258                    }
259                }
260                else
261                {
262                    // Expired.
263                    removeObject(id);
264                    throw new ObjectExpiredException();
265                }
266            }
267    
268            if (obj instanceof RefreshableCachedObject)
269            {
270                // notify it that it's being accessed.
271                RefreshableCachedObject rco = (RefreshableCachedObject) obj;
272                rco.touch();
273            }
274    
275            return obj;
276        }
277    
278        /**
279         * @see org.apache.fulcrum.cache.GlobalCacheService#removeObject(java.lang.String)
280         */
281        public void removeObject(String id)
282        {
283            this.cache.remove(id);
284        }
285    
286        /**
287         * Circle through the cache and refresh stale objects. Frequency is
288         * determined by the cacheCheckFrequency property.
289         */
290        public void run()
291        {
292            while (this.continueThread)
293            {
294                // Sleep for amount of time set in cacheCheckFrequency -
295                // default = 5 seconds.
296                try
297                {
298                    Thread.sleep(this.cacheCheckFrequency);
299                }
300                catch (InterruptedException exc)
301                {
302                    if (!this.continueThread)
303                    {
304                        return;
305                    }
306                }
307    
308                for (Iterator i = getKeys().iterator(); i.hasNext();)
309                {
310                    String key = (String) i.next();
311                    
312                    Element cachedElement = this.cache.get(key);
313                    
314                    if (cachedElement == null)
315                    {
316                        this.cache.remove(key);
317                        continue;
318                    }
319                    
320                    Object o = cachedElement.getObjectValue();
321                    
322                    if (o instanceof RefreshableCachedObject)
323                    {
324                        RefreshableCachedObject rco = (RefreshableCachedObject) o;
325                        if (rco.isUntouched())
326                        {
327                            this.cache.remove(key);
328                        }
329                        else if (rco.isStale())
330                        {
331                            rco.refresh();
332                        }
333                    }
334                }
335            }
336        }
337    }