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 }