/*
* Copyright (C) 2000 - 2015 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/
*/
package com.naryx.tagfusion.expression.function.string;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import javolution.util.FastMap;
import org.aw20.util.SystemClockEvent;
import com.naryx.tagfusion.cfm.engine.cfArgStructData;
import com.naryx.tagfusion.cfm.engine.cfData;
import com.naryx.tagfusion.cfm.engine.cfSession;
import com.naryx.tagfusion.cfm.engine.cfmRunTimeException;
import com.naryx.tagfusion.cfm.tag.cfLOCK;
import com.naryx.tagfusion.cfm.tag.cfLockingObject;
import com.naryx.tagfusion.expression.function.functionBase;
/*
* Reads a file, converting it to JSON and caches this object
*/
public class JsonFileRead extends functionBase {
private static final long serialVersionUID = 1L;
private static long CACHE_IDLE_MS = 60000;
private static Map<File, JsonElement> cacheMap;
static {
cacheMap = new FastMap<File, JsonElement>();
org.aw20.util.SystemClock.setListenerMinute( new SystemClockEvent() {
@Override
public void clockEvent( int arg0 ) {
if ( cacheMap.isEmpty() )
return;
synchronized ( cacheMap ) {
Iterator<JsonElement> it = cacheMap.values().iterator();
while ( it.hasNext() ) {
JsonElement je = it.next();
if ( ( System.currentTimeMillis() - je.lastRead ) > CACHE_IDLE_MS )
it.remove();
}
}
}
} );
}
private class JsonElement {
long lastModifiedAtRead;
long lastRead;
cfData data;
private boolean isValid( long lastModified ) {
return lastModified == lastModifiedAtRead;
}
}
public JsonFileRead() {
min = 1;
max = 3;
setNamedParams( new String[] { "filesrc", "strictmapping", "charset" } );
}
@Override
public String[] getParamInfo() {
return new String[] {
"path to the file source, full path name is required.",
"Flag to determine if CFML Query objects should be recognized and converted to a Query object; defaults to true",
"the character set to use for reading the file"
};
}
@Override
public java.util.Map getInfo() {
return makeInfo(
"conversion",
"Reads and decodes the given file to JSON. This function caches the result so it won't keep reading/decoding the file on every request. The cache will remove items from memory that haven't been touched in over 60 seconds.",
ReturnType.OBJECT );
}
@Override
public cfData execute( cfSession _session, cfArgStructData argStruct ) throws cfmRunTimeException {
// Pull in the file name
String srcname = getNamedStringParam( argStruct, "filesrc", null );
if ( srcname == null )
throwException( _session, "missing 'filesrc' attribute" );
File srcFile = new File( srcname );
if ( !srcFile.exists() )
throwException( _session, "file does not exist: " + srcname );
// Check to see if we have it in the cache
JsonElement je = getFromMap( srcFile );
if ( je != null ) {
return je.data;
}
// We must read the file and put it into the cache
String lockname = "lock" + org.aw20.security.MD5.getDigest( srcFile.toString() );
cfLockingObject lock = cfLOCK.getLock( _session, lockname );
try {
if ( lock.lock( cfLOCK.TYPE_EXCLUSIVE, 1000 ) ) {
try {
// Do a quick check to see if hasn't been done in a previous lock
je = getFromMap( srcFile );
if ( je != null ) {
return je.data;
}
// Now to read it
String charset = getNamedStringParam( argStruct, "charset", null );
String json = null;
try {
if ( charset == null )
json = org.aw20.io.FileUtil.readToString( srcFile );
else
json = org.aw20.io.FileUtil.readToString( srcFile, charset );
} catch ( IOException ioe ) {
throwException( _session, "file: " + srcname + "; " + ioe.getMessage() );
}
// Deserialize the object now
argStruct.setNamedBasedMode();
argStruct.setData( "jsonstring", json );
je = new JsonElement();
je.data = new DeserializeJSONJackson().execute( _session, argStruct );
je.lastRead = System.currentTimeMillis();
je.lastModifiedAtRead = srcFile.lastModified();
synchronized ( cacheMap ) {
cacheMap.put( srcFile, je );
}
return je.data;
} finally {
lock.unlock( cfLOCK.TYPE_EXCLUSIVE );
}
} else {
throwException( _session, "file: " + srcname + "; timeout waiting to lock" );
}
} finally {
cfLOCK.freeLock( _session, lockname, lock );
}
return null; // will never reach here but keeps the compiler happy
}
private JsonElement getFromMap( File srcFile ) {
JsonElement je;
synchronized ( cacheMap ) {
je = cacheMap.get( srcFile );
if ( je != null ) {
if ( je.isValid( srcFile.lastModified() ) ) {
je.lastRead = System.currentTimeMillis();
return je;
} else {
cacheMap.remove( srcFile );
}
}
}
return null;
}
}