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    }