/**
* Copyright (c) 2014, the Railo Company Ltd.
* Copyright (c) 2015, Lucee Assosication Switzerland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lucee.runtime.cache.ram;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import lucee.commons.io.SystemUtil;
import lucee.commons.io.cache.CacheEntry;
import lucee.commons.io.cache.CachePro;
import lucee.commons.io.cache.exp.CacheException;
import lucee.commons.lang.ExceptionUtil;
import lucee.loader.engine.CFMLEngine;
import lucee.runtime.cache.CacheSupport;
import lucee.runtime.config.Config;
import lucee.runtime.config.ConfigWebUtil;
import lucee.runtime.engine.CFMLEngineImpl;
import lucee.runtime.engine.ThreadLocalPageContext;
import lucee.runtime.op.Caster;
import lucee.runtime.op.Constants;
import lucee.runtime.op.Duplicator;
import lucee.runtime.type.Struct;
public class RamCache extends CacheSupport {
public static final int DEFAULT_CONTROL_INTERVAL = 60;
private Map<String, RamCacheEntry> entries= new ConcurrentHashMap<String, RamCacheEntry>();
private long missCount;
private int hitCount;
private long idleTime;
private long until;
private int controlInterval=DEFAULT_CONTROL_INTERVAL*1000;
private boolean decouple;
private Thread controller;
// this is used by the config by reflection
public RamCache(){
Config config = ThreadLocalPageContext.getConfig();
if(config!=null) {
CFMLEngine engine=ConfigWebUtil.getEngine(config);
if(engine instanceof CFMLEngineImpl) {
controller=new Controler((CFMLEngineImpl)engine,this);
controller.start();
}
}
}
public static void init(Config config,String[] cacheNames,Struct[] arguments) {//print.ds();
}
@Override
public void init(Config config,String cacheName, Struct arguments) throws IOException {
// RamCache is also used without calling init, because of that we have this test in constructor and here
if(controller==null) {
CFMLEngine engine=ConfigWebUtil.getEngine(config);
if(engine instanceof CFMLEngineImpl) {
controller=new Controler((CFMLEngineImpl)engine,this);
controller.start();
}
}
if(controller==null) throw new IOException("was not able to start controller");
// until
long until=Caster.toLongValue(arguments.get("timeToLiveSeconds",Constants.LONG_ZERO),Constants.LONG_ZERO)*1000;
long idleTime=Caster.toLongValue(arguments.get("timeToIdleSeconds",Constants.LONG_ZERO),Constants.LONG_ZERO)*1000;
Object ci = arguments.get("controlIntervall",null);
if(ci==null)ci = arguments.get("controlInterval",null);
int intervalInSeconds=Caster.toIntValue(ci,DEFAULT_CONTROL_INTERVAL);
init(until,idleTime,intervalInSeconds);
}
public RamCache init(long until, long idleTime, int intervalInSeconds) {
this.until=until;
this.idleTime=idleTime;
this.controlInterval=intervalInSeconds*1000;
return this;
}
public void release() {
entries.clear();
missCount=0;
hitCount=0;
idleTime=0;
until=0;
controlInterval=DEFAULT_CONTROL_INTERVAL*1000;
decouple=false;
if(controller!=null && controller.isAlive())controller.interrupt();
}
@Override
public boolean contains(String key) {
return _getQuiet(key,null)!=null;
}
@Override
public CacheEntry getQuiet(String key, CacheEntry defaultValue) {
RamCacheEntry entry = entries.get(key);
if(entry==null) {
return defaultValue;
}
if(!valid(entry)) {
entries.remove(key);
return defaultValue;
}
if(decouple)entry=new RamCacheEntry(entry.getKey(), decouple(entry.getValue()), entry.idleTimeSpan(), entry.liveTimeSpan());
return entry;
}
private CacheEntry _getQuiet(String key, CacheEntry defaultValue) {
RamCacheEntry entry = entries.get(key);
if(entry==null) {
return defaultValue;
}
if(!valid(entry)) {
entries.remove(key);
return defaultValue;
}
return entry;
}
@Override
public CacheEntry getCacheEntry(String key, CacheEntry defaultValue) {
RamCacheEntry ce = (RamCacheEntry) _getQuiet(key, null);
if(ce!=null) {
if(decouple)ce=new RamCacheEntry(ce.getKey(), decouple(ce.getValue()), ce.idleTimeSpan(), ce.liveTimeSpan());
hitCount++;
return ce.read();
}
missCount++;
return defaultValue;
}
@Override
public long hitCount() {
return hitCount;
}
@Override
public long missCount() {
return missCount;
}
@Override
public List<String> keys() {
List<String> list=new ArrayList<String>();
Iterator<Entry<String, RamCacheEntry>> it = entries.entrySet().iterator();
RamCacheEntry entry;
while(it.hasNext()){
entry=it.next().getValue();
if(valid(entry))list.add(entry.getKey());
}
return list;
}
@Override
public void put(String key, Object value, Long idleTime, Long until) {
RamCacheEntry entry= entries.get(key);
if(entry==null){
entries.put(key, new RamCacheEntry(key,decouple(value),
idleTime==null?this.idleTime:idleTime.longValue(),
until==null?this.until:until.longValue()));
}
else
entry.update(value);
}
@Override
public boolean remove(String key) {
RamCacheEntry entry = entries.remove(key);
if(entry==null) {
return false;
}
return valid(entry);
}
@Override
public int clear() throws IOException {
int size=entries.size();
entries.clear();
return size;
}
public static class Controler extends Thread {
private RamCache ramCache;
private CFMLEngineImpl engine;
public Controler(CFMLEngineImpl engine, RamCache ramCache) {
this.engine=engine;
this.ramCache=ramCache;
}
@Override
public void run(){
while(engine.isRunning()){
try{
_run();
SystemUtil.sleep(ramCache.controlInterval);
}
catch(Throwable t){ExceptionUtil.rethrowIfNecessary(t);}
}
}
private void _run() {
RamCacheEntry[] values = ramCache.entries.values().toArray(new RamCacheEntry[ramCache.entries.size()]);
for(int i=0;i<values.length;i++){
if(!CacheSupport.valid(values[i])){
ramCache
.entries
.remove(
values[i].getKey()
);
}
}
}
}
@Override
public void verify() throws CacheException {
// this cache is in memory and always ok
}
@Override
public CachePro decouple() {
decouple = true;
return this;
}
private Object decouple(Object value) {
if(!decouple) return value;
return Duplicator.duplicate(value, true);
}
}