001 package railo.runtime.cache.eh; 002 003 import java.io.ByteArrayInputStream; 004 import java.io.IOException; 005 import java.io.OutputStream; 006 import java.util.HashMap; 007 import java.util.HashSet; 008 import java.util.Iterator; 009 import java.util.Map; 010 import java.util.Map.Entry; 011 012 import net.sf.ehcache.CacheManager; 013 import net.sf.ehcache.Element; 014 import railo.commons.io.cache.CacheEntry; 015 import railo.commons.io.cache.exp.CacheException; 016 import railo.commons.io.res.Resource; 017 import railo.commons.io.res.filter.ResourceNameFilter; 018 import railo.loader.engine.CFMLEngine; 019 import railo.loader.engine.CFMLEngineFactory; 020 import railo.loader.util.Util; 021 import railo.runtime.config.Config; 022 import railo.runtime.type.Struct; 023 024 public class EHCacheLite extends EHCacheSupport { 025 026 private static final boolean DISK_PERSISTENT = true; 027 private static final boolean ETERNAL = false; 028 private static final int MAX_ELEMENTS_IN_MEMEORY = 10000; 029 private static final int MAX_ELEMENTS_ON_DISK = 10000000; 030 private static final String MEMORY_EVICTION_POLICY = "LRU"; 031 private static final boolean OVERFLOW_TO_DISK = true; 032 private static final long TIME_TO_IDLE_SECONDS = 86400; 033 private static final long TIME_TO_LIVE_SECONDS = 86400; 034 035 private static final boolean REPLICATE_PUTS = true; 036 private static final boolean REPLICATE_PUTS_VIA_COPY = true; 037 private static final boolean REPLICATE_UPDATES = true; 038 private static final boolean REPLICATE_UPDATES_VIA_COPY = true; 039 private static final boolean REPLICATE_REMOVALS = true; 040 private static final boolean REPLICATE_ASYNC = true; 041 private static final int ASYNC_REP_INTERVAL = 1000; 042 private static Map managers=new HashMap(); 043 044 //private net.sf.ehcache.Cache cache; 045 private int hits; 046 private int misses; 047 private String cacheName; 048 private CacheManager manager; 049 private ClassLoader classLoader; 050 051 052 public static void init(Config config,String[] cacheNames,Struct[] arguments) throws IOException { 053 System.setProperty("net.sf.ehcache.enableShutdownHook", "true"); 054 Thread.currentThread().setContextClassLoader(config.getClassLoader()); 055 056 057 058 Resource dir = config.getConfigDir().getRealResource("ehcache"),hashDir; 059 if(!dir.isDirectory())dir.createDirectory(true); 060 String[] hashArgs=createHash(arguments); 061 062 // create all xml 063 HashMap<String,String> mapXML = new HashMap<String,String>(); 064 HashMap newManagers = new HashMap(); 065 for(int i=0;i<hashArgs.length;i++){ 066 if(mapXML.containsKey(hashArgs[i])) continue; 067 068 hashDir=dir.getRealResource(hashArgs[i]); 069 String xml=createXML(hashDir.getAbsolutePath(), cacheNames,arguments,hashArgs,hashArgs[i]); 070 String hash=MD5.getDigestAsString(xml); 071 072 CacheManagerAndHashLite manager=(CacheManagerAndHashLite) managers.remove(hashArgs[i]); 073 if(manager!=null && manager.hash.equals(hash)) { 074 newManagers.put(hashArgs[i], manager); 075 } 076 else mapXML.put(hashArgs[i], xml); 077 } 078 079 // shutdown all existing managers that have changed 080 Map.Entry entry; 081 Iterator it; 082 synchronized(managers){ 083 it = managers.entrySet().iterator(); 084 while(it.hasNext()){ 085 entry=(Entry) it.next(); 086 if(entry.getKey().toString().startsWith(dir.getAbsolutePath())){ 087 ((CacheManagerAndHashLite)entry.getValue()).manager.shutdown(); 088 } 089 else newManagers.put(entry.getKey(), entry.getValue()); 090 091 } 092 managers=newManagers; 093 } 094 095 it = mapXML.entrySet().iterator(); 096 String xml,hashArg,hash; 097 while(it.hasNext()){ 098 entry=(Entry) it.next(); 099 hashArg=(String) entry.getKey(); 100 xml=(String) entry.getValue(); 101 102 hashDir=dir.getRealResource(hashArg); 103 if(!hashDir.isDirectory())hashDir.createDirectory(true); 104 105 writeEHCacheXML(hashDir,xml); 106 hash=MD5.getDigestAsString(xml); 107 108 moveData(dir,hashArg,cacheNames,arguments); 109 110 CacheManagerAndHashLite m = new CacheManagerAndHashLite(new CacheManager(new ByteArrayInputStream(xml.getBytes())),hash); 111 newManagers.put(hashDir.getAbsolutePath(), m); 112 } 113 114 clean(dir); 115 } 116 117 public static void flushAllCaches() { 118 Map.Entry entry; 119 String[] names; 120 Iterator it = managers.entrySet().iterator(); 121 while(it.hasNext()){ 122 entry=(Entry) it.next(); 123 CacheManager manager=((CacheManagerAndHashLite)entry.getValue()).manager; 124 names = manager.getCacheNames(); 125 for(int i=0;i<names.length;i++){ 126 manager.getCache(names[i]).flush(); 127 } 128 } 129 } 130 131 private static void clean(Resource dir) { 132 Resource[] dirs = dir.listResources(); 133 Resource[] children; 134 135 for(int i=0;i<dirs.length;i++){ 136 if(dirs[i].isDirectory()){ 137 //print.out(dirs[i]+":"+pathes.contains(dirs[i].getAbsolutePath())); 138 children=dirs[i].listResources(); 139 if(children!=null && children.length>1)continue; 140 clean(children); 141 dirs[i].delete(); 142 } 143 } 144 } 145 146 private static void clean(Resource[] arr) { 147 if(arr!=null)for(int i=0;i<arr.length;i++){ 148 if(arr[i].isDirectory()){ 149 clean(arr[i].listResources()); 150 } 151 arr[i].delete(); 152 } 153 } 154 155 private static void moveData(Resource dir, String hash, String[] cacheNames, Struct[] arguments) { 156 String h; 157 Resource trg = dir.getRealResource(hash); 158 deleteData(dir, cacheNames); 159 for(int i=0;i<cacheNames.length;i++){ 160 h=createHash(arguments[i]); 161 if(h.equals(hash)){ 162 moveData(dir,cacheNames[i],trg); 163 } 164 } 165 166 } 167 168 private static void moveData(Resource dir, String cacheName, Resource trg) { 169 Resource[] dirs = dir.listResources(); 170 Resource index,data; 171 // move 172 for(int i=0;i<dirs.length;i++){ 173 if(!dirs[i].equals(trg) && 174 dirs[i].isDirectory() && 175 (data=dirs[i].getRealResource(cacheName+".data")).exists() && 176 (index=dirs[i].getRealResource(cacheName+".index")).exists() ){ 177 178 try { 179 index.moveTo(trg.getRealResource(cacheName+".index")); 180 data.moveTo(trg.getRealResource(cacheName+".data")); 181 } catch (IOException e) { 182 e.printStackTrace(); 183 } 184 } 185 } 186 } 187 188 private static void deleteData(Resource dir, String[] cacheNames) { 189 HashSet names=new HashSet(); 190 for(int i=0;i<cacheNames.length;i++){ 191 names.add(cacheNames[i]); 192 } 193 194 Resource[] dirs = dir.listResources(); 195 String name; 196 // move 197 for(int i=0;i<dirs.length;i++){ 198 if(dirs[i].isDirectory()){ 199 Resource[] datas = dirs[i].listResources(new DataFiterLite()); 200 if(datas!=null) for(int y=0;y<datas.length;y++){ 201 name=datas[y].getName(); 202 name=name.substring(0,name.length()-5); 203 if(!names.contains(name)){ 204 datas[y].delete(); 205 dirs[i].getRealResource(name+".index").delete(); 206 } 207 208 } 209 } 210 } 211 } 212 213 private static void writeEHCacheXML(Resource hashDir, String xml) { 214 ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes()); 215 OutputStream os=null; 216 try{ 217 os = hashDir.getRealResource("ehcache.xml").getOutputStream(); 218 Util.copy(is, os); 219 } 220 catch(IOException ioe){ioe.printStackTrace();} 221 finally{ 222 Util.closeEL(os); 223 } 224 } 225 226 private static String createHash(Struct args) { 227 try { 228 return MD5.getDigestAsString("off"); 229 } catch (IOException e) { 230 return ""; 231 } 232 } 233 private static String[] createHash(Struct[] arguments) { 234 String[] hashes=new String[arguments.length]; 235 for(int i=0;i<arguments.length;i++){ 236 hashes[i]=createHash(arguments[i]); 237 } 238 return hashes; 239 } 240 241 private static String createXML(String path, String[] cacheNames,Struct[] arguments, String[] hashes, String hash) { 242 243 Struct global=null; 244 for(int i=0;i<hashes.length;i++){ 245 if(hash.equals(hashes[i])){ 246 global=arguments[i]; 247 break; 248 } 249 } 250 251 252 StringBuffer xml=new StringBuffer(); 253 xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); 254 xml.append("<ehcache xsi:noNamespaceSchemaLocation=\"ehcache.xsd\">\n"); 255 256 // disk storage 257 xml.append("<diskStore path=\""); 258 xml.append(path); 259 xml.append("\"/>\n"); 260 xml.append("<cacheManagerEventListenerFactory class=\"\" properties=\"\"/>\n"); 261 262 xml.append("<defaultCache \n"); 263 xml.append(" diskPersistent=\"true\"\n"); 264 xml.append(" eternal=\"false\"\n"); 265 xml.append(" maxElementsInMemory=\"10000\"\n"); 266 xml.append(" maxElementsOnDisk=\"10000000\"\n"); 267 xml.append(" memoryStoreEvictionPolicy=\"LRU\"\n"); 268 xml.append(" timeToIdleSeconds=\"86400\"\n"); 269 xml.append(" timeToLiveSeconds=\"86400\"\n"); 270 xml.append(" overflowToDisk=\"true\"\n"); 271 xml.append(" diskSpoolBufferSizeMB=\"30\"\n"); 272 xml.append(" diskExpiryThreadIntervalSeconds=\"3600\"\n"); 273 xml.append(" />\n"); 274 275 // cache 276 for(int i=0;i<cacheNames.length && i<arguments.length;i++){ 277 if(hashes[i].equals(hash))createCacheXml(xml,cacheNames[i],arguments[i]); 278 } 279 280 xml.append("</ehcache>\n"); 281 return xml.toString(); 282 } 283 284 285 private static void createCacheXml(StringBuffer xml, String cacheName, Struct arguments) { 286 287 // disk Persistent 288 boolean diskPersistent=toBooleanValue(arguments.get("diskpersistent",Boolean.FALSE),DISK_PERSISTENT); 289 290 // eternal 291 boolean eternal=toBooleanValue(arguments.get("eternal",Boolean.FALSE),ETERNAL); 292 293 // max elements in memory 294 int maxElementsInMemory=toIntValue(arguments.get("maxelementsinmemory",new Integer(MAX_ELEMENTS_IN_MEMEORY)),MAX_ELEMENTS_IN_MEMEORY); 295 296 // max elements on disk 297 int maxElementsOnDisk=toIntValue(arguments.get("maxelementsondisk",new Integer(MAX_ELEMENTS_ON_DISK)),MAX_ELEMENTS_ON_DISK); 298 299 // memory eviction policy 300 String strPolicy=toString(arguments.get("memoryevictionpolicy",MEMORY_EVICTION_POLICY),MEMORY_EVICTION_POLICY); 301 String policy = "LRU"; 302 if("FIFO".equalsIgnoreCase(strPolicy)) policy="FIFO"; 303 else if("LFU".equalsIgnoreCase(strPolicy)) policy="LFU"; 304 305 // overflow to disk 306 boolean overflowToDisk=toBooleanValue(arguments.get("overflowtodisk",Boolean.FALSE),OVERFLOW_TO_DISK); 307 308 // time to idle seconds 309 long timeToIdleSeconds=toLongValue(arguments.get("timeToIdleSeconds",new Long(TIME_TO_IDLE_SECONDS)),TIME_TO_IDLE_SECONDS); 310 311 // time to live seconds 312 long timeToLiveSeconds=toLongValue(arguments.get("timeToLiveSeconds",new Long(TIME_TO_LIVE_SECONDS)),TIME_TO_LIVE_SECONDS); 313 314 xml.append("<cache name=\""+cacheName+"\"\n"); 315 xml.append(" diskPersistent=\""+diskPersistent+"\"\n"); 316 xml.append(" eternal=\""+eternal+"\"\n"); 317 xml.append(" maxElementsInMemory=\""+maxElementsInMemory+"\"\n"); 318 xml.append(" maxElementsOnDisk=\""+maxElementsOnDisk+"\"\n"); 319 xml.append(" memoryStoreEvictionPolicy=\""+policy+"\"\n"); 320 xml.append(" timeToIdleSeconds=\""+timeToIdleSeconds+"\"\n"); 321 xml.append(" timeToLiveSeconds=\""+timeToLiveSeconds+"\"\n"); 322 xml.append(" overflowToDisk=\""+overflowToDisk+"\""); 323 xml.append(">\n"); 324 xml.append(" </cache>\n"); 325 } 326 327 328 329 /** 330 * @param cacheName 331 * @throws IOException 332 * @see railo.commons.io.cache.Cache#init(railo.runtime.type.Struct) 333 */ 334 public void init(String cacheName, Struct arguments) throws IOException { 335 CFMLEngine engine = CFMLEngineFactory.getInstance(); 336 init(engine.getThreadPageContext().getConfig(),cacheName, arguments); 337 338 } 339 public void init(Config config,String cacheName, Struct arguments) { 340 341 this.classLoader=config.getClassLoader(); 342 this.cacheName=cacheName; 343 344 setClassLoader(); 345 Resource hashDir = config.getConfigDir().getRealResource("ehcache").getRealResource(createHash(arguments)); 346 manager =((CacheManagerAndHashLite) managers.get(hashDir.getAbsolutePath())).manager; 347 } 348 349 private void setClassLoader() { 350 if(classLoader!=Thread.currentThread().getContextClassLoader()) 351 Thread.currentThread().setContextClassLoader(classLoader); 352 } 353 354 protected net.sf.ehcache.Cache getCache() { 355 setClassLoader(); 356 return manager.getCache(cacheName); 357 } 358 359 /** 360 * @see railo.commons.io.cache.Cache#remove(String) 361 */ 362 public boolean remove(String key) { 363 try { 364 return getCache().remove(key); 365 } 366 catch(Throwable t){ 367 return false; 368 } 369 } 370 371 public CacheEntry getCacheEntry(String key) throws CacheException { 372 try { 373 misses++; 374 Element el = getCache().get(key); 375 if(el==null)throw new CacheException("there is no entry in cache with key ["+key+"]"); 376 hits++; 377 misses--; 378 return new EHCacheEntry(el); 379 } 380 catch(IllegalStateException ise) { 381 throw new CacheException(ise.getMessage()); 382 } 383 catch(net.sf.ehcache.CacheException ce) { 384 throw new CacheException(ce.getMessage()); 385 } 386 } 387 388 public CacheEntry getCacheEntry(String key, CacheEntry defaultValue) { 389 try { 390 Element el = getCache().get(key); 391 if(el!=null){ 392 hits++; 393 return new EHCacheEntry(el); 394 } 395 } 396 catch(Throwable t) { 397 misses++; 398 } 399 return defaultValue; 400 } 401 402 /** 403 * @see railo.commons.io.cache.Cache#getValue(String) 404 */ 405 public Object getValue(String key) throws CacheException { 406 try { 407 misses++; 408 Element el = getCache().get(key); 409 if(el==null)throw new CacheException("there is no entry in cache with key ["+key+"]"); 410 misses--; 411 hits++; 412 return el.getObjectValue(); 413 } 414 catch(IllegalStateException ise) { 415 throw new CacheException(ise.getMessage()); 416 } 417 catch(net.sf.ehcache.CacheException ce) { 418 throw new CacheException(ce.getMessage()); 419 } 420 } 421 422 /** 423 * @see railo.commons.io.cache.Cache#getValue(String, java.lang.Object) 424 */ 425 public Object getValue(String key, Object defaultValue) { 426 try { 427 Element el = getCache().get(key); 428 if(el!=null){ 429 hits++; 430 return el.getObjectValue(); 431 } 432 } 433 catch(Throwable t) { 434 misses++; 435 } 436 return defaultValue; 437 } 438 439 /** 440 * @see railo.commons.io.cache.Cache#hitCount() 441 */ 442 public long hitCount() { 443 return hits; 444 } 445 446 /** 447 * @see railo.commons.io.cache.Cache#missCount() 448 */ 449 public long missCount() { 450 return misses; 451 } 452 453 public void remove() { 454 setClassLoader(); 455 CacheManager singletonManager = CacheManager.getInstance(); 456 if(singletonManager.cacheExists(cacheName)) 457 singletonManager.removeCache(cacheName); 458 459 } 460 461 462 463 464 private static boolean toBooleanValue(Object o, boolean defaultValue) { 465 if(o instanceof Boolean) return ((Boolean)o).booleanValue(); 466 else if(o instanceof Number) return (((Number)o).doubleValue())!=0; 467 else if(o instanceof String) { 468 String str = o.toString().trim().toLowerCase(); 469 if(str.equals("yes") || str.equals("on") || str.equals("true")) return true; 470 else if(str.equals("no") || str.equals("false") || str.equals("off")) return false; 471 } 472 return defaultValue; 473 } 474 475 private static String toString(Object o, String defaultValue) { 476 if(o instanceof String)return o.toString(); 477 return defaultValue; 478 } 479 480 private static int toIntValue(Object o, int defaultValue) { 481 if(o instanceof Number) return ((Number)o).intValue(); 482 try{ 483 return Integer.parseInt(o.toString()); 484 } 485 catch(Throwable t){ 486 return defaultValue; 487 } 488 } 489 490 private static long toLongValue(Object o, long defaultValue) { 491 if(o instanceof Number) return ((Number)o).longValue(); 492 try{ 493 return Long.parseLong(o.toString()); 494 } 495 catch(Throwable t){ 496 return defaultValue; 497 } 498 } 499 500 501 } 502 class CacheManagerAndHashLite { 503 504 CacheManager manager; 505 String hash; 506 507 public CacheManagerAndHashLite(CacheManager manager, String hash) { 508 this.manager=manager; 509 this.hash=hash; 510 } 511 512 } 513 514 class DataFiterLite implements ResourceNameFilter { 515 516 /** 517 * @see railo.commons.io.res.filter.ResourceNameFilter#accept(railo.commons.io.res.Resource, java.lang.String) 518 */ 519 public boolean accept(Resource parent, String name) { 520 return name.endsWith(".data"); 521 } 522 523 }