/*
* Copyright (C) 2000 - 2013 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: cfMODULE.java 2374 2013-06-10 22:14:24Z alan $
*/
package com.naryx.tagfusion.cfm.tag;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.nary.cache.cfmlURICache;
import com.nary.util.string;
import com.naryx.tagfusion.cfm.application.cfAPPLICATION;
import com.naryx.tagfusion.cfm.engine.catchDataFactory;
import com.naryx.tagfusion.cfm.engine.cfBooleanData;
import com.naryx.tagfusion.cfm.engine.cfCatchData;
import com.naryx.tagfusion.cfm.engine.cfData;
import com.naryx.tagfusion.cfm.engine.cfEngine;
import com.naryx.tagfusion.cfm.engine.cfSession;
import com.naryx.tagfusion.cfm.engine.cfStringData;
import com.naryx.tagfusion.cfm.engine.cfStructData;
import com.naryx.tagfusion.cfm.engine.cfmAbortException;
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.cfFile;
import com.naryx.tagfusion.cfm.file.cfmlFileCache;
import com.naryx.tagfusion.cfm.file.cfmlURI;
/**
* Inherits the TEMPLATE processing from CFINCLUDE tag.
*/
public class cfMODULE extends cfINCLUDE implements cfOptionalBodyTag, Serializable, Cloneable {
static final long serialVersionUID = 1;
public static final String THISTAG_SCOPE = "thistag";
public static final String EXECUTION_MODE = "executionmode";
public static final cfStringData START_MODE = new cfStringData("start");
public static final cfStringData END_MODE = new cfStringData("end");
public static final cfStringData INACTIVE_MODE = new cfStringData("inactive");
public static final String GENERATED_CONTENT = "generatedcontent";
public static final String HAS_END_TAG = "hasendtag";
private static cfmlURICache locationCache = new cfmlURICache(10 * 60);
private String endMarker = null;
private static transient List<String> directoryList;
private transient boolean isRendering = false;
protected String customTagName; // the name of the tag deduced from it's attributes
protected String customTagKey;
static {
cfEngine.registerEngineListener(new EngineListener());
}
public cfMODULE() {
// allows definition of subclasses
}
public String getEndMarker() {
return endMarker;
}
public boolean isRendering() {
return isRendering;
}
public void setRendering(boolean rendering) {
isRendering = rendering;
}
protected void defaultParameters(String _tag) throws cfmBadFileException {
tagName = "CFMODULE";
parseTagHeader(_tag);
if (containsAttribute("TEMPLATE") && containsAttribute("NAME"))
throw invalidAttributeException("cfmodule.tooManyAttributes", null);
if (containsAttribute("NAME")) {
// Need to get the file mapping
directoryList = cfmlFileCache.getCustomDirMapping("CF_XYZ");
if (directoryList == null)
throw invalidAttributeException("cfmodule.missingMapping", new String[] { "CF_" });
}
}
public void setEndTag() {
// This is called once from the cfParseTag class. its to handle <CFMODULE/> which is to trigger double execution
endMarker = "";
}
public void lookAheadForEndTag(tagReader inFile) {
endMarker = new tagLocator(tagName, inFile).findEndMarker();
}
/**
* We have to clone this tag's instance because this tag allows us to have a <cfmodule name="#abc#">
*/
public cfTagReturnType render(cfSession _Session) throws cfmRunTimeException {
try {
cfMODULE clone = (cfMODULE) this.clone();
clone.customTagName = clone.getRealTagName(_Session);
_Session.replaceTag(clone);
return clone.realCustomRender(_Session, clone.getCustomTagFile(_Session));
} catch (CloneNotSupportedException e) {
return cfTagReturnType.NORMAL;
}
}
protected cfTagReturnType realCustomRender(cfSession _Session, cfFile customTag) throws cfmRunTimeException {
// Setup the paramters
cfStructData thisTag = new cfStructData();
thisTag.setData(GENERATED_CONTENT, new cfStringData(""));
if (endMarker == null)
thisTag.setData(HAS_END_TAG, cfBooleanData.FALSE);
else
thisTag.setData(HAS_END_TAG, cfBooleanData.TRUE);
cfStructData attributes = packageUpAttributes(_Session);
_Session.enterCustomTag(thisTag, attributes, customTagName);
isRendering = true;
thisTag.setData(EXECUTION_MODE, START_MODE);
// Render the file
boolean suppressWhitespace = _Session.setSuppressWhiteSpace(cfEngine.getSuppressWhiteSpaceDefault());
try {
cfTagReturnType rt = renderCustomTagStart(_Session, customTag); // render the start tag
if (rt.isExit()) {
if (rt.getMethod().equals(cfEXIT.METHOD_EXITTAG)) {
_Session.clearCustomTag();
isRendering = false;
return cfTagReturnType.NORMAL;
} else if (rt.getMethod().equals(cfEXIT.METHOD_LOOP)) {
throw newRunTimeException("CFEXIT of type METHOD=LOOP is not valid in START mode");
}
// keep going for cfEXIT.METHOD_EXITTEMPLATE
}
} finally {
_Session.setSuppressWhiteSpace(suppressWhitespace);
}
// Check to see if the tag has an end tag
if (endMarker != null) {
boolean processingCfOutput = _Session.setProcessingCfOutput(true);
suppressWhitespace = _Session.setSuppressWhiteSpace(false);
thisTag.setData(EXECUTION_MODE, INACTIVE_MODE);
while (true) {
// render the body between the start/end tags
cfStructData varScope = _Session.leaveCustomTag();
isRendering = false;
int renderOptions = cfTag.DEFAULT_OPTIONS;
if (!processingCfOutput)
renderOptions = renderOptions | cfTag.CF_OUTPUT_ONLY;
if (suppressWhitespace)
renderOptions = renderOptions | cfTag.SUPPRESS_WHITESPACE;
thisTag.setData(GENERATED_CONTENT, new cfStringData(renderToString(_Session, renderOptions).getOutput()));
// if any attributes have been added via cfASSOCIATE in child tags
addAssociatedAttributes(_Session, this, thisTag);
_Session.enterCustomTag(thisTag, attributes, customTagName, varScope, false);
isRendering = true;
try {
thisTag.setData(EXECUTION_MODE, END_MODE);
// render the end tag
cfTagReturnType rt = renderCustomTagEnd(_Session, customTag, cfEngine.getSuppressWhiteSpaceDefault());
// The content buffer may have been reset at this point (e.g. by cfcontent reset="yes") so don't
// write out the generated content if it has
if (!_Session.hasBufferReset()) {
_Session.write(thisTag.getData(GENERATED_CONTENT).getString());
}
_Session.write(rt.getOutput());
if (rt.isExit() && rt.getMethod().equals(cfEXIT.METHOD_LOOP)) {
continue;
}
break; // if no CFEXIT METHOD=LOOP then we're done
} catch (cfmAbortException ae) {
if (ae.flushOutput()) {
_Session.write(thisTag.getData(GENERATED_CONTENT).getString());
_Session.write(ae.getOutput());
}
throw ae;
}
}
_Session.setSuppressWhiteSpace(suppressWhitespace);
_Session.setProcessingCfOutput(processingCfOutput);
}
_Session.clearCustomTag();
isRendering = false;
return cfTagReturnType.NORMAL;
}
// ----------------------------------------------------
public static cfTagReturnType renderCustomTagStart(cfSession _Session, cfFile customTag) throws cfmRunTimeException {
boolean isFiltered = pushAndSuspend(_Session, customTag);
cfTagReturnType rt = customTag.render(_Session);
if (!rt.isExit() || !rt.getMethod().equals(cfEXIT.METHOD_LOOP)) {
// cfmExitExceptions are normal if a CFEXIT tag gets processed, except that
// METHOD=LOOP is an error in start mode (this method is always in start mode)
unsuspendAndPop(_Session, isFiltered);
}
return rt;
}
public static cfTagReturnType renderCustomTagEnd(cfSession _Session, cfFile customTag, boolean suppressWhiteSpace) throws cfmRunTimeException {
boolean isFiltered = pushAndSuspend(_Session, customTag);
int renderOptions = cfTag.CF_OUTPUT_ONLY | cfTag.SUPPRESS_OUTPUT_AFTER_ABORT | (suppressWhiteSpace ? cfTag.SUPPRESS_WHITESPACE : 0);
cfTagReturnType rt = customTag.renderToString(_Session, renderOptions);
unsuspendAndPop(_Session, isFiltered);
return rt;
}
public static void addAssociatedAttributes(cfSession _Session, cfMODULE customTag, cfStructData _thisTag) {
String basetag = customTag.getCustomTagName();
// TODO: where is the _Session.putDataBin() that goes with this? we need to very the type cast
Map<cfMODULE, cfStructData> associateVars = (Map<cfMODULE, cfStructData>) _Session.getDataBin(cfASSOCIATE.DATA_BIN_KEY);
if (associateVars == null) {
return;
}
// --[ get the datacollections associated with this basetag
cfStructData dataColls = associateVars.get(customTag);
// if it doesn't exist, create it
if (dataColls == null) {
return;
}
Object[] keys = dataColls.keys();
for (int i = 0; i < keys.length; i++) {
String nextKey = (String) keys[i];
_thisTag.setData(nextKey, dataColls.getData(nextKey));
}
// now remove these associated attributes
associateVars.remove(basetag);
}
private static boolean pushAndSuspend(cfSession _Session, cfFile customTag) {
_Session.pushActiveFile(customTag);
if (_Session.isFiltered()) {
_Session.suspendFilter();
return true;
}
return false;
}
private static void unsuspendAndPop(cfSession _Session, boolean isFiltered) {
if (isFiltered)
_Session.unsuspendFilter();
_Session.popActiveFile();
}
// ----------------------------------------------------
// returns the tagName
public String getCustomTagName() {
return customTagName;
}
// ----------------------------------------------------
protected cfStructData packageUpAttributes(cfSession _Session) throws cfmRunTimeException {
cfStructData attributeValues = new cfStructData();
String key;
// Look to see if the ATTRIBUTECOLLECTION has been passed through
// Note that the ATTRIBUTECOLLECTION is handled first so that specified
// parameters can override those also supplied in the ATTRIBUTECOLLECTION
if (properties.containsKey("ATTRIBUTECOLLECTION")) {
cfData attributeCollection = getDynamic(_Session, "ATTRIBUTECOLLECTION");
if (attributeCollection.getDataType() == cfData.CFSTRUCTDATA) {
Object[] keys = ((cfStructData) attributeCollection).keys();
for (int i = 0; i < keys.length; i++) {
key = (String) keys[i];
attributeValues.setData(key, ((cfStructData) attributeCollection).getData(key));
}
}
}
Iterator<String> iter = properties.keySet().iterator();
while (iter.hasNext()) {
key = iter.next();
if (!key.equalsIgnoreCase("TEMPLATE") && !key.equalsIgnoreCase("NAME") && !key.equalsIgnoreCase("ATTRIBUTECOLLECTION")) {
attributeValues.setData(key, getDynamic(_Session, key));
}
}
return attributeValues;
}
// ----------------------------------------------------
private cfFile getCustomTagFile(cfSession _Session) throws cfmRunTimeException {
// If the custom tag is a TEMPLATE directive
if (containsAttribute("TEMPLATE"))
return loadTemplate(this, _Session, getDynamic(_Session, "TEMPLATE").getString());
// --[ Using the NAME attribute
String name = getDynamic(_Session, "NAME").getString();
setCustomTagKey(name);
return getCustomTagFile(_Session, name, getCustomTagDirectories(_Session, name + ".cfm"), false, true);
}
/**
* Searches for a custom tag file:
*
* 1. If "trust cache" is true, return the file if we already have it. 2. Search the custom tag directories.
*/
protected cfFile getCustomTagFile(cfSession _Session, String _name, cfmlURI[] _directory, boolean searchCurrentDir, boolean deepFind) throws cfmRunTimeException {
return getCustomTagFile(_Session, _name, _directory, searchCurrentDir, deepFind, true);
}
protected cfFile getCustomTagFile(cfSession _Session, String _name, cfmlURI[] _directory, boolean searchCurrentDir, boolean deepFind, boolean _checkCache) throws cfmRunTimeException {
cfFile cfTagFile = null;
if (cfmlFileCache.isTrustCache()) {
cfmlURI customTagURI = (cfmlURI) locationCache.getFromCache(customTagKey);
if (customTagURI != null)
cfTagFile = _Session.getFile(customTagURI);
if (cfTagFile != null)
return cfTagFile;
}
// search the custom tag directories
cfTagFile = getCustomTagFile(_Session, _name, "cfm", customTagKey, _directory, searchCurrentDir, deepFind, _checkCache).setURI(_name.replace('.', '/'));
return cfTagFile;
}
/**
* This version of getCustomTagFile is a convenience method that could be used by a JSP custom tag library.
*/
public static cfFile getCustomTagFile(cfSession _Session, String name) throws cfmRunTimeException {
return getCustomTagFile(_Session, name, "cfm", name.toLowerCase(), getCustomTagDirectories(_Session, name + ".cfm"), true, true, true);
}
/**
* First check the locationCache for the tag file URI; if it's not there, then search for the custom tag file and then add it to the location cache.
*/
protected static cfFile getCustomTagFile(cfSession _Session, String _name, String fileExt, String customTagKey, cfmlURI[] _directory, boolean searchCurrentDir, boolean deepFind, boolean _checkLocCache) throws cfmRunTimeException {
// Try the directory that was in the cache
if (_checkLocCache) {
cfmlURI customTagUri = (cfmlURI) locationCache.getFromCache(customTagKey);
if (customTagUri != null) {
try {
return _Session.getFile(customTagUri);
} catch (cfmBadFileException BFEE) {
locationCache.flushEntry(customTagKey);
if (!BFEE.fileNotFound()) {
cfCatchData catchData = catchDataFactory.summarizeBadFileException(_Session.activeTag(), "Badly formatted template", BFEE);
catchData.setSession(_Session);
throw new cfmRunTimeException(catchData, BFEE);
}
}
}
}
cfFile cfTagFile = null;
if (searchCurrentDir) {
// look in the current (local) directory; return null if not found
cfTagFile = getLocalCustomTagFile(_Session, _name + "." + fileExt);
}
// now search for the tag file
if (cfTagFile == null) {
// throw exception if not found
cfTagFile = getCustomTagFile(_Session, _name, fileExt, _directory, deepFind);
locationCache.setInCache(customTagKey, cfTagFile.getCfmlURI().copy());
}
// don't put in locationCache if found in local directory
return cfTagFile;
}
/**
* Search for the custom tag file in the specified directories. Do a shallow or deep directory search.
*/
public static cfFile getCustomTagFile(cfSession _Session, String _name, String fileExt, cfmlURI[] _directory, boolean deepFind) throws cfmRunTimeException {
cfFile cfTagFile;
if (_name.indexOf(".") == -1) {
// This tag is a simple <CFMODULE NAME='mycustom'> or <CF_mycustomtag>
if (deepFind) {
cfTagFile = deepFindTagFile(_Session, fileExt, _directory);
} else {
cfTagFile = shallowFindTagFile(_Session, _directory);
}
if (cfTagFile == null) {
throw newRunTimeException(catchDataFactory.missingCustomTagException(_name));
}
} else {
// This tag is a <CFMODULE NAME='directory.directory2.mycustom'>
cfTagFile = huntDirectories(_Session, _name, fileExt);
}
return cfTagFile;
}
// Methods for locating the custom tags
/**
* Look for the custom tag file in the current directory.
*/
public static cfFile getLocalCustomTagFile(cfSession _Session, String tagFileName) throws cfmRunTimeException {
cfFile svrFile = null;
String presentFilePath = _Session.getPresentFilePath();
if (presentFilePath == null) { // running in packed WAR
try {
svrFile = _Session.getUriFile(tagFileName);
} catch (cfmBadFileException bfe) {
handleBadFileException(_Session, bfe);
}
return svrFile;
}
try {
// first try lowercase for UNIX/Linux
svrFile = _Session.getFile(new cfmlURI(presentFilePath, tagFileName.toLowerCase()));
} catch (cfmBadFileException bfe) {
handleBadFileException(_Session, bfe);
}
if (svrFile == null) {
try {
// try exact-case match
svrFile = _Session.getFile(new cfmlURI(presentFilePath, tagFileName));
} catch (cfmBadFileException bfe) {
handleBadFileException(_Session, bfe);
}
}
return svrFile;
}
private static cfFile shallowFindTagFile(cfSession _Session, cfmlURI[] directoryMappings) throws cfmRunTimeException {
cfFile svrFile = null;
for (int i = 0; i < directoryMappings.length; i++) {
try {
svrFile = cfmlFileCache.getCfmlFile(_Session.CTX, directoryMappings[i], _Session.REQ);
if (svrFile != null) {
return svrFile;
}
} catch (cfmBadFileException bfe) {
handleBadFileException(_Session, bfe);
}
}
return svrFile;
}
private static cfFile deepFindTagFile(cfSession _Session, String fileExt, cfmlURI[] directoryMappings) throws cfmRunTimeException {
cfFile svrFile = null;
cfmlURI activeURI = null;
try {
for (int i = 0; i < directoryMappings.length; i++) {
activeURI = cfmlFileCache.deepFindFile(_Session, directoryMappings[i], fileExt);
if (activeURI != null) {
break;
}
}
if (activeURI != null)
svrFile = _Session.getFile(activeURI);
} catch (cfmBadFileException bfe) {
handleBadFileException(_Session, bfe);
}
return svrFile;
}
private static void handleBadFileException(cfSession _Session, cfmBadFileException bfe) throws cfmRunTimeException {
if (!bfe.fileNotFound()) {
throw bfe;
}
}
private static cfFile huntDirectories(cfSession _Session, String namePath, String fileExt) throws cfmRunTimeException {
// need to perform a case-insenstive search of the directories
cfmlURI[] directoryMappings = getCustomTagDirectories(_Session, "");
// Put the names into an array for easy access
List<String> tokens = string.split(namePath, ".");
String[] dirList = new String[tokens.size()];
int x = 0;
for (int i = 0; i < tokens.size(); i++)
dirList[x++] = tokens.get(i).trim(); // preserve case here
Set<cfmlURI> dirSet = new HashSet<cfmlURI>();
for (int i = 0; i < directoryMappings.length; i++) {
dirSet.addAll(cfmlFileCache.listFiles(_Session, directoryMappings[i]));
}
cfmlURI fileToFind = null;
while (fileToFind == null) {
// there may be duplicate directory names in the custom tag paths, so search them all
cfmlURI dir = findMatch(dirSet, dirList[0]);
if (dir == null) {
// searched all custom tag directories, need to throw a bad file exception
throw newRunTimeException(catchDataFactory.missingCustomTagException(namePath));
}
dirSet.remove(dir); // for the next loop iteration
// search the sub-directories of the current custom tag directory
Set<cfmlURI> subdirSet = cfmlFileCache.listFiles(_Session, dir);
boolean foundSubdir = true;
for (int i = 1; i < dirList.length - 1; i++) {
cfmlURI subdir = findMatch(subdirSet, dirList[i]);
if (subdir == null) {
foundSubdir = false;
break;
}
subdirSet = cfmlFileCache.listFiles(_Session, subdir);
}
// if we found the sub-directory, see if the file is there
if (foundSubdir) {
fileToFind = findMatch(subdirSet, dirList[dirList.length - 1] + "." + fileExt);
}
}
try {
return _Session.getFile(fileToFind);
} catch (cfmBadFileException BFEE) {
throw (!BFEE.fileNotFound() ? BFEE : newRunTimeException(catchDataFactory.missingCustomTagException(namePath)));
}
}
public static cfmlURI[] getCustomTagDirectories(cfSession session, String filename) throws cfmRunTimeException {
if (directoryList == null) {
directoryList = cfmlFileCache.getCustomDirMapping("CF_XYZ");
if (directoryList == null) {
throw newRunTimeException(catchDataFactory.missingCustomTagException("CFMODULE"));
}
}
cfmlURI[] directories = new cfmlURI[directoryList.size()];
for (int i = 0; i < directoryList.size(); i++)
directories[i] = new cfmlURI(directoryList.get(i), filename);
// See if this has any custom mappings for this directory
return getCombinedCustomTagDirectories(session, filename, directories);
}
public static cfmlURI[] getCombinedCustomTagDirectories(cfSession session, String filename, cfmlURI[] directories) {
String customTagPaths = (String) session.getDataBin(cfAPPLICATION.CUSTOMTAGPATHS);
if (customTagPaths != null) {
String[] paths = customTagPaths.split(",");
cfmlURI[] customDirectories = new cfmlURI[paths.length];
for (int x = 0; x < paths.length; x++)
customDirectories[x] = new cfmlURI(paths[x], filename);
cfmlURI[] combinedDirs = new cfmlURI[directories.length + customDirectories.length];
int c = 0;
for (int x = 0; x < customDirectories.length; x++)
combinedDirs[c++] = customDirectories[x];
for (int x = 0; x < directories.length; x++)
combinedDirs[c++] = directories[x];
return combinedDirs;
} else
return directories;
}
private static cfmlURI findMatch(Set<cfmlURI> dirList, String directory) {
// Runs through the directories seeing if there is a match
Iterator<cfmlURI> IT = dirList.iterator();
while (IT.hasNext()) {
cfmlURI thisDir = IT.next();
// Remove a trailing slash from the URLs if its present
String dirToTest = thisDir.getURI().replace('\\', '/');
if (dirToTest.endsWith("/"))
dirToTest = dirToTest.substring(0, dirToTest.length() - 1);
dirToTest = dirToTest.substring(dirToTest.lastIndexOf('/') + 1);
if (dirToTest.equalsIgnoreCase(directory))
return thisDir;
}
return null;
}
protected void setCustomTagKey(String key) {
customTagKey = key.toLowerCase();
}
/**
* Gets the custom tag name for this CFMODULE. This is inferred from the specified NAME/TEMPLATE attribute. It is used in functions such as GetBaseTagList() and in matching tags with CFASSOCIATE.
*/
protected String getRealTagName(cfSession _session) {
try {
String template = null;
String name = null;
if (containsAttribute("TEMPLATE")) {
template = getDynamic(_session, "TEMPLATE").getString();
} else {
name = getDynamic(_session, "NAME").getString();
}
return getRealTagName(template, name);
} catch (cfmRunTimeException ignored) {
// should never happen
}
return "CFMODULE"; // won't get here
}
public static String getRealTagName(String template, String name) {
if (template != null) {
// note if lastIndexOf() returns -1, slashIndex = 0 which is what we want
int slashIndex = template.lastIndexOf('/') + 1;
int dotIndex = template.lastIndexOf('.');
if (dotIndex == -1)
dotIndex = template.length();
return "CF_" + template.substring(slashIndex, dotIndex).toUpperCase();
} else if (name != null) {
return "CF_" + name.toUpperCase();
}
return "CFMODULE"; // won't get here
}
private static class EngineListener implements engineListener {
public void engineAdminUpdate(com.naryx.tagfusion.xmlConfig.xmlCFML config) {
directoryList = null;
locationCache.flushAll();
}
public void engineShutdown() { // do nothing
}
}
}