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 }