001package icy.image.cache; 002 003import java.io.File; 004import java.io.FileFilter; 005import java.util.Collection; 006import java.util.HashSet; 007import java.util.Set; 008 009import icy.file.FileUtil; 010import icy.system.IcyExceptionHandler; 011import net.sf.ehcache.Cache; 012import net.sf.ehcache.CacheManager; 013import net.sf.ehcache.Ehcache; 014import net.sf.ehcache.Element; 015import net.sf.ehcache.config.CacheConfiguration; 016import net.sf.ehcache.config.Configuration; 017import net.sf.ehcache.config.DiskStoreConfiguration; 018import net.sf.ehcache.config.MemoryUnit; 019import net.sf.ehcache.config.PersistenceConfiguration; 020import net.sf.ehcache.config.PersistenceConfiguration.Strategy; 021import net.sf.ehcache.event.CacheEventListener; 022import net.sf.ehcache.statistics.StatisticsGateway; 023import net.sf.ehcache.store.MemoryStoreEvictionPolicy; 024 025public class EHCache2 extends AbstractCache 026{ 027 private class CustomCacheEventListener implements CacheEventListener 028 { 029 public CustomCacheEventListener() 030 { 031 super(); 032 } 033 034 @Override 035 public void dispose() 036 { 037 System.out.println("EHCache disposed"); 038 } 039 040 @Override 041 public Object clone() throws CloneNotSupportedException 042 { 043 return super.clone(); 044 } 045 046 @Override 047 public void notifyElementEvicted(Ehcache ehCache, Element element) 048 { 049 // Note (Stephane): sometime ehcache evict element from cache even if we have unlimited disk storage tier enabled. 050 // I guess this happen when it try to cache into the heap cache and there is not enough free space to do so. 051 // Ideally it would move some elements from heap to disk or directly save element in disk storage 052 // but it looks like it doesn't work that way (maybe for performance reason), even worse: ehcache can actually evict eternal element 053 // while it keep non eternal elements in cache !! 054 055 // eternal element eviction ? 056 if (element.isEternal()) 057 { 058 System.out.println("Warning: eternal element " + element.getObjectKey() + " evicted from cache"); 059 System.out.println("Trying to put it back..."); 060 // try to force GC and put it back in cache 061 System.gc(); 062 cache.put(new Element(element.getObjectKey(), element.getObjectValue(), element.isEternal())); 063 } 064 } 065 066 @Override 067 public void notifyElementExpired(Ehcache ehCache, Element element) 068 { 069 // 070 } 071 072 @Override 073 public void notifyElementPut(Ehcache ehCache, Element element) throws net.sf.ehcache.CacheException 074 { 075 // 076 } 077 078 @Override 079 public void notifyElementRemoved(Ehcache ehCache, Element element) throws net.sf.ehcache.CacheException 080 { 081 // 082 } 083 084 @Override 085 public void notifyElementUpdated(Ehcache ehCache, Element element) throws net.sf.ehcache.CacheException 086 { 087 // 088 } 089 090 @Override 091 public void notifyRemoveAll(Ehcache ehCache) 092 { 093 // 094 } 095 } 096 097 final Set<Integer> eternalStoredKeys; 098 CacheManager cacheManager; 099 Cache cache; 100 boolean enabled; 101 102 public EHCache2(int cacheSizeMB, String path) 103 { 104 super(); 105 106 eternalStoredKeys = new HashSet<Integer>(); 107 108 // get old ehcache agent JAR files 109 final String[] oldFiles = FileUtil.getFiles(FileUtil.getTempDirectory(), new FileFilter() 110 { 111 @Override 112 public boolean accept(File pathname) 113 { 114 // old ehcache temp agent JAR files 115 return FileUtil.getFileName(pathname.getAbsolutePath(), false).startsWith("ehcache"); 116 } 117 }, false, false, false); 118 // delete these files as ehcache don't do it itself 119 for (String file : oldFiles) 120 FileUtil.delete(file, false); 121 122 // delete previous cache file 123 FileUtil.delete(path, true); 124 125 try 126 { 127 final DiskStoreConfiguration diskConfig = new DiskStoreConfiguration().path(path); 128 final Configuration cacheManagerConfig = new Configuration().diskStore(diskConfig); 129 130 final PersistenceConfiguration persistenceConfig = new PersistenceConfiguration() 131 .strategy(Strategy.LOCALTEMPSWAP); 132 // CacheWriterFactoryConfiguration c = new CacheWriterFactoryConfiguration(); 133 // c.setClass(path); 134 135 final long freeBytes = new File(FileUtil.getDrive(path)).getUsableSpace(); 136 // subtract 200 MB to available space for safety 137 final long freeMB = (freeBytes <= 0) ? Long.MAX_VALUE : Math.max(0, (freeBytes / (1024 * 1024)) - 200); 138 139 final CacheConfiguration cacheConfig = new CacheConfiguration().name("ehCache2") 140 .maxBytesLocalHeap(cacheSizeMB, MemoryUnit.MEGABYTES) 141 // .maxBytesLocalOffHeap(cacheSizeMB, MemoryUnit.MEGABYTES) 142 .maxBytesLocalDisk(Math.min(freeMB, 500000L), MemoryUnit.MEGABYTES) 143 // we want the disk write buffer to be at least 32 MB and 256 MB max 144 .diskSpoolBufferSizeMB(Math.max(32, Math.min(256, cacheSizeMB / 16))) 145 .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU).timeToIdleSeconds(20) 146 .timeToLiveSeconds(60).diskExpiryThreadIntervalSeconds(120).persistence(persistenceConfig); 147 // .pinning(new PinningConfiguration().store(Store.INCACHE)); 148 // .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU).timeToIdleSeconds(2).timeToLiveSeconds(2) 149 150 cacheManagerConfig.addCache(cacheConfig); 151 152 // singleton cache manager creation 153 cacheManager = CacheManager.create(cacheManagerConfig); 154 155 // get the cache 156 cache = cacheManager.getCache("ehCache2"); 157 // // add the custom Tile cache loader to it 158 // cache.registerCacheLoader(new ImageCacheLoader()); 159 160 cache.getCacheEventNotificationService().registerListener(new CustomCacheEventListener()); 161 enabled = true; 162 } 163 catch (Exception e) 164 { 165 System.err.println("Error while initialize image cache:"); 166 IcyExceptionHandler.showErrorMessage(e, false, true); 167 enabled = false; 168 } 169 } 170 171 @Override 172 public boolean isEnabled() 173 { 174 return enabled; 175 } 176 177 @SuppressWarnings("deprecation") 178 @Override 179 public long usedMemory() 180 { 181 return cache.calculateInMemorySize() + cache.calculateOffHeapSize(); 182 } 183 184 @SuppressWarnings("deprecation") 185 @Override 186 public long usedDisk() 187 { 188 return cache.calculateOnDiskSize(); 189 } 190 191 @Override 192 public boolean isOnMemoryCache(Integer key) 193 { 194 if (profiling) 195 startProf(); 196 197 try 198 { 199 return cache.isElementInMemory(key) | cache.isElementOffHeap(key); 200 } 201 finally 202 { 203 if (profiling) 204 endProf(); 205 } 206 } 207 208 @Override 209 public boolean isOnDiskCache(Integer key) 210 { 211 if (profiling) 212 startProf(); 213 214 try 215 { 216 return cache.isElementOnDisk(key); 217 } 218 finally 219 { 220 if (profiling) 221 endProf(); 222 } 223 } 224 225 @Override 226 public boolean isInCache(Integer key) 227 { 228 if (profiling) 229 startProf(); 230 231 try 232 { 233 return cache.isKeyInCache(key); 234 } 235 finally 236 { 237 if (profiling) 238 endProf(); 239 } 240 } 241 242 @SuppressWarnings("unchecked") 243 @Override 244 public Collection<Integer> getAllKeys() throws CacheException 245 { 246 if (profiling) 247 startProf(); 248 249 try 250 { 251 return cache.getKeys(); 252 } 253 catch (Exception e) 254 { 255 throw new CacheException("ImageCache: an error occured while retrieving all keys from cache", e); 256 } 257 finally 258 { 259 if (profiling) 260 endProf(); 261 } 262 } 263 264 @Override 265 public Object get(Integer key) throws CacheException 266 { 267 if (profiling) 268 startProf(); 269 270 final boolean checkNull; 271 Object result = null; 272 273 // test if we need to check for null result 274 synchronized (eternalStoredKeys) 275 { 276 checkNull = eternalStoredKeys.contains(key); 277 } 278 279 try 280 { 281 final Element e = cache.get(key); 282 283 if (e != null) 284 result = e.getObjectValue(); 285 286 // check if eternal data was lost (it seems that sometime EhCache loss data put in eternal state !!) 287 if (checkNull && (result == null)) 288 throw new CacheException("ImageCache error: data '" + key + "' couldn't be retrieved (data lost)"); 289 290 return result; 291 } 292 finally 293 { 294 if (profiling) 295 endProf(); 296 } 297 } 298 299 @Override 300 public void set(Integer key, Object object, boolean eternal) throws CacheException 301 { 302 if (profiling) 303 startProf(); 304 305 synchronized (eternalStoredKeys) 306 { 307 // save in keyset (only for non null eternal data) 308 if ((object != null) && eternal) 309 eternalStoredKeys.add(key); 310 else 311 eternalStoredKeys.remove(key); 312 } 313 314 try 315 { 316 cache.put(new Element(key, object, eternal)); 317 } 318 catch (Exception e) 319 { 320 throw new CacheException("ImageCache error: data '" + key + "' couldn't be saved in cache", e); 321 } 322 finally 323 { 324 if (profiling) 325 endProf(); 326 } 327 } 328 329 @Override 330 public void clear() throws CacheException 331 { 332 if (profiling) 333 startProf(); 334 335 eternalStoredKeys.clear(); 336 337 try 338 { 339 cache.removeAll(); 340 } 341 catch (Exception e) 342 { 343 throw new CacheException("ImageCache: an error occured while clearing cache", e); 344 } 345 finally 346 { 347 if (profiling) 348 endProf(); 349 } 350 } 351 352 @Override 353 public void remove(Integer key) throws CacheException 354 { 355 if (profiling) 356 startProf(); 357 358 synchronized (eternalStoredKeys) 359 { 360 // remove from keyset 361 eternalStoredKeys.remove(key); 362 } 363 364 try 365 { 366 cache.remove(key); 367 } 368 catch (Exception e) 369 { 370 throw new CacheException("ImageCache: an error occured while removing data '" + key + "' from cache", e); 371 } 372 finally 373 { 374 if (profiling) 375 endProf(); 376 } 377 } 378 379 public StatisticsGateway getStats() 380 { 381 return cache.getStatistics(); 382 } 383 384 @Override 385 public void end() 386 { 387 eternalStoredKeys.clear(); 388 cacheManager.shutdown(); 389 } 390 391 @Override 392 public String getName() 393 { 394 return "EHCache 2"; 395 } 396}