/*
* Copyright (C) 2000 - 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: cfmlFileCache.java 2220 2012-07-30 01:11:52Z alan $
*/
package com.naryx.tagfusion.cfm.file;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import com.nary.cache.FileCache;
import com.nary.io.FileUtils;
import com.nary.util.FastMap;
import com.nary.util.stringtokenizer;
import com.naryx.tagfusion.cfm.engine.ComponentFactory;
import com.naryx.tagfusion.cfm.engine.ComponentScriptFactory;
import com.naryx.tagfusion.cfm.engine.cfEngine;
import com.naryx.tagfusion.cfm.engine.cfSession;
import com.naryx.tagfusion.cfm.engine.cfmBadFileException;
import com.naryx.tagfusion.cfm.engine.cfmRunTimeException;
import com.naryx.tagfusion.cfm.engine.engineListener;
import com.naryx.tagfusion.cfm.file.mapping.OpenBDArchive;
import com.naryx.tagfusion.cfm.tag.cfINCLUDE;
import com.naryx.tagfusion.xmlConfig.xmlCFML;
/**
* This class handles the caching and creation of the actual class files that handle the CFML page.
*/
public class cfmlFileCache extends Object implements engineListener {
public static final String DEFAULT_TRUST_CACHE = "false";
public static final String DEFAULT_MAX_FILES = "1000";
private static cfmlFileCache thisObject;
private int maxFiles = 1000;
private static boolean bTrustCache = false;
private FileCache loadedFiles; // the actual file cache
private Map<String, List<String>> customTags; // custom tag paths
private Map<String, String> cfMappings; // mapped paths
public static void init(ServletContext context, xmlCFML _iniFile) {
// This routine is called from the cfEngine class at startup
thisObject = new cfmlFileCache(context, _iniFile);
}
public static boolean isCustomTag(String _tag) {
return thisObject._isCustomTag(_tag);
}
// removes non-"cf" custom tag mappings
public static void removeAllNonCFMappings() {
thisObject._removeAllNonCFMappings();
}
public static Map<String, String> getCFMappings() {
return thisObject.cfMappings;
}
public static List<String> getCustomDirMapping(String tagname) {
return thisObject._getCustomDirMapping(tagname);
}
public static cfFile getCfmlFile(ServletContext context, cfmlURI uri, HttpServletRequest REQ) throws cfmBadFileException {
return thisObject._getCfmlFile(context, uri, REQ);
}
public static void removeCfmlFile(cfmlURI uri, HttpServletRequest REQ) {
thisObject._removeCfmlFile(uri, REQ);
}
public static void insertCfmlFile(cfFile _cfmlFile, cfmlURI uri, HttpServletRequest REQ) {
thisObject._insertCfmlFile(_cfmlFile, uri, REQ);
}
public static void flushFile(String _file) {
thisObject.loadedFiles._flushFile(_file);
}
public static boolean isTrustCache() {
return bTrustCache;
}
// note: not i18n safe
public static InputStream getInputStream(ServletContext context, String _bdDirectory) throws IOException {
// This method takes in the directory description for BD and returns an InputStream to that
// resource. This is mainly a helper method for configuration files.
if (_bdDirectory.length() == 0)
return null;
if (_bdDirectory.length() > 0 && _bdDirectory.charAt(0) == '/') {
// Relative path
return new FileInputStream(new File(FileUtils.getRealPath(_bdDirectory)));
} else if (_bdDirectory.length() > 0 && _bdDirectory.charAt(0) == '$') {
// Real path (UNIX/Linux)
return new FileInputStream(new File(_bdDirectory.substring(1)));
} else {
// Real path (Windows)
return new FileInputStream(new File(_bdDirectory));
}
}
// this method is only invoked by cfmRunTimeException.printSourceCode()
@SuppressWarnings("deprecation")
public static BufferedReader getReader(HttpServletRequest req, ServletContext context, cfmlURI uri) throws Exception {
if (!uri.isRealFile()) {
try {
// The resource is a normal URI request This method is called at request time so it must use
// request.getRealPath() instead of context.getRealPath().
File fin = FileUtils.getRealFile(req, uri.getURI());
cfFileEncoding fileEncoding = new cfFileEncoding(fin);
return fileEncoding.getReader(fin);
} catch (cfmBadFileException e) {
if (e.isPageEncodingException()) {
return new BufferedReader(new FileReader(req.getRealPath(uri.getURI())));
} else {
throw e;
}
}
} else {
try {
// --[ The URI object does not represent a URL but a real path
cfFileEncoding fileEncoding = new cfFileEncoding(uri.getFile());
return fileEncoding.getReader(uri.getFile());
} catch (cfmBadFileException e) {
if (e.isPageEncodingException()) {
return new BufferedReader(new FileReader(uri.getRealPath()));
} else {
throw e;
}
}
}
}
public static Set<cfmlURI> listFiles(cfSession _Session, cfmlURI uriDir) {
if (uriDir.isRealFile())
return getFileSet(uriDir.getFile());
File realFile = FileUtils.getRealFile(_Session.REQ, uriDir.getURI());
if (realFile != null)
return getFileSet(realFile);
// Now we must search the BDA / WAR files
return thisObject.getResourcePaths(_Session.CTX, uriDir.getURI());
}
private static Set<cfmlURI> getFileSet(File dir) {
File[] files = dir.listFiles();
Set<cfmlURI> fileSet = new HashSet<cfmlURI>();
if (files != null) {
for (int i = 0; i < files.length; i++) {
fileSet.add(new cfmlURI(files[i]));
}
}
return fileSet;
}
public static cfmlURI deepFindFile(cfSession _Session, cfmlURI uri, String fileExt) {
if (uri.isRealFile()) {
// If the uri is a real file then we need to get the files from disk
try {
// Build a list of the directories underneath the parent directory
List<File> rawDirs = new com.nary.io.fileList(uri.getFile().getParent(), "*." + fileExt).list();
Iterator<File> E = rawDirs.iterator();
File possibleFile;
while (E.hasNext()) {
possibleFile = E.next();
if (possibleFile.getName().equalsIgnoreCase(uri.getFile().getName()))
return new cfmlURI(possibleFile);
}
} catch (FileNotFoundException EE) {
return null;
}
} else {
// The directory is defined as a URI and we must search the BDA or the URI space
Set<cfmlURI> dirs = thisObject.getDeepResourcePaths(_Session.CTX, uri.getParentURI());
Iterator<cfmlURI> EE = dirs.iterator();
while (EE.hasNext()) {
cfmlURI path = EE.next();
if (path.getURI().indexOf(uri.getParentURI()) != 0)
continue;
if (path.getChildURI().equalsIgnoreCase(uri.getChildURI()))
return path;
}
}
return null;
}
public static void flushCache() {
thisObject.flush();
}
public static int filesInCache() {
return thisObject.loadedFiles.size();
}
public static long getStatsHits() {
return thisObject.loadedFiles.getStatsHits();
}
public static long getStatsMisses() {
return thisObject.loadedFiles.getStatsMisses();
}
public static cachedFile[] getCachedFiles() {
return (cachedFile[]) thisObject.loadedFiles.getEntries();
}
public void engineAdminUpdate(xmlCFML config) {
engineAdminUpdate(null, config);
}
public void engineAdminUpdate(ServletContext context, xmlCFML config) {
maxFiles = config.getInt("server.file.maxfiles", Integer.parseInt(DEFAULT_MAX_FILES));
bTrustCache = config.getBoolean("server.file.trustcache", Boolean.valueOf(DEFAULT_TRUST_CACHE).booleanValue());
cfEngine.log("cfmlFileCache Configuration. Caching=" + maxFiles + " files; trusted cache=" + bTrustCache);
// Reload/load the custom tag mappings
loadCustomTagMappings(config);
// Reload/load the cf mappings for cfinclude/cfmodule
loadCFMappings(config);
}
public void engineShutdown() {
flush();
cfEngine.log("cfmlFileCache was shutdown");
}
private cfmlFileCache(ServletContext context, xmlCFML _iniFile) {
// operations that iterate over loadedFiles must be explicitly synchronized
loadedFiles = new com.nary.cache.FileCache(maxFiles);
customTags = new FastMap<String, List<String>>();
engineAdminUpdate(context, _iniFile);
cfEngine.registerEngineListener(this);
}
public void flush() {
loadedFiles.flushAll();
cfEngine.log("cfmlFileCache was flushed");
}
private void _removeCfmlFile(cfmlURI uri, HttpServletRequest REQ) {
loadedFiles.flushEntry(uri.getKey(REQ));
}
private cfFile _getCfmlFile(ServletContext context, cfmlURI uri, HttpServletRequest REQ) throws cfmBadFileException {
// Check to see if this uri is in the current cache
String cacheKey = uri.getKey(REQ);
cachedFile fileInCache = (cachedFile) loadedFiles.getFromCache(cacheKey);
if (fileInCache != null) {
if (bTrustCache || !fileInCache.wasModified()) {
return fileInCache.get();
} else {
loadedFiles.flushEntry(uri.getKey(REQ));
}
}
try {
// Get a lock just for this file
synchronized (loadedFiles.getLock(cacheKey)) {
// we need to check the loadedFiles again should the last thread have
// loaded in the file
fileInCache = (cachedFile) loadedFiles.getFromCache(cacheKey);
if (fileInCache != null) {
return fileInCache.get();
}
// At this point, the file was either not in the cache, or has expired
fileInCache = new cachedFile();
loadFile(context, uri, fileInCache, REQ);
fileInCache.setURL(uri.getURI());
fileInCache.file.setCfmlURI(uri);
// Insert into the cache
String fileDependency = null;
if (!bTrustCache && uri.isRealFile())
fileDependency = uri.getRealPath();
loadedFiles.setInCache(cacheKey, fileInCache, fileDependency);
// Quick check to delete the oldest files
if (loadedFiles.size() > maxFiles) {
loadedFiles.deleteOldestFile();
}
// Return the file
return fileInCache.get();
}
} finally {
// Be sure to remove the lock for this file
loadedFiles.removeLock(cacheKey);
}
}
private void loadFile(ServletContext context, cfmlURI cfmluri, cachedFile _fileInCache, HttpServletRequest REQ) throws cfmBadFileException {
if ( cfmluri.isArchiveFile() ){
try {
Reader reader = OpenBDArchive.getReader( cfmluri.getArchiveFile(), cfmluri.getURI() );
_fileInCache.file = new cfFile(cfmluri, reader, "UTF-8" );
} catch (Exception e) {
throw new cfmBadFileException( e.getMessage() );
}
}else{
if (!cfmluri.isRealFile()) {
// The resource is a normal URI request
_fileInCache.setRealPath(cfmluri.getRealPath(REQ));
} else {
// The URI object does not represent a URL but a real path
_fileInCache.setRealPath(cfmluri.getRealPath());
}
String realPath = _fileInCache.getRealPath();
if (realPath == null)
throw new cfmBadFileException(cfmluri.getURI());
File theFile = new File(realPath);
if (FileUtils.exists(theFile, cfmluri)){
_fileInCache.file = new cfFile(cfmluri, theFile);
if ( theFile.getName().endsWith(".cfc") && ComponentFactory.emptyComponentFile(_fileInCache.file) ){
try {
_fileInCache.file = ComponentScriptFactory.load( _fileInCache.file );
if ( _fileInCache.file == null )
throw new cfmBadFileException(_fileInCache.getRealPath());
} catch (cfmRunTimeException e) {
e.getCatchData().setFileURI(cfmluri);
throw new cfmBadFileException( e.getCatchData(), null );
}
}
}else{
if ( cfmluri.isMapSearch() && cfMappings.size() > 0 ){
cfmlURI cfmluriTryFromArchive = cfINCLUDE.getMappedCfmlURI( null, cfmluri.getURI() );
if ( cfmluriTryFromArchive != null )
loadFile( context, cfmluriTryFromArchive, _fileInCache, REQ );
else
throw new cfmBadFileException(_fileInCache.getRealPath());
}else
throw new cfmBadFileException(_fileInCache.getRealPath());
}
}
}
private void _insertCfmlFile(cfFile _cfmlFile, cfmlURI uri, HttpServletRequest REQ) {
String cacheKey = uri.getKey(REQ);
try {
// Get a lock just for this file
synchronized (loadedFiles.getLock(cacheKey)) {
cachedFile fW = new cachedFile(_cfmlFile, null, uri.getURI());
fW.setNeverExpire();
// Since the file is set to never expire, don't set a file dependency.
String fileDependency = null;
loadedFiles.setInCache(cacheKey, fW, fileDependency);
}
} finally {
// Be sure to remove the lock for this file
loadedFiles.removeLock(cacheKey);
}
}
// ------------------------------------------------------
// --[ BDA Support
// ------------------------------------------------------
private Set<cfmlURI> getDeepResourcePaths(ServletContext context, String path) {
Set<cfmlURI> paths = getResourcePaths(context, path);
Set<cfmlURI> subPaths = new HashSet<cfmlURI>();
Iterator<cfmlURI> iter = paths.iterator();
while (iter.hasNext()) {
String nextPath = iter.next().getURI();
if (nextPath.endsWith("/") && !nextPath.equals(path)) {
subPaths.addAll(getDeepResourcePaths(context, nextPath));
}
}
paths.addAll(subPaths);
return paths;
}
private Set<cfmlURI> getResourcePaths(ServletContext context, String path) {
// Check the repositories
Set<String> resultSet = context.getResourcePaths(path);
Set<cfmlURI> newResults = new HashSet<cfmlURI>();
// Check for null because if the path passed to context.getResourcePaths()
// doesn't contain any files then WLS will return null.
if (resultSet != null) {
// Transform the list of strings into a list of cfmlURI's
Iterator<String> it = resultSet.iterator();
while (it.hasNext()) {
newResults.add(new cfmlURI(it.next(), ""));
}
}
return newResults;
}
// ----------------------------------------------------------
// ---] Custom Tag support
// ----------------------------------------------------------
private void loadCustomTagMappings(xmlCFML _properties) {
// Load in the CFML Custom tags
customTags = new FastMap<String, List<String>>();
Vector elements = null;
elements = _properties.getKeys("server.cfmlcustomtags.mapping[]");
if (elements == null) {
cfEngine.log("cfmlFileCache.loadCustomTagMappings: no custom tag mappings defined.");
return;
}
Enumeration E = elements.elements();
while (E.hasMoreElements()) {
String key = (String) E.nextElement();
String prefix = key.substring(key.indexOf("[") + 1, key.lastIndexOf("]"));
if (prefix.indexOf("_") == -1 && prefix.indexOf(":") == -1)
prefix += "_";
prefix = prefix.toUpperCase().replace(':', '_').trim();
// Read in the directories specified for this prefix
List<String> directories = new ArrayList<String>();
String dirString = _properties.getString(key + ".directory");
if (dirString != null) {
stringtokenizer dirTokenizer = new stringtokenizer(dirString, ";");
while (dirTokenizer.hasMoreTokens())
directories.add(dirTokenizer.nextToken());
// Add the prefix and its directories to the customTags hashtable
customTags.put(prefix, directories);
cfEngine.log("cfmlFileCache.loadCustomTagMappings [" + prefix + "] >> [" + dirString + "]");
} else {
cfEngine.log("cfmlFileCache.loadCustomTagMappings: Failed to read custom tag Mappings. Invalid or missing data.");
return;
}
}
}
public void loadCFMappings(xmlCFML _config) {
cfMappings = new FastMap<String, String>();
Vector elements = _config.getKeys("server.cfmappings.mapping[]");
if (elements == null) {
cfEngine.log("cfmlFileCache.loadCFMappings: no CF mappings defined.");
return;
}
Enumeration E = elements.elements();
while (E.hasMoreElements()) {
String key = (String) E.nextElement();
String logicalPath = _config.getString(key + ".name");
String directory = _config.getString(key + ".directory");
if (logicalPath != null && directory != null) {
cfMappings.put(logicalPath, directory);
cfEngine.log("cfmlFileCache.CFMapping [" + logicalPath + "] >> [" + directory + "]");
} else {
cfEngine.log("cfmlFileCache.CFMapping: Failed to read CF Mappings. Invalid or missing data.");
}
}
}
private void _removeAllNonCFMappings() {
if (customTags != null) {
Iterator<String> iter = customTags.keySet().iterator();
while (iter.hasNext()) {
String key = iter.next();
if (key.indexOf("CF_") == -1) {
cfEngine.log("cfmlFileCache.Removed CustomTagMapping [" + key + "]");
customTags.remove(key);
}
}
}
}
private List<String> _getCustomDirMapping(String tagname) {
if (customTags != null) {
Iterator<String> iter = customTags.keySet().iterator();
while (iter.hasNext()) {
String key = iter.next();
if (tagname.indexOf(key) == 0) {
return customTags.get(key);
}
}
}
return null;
}
private boolean _isCustomTag(String tag) {
if (tag.toLowerCase().indexOf("cfx") == 0)
return true;
if (customTags != null) {
tag = tag.replace(':', '_');
Iterator<String> iter = customTags.keySet().iterator();
while (iter.hasNext()) {
String key = iter.next();
if (tag.indexOf(key) == 0)
return true;
}
}
return false;
}
}