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.ByteArrayOutputStream;
023    import java.io.IOException;
024    import java.io.ObjectOutputStream;
025    import java.io.Serializable;
026    import java.util.ArrayList;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Set;
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    import org.apache.jcs.JCS;
043    import org.apache.jcs.access.exception.CacheException;
044    import org.apache.jcs.engine.ElementAttributes;
045    
046    /**
047     * Default implementation of JCSCacheService
048     * 
049     * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
050     * @version $Id:$
051     */
052    public class JCSCacheService extends AbstractLogEnabled implements
053            GlobalCacheService, Runnable, Configurable, Disposable, Initializable,
054            ThreadSafe
055    {
056        /**
057         * Cache check frequency in Millis (1000 Millis = 1 second). Value must be >
058         * 0. Default = 5 seconds
059         */
060        public static final long DEFAULT_CACHE_CHECK_FREQUENCY = 5000; // 5 seconds
061    
062        /**
063         * cacheCheckFrequency (default - 5 seconds)
064         */
065        private long cacheCheckFrequency;
066    
067        /**
068         * Instance of the JCS cache
069         */
070        private JCS cacheManager;
071    
072        /**
073         * JCS region to use
074         */
075        private String region;
076    
077        /**
078         * Path name of the JCS configuration file
079         */
080        private String configFile;
081    
082        /**
083         * Constant value which provides a group name
084         */
085        private static String group = "default_group";
086    
087        /** thread for refreshing stale items in the cache */
088        private Thread refreshing;
089    
090        /** flag to stop the housekeeping thread when the component is disposed. */
091        private boolean continueThread;
092    
093        // ---------------- Avalon Lifecycle Methods ---------------------
094    
095        /**
096         * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
097         */
098        public void configure(Configuration config) throws ConfigurationException
099        {
100            this.cacheCheckFrequency = config.getChild("cacheCheckFrequency")
101                    .getValueAsLong(DEFAULT_CACHE_CHECK_FREQUENCY);
102            this.region = config.getChild("region").getValue("fulcrum");
103            this.configFile = config.getChild("configurationFile").getValue(
104                    "/cache.ccf");
105        }
106    
107        /**
108         * @see org.apache.avalon.framework.activity.Initializable#initialize()
109         */
110        public void initialize() throws Exception
111        {
112            JCS.setConfigFilename(this.configFile);
113            this.cacheManager = JCS.getInstance(this.region);
114    
115            // Start housekeeping thread.
116            this.continueThread = true;
117            this.refreshing = new Thread(this);
118    
119            // Indicate that this is a system thread. JVM will quit only when
120            // there are no more active user threads. Settings threads spawned
121            // internally by Turbine as daemons allows commandline applications
122            // using Turbine to terminate in an orderly manner.
123            this.refreshing.setDaemon(true);
124            this.refreshing.setName("JCSCacheService Refreshing");
125            this.refreshing.start();
126    
127            getLogger().debug("JCSCacheService started.");
128        }
129    
130        /**
131         * @see org.apache.avalon.framework.activity.Disposable#dispose()
132         */
133        public void dispose()
134        {
135            this.continueThread = false;
136            this.refreshing.interrupt();
137    
138            this.cacheManager.dispose();
139            this.cacheManager = null;
140    
141            getLogger().debug("JCSCacheService stopped.");
142        }
143    
144        /**
145         * @see org.apache.fulcrum.cache.GlobalCacheService#getObject(java.lang.String)
146         */
147        public CachedObject getObject(String id) throws ObjectExpiredException
148        {
149            CachedObject obj = (CachedObject) this.cacheManager.getFromGroup(id,
150                    group);
151    
152            if (obj == null)
153            {
154                // Not in the cache.
155                throw new ObjectExpiredException();
156            }
157    
158            if (obj.isStale())
159            {
160                if (obj instanceof RefreshableCachedObject)
161                {
162                    RefreshableCachedObject rco = (RefreshableCachedObject) obj;
163                    if (rco.isUntouched())
164                    {
165                        // Do not refresh an object that has exceeded TimeToLive
166                        removeObject(id);
167                        throw new ObjectExpiredException();
168                    }
169    
170                    // Refresh Object
171                    rco.refresh();
172                    if (rco.isStale())
173                    {
174                        // Object is Expired, remove it from cache.
175                        removeObject(id);
176                        throw new ObjectExpiredException();
177                    }
178                }
179                else
180                {
181                    // Expired.
182                    removeObject(id);
183                    throw new ObjectExpiredException();
184                }
185            }
186    
187            if (obj instanceof RefreshableCachedObject)
188            {
189                // notify it that it's being accessed.
190                RefreshableCachedObject rco = (RefreshableCachedObject) obj;
191                rco.touch();
192            }
193    
194            return obj;
195        }
196    
197        /**
198         * @see org.apache.fulcrum.cache.GlobalCacheService#addObject(java.lang.String,
199         *      org.apache.fulcrum.cache.CachedObject)
200         */
201        public void addObject(String id, CachedObject o)
202        {
203            try
204            {
205                if (!(o.getContents() instanceof Serializable))
206                {
207                    getLogger()
208                            .warn(
209                                    "Object with id ["
210                                            + id
211                                            + "] is not serializable. Expect problems with auxiliary caches.");
212                }
213    
214                ElementAttributes attrib = (ElementAttributes) this.cacheManager
215                        .getDefaultElementAttributes();
216    
217                if (o instanceof RefreshableCachedObject)
218                {
219                    attrib.setIsEternal(true);
220                }
221                else
222                {
223                    attrib.setIsEternal(false);
224                    attrib.setMaxLifeSeconds((o.getExpires() + 500) / 1000);
225                }
226    
227                attrib.setLastAccessTimeNow();
228                attrib.setCreateTime();
229    
230                this.cacheManager.putInGroup(id, group, o, attrib);
231            }
232            catch (CacheException e)
233            {
234                getLogger().error("Could not add object " + id + " to cache", e);
235            }
236        }
237    
238        /**
239         * @see org.apache.fulcrum.cache.GlobalCacheService#removeObject(java.lang.String)
240         */
241        public void removeObject(String id)
242        {
243            this.cacheManager.remove(id, group);
244        }
245    
246        /**
247         * @see org.apache.fulcrum.cache.GlobalCacheService#getKeys()
248         */
249        public List getKeys()
250        {
251            ArrayList keys = new ArrayList();
252    
253            keys.addAll(this.cacheManager.getGroupKeys(group));
254            return keys;
255        }
256    
257        /**
258         * @see org.apache.fulcrum.cache.GlobalCacheService#getCachedObjects()
259         */
260        public List getCachedObjects()
261        {
262            ArrayList values = new ArrayList();
263    
264            for (Iterator i = this.cacheManager.getGroupKeys(group).iterator(); i
265                    .hasNext();)
266            {
267                Object o = this.cacheManager.getFromGroup(i.next(), group);
268                if (o != null)
269                {
270                    values.add(o);
271                }
272            }
273    
274            return values;
275        }
276    
277        /**
278         * Circle through the cache and refresh stale objects. Frequency is
279         * determined by the cacheCheckFrequency property.
280         */
281        public void run()
282        {
283            while (this.continueThread)
284            {
285                // Sleep for amount of time set in cacheCheckFrequency -
286                // default = 5 seconds.
287                try
288                {
289                    Thread.sleep(this.cacheCheckFrequency);
290                }
291                catch (InterruptedException exc)
292                {
293                    if (!this.continueThread)
294                    {
295                        return;
296                    }
297                }
298    
299                for (Iterator i = this.cacheManager.getGroupKeys(group).iterator(); i
300                        .hasNext();)
301                {
302                    String key = (String) i.next();
303                    Object o = this.cacheManager.getFromGroup(key, group);
304                    if (o == null)
305                    {
306                        removeObject(key);
307                    }
308                    else
309                    {
310                        if (o instanceof RefreshableCachedObject)
311                        {
312                            RefreshableCachedObject rco = (RefreshableCachedObject) o;
313                            if (rco.isUntouched())
314                            {
315                                this.cacheManager.remove(key, group);
316                            }
317                            else if (rco.isStale())
318                            {
319                                rco.refresh();
320                            }
321                        }
322                    }
323                }
324            }
325        }
326    
327        /**
328         * @see org.apache.fulcrum.cache.GlobalCacheService#getCacheSize()
329         */
330        public int getCacheSize() throws IOException
331        {
332            // This is evil!
333            ByteArrayOutputStream baos = new ByteArrayOutputStream();
334            ObjectOutputStream out = new ObjectOutputStream(baos);
335            Set keys = this.cacheManager.getGroupKeys(group);
336    
337            for (Iterator i = keys.iterator(); i.hasNext();)
338            {
339                out.writeObject(this.cacheManager.getFromGroup(i.next(), group));
340            }
341    
342            out.flush();
343    
344            //
345            // Subtract 4 bytes from the length, because the serialization
346            // magic number (2 bytes) and version number (2 bytes) are
347            // both written to the stream before the object
348            //
349            int objectsize = baos.toByteArray().length - 4 * keys.size();
350            return objectsize;
351        }
352    
353        /**
354         * @see org.apache.fulcrum.cache.GlobalCacheService#getNumberOfObjects()
355         */
356        public int getNumberOfObjects()
357        {
358            int count = 0;
359    
360            for (Iterator i = this.cacheManager.getGroupKeys(group).iterator(); i
361                    .hasNext();)
362            {
363                if (this.cacheManager.getFromGroup(i.next(), group) != null)
364                {
365                    count++;
366                }
367            }
368    
369            return count;
370        }
371    
372        /**
373         * @see org.apache.fulcrum.cache.GlobalCacheService#flushCache()
374         */
375        public void flushCache()
376        {
377            this.cacheManager.invalidateGroup(group);
378        }
379    }