/*
* Copyright (C) 2012 TagServlet Ltd
*
* This file is part of Open BlueDragon (OpenBD) CFML Server Engine.
*
* OpenBD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* Free Software Foundation,version 3.
*
* OpenBD 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenBD. If not, see http://www.gnu.org/licenses/
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or combining
* it with any of the JARS listed in the README.txt (or a modified version of
* (that library), containing parts covered by the terms of that JAR, the
* licensors of this Program grant you additional permission to convey the
* resulting work.
* README.txt @ http://www.openbluedragon.org/license/README.txt
*
* http://openbd.org/
* $Id: MemoryDiskCacheImpl.java 2426 2014-03-30 18:53:18Z alan $
*
* size int size of items in memory
* diskpersistent true / false
* diskspooldir full path
* diskcleanonstart true/false
* diskmaxsizemb maximum size on disk
*/
package com.naryx.tagfusion.cfm.cache.impl;
import java.io.File;
import java.io.IOException;
import org.aw20.io.FileUtil;
import com.nary.io.FileUtils;
import com.naryx.tagfusion.cfm.cache.CacheFactory;
import com.naryx.tagfusion.cfm.cache.CacheInterface;
import com.naryx.tagfusion.cfm.engine.cfArrayData;
import com.naryx.tagfusion.cfm.engine.cfData;
import com.naryx.tagfusion.cfm.engine.cfEngine;
import com.naryx.tagfusion.cfm.engine.cfNumberData;
import com.naryx.tagfusion.cfm.engine.cfStructData;
import com.naryx.tagfusion.cfm.engine.dataNotSupportedException;
public class MemoryDiskCacheImpl extends java.util.LinkedHashMap<String,CacheUnit> implements CacheInterface {
private static final long serialVersionUID = 1L;
private cfStructData props;
private int maxItems = 0;
private File cacheDir = null;
private int
statsSet = 0,
statsGet = 0,
statsDelete = 0,
statsMiss = 0,
statsMissAge = 0,
statsHitMem = 0,
statsHitDisk = 0,
statsDiskPage = 0,
statsDiskPrune = 0;
@Override
public cfStructData getStats() {
cfStructData stats = new cfStructData();
stats.setData("set", statsSet);
stats.setData("get", statsGet);
stats.setData("miss", statsMiss);
stats.setData("missage", statsMissAge);
stats.setData("remove", statsDelete);
stats.setData("hitmem", statsHitMem);
stats.setData("hitdisk", statsHitDisk);
stats.setData("diskpage", statsDiskPage);
stats.setData("diskprune",statsDiskPrune);
stats.setData("inmem", super.size() );
return stats;
}
@Override
public void setProperties(String region, cfStructData _props) throws Exception {
this.props = _props;
// Maximum the size
if ( !props.containsKey("size") )
props.setData("size", new cfNumberData(100) );
if ( !props.containsKey("diskmaxsizemb") )
props.setData("diskmaxsizemb", new cfNumberData(25) );
maxItems = props.getData("size").getInt();
cfEngine.log( getName() + "." + region + ": MaxSize: " + maxItems );
// Determine the disk side
if ( props.containsKey("diskpersistent") ){
if ( props.getData("diskpersistent").getBoolean() ){
if ( props.containsKey("diskspooldir") ){
cacheDir = new File( props.getData("diskspooldir").getString() );
}else{
cacheDir = new File( cfEngine.thisPlatform.getFileIO().getWorkingDirectory(), "cache-" + region );
}
if ( !cacheDir.isDirectory() && !cacheDir.mkdirs() )
throw new Exception( "Failed to create: " + cacheDir );
cfEngine.log( getName() + "." + region + ": Directory set: " + cacheDir );
if ( props.containsKey("diskcleanonstart") && props.getData("diskcleanonstart").getBoolean() ){
FileUtils.recursiveDelete( cacheDir, false );
cfEngine.log( getName() + "." + region + ": Directory cleaned");
}
}
}else{
if ( cacheDir != null )
FileUtils.recursiveDelete( cacheDir, false );
cacheDir = null;
}
}
@Override
public void set(String id, cfData data, long ageMS, long idleTime) {
statsSet++;
String idMd5 = CacheFactory.createCacheKey(id);
synchronized(this){
CacheUnit cu = new CacheUnit( id, data, ageMS );
super.put(idMd5, cu);
}
// if we are spooling to disk, then we need to delete the one that is on disk
if ( cacheDir != null )
new File( cacheDir, idMd5 + ".cache" ).delete();
}
@Override
public cfData get(String id) {
statsGet++;
String idMd5 = CacheFactory.createCacheKey(id);
// Check to see if in memory
synchronized(this){
if ( this.containsKey(idMd5) ){
CacheUnit cu = super.get(idMd5);
if ( cu.stillYoung() ){
statsHitMem++;
return cu.val;
}else{
super.remove(idMd5);
if ( cacheDir != null )
new File( cacheDir, idMd5 + ".cache" ).delete();
statsMissAge++;
return null;
}
}
// Check to see if on disk
if ( cacheDir != null ){
File fu = new File( cacheDir, idMd5 + ".cache" );
if ( fu.isFile() ){
CacheUnit cu = (CacheUnit)org.aw20.io.FileUtil.loadClass(fu);
fu.delete();
if ( cu != null ){
if ( cu.stillYoung() ){
super.put( idMd5, cu );
statsHitDisk++;
return cu.val;
}else{
statsMissAge++;
return null;
}
}
}
}
}
// not found
statsMiss++;
return null;
}
@Override
public void delete(String id, boolean exact) {
statsDelete++;
String idMd5 = CacheFactory.createCacheKey(id);
synchronized(this){
this.remove(idMd5);
}
if ( cacheDir != null )
new File( cacheDir, idMd5 + ".cache" ).delete();
}
@Override
public synchronized void deleteAll() {
super.clear();
if ( cacheDir != null ){
try {
FileUtils.recursiveDelete( cacheDir, false );
} catch (IOException e) {
cfEngine.log( getName() + " " + e.getMessage() );
}
}
}
@Override
public String getName() {
return "MemoryDiskCache";
}
@Override
public cfArrayData getAllIds() {
return cfArrayData.createArray(1);
}
@Override
public cfStructData getProperties() {
return props;
}
@Override
public void shutdown() {
clear();
}
protected synchronized boolean removeEldestEntry(java.util.Map.Entry eldest) {
if ( size() > maxItems ){
if ( cacheDir != null ){
CacheUnit cu = (CacheUnit)eldest.getValue();
// Only page to the disk if this object is still young
if ( cu.stillYoung() ){
try{
File f = new File( cacheDir, eldest.getKey() + ".cache" );
f.delete();
FileUtil.saveClass( f, cu );
statsDiskPage++;
pruneDiskSpace();
}catch(Exception e){
cfEngine.log( getName() + " " + e.getMessage() );
}
}
}
return true;
}else
return false;
}
/**
* Runs around the disk space and make sure we have enough space, deleting files if
* need be.
*/
private void pruneDiskSpace(){
long maxBytes = 0;
try {
maxBytes = props.getData("diskmaxsizemb").getLong() * 1024000;
} catch (dataNotSupportedException e) {
maxBytes = 25 * 1024000;
}
// Go through the files making sure we have enough space
File[] allFiles = cacheDir.listFiles();
if ( allFiles != null && allFiles.length > 0 ){
long totalBytes = 0, fileLen = 0;
for (int x=0; x < allFiles.length; x++ ){
fileLen = allFiles[x].length();
totalBytes += fileLen;
if ( totalBytes > maxBytes ){
allFiles[x].delete();
totalBytes -= fileLen;
statsDiskPrune++;
}
}
}
}
}