package railo.runtime.cache.eh;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import railo.commons.io.CharsetUtil;
import railo.commons.io.cache.CacheEntry;
import railo.commons.io.cache.exp.CacheException;
import railo.commons.io.res.Resource;
import railo.commons.io.res.filter.ResourceNameFilter;
import railo.loader.util.Util;
import railo.runtime.config.Config;
import railo.runtime.engine.ThreadLocalPageContext;
import railo.runtime.type.Struct;
public class EHCache extends EHCacheSupport {
private static final boolean DISK_PERSISTENT = true;
private static final boolean ETERNAL = false;
private static final int MAX_ELEMENTS_IN_MEMEORY = 10000;
private static final int MAX_ELEMENTS_ON_DISK = 10000000;
private static final String MEMORY_EVICTION_POLICY = "LRU";
private static final boolean OVERFLOW_TO_DISK = true;
private static final long TIME_TO_IDLE_SECONDS = 86400;
private static final long TIME_TO_LIVE_SECONDS = 86400;
private static final boolean REPLICATE_PUTS = true;
private static final boolean REPLICATE_PUTS_VIA_COPY = true;
private static final boolean REPLICATE_UPDATES = true;
private static final boolean REPLICATE_UPDATES_VIA_COPY = true;
private static final boolean REPLICATE_REMOVALS = true;
private static final boolean REPLICATE_ASYNC = true;
private static final int ASYNC_REP_INTERVAL = 1000;
private static Map managers=new HashMap();
//private net.sf.ehcache.Cache cache;
private int hits;
private int misses;
private String cacheName;
private CacheManager manager;
private ClassLoader classLoader;
public static void init(Config config,String[] cacheNames,Struct[] arguments) throws IOException {
System.setProperty("net.sf.ehcache.enableShutdownHook", "true");
Thread.currentThread().setContextClassLoader(config.getClassLoader());
Resource dir = config.getConfigDir().getRealResource("ehcache"),hashDir;
if(!dir.isDirectory())dir.createDirectory(true);
String[] hashArgs=createHash(arguments);
// create all xml
HashMap mapXML = new HashMap();
HashMap newManagers = new HashMap();
for(int i=0;i<hashArgs.length;i++){
if(mapXML.containsKey(hashArgs[i])) continue;
hashDir=dir.getRealResource(hashArgs[i]);
String xml=createXML(hashDir.getAbsolutePath(), cacheNames,arguments,hashArgs,hashArgs[i]);
String hash=MD5.getDigestAsString(xml);
CacheManagerAndHash manager=(CacheManagerAndHash) managers.remove(hashArgs[i]);
if(manager!=null && manager.hash.equals(hash)) {
newManagers.put(hashArgs[i], manager);
}
else mapXML.put(hashArgs[i], xml);
}
// shutdown all existing managers that have changed
Map.Entry entry;
Iterator it;
synchronized(managers){
it = managers.entrySet().iterator();
while(it.hasNext()){
entry=(Entry) it.next();
if(entry.getKey().toString().startsWith(dir.getAbsolutePath())){
((CacheManagerAndHash)entry.getValue()).manager.shutdown();
}
else newManagers.put(entry.getKey(), entry.getValue());
}
managers=newManagers;
}
it = mapXML.entrySet().iterator();
String xml,hashArg,hash;
while(it.hasNext()){
entry=(Entry) it.next();
hashArg=(String) entry.getKey();
xml=(String) entry.getValue();
hashDir=dir.getRealResource(hashArg);
if(!hashDir.isDirectory())hashDir.createDirectory(true);
writeEHCacheXML(hashDir,xml);
hash=MD5.getDigestAsString(xml);
moveData(dir,hashArg,cacheNames,arguments);
CacheManagerAndHash m = new CacheManagerAndHash(new CacheManager(new ByteArrayInputStream(xml.getBytes(CharsetUtil.UTF8))),hash);
newManagers.put(hashDir.getAbsolutePath(), m);
}
clean(dir);
}
public static void flushAllCaches() {
Map.Entry entry;
String[] names;
Iterator it = managers.entrySet().iterator();
while(it.hasNext()){
entry=(Entry) it.next();
CacheManager manager=((CacheManagerAndHash)entry.getValue()).manager;
names = manager.getCacheNames();
for(int i=0;i<names.length;i++){
manager.getCache(names[i]).flush();
}
}
}
private static void clean(Resource dir) {
Resource[] dirs = dir.listResources();
Resource[] children;
for(int i=0;i<dirs.length;i++){
if(dirs[i].isDirectory()){
//print.out(dirs[i]+":"+pathes.contains(dirs[i].getAbsolutePath()));
children=dirs[i].listResources();
if(children!=null && children.length>1)continue;
clean(children);
dirs[i].delete();
}
}
}
private static void clean(Resource[] arr) {
if(arr!=null)for(int i=0;i<arr.length;i++){
if(arr[i].isDirectory()){
clean(arr[i].listResources());
}
arr[i].delete();
}
}
private static void moveData(Resource dir, String hash, String[] cacheNames, Struct[] arguments) {
String h;
Resource trg = dir.getRealResource(hash);
deleteData(dir, cacheNames);
for(int i=0;i<cacheNames.length;i++){
h=createHash(arguments[i]);
if(h.equals(hash)){
moveData(dir,cacheNames[i],trg);
}
}
}
private static void moveData(Resource dir, String cacheName, Resource trg) {
Resource[] dirs = dir.listResources();
Resource index,data;
// move
for(int i=0;i<dirs.length;i++){
if(!dirs[i].equals(trg) &&
dirs[i].isDirectory() &&
(data=dirs[i].getRealResource(cacheName+".data")).exists() &&
(index=dirs[i].getRealResource(cacheName+".index")).exists() ){
try {
index.moveTo(trg.getRealResource(cacheName+".index"));
data.moveTo(trg.getRealResource(cacheName+".data"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static void deleteData(Resource dir, String[] cacheNames) {
HashSet names=new HashSet();
for(int i=0;i<cacheNames.length;i++){
names.add(cacheNames[i]);
}
Resource[] dirs = dir.listResources();
String name;
// move
for(int i=0;i<dirs.length;i++){
if(dirs[i].isDirectory()){
Resource[] datas = dirs[i].listResources(new DataFiter());
if(datas!=null) for(int y=0;y<datas.length;y++){
name=datas[y].getName();
name=name.substring(0,name.length()-5);
if(!names.contains(name)){
datas[y].delete();
dirs[i].getRealResource(name+".index").delete();
}
}
}
}
}
private static void writeEHCacheXML(Resource hashDir, String xml) {
ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(CharsetUtil.UTF8));
OutputStream os=null;
try{
os = hashDir.getRealResource("ehcache.xml").getOutputStream();
Util.copy(is, os);
}
catch(IOException ioe){ioe.printStackTrace();}
finally{
Util.closeEL(os);
}
}
private static String createHash(Struct args) {
String dist = args.get("distributed","").toString().trim().toLowerCase();
try {
if(dist.equals("off")){
return MD5.getDigestAsString(dist);
}
else if(dist.equals("automatic")){
return MD5.getDigestAsString(
dist+
args.get("automatic_timeToLive","").toString().trim().toLowerCase()+
args.get("automatic_addional","").toString().trim().toLowerCase()+
args.get("automatic_multicastGroupPort","").toString().trim().toLowerCase()+
args.get("automatic_multicastGroupAddress","").toString().trim().toLowerCase()+
args.get("automatic_hostName","").toString().trim().toLowerCase()
);
}
else {
return MD5.getDigestAsString(
dist+
args.get("manual_rmiUrls","").toString().trim().toLowerCase()+
args.get("manual_addional","").toString().trim().toLowerCase()+
args.get("listener_hostName","").toString().trim().toLowerCase()+
args.get("listener_port","").toString().trim().toLowerCase()+
args.get("listener_remoteObjectPort","").toString().trim().toLowerCase()+
args.get("listener_socketTimeoutMillis","120000").toString().trim().toLowerCase()
);
}
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
private static String[] createHash(Struct[] arguments) {
String[] hashes=new String[arguments.length];
for(int i=0;i<arguments.length;i++){
hashes[i]=createHash(arguments[i]);
}
return hashes;
}
private static String createXML(String path, String[] cacheNames,Struct[] arguments, String[] hashes, String hash) {
boolean isDistributed=false;
//Cast caster = CFMLEngineFactory.getInstance().getCastUtil();
Struct global=null;
for(int i=0;i<hashes.length;i++){
if(hash.equals(hashes[i])){
global=arguments[i];
break;
}
}
StringBuffer xml=new StringBuffer();
xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
xml.append("<ehcache xsi:noNamespaceSchemaLocation=\"ehcache.xsd\">\n");
// disk storage
xml.append("<diskStore path=\"");
xml.append(path);
xml.append("\"/>\n");
xml.append("<cacheManagerEventListenerFactory class=\"\" properties=\"\"/>\n");
// RMI
// Automatic
if(global!=null && global.get("distributed","").equals("automatic")){
// provider
isDistributed=true;
xml.append("<cacheManagerPeerProviderFactory \n");
xml.append(" class=\"net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory\"\n ");
String add = global.get("automatic_addional","").toString().trim();
String hostName=global.get("automatic_hostName","").toString().trim().toLowerCase();
if(!Util.isEmpty(hostName)) add+=",hostName="+hostName;
if(!Util.isEmpty(add) && !add.startsWith(","))add=","+add;
add=add.replace('\n', ' ');
xml.append(" properties=\"peerDiscovery=automatic" +
", multicastGroupAddress="+global.get("automatic_multicastGroupAddress","").toString().trim().toLowerCase()+
", multicastGroupPort="+global.get("automatic_multicastGroupPort","").toString().trim().toLowerCase()+
", timeToLive="+toTimeToLive(global.get("automatic_timeToLive","").toString().trim().toLowerCase())+
add+" \" />\n");
//hostName=fully_qualified_hostname_or_ip,
// listener
xml.append("<cacheManagerPeerListenerFactory class=\"net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory\"/>\n");
}
// Manual
else if(global!=null && global.get("distributed","").equals("manual")){
// provider
isDistributed=true;
xml.append("<cacheManagerPeerProviderFactory");
xml.append(" class=\"net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory\" ");
String add = global.get("manual_addional","").toString().trim();
if(!Util.isEmpty(add) && !add.startsWith(","))add=","+add;
add=add.replace('\n', ' ');
xml.append(" properties=\"peerDiscovery=manual, rmiUrls="+global.get("manual_rmiUrls","").toString().trim().toLowerCase().replace('\n', ' ')+
add+"\"/>\n"); //propertySeparator=\",\"
// listener
StringBuffer sb=new StringBuffer();
String hostName=global.get("listener_hostName","").toString().trim().toLowerCase();
if(!Util.isEmpty(hostName)) add(sb,"hostName="+hostName);
String port = global.get("listener_port","").toString().trim().toLowerCase();
if(!Util.isEmpty(port)) add(sb,"port="+port);
String remoteObjectPort = global.get("listener_remoteObjectPort","").toString().trim().toLowerCase();
if(!Util.isEmpty(remoteObjectPort)) add(sb,"remoteObjectPort="+remoteObjectPort);
String socketTimeoutMillis = global.get("listener_socketTimeoutMillis","").toString().trim().toLowerCase();
if(!Util.isEmpty(socketTimeoutMillis) && !"120000".equals(socketTimeoutMillis))
add(sb,"socketTimeoutMillis="+socketTimeoutMillis);
xml.append("<cacheManagerPeerListenerFactory");
xml.append(" class=\"net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory\"");
if(sb.length()>0)xml.append(" properties=\""+sb+"\"");
xml.append("/>\n");
}
if(isDistributed){
}
xml.append("<defaultCache \n");
xml.append(" diskPersistent=\"true\"\n");
xml.append(" eternal=\"false\"\n");
xml.append(" maxElementsInMemory=\"10000\"\n");
xml.append(" maxElementsOnDisk=\"10000000\"\n");
xml.append(" memoryStoreEvictionPolicy=\"LRU\"\n");
xml.append(" timeToIdleSeconds=\"86400\"\n");
xml.append(" timeToLiveSeconds=\"86400\"\n");
xml.append(" overflowToDisk=\"true\"\n");
xml.append(" diskSpoolBufferSizeMB=\"30\"\n");
xml.append(" diskExpiryThreadIntervalSeconds=\"3600\"\n");
xml.append(" />\n");
// cache
for(int i=0;i<cacheNames.length && i<arguments.length;i++){
if(hashes[i].equals(hash))createCacheXml(xml,cacheNames[i],arguments[i],isDistributed);
}
xml.append("</ehcache>\n");
return xml.toString();
}
private static void add(StringBuffer sb,String str) {
if(sb.length()>0)sb.append(", ");
sb.append(str);
}
private static int toTimeToLive(String str) {
if(str.indexOf("host")!=-1) return 0;
if(str.indexOf("site")!=-1) return 1;
if(str.indexOf("region")!=-1) return 64;
if(str.indexOf("continent")!=-1) return 128;
return 255;
}
private static void createCacheXml(StringBuffer xml, String cacheName, Struct arguments, boolean isDistributed) {
// disk Persistent
boolean diskPersistent=toBooleanValue(arguments.get("diskpersistent",Boolean.FALSE),DISK_PERSISTENT);
// eternal
boolean eternal=toBooleanValue(arguments.get("eternal",Boolean.FALSE),ETERNAL);
// max elements in memory
int maxElementsInMemory=toIntValue(arguments.get("maxelementsinmemory",new Integer(MAX_ELEMENTS_IN_MEMEORY)),MAX_ELEMENTS_IN_MEMEORY);
// max elements on disk
int maxElementsOnDisk=toIntValue(arguments.get("maxelementsondisk",new Integer(MAX_ELEMENTS_ON_DISK)),MAX_ELEMENTS_ON_DISK);
// memory eviction policy
String strPolicy=toString(arguments.get("memoryevictionpolicy",MEMORY_EVICTION_POLICY),MEMORY_EVICTION_POLICY);
String policy = "LRU";
if("FIFO".equalsIgnoreCase(strPolicy)) policy="FIFO";
else if("LFU".equalsIgnoreCase(strPolicy)) policy="LFU";
// overflow to disk
boolean overflowToDisk=toBooleanValue(arguments.get("overflowtodisk",Boolean.FALSE),OVERFLOW_TO_DISK);
// time to idle seconds
long timeToIdleSeconds=toLongValue(arguments.get("timeToIdleSeconds",new Long(TIME_TO_IDLE_SECONDS)),TIME_TO_IDLE_SECONDS);
// time to live seconds
long timeToLiveSeconds=toLongValue(arguments.get("timeToLiveSeconds",new Long(TIME_TO_LIVE_SECONDS)),TIME_TO_LIVE_SECONDS);
// REPLICATION
boolean replicatePuts=toBooleanValue(arguments.get("replicatePuts",Boolean.FALSE),REPLICATE_PUTS);
boolean replicatePutsViaCopy=toBooleanValue(arguments.get("replicatePutsViaCopy",Boolean.FALSE),REPLICATE_PUTS_VIA_COPY);
boolean replicateUpdates=toBooleanValue(arguments.get("replicateUpdates",Boolean.FALSE),REPLICATE_UPDATES);
boolean replicateUpdatesViaCopy=toBooleanValue(arguments.get("replicateUpdatesViaCopy",Boolean.FALSE),REPLICATE_UPDATES_VIA_COPY);
boolean replicateRemovals=toBooleanValue(arguments.get("replicateRemovals",Boolean.FALSE),REPLICATE_REMOVALS);
boolean replicateAsynchronously=toBooleanValue(arguments.get("replicateAsynchronously",Boolean.FALSE),REPLICATE_ASYNC);
int asynchronousReplicationInterval=toIntValue(arguments.get("asynchronousReplicationIntervalMillis",new Integer(ASYNC_REP_INTERVAL)),ASYNC_REP_INTERVAL);
xml.append("<cache name=\""+cacheName+"\"\n");
xml.append(" diskPersistent=\""+diskPersistent+"\"\n");
xml.append(" eternal=\""+eternal+"\"\n");
xml.append(" maxElementsInMemory=\""+maxElementsInMemory+"\"\n");
xml.append(" maxElementsOnDisk=\""+maxElementsOnDisk+"\"\n");
xml.append(" memoryStoreEvictionPolicy=\""+policy+"\"\n");
xml.append(" timeToIdleSeconds=\""+timeToIdleSeconds+"\"\n");
xml.append(" timeToLiveSeconds=\""+timeToLiveSeconds+"\"\n");
xml.append(" overflowToDisk=\""+overflowToDisk+"\"");
xml.append(">\n");
if(isDistributed){
xml.append(" <cacheEventListenerFactory \n");
//xml.append(" class=\"net.sf.ehcache.distribution.RMICacheReplicatorFactory\" ");
xml.append(" class=\"railo.extension.io.cache.eh.RailoRMICacheReplicatorFactory\" \n");
xml.append(" properties=\"replicateAsynchronously="+replicateAsynchronously+
", asynchronousReplicationIntervalMillis="+asynchronousReplicationInterval+
", replicatePuts="+replicatePuts+
", replicatePutsViaCopy="+replicatePutsViaCopy+
", replicateUpdates="+replicateUpdates+
", replicateUpdatesViaCopy="+replicateUpdatesViaCopy+
", replicateRemovals="+replicateRemovals+" \"");
xml.append("/>\n");
// BootStrap
if(toBooleanValue(arguments.get("bootstrapType","false"),false)){
xml.append("<bootstrapCacheLoaderFactory \n");
xml.append(" class=\"net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory\" \n");
xml.append(" properties=\"bootstrapAsynchronously="+toBooleanValue(arguments.get("bootstrapAsynchronously","true"),true)+
", maximumChunkSizeBytes="+toLongValue(arguments.get("maximumChunkSizeBytes","5000000"),5000000L)+"\" \n");
xml.append(" propertySeparator=\",\" /> \n");
}
}
xml.append(" </cache>\n");
}
public void init(String cacheName, Struct arguments) {
init(ThreadLocalPageContext.getConfig(),cacheName, arguments);
}
@Override
public void init(Config config,String cacheName, Struct arguments) {
this.classLoader=config.getClassLoader();
this.cacheName=cacheName;
setClassLoader();
Resource hashDir = config.getConfigDir().getRealResource("ehcache").getRealResource(createHash(arguments));
manager =((CacheManagerAndHash) managers.get(hashDir.getAbsolutePath())).manager;
}
private void setClassLoader() {
if(classLoader!=Thread.currentThread().getContextClassLoader())
Thread.currentThread().setContextClassLoader(classLoader);
}
protected net.sf.ehcache.Cache getCache() {
setClassLoader();
return manager.getCache(cacheName);
}
@Override
public boolean remove(String key) {
try {
return getCache().remove(key);
}
catch(Throwable t){
return false;
}
}
public CacheEntry getCacheEntry(String key) throws CacheException {
try {
misses++;
Element el = getCache().get(key);
if(el==null)throw new CacheException("there is no entry in cache with key ["+key+"]");
hits++;
misses--;
return new EHCacheEntry(el);
}
catch(IllegalStateException ise) {
throw new CacheException(ise.getMessage());
}
catch(net.sf.ehcache.CacheException ce) {
throw new CacheException(ce.getMessage());
}
}
public CacheEntry getCacheEntry(String key, CacheEntry defaultValue) {
try {
Element el = getCache().get(key);
if(el!=null){
hits++;
return new EHCacheEntry(el);
}
}
catch(Throwable t) {
misses++;
}
return defaultValue;
}
@Override
public Object getValue(String key) throws CacheException {
try {
misses++;
Element el = getCache().get(key);
if(el==null)throw new CacheException("there is no entry in cache with key ["+key+"]");
misses--;
hits++;
return el.getObjectValue();
}
catch(IllegalStateException ise) {
throw new CacheException(ise.getMessage());
}
catch(net.sf.ehcache.CacheException ce) {
throw new CacheException(ce.getMessage());
}
}
@Override
public Object getValue(String key, Object defaultValue) {
try {
Element el = getCache().get(key);
if(el!=null){
hits++;
return el.getObjectValue();
}
}
catch(Throwable t) {
misses++;
}
return defaultValue;
}
@Override
public long hitCount() {
return hits;
}
@Override
public long missCount() {
return misses;
}
public void remove() {
setClassLoader();
CacheManager singletonManager = CacheManager.getInstance();
if(singletonManager.cacheExists(cacheName))
singletonManager.removeCache(cacheName);
}
private static boolean toBooleanValue(Object o, boolean defaultValue) {
if(o instanceof Boolean) return ((Boolean)o).booleanValue();
else if(o instanceof Number) return (((Number)o).doubleValue())!=0;
else if(o instanceof String) {
String str = o.toString().trim().toLowerCase();
if(str.equals("yes") || str.equals("on") || str.equals("true")) return true;
else if(str.equals("no") || str.equals("false") || str.equals("off")) return false;
}
return defaultValue;
}
private static String toString(Object o, String defaultValue) {
if(o instanceof String)return o.toString();
return defaultValue;
}
private static int toIntValue(Object o, int defaultValue) {
if(o instanceof Number) return ((Number)o).intValue();
try{
return Integer.parseInt(o.toString());
}
catch(Throwable t){
return defaultValue;
}
}
private static long toLongValue(Object o, long defaultValue) {
if(o instanceof Number) return ((Number)o).longValue();
try{
return Long.parseLong(o.toString());
}
catch(Throwable t){
return defaultValue;
}
}
}class CacheManagerAndHash {
CacheManager manager;
String hash;
public CacheManagerAndHash(CacheManager manager, String hash) {
this.manager=manager;
this.hash=hash;
}
}
class DataFiter implements ResourceNameFilter {
@Override
public boolean accept(Resource parent, String name) {
return name.endsWith(".data");
}
}