/*
* 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://www.openbluedragon.org/
* $Id: ComponentFactory.java 2374 2013-06-10 22:14:24Z alan $
*/
package com.naryx.tagfusion.cfm.engine;
import java.io.StringReader;
import java.util.List;
import com.nary.cache.cfmlURICache;
import com.naryx.tagfusion.cfm.file.cfFile;
import com.naryx.tagfusion.cfm.file.cfmlURI;
import com.naryx.tagfusion.cfm.tag.cfCOMPONENT;
import com.naryx.tagfusion.cfm.tag.cfFUNCTION;
import com.naryx.tagfusion.cfm.tag.cfINCLUDE;
import com.naryx.tagfusion.cfm.tag.cfMODULE;
import com.naryx.tagfusion.cfm.tag.cfTag;
import com.naryx.tagfusion.xmlConfig.xmlCFML;
public class ComponentFactory {
// maps component names to cfmlURIs
private static cfmlURICache nameCache = new cfmlURICache(10 * 60);
public static final String GLOBAL_CFC_NAME = "WEB-INF.cftags.component";
private static cfmlURI _globalComponentURI = null;
public static void init(xmlCFML configFile) {
// Determine the location of component.cfc
String uri = configFile.getString("server.system.component-cfc");
if ((uri == null) || (uri.trim().length() == 0)) {
cfEngine.log("Unable to find component.cfc");
} else {
_globalComponentURI = new cfmlURI(uri).setComponentName(GLOBAL_CFC_NAME);
}
}
// normalize component name to replace "/" with ".", and remove leading "/" or "."
public static String normalizeComponentName(String componentName) {
if ((componentName == null) || (componentName.length() == 0))
throw new IllegalArgumentException("Component name cannot be null or empty string");
componentName = componentName.replace('/', '.');
if (componentName.charAt(0) == '.')
componentName = componentName.substring(1);
return componentName;
}
/**
* This method takes care of all caching related to component (.cfc) files.
* The basic concept of caching is that component names are mapped by this
* class to cfmlURIs, which in turn contain either the relative or physical
* path to the component file. The cfmlFileCache only understands cfmlURIs, so
* all interaction with the cfmlFileCache related to CFCs needs to be done
* through this class.
*
* If a component is referenced via multiple names, there will be multiple
* entries in the name cache. However, these entries should map to the same
* cfmlURI value, so multiple names can be used to reference a component file
* in the cfmlFileCache.
*
* Search for component files in this order:
*
* 1. Local directory of the calling CFML page.
* 2. Directories specified in the Mappings page of the admin console (essentially equivalent to CFINCLUDE lookup).
* 3. Web root.
* 4. Directories specified in the Custom Tag Paths of the admin console (essentially equivalent to CFMODULE NAME lookup).
*/
public static cfFile loadRawComponent(cfSession session, String componentName, List<String> importPaths ) throws cfmRunTimeException {
if ((componentName == null) || (componentName.length() == 0)) {
cfCatchData catchData = new cfCatchData();
catchData.setMessage("Missing component name");
throw new cfmRunTimeException(catchData);
}
componentName = normalizeComponentName(componentName);
String rawComponentName = componentName;
// check for the global component; that is, "WEB-INF.cftags.component"
if (componentName.equals(GLOBAL_CFC_NAME)) {
cfFile globalComponentFile = null;
try {
if (_globalComponentURI != null) {
_globalComponentURI.getRealPath(session.REQ); // sets real path
globalComponentFile = session.getFile(_globalComponentURI);
if (emptyComponentFile(globalComponentFile)) {
globalComponentFile = createGlobalComponentFile();
} else {
globalComponentFile.setComponentName(GLOBAL_CFC_NAME);
}
}
} catch (cfmBadFileException e) {
cfEngine.log("Unable to find component.cfc, configured path = " + _globalComponentURI.getURI() + ", real path = " + _globalComponentURI.getRealPath());
_globalComponentURI = null; // don't take this path again
}
if (globalComponentFile != null){
globalComponentFile.setRawComponentName(rawComponentName);
return globalComponentFile;
}else{
return createGlobalComponentFile();
}
}
String componentKey = componentName.toLowerCase();
cfFile componentFile = null;
cfmlURI componentURI = null;
// check file based name cache first
cfFile activeFile = session.activeFile();
if ( activeFile != null ){
componentURI = activeFile.getComponentPath( componentKey );
}
if ( componentURI == null ){
// now check name cache
componentURI = (cfmlURI) nameCache.getFromCache(componentKey);
}
if (componentURI != null) {
try {
componentFile = session.getFile(componentURI);
if (componentFile.getComponentName() == null)
componentFile.setComponentName(componentURI.getComponentName());
if ( componentFile.getRawComponentName() == null )
componentFile.setRawComponentName(rawComponentName);
return componentFile;
} catch (cfmBadFileException e) {
nameCache.flushEntry(componentKey);
if (!e.fileNotFound()) {
cfCatchData catchData = catchDataFactory.summarizeBadFileException(session.activeTag(), "Badly formatted template", e);
catchData.setSession(session);
throw new cfmRunTimeException(catchData, e);
}
}
}
String componentUri = componentName.replace('.', '/') + ".cfc";
// look in current directory
if (componentName.indexOf(".") == -1) { // unqualified name
componentFile = cfMODULE.getLocalCustomTagFile(session, componentUri);
} else {
try {
// qualified name; these are case-sensitive on UNIX/Linux/MacOSX
componentFile = cfINCLUDE.loadTemplate(session, componentUri);
} catch (cfmBadFileException bfe) {
handleBadFileException(bfe);
}
}
if (componentFile != null) { // found file in current directory
if (emptyComponentFile(componentFile)) {
throw emptyComponentFileException(session, componentName);
}
componentName = getFullComponentName(session, componentName);
} else {
// search mapped directories (including web root)
try {
componentFile = cfINCLUDE.loadTemplate(session, "/" + componentUri);
} catch (cfmBadFileException bfe) {
handleBadFileException(bfe);
}
}
boolean addToNameCache = false; // don't add to name cache if found in local or mapped directory, above
cfmRunTimeException rte = null;
// search custom tag directories
if (componentFile == null) {
try {
// this method throws a cfmRunTimeException if the file is not found
componentFile = cfMODULE.getCustomTagFile(session, componentName, "cfc", cfMODULE.getCustomTagDirectories(session, componentName + ".cfc"), true);
addToNameCache = true;
} catch (cfmRunTimeException e) {
cfmBadFileException bfe = new cfmBadFileException(componentName);
cfCatchData catchData = bfe.catchData;
catchData.setType(cfCatchData.TYPE_APPLICATION);
rte = new cfmRunTimeException(catchData);
}
}
// if we haven't found the component yet and there are import paths defined
if ( componentFile == null && importPaths != null ){
componentFile = loadFromImportedPaths( session, componentName, importPaths );
if ( componentFile != null ){
activeFile.addComponentPath( componentKey, componentFile.getCfmlURI().copy() );
}
}
if ( componentFile == null && rte != null ){
throw rte;
}
if (emptyComponentFile(componentFile)) {
componentFile = ComponentScriptFactory.load( componentFile );
if ( componentFile == null )
throw emptyComponentFileException(session, componentName);
}
componentFile.setComponentName(componentName);
componentFile.setRawComponentName(rawComponentName);
if (addToNameCache)
nameCache.setInCache(componentKey, componentFile.getCfmlURI().copy());
return componentFile;
}
public static String getFullComponentName(cfSession session, String componentName) {
String componentPath = null;
cfTag activeTag = session.activeTag();
if (activeTag instanceof cfFUNCTION)
componentPath = ((cfFUNCTION) activeTag).getParentComponentPath();
if (componentPath == null) {
cfFUNCTION activeFunction = session.getActiveComponentTag();
if (activeFunction != null)
componentPath = activeFunction.getParentComponentPath();
}
if (componentPath == null)
componentPath = session.getActiveComponentPath();
if ((componentPath != null) && (componentPath.length() > 0))
componentName = componentPath + "." + componentName;
else
componentName = normalizeComponentName(session.getPresentURIPath() + componentName);
return componentName;
}
private static cfFile createGlobalComponentFile() throws cfmBadFileException {
cfFile f = new cfFile(new cfmlURI((String) null).setComponentName(GLOBAL_CFC_NAME), new StringReader("<cfcomponent></cfcomponent>"), "UTF-8");
f.setRawComponentName(GLOBAL_CFC_NAME);
return f;
}
public static boolean emptyComponentFile(cfFile componentFile) {
return ((componentFile == null)
|| (componentFile.getFileBody().getTagList().length == 0)
|| !(componentFile.getFileBody().getTagList()[0] instanceof cfCOMPONENT));
}
private static cfmRunTimeException emptyComponentFileException(cfSession session, String componentName) {
cfCatchData catchData = new cfCatchData(session);
catchData.setType("Empty Source File");
catchData.setDetail("Component Creation");
catchData.setMessage("The component source file is empty or does not contain a CFCOMPONENT tag pair: " + componentName);
return new cfmRunTimeException(catchData);
}
private static void handleBadFileException(cfmBadFileException _bfe) throws cfmRunTimeException {
if (!_bfe.fileNotFound()) {
cfCatchData catchData = _bfe.catchData;
catchData.setType(cfCatchData.TYPE_APPLICATION);
throw new cfmRunTimeException(catchData);
}
}
private static cfFile loadFromImportedPaths( cfSession _session, String _componentName, List<String> _paths ) throws cfmRunTimeException{
String componentKey = _componentName.toLowerCase();
cfFile componentFile = null;
String [] componentNames = new String[ _paths.size() ];
String [] componentUri = new String[ _paths.size() ];
//search off current directory
for ( int i = 0; i < _paths.size(); i++ ){
// on the first iteration, let initialize these arrays as we need them
componentNames[i] = getNormalizedImportPath( _paths.get(i), componentKey );
componentUri[i] = componentNames[i].replace('.', '/') + ".cfc";
try {
// qualified name; these are case-sensitive on UNIX/Linux/MacOSX
componentFile = cfINCLUDE.loadTemplate( _session, componentUri[i] );
} catch (cfmBadFileException bfe) {
handleBadFileException(bfe);
}
if ( componentFile != null ){
return componentFile;
}
}
//search off custom tags dirs
//INVARIANT: componentFile == null
for ( int i = 0; i < _paths.size(); i++ ){
try {
// this method throws a cfmRunTimeException if the file is not found
componentFile = cfMODULE.getCustomTagFile( _session, componentNames[i], "cfc", cfMODULE.getCustomTagDirectories( _session, componentNames[i] + ".cfc"), true );
} catch (cfmRunTimeException e) {
cfmBadFileException bfe = new cfmBadFileException( componentNames[i] );
cfCatchData catchData = bfe.catchData;
catchData.setType(cfCatchData.TYPE_APPLICATION);
throw new cfmRunTimeException(catchData);
}
if ( componentFile != null ){
return componentFile;
}
}
// search mapped directories (including web root)
//INVARIANT: componentFile == null
for ( int i = 0; i < _paths.size(); i++ ){
try {
componentFile = cfINCLUDE.loadTemplate( _session, "/" + componentUri[i] );
} catch (cfmBadFileException bfe) {
handleBadFileException(bfe);
}
if ( componentFile != null )
return componentFile;
}
// if we've reached here, then component wasn't found
return null;
}
private static String getNormalizedImportPath(String _path, String _component) {
if (_path.endsWith(".*")) {
return _path.substring(0, _path.length() - 1) + _component;
} else if (_path.toLowerCase().endsWith("." + _component.toLowerCase())) {
return _path;
} else {
return null;
}
}
}