001package 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 022import java.io.ByteArrayOutputStream; 023import java.io.IOException; 024import java.io.ObjectOutputStream; 025import java.io.Serializable; 026import java.util.ArrayList; 027import java.util.List; 028import java.util.Set; 029 030import org.apache.avalon.framework.activity.Disposable; 031import org.apache.avalon.framework.activity.Initializable; 032import org.apache.avalon.framework.configuration.Configurable; 033import org.apache.avalon.framework.configuration.Configuration; 034import org.apache.avalon.framework.configuration.ConfigurationException; 035import org.apache.avalon.framework.logger.AbstractLogEnabled; 036import org.apache.avalon.framework.thread.ThreadSafe; 037import org.apache.commons.jcs3.JCS; 038import org.apache.commons.jcs3.access.GroupCacheAccess; 039import org.apache.commons.jcs3.access.exception.CacheException; 040import org.apache.commons.jcs3.engine.ElementAttributes; 041import org.apache.commons.jcs3.engine.control.CompositeCacheManager; 042import org.apache.fulcrum.cache.CachedObject; 043import org.apache.fulcrum.cache.GlobalCacheService; 044import org.apache.fulcrum.cache.ObjectExpiredException; 045import org.apache.fulcrum.cache.RefreshableCachedObject; 046 047/** 048 * Default implementation of JCSCacheService 049 * 050 * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a> 051 * @version $Id$ 052 */ 053public class JCSCacheService extends AbstractLogEnabled implements 054 GlobalCacheService, Runnable, Configurable, Disposable, Initializable, 055 ThreadSafe 056{ 057 /** 058 * Cache check frequency in Millis (1000 Millis = 1 second). Value must be > 059 * 0. Default = 5 seconds 060 */ 061 public static final long DEFAULT_CACHE_CHECK_FREQUENCY = 5000; // 5 seconds 062 063 /** 064 * cacheCheckFrequency (default - 5 seconds) 065 */ 066 private long cacheCheckFrequency; 067 068 /** 069 * Instance of the JCS cache 070 */ 071 private GroupCacheAccess<String, CachedObject<?>> cacheManager; 072 073 /** 074 * JCS region to use 075 */ 076 private String region; 077 078 /** 079 * Path name of the JCS configuration file 080 */ 081 private String configFile; 082 083 /** 084 * Constant value which provides a group name 085 */ 086 private static String group = "default_group"; 087 088 /** thread for refreshing stale items in the cache */ 089 private Thread refreshing; 090 091 /** flag to stop the housekeeping thread when the component is disposed. */ 092 private boolean continueThread; 093 094 public JCSCacheService() 095 { 096 097 } 098 099 // ---------------- 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}