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 public void init(String cacheName, Struct arguments) { 330 CFMLEngine engine = CFMLEngineFactory.getInstance(); 331 init(engine.getThreadPageContext().getConfig(),cacheName, arguments); 332 333 } 334 335 @Override 336 public void init(Config config,String cacheName, Struct arguments) { 337 338 this.classLoader=config.getClassLoader(); 339 this.cacheName=cacheName; 340 341 setClassLoader(); 342 Resource hashDir = config.getConfigDir().getRealResource("ehcache").getRealResource(createHash(arguments)); 343 manager =((CacheManagerAndHashLite) managers.get(hashDir.getAbsolutePath())).manager; 344 } 345 346 private void setClassLoader() { 347 if(classLoader!=Thread.currentThread().getContextClassLoader()) 348 Thread.currentThread().setContextClassLoader(classLoader); 349 } 350 351 protected net.sf.ehcache.Cache getCache() { 352 setClassLoader(); 353 return manager.getCache(cacheName); 354 } 355 356 @Override 357 public boolean remove(String key) { 358 try { 359 return getCache().remove(key); 360 } 361 catch(Throwable t){ 362 return false; 363 } 364 } 365 366 public CacheEntry getCacheEntry(String key) throws CacheException { 367 try { 368 misses++; 369 Element el = getCache().get(key); 370 if(el==null)throw new CacheException("there is no entry in cache with key ["+key+"]"); 371 hits++; 372 misses--; 373 return new EHCacheEntry(el); 374 } 375 catch(IllegalStateException ise) { 376 throw new CacheException(ise.getMessage()); 377 } 378 catch(net.sf.ehcache.CacheException ce) { 379 throw new CacheException(ce.getMessage()); 380 } 381 } 382 383 public CacheEntry getCacheEntry(String key, CacheEntry defaultValue) { 384 try { 385 Element el = getCache().get(key); 386 if(el!=null){ 387 hits++; 388 return new EHCacheEntry(el); 389 } 390 } 391 catch(Throwable t) { 392 misses++; 393 } 394 return defaultValue; 395 } 396 397 @Override 398 public Object getValue(String key) throws CacheException { 399 try { 400 misses++; 401 Element el = getCache().get(key); 402 if(el==null)throw new CacheException("there is no entry in cache with key ["+key+"]"); 403 misses--; 404 hits++; 405 return el.getObjectValue(); 406 } 407 catch(IllegalStateException ise) { 408 throw new CacheException(ise.getMessage()); 409 } 410 catch(net.sf.ehcache.CacheException ce) { 411 throw new CacheException(ce.getMessage()); 412 } 413 } 414 415 @Override 416 public Object getValue(String key, Object defaultValue) { 417 try { 418 Element el = getCache().get(key); 419 if(el!=null){ 420 hits++; 421 return el.getObjectValue(); 422 } 423 } 424 catch(Throwable t) { 425 misses++; 426 } 427 return defaultValue; 428 } 429 430 @Override 431 public long hitCount() { 432 return hits; 433 } 434 435 @Override 436 public long missCount() { 437 return misses; 438 } 439 440 public void remove() { 441 setClassLoader(); 442 CacheManager singletonManager = CacheManager.getInstance(); 443 if(singletonManager.cacheExists(cacheName)) 444 singletonManager.removeCache(cacheName); 445 446 } 447 448 449 450 451 private static boolean toBooleanValue(Object o, boolean defaultValue) { 452 if(o instanceof Boolean) return ((Boolean)o).booleanValue(); 453 else if(o instanceof Number) return (((Number)o).doubleValue())!=0; 454 else if(o instanceof String) { 455 String str = o.toString().trim().toLowerCase(); 456 if(str.equals("yes") || str.equals("on") || str.equals("true")) return true; 457 else if(str.equals("no") || str.equals("false") || str.equals("off")) return false; 458 } 459 return defaultValue; 460 } 461 462 private static String toString(Object o, String defaultValue) { 463 if(o instanceof String)return o.toString(); 464 return defaultValue; 465 } 466 467 private static int toIntValue(Object o, int defaultValue) { 468 if(o instanceof Number) return ((Number)o).intValue(); 469 try{ 470 return Integer.parseInt(o.toString()); 471 } 472 catch(Throwable t){ 473 return defaultValue; 474 } 475 } 476 477 private static long toLongValue(Object o, long defaultValue) { 478 if(o instanceof Number) return ((Number)o).longValue(); 479 try{ 480 return Long.parseLong(o.toString()); 481 } 482 catch(Throwable t){ 483 return defaultValue; 484 } 485 } 486 487 488 } 489 class CacheManagerAndHashLite { 490 491 CacheManager manager; 492 String hash; 493 494 public CacheManagerAndHashLite(CacheManager manager, String hash) { 495 this.manager=manager; 496 this.hash=hash; 497 } 498 499 } 500 501 class DataFiterLite implements ResourceNameFilter { 502 503 @Override 504 public boolean accept(Resource parent, String name) { 505 return name.endsWith(".data"); 506 } 507 508 }