/**
* Copyright (c) 2014, the Railo Company Ltd.
* Copyright (c) 2015, Lucee Assosication Switzerland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lucee.runtime.component;
import javax.servlet.jsp.tagext.BodyContent;
import lucee.commons.io.res.filter.DirectoryResourceFilter;
import lucee.commons.io.res.filter.ExtensionResourceFilter;
import lucee.commons.io.res.filter.OrResourceFilter;
import lucee.commons.io.res.filter.ResourceFilter;
import lucee.commons.lang.MappingUtil;
import lucee.commons.lang.StringUtil;
import lucee.loader.engine.CFMLEngine;
import lucee.runtime.CIObject;
import lucee.runtime.CIPage;
import lucee.runtime.Component;
import lucee.runtime.ComponentImpl;
import lucee.runtime.ComponentPageImpl;
import lucee.runtime.InterfaceImpl;
import lucee.runtime.InterfacePageImpl;
import lucee.runtime.Mapping;
import lucee.runtime.MappingImpl;
import lucee.runtime.Page;
import lucee.runtime.PageContext;
import lucee.runtime.PageContextImpl;
import lucee.runtime.PageSource;
import lucee.runtime.PageSourceImpl;
import lucee.runtime.config.ConfigImpl;
import lucee.runtime.config.Constants;
import lucee.runtime.debug.DebugEntryTemplate;
import lucee.runtime.exp.ApplicationException;
import lucee.runtime.exp.ExpressionException;
import lucee.runtime.exp.PageException;
import lucee.runtime.op.Caster;
import lucee.runtime.type.util.ArrayUtil;
import lucee.runtime.writer.BodyContentUtil;
public class ComponentLoader {
private static final short RETURN_TYPE_PAGE=1;
private static final short RETURN_TYPE_INTERFACE=2;
private static final short RETURN_TYPE_COMPONENT=3;
private static final ResourceFilter DIR_OR_EXT=new OrResourceFilter(new ResourceFilter[]{DirectoryResourceFilter.FILTER,new ExtensionResourceFilter(Constants.getComponentExtensions())});
/**
*
* @param pc
* @param loadingLocation
* @param rawPath
* @param searchLocal
* @param searchRoot
* @param isExtendedComponent if set to true this is a base component loaded because a other component has defined this component via extends
* @return
* @throws PageException
*/
public static ComponentImpl searchComponent(PageContext pc,PageSource loadingLocation,String rawPath, Boolean searchLocal, Boolean searchRoot, boolean isExtendedComponent) throws PageException {
return (ComponentImpl)_search(pc,loadingLocation, rawPath, searchLocal, searchRoot,true,RETURN_TYPE_COMPONENT,isExtendedComponent);
}
public static ComponentImpl searchComponent(PageContext pc,PageSource loadingLocation,String rawPath, Boolean searchLocal, Boolean searchRoot, final boolean isExtendedComponent,boolean executeConstr) throws PageException {
return (ComponentImpl)_search(pc,loadingLocation, rawPath, searchLocal, searchRoot,executeConstr,RETURN_TYPE_COMPONENT,isExtendedComponent);
}
public static InterfaceImpl searchInterface(PageContext pc,PageSource loadingLocation,String rawPath,boolean executeConstr) throws PageException {
return (InterfaceImpl)_search(pc,loadingLocation, rawPath, Boolean.TRUE, Boolean.TRUE,executeConstr,RETURN_TYPE_INTERFACE,false);
}
public static InterfaceImpl searchInterface(PageContext pc,PageSource loadingLocation,String rawPath) throws PageException {
return (InterfaceImpl)_search(pc,loadingLocation, rawPath, Boolean.TRUE, Boolean.TRUE,true,RETURN_TYPE_INTERFACE,false);
}
public static Page searchPage(PageContext pc,PageSource child,String rawPath, Boolean searchLocal, Boolean searchRoot) throws PageException {
return (Page)_search(pc, child,rawPath, searchLocal, searchRoot,false,RETURN_TYPE_PAGE,false);
}
private static Object _search(PageContext pc,PageSource loadingLocation,String rawPath, Boolean searchLocal,
Boolean searchRoot, boolean executeConstr, short returnType, final boolean isExtendedComponent) throws PageException {
PageSource currPS = pc.getCurrentPageSource();
Page currP=currPS==null?null:currPS.loadPage(pc,false);
int dialect = currPS==null?pc.getCurrentTemplateDialect():currPS.getDialect();
// first try for the current dialect
Object obj= _search(pc, loadingLocation, rawPath, searchLocal, searchRoot, executeConstr, returnType,currP, dialect, isExtendedComponent);
// then we try the opposite dialect
if(obj==null && ((ConfigImpl)pc.getConfig()).allowLuceeDialect()) { // only when the lucee dialect is enabled we have to check the opposite
obj= _search(pc, loadingLocation, rawPath, searchLocal, searchRoot, executeConstr, returnType,currP, dialect==CFMLEngine.DIALECT_CFML?CFMLEngine.DIALECT_LUCEE:CFMLEngine.DIALECT_CFML, isExtendedComponent);
}
if(obj==null)throw new ExpressionException("invalid "+toStringType(returnType,dialect)+" definition, can't find "+toStringType(returnType,dialect)+" ["+rawPath+"]");
return obj;
}
private static Object _search(PageContext pc,PageSource loadingLocation,String rawPath, Boolean searchLocal,
Boolean searchRoot, boolean executeConstr, short returnType, Page currP, int dialect, final boolean isExtendedComponent) throws PageException {
ConfigImpl config=(ConfigImpl) pc.getConfig();
if(dialect==CFMLEngine.DIALECT_LUCEE && !config.allowLuceeDialect())PageContextImpl.notSupported();
boolean doCache=config.useComponentPathCache();
String sub=null;
if(returnType!=RETURN_TYPE_PAGE && rawPath.indexOf(':')!=-1) {
int d = rawPath.indexOf(':');
int s = rawPath.indexOf('.');
if(d>s) {
sub=rawPath.substring(d+1);
rawPath=rawPath.substring(0,d);
}
}
//app-String appName=pc.getApplicationContext().getName();
rawPath=rawPath.trim().replace('\\','/');
String path=(rawPath.indexOf("./")==-1)?rawPath.replace('.','/'):rawPath;
boolean isRealPath=!StringUtil.startsWith(path,'/');
//PageSource currPS = pc.getCurrentPageSource();
//Page currP=currPS.loadPage(pc,false);
PageSource ps=null;
CIPage page=null;
// MUSTMUST improve to handle different extensions
String pathWithCFC=path.concat("."+(dialect==CFMLEngine.DIALECT_CFML?
Constants.getCFMLComponentExtension():Constants.getLuceeComponentExtension()));
// no cache for per application pathes
Mapping[] acm = pc.getApplicationContext().getComponentMappings();
if(!ArrayUtil.isEmpty(acm)) {
Mapping m;
for(int y=0;y<acm.length;y++){
m=acm[y];
ps=m.getPageSource(pathWithCFC);
page=toCIPage(ps.loadPageThrowTemplateException(pc,false,(Page)null));
if(page!=null) {
return returnType==RETURN_TYPE_PAGE?
page:
load(pc,page,trim(path.replace('/', '.')),sub,isRealPath,returnType,isExtendedComponent,executeConstr);
}
}
}
if(searchLocal==null)
searchLocal=Caster.toBoolean(rawPath.indexOf('.')==-1?true:config.getComponentLocalSearch());
if(searchRoot==null)
searchRoot=Caster.toBoolean(config.getComponentRootSearch());
// CACHE
// check local in cache
String localCacheName=null;
if(searchLocal && isRealPath && currP!=null){
localCacheName=currP.getPageSource().getDisplayPath().replace('\\', '/');
localCacheName=localCacheName.substring(0,localCacheName.lastIndexOf('/')+1).concat(pathWithCFC);
if(doCache){
page=config.getCachedPage(pc, localCacheName);
if(page!=null) return returnType==RETURN_TYPE_PAGE?
page:
load(pc,page,trim(path.replace('/', '.')),sub,isRealPath,returnType,isExtendedComponent,executeConstr);
}
}
// check import cache
if(doCache && isRealPath){
ImportDefintion impDef = config.getComponentDefaultImport();
ImportDefintion[] impDefs=currP==null?new ImportDefintion[0]:currP.getImportDefintions();
int i=-1;
do{
if(impDef.isWildcard() || impDef.getName().equalsIgnoreCase(path)){
page=config.getCachedPage(pc, "import:"+impDef.getPackageAsPath()+pathWithCFC);
if(page!=null) return returnType==RETURN_TYPE_PAGE?
page:
load(pc,page,trim(path.replace('/', '.')),sub,isRealPath,returnType,isExtendedComponent,executeConstr);
}
impDef=++i<impDefs.length?impDefs[i]:null;
}
while(impDef!=null);
}
if(doCache) {
// check global in cache
page=config.getCachedPage(pc, pathWithCFC);
if(page!=null) return returnType==RETURN_TYPE_PAGE?
page:
load(pc,page,trim(path.replace('/', '.')),sub,isRealPath,returnType,isExtendedComponent,executeConstr);
}
// SEARCH
// search from local
if(searchLocal && isRealPath) {
// check realpath
PageSource[] arr = ((PageContextImpl)pc).getRelativePageSources(pathWithCFC);
page=toCIPage(PageSourceImpl.loadPage(pc, arr,null));
if(page!=null){
if(doCache)config.putCachedPageSource(localCacheName, page.getPageSource());
return returnType==RETURN_TYPE_PAGE?
page:
load(pc,page,trim(path.replace('/', '.')),sub,isRealPath,returnType,isExtendedComponent,executeConstr);
}
}
// search with imports
Mapping[] cMappings = config.getComponentMappings();
if(isRealPath){
ImportDefintion impDef = config.getComponentDefaultImport();
ImportDefintion[] impDefs=currP==null?new ImportDefintion[0]:currP.getImportDefintions();
PageSource[] arr;
int i=-1;
do{
if(impDef.isWildcard() || impDef.getName().equalsIgnoreCase(path)){
// search from local first
if(searchLocal){
arr = ((PageContextImpl)pc).getRelativePageSources(impDef.getPackageAsPath()+pathWithCFC);
page=toCIPage(PageSourceImpl.loadPage(pc, arr,null));
if(page!=null) {
if(doCache)config.putCachedPageSource("import:"+impDef.getPackageAsPath()+pathWithCFC, page.getPageSource());
return returnType==RETURN_TYPE_PAGE?
page:
load(pc,page,trim(path.replace('/', '.')),sub,isRealPath,returnType,isExtendedComponent,executeConstr);
}
}
// search mappings and webroot
page=toCIPage(PageSourceImpl.loadPage(pc, ((PageContextImpl)pc).getPageSources("/"+impDef.getPackageAsPath()+pathWithCFC), null));
if(page!=null){
String key=impDef.getPackageAsPath()+pathWithCFC;
if(doCache && !((MappingImpl)page.getPageSource().getMapping()).isAppMapping())
config.putCachedPageSource("import:"+key, page.getPageSource());
return returnType==RETURN_TYPE_PAGE?
page:
load(pc,page,trim(path.replace('/', '.')),sub,isRealPath,returnType,isExtendedComponent,executeConstr);
}
// search component mappings
Mapping m;
for(int y=0;y<cMappings.length;y++){
m=cMappings[y];
ps=m.getPageSource(impDef.getPackageAsPath()+pathWithCFC);
page=toCIPage(ps.loadPageThrowTemplateException(pc,false,(Page)null));
if(page!=null) {
if(doCache)config.putCachedPageSource("import:"+impDef.getPackageAsPath()+pathWithCFC, page.getPageSource());
return returnType==RETURN_TYPE_PAGE?
page:
load(pc,page,trim(path.replace('/', '.')),sub,isRealPath,returnType,isExtendedComponent,executeConstr);
}
}
}
impDef=++i<impDefs.length?impDefs[i]:null;
}
while(impDef!=null);
}
String p;
if(isRealPath) p='/'+pathWithCFC;
else p=pathWithCFC;
// search mappings and webroot
page=toCIPage(PageSourceImpl.loadPage(pc,((PageContextImpl)pc).getPageSources(p),null));
if(page!=null){
String key=pathWithCFC;
if(doCache && !((MappingImpl)page.getPageSource().getMapping()).isAppMapping())
config.putCachedPageSource(key, page.getPageSource());
return returnType==RETURN_TYPE_PAGE?
page:
load(pc,page,trim(path.replace('/', '.')),sub,isRealPath,returnType,isExtendedComponent,executeConstr);
}
// search component mappings
Mapping m;
for(int i=0;i<cMappings.length;i++){
m=cMappings[i];
ps=m.getPageSource(p);
page=toCIPage(ps.loadPageThrowTemplateException(pc,false,(Page)null));
// recursive search
if(page==null && config.doComponentDeepSearch() && path.indexOf('/')==-1) {
ps=MappingUtil.searchMappingRecursive(m, pathWithCFC, true);
if(ps!=null) {
page = toCIPage(ps.loadPageThrowTemplateException(pc,false,(Page)null));
if(page!=null) doCache=false;// do not cache this, it could be ambigous
}
}
if(page!=null){
if(doCache)config.putCachedPageSource(pathWithCFC, page.getPageSource());
return returnType==RETURN_TYPE_PAGE?
page:
load(pc,page,trim(path.replace('/', '.')),sub,isRealPath,returnType,isExtendedComponent,executeConstr);
}
}
// search relative to active component (this get not cached because the cache get ambigous if we do)
if(searchLocal && isRealPath) {
if(loadingLocation==null) {
Component c = pc.getActiveComponent();
if(c!=null) loadingLocation = c.getPageSource();
}
if(loadingLocation!=null) {
ps=loadingLocation.getRealPage(pathWithCFC);
if(ps!=null) {
page=toCIPage(ps.loadPageThrowTemplateException(pc,false,(Page)null));
if(page!=null){
return returnType==RETURN_TYPE_PAGE?
page:
load(pc,page,trim(path.replace('/', '.')),sub,isRealPath,returnType,isExtendedComponent,executeConstr);
}
}
}
}
// translate cfide. to org.lucee.cfml
if(StringUtil.startsWithIgnoreCase(rawPath, "cfide.")) {
String rpm=Constants.DEFAULT_PACKAGE+"."+rawPath.substring(6);
try{
return _search(pc,loadingLocation,rpm, searchLocal, searchRoot,executeConstr,returnType,currP,dialect,false);
}
catch(ExpressionException ee){
return null;
//throw new ExpressionException("invalid "+toStringType(returnType)+" definition, can't find "+rawPath+" or "+rpm);
}
}
return null;
// throw new ExpressionException("invalid "+toStringType(returnType)+" definition, can't find "+toStringType(returnType)+" ["+rawPath+"]");
}
private static String toStringType(short returnType, int dialect) {
if(RETURN_TYPE_COMPONENT==returnType) return dialect==CFMLEngine.DIALECT_LUCEE?"class":"component";
if(RETURN_TYPE_INTERFACE==returnType) return "interface";
return "component/interface";
}
private static String trim(String str) {
if(StringUtil.startsWith(str, '.'))str=str.substring(1);
return str;
}
public static ComponentImpl loadComponent(PageContext pc, PageSource ps,String callPath, boolean isRealPath, boolean silent) throws PageException {
return _loadComponent(pc, toCIPage(ps.loadPage(pc,false),callPath), callPath, isRealPath,false,true);
}
public static ComponentImpl loadComponent(PageContext pc, PageSource ps,String callPath, boolean isRealPath, boolean silent, boolean executeConstr) throws PageException {
return _loadComponent(pc, toCIPage(ps.loadPage(pc,false),callPath), callPath, isRealPath,false,executeConstr);
}
// do not change, method is used in flex extension
public static ComponentImpl loadComponent(PageContext pc,Page page, String callPath, boolean isRealPath, boolean silent,boolean isExtendedComponent, boolean executeConstr) throws PageException {
CIPage cip = toCIPage(page, callPath);
if(silent) {
// TODO is there a more direct way
BodyContent bc = pc.pushBody();
try {
return _loadComponent(pc,cip,callPath,isRealPath,isExtendedComponent,executeConstr);
}
finally {
BodyContentUtil.clearAndPop(pc, bc);
}
}
return _loadComponent(pc,cip,callPath,isRealPath,isExtendedComponent,executeConstr);
}
private static CIObject load(PageContext pc,Page page, String callPath,String sub,boolean isRealPath, short returnType,
final boolean isExtendedComponent, boolean executeConstr) throws PageException {
CIPage cip = toCIPage(page, callPath);
if(sub!=null) {
cip=loadSub(cip,sub);
//page=page.loadSub(sub);
}
if(cip instanceof ComponentPageImpl) {
if(returnType!=RETURN_TYPE_COMPONENT)
throw new ApplicationException("the component ["+cip.getPageSource().getComponentName()+"] cannot be used as an interface.");
return _loadComponent(pc,cip, callPath, isRealPath,isExtendedComponent,executeConstr);
}
if(returnType!=RETURN_TYPE_INTERFACE)
throw new ApplicationException("the interface ["+cip.getPageSource().getComponentName()+"] cannot be used as a component.");
return loadInterface(pc,cip, cip.getPageSource(), callPath, isRealPath);
}
private static CIPage loadSub(CIPage page, String sub) throws ApplicationException {
String subClassName = lucee.transformer.bytecode.Page.createSubClass(page.getClass().getName(), sub,page.getPageSource().getDialect());
CIPage[] subs = page.getSubPages();
for(int i=0;i<subs.length;i++){
if(subs[i].getClass().getName().equals(subClassName))
return subs[i];
;
}
throw new ApplicationException("There is no Sub component ["+sub+"] in ["+page.getPageSource().getDisplayPath()+"]");
}
public static Page loadPage(PageContext pc,PageSource ps, boolean forceReload) throws PageException {
if(pc.getConfig().debug()) {
DebugEntryTemplate debugEntry=pc.getDebugger().getEntry(pc,ps);
pc.addPageSource(ps,true);
long currTime=pc.getExecutionTime();
long exeTime=0;
long time=System.currentTimeMillis();
try {
debugEntry.updateFileLoadTime((int)(System.currentTimeMillis()-time));
exeTime=System.currentTimeMillis();
return ps.loadPage(pc,forceReload);
}
finally {
long diff= ((System.currentTimeMillis()-exeTime)-(pc.getExecutionTime()-currTime));
pc.setExecutionTime(pc.getExecutionTime()+(System.currentTimeMillis()-time));
debugEntry.updateExeTime(diff);
pc.removeLastPageSource(true);
}
}
// no debug
pc.addPageSource(ps,true);
try {
return ps.loadPage(pc,forceReload);
}
finally {
pc.removeLastPageSource(true);
}
}
private static ComponentImpl _loadComponent(PageContext pc,CIPage page, String callPath, boolean isRealPath, final boolean isExtendedComponent, boolean executeConstr) throws PageException {
ComponentImpl rtn=null;
if(pc.getConfig().debug()) {
DebugEntryTemplate debugEntry=pc.getDebugger().getEntry(pc,page.getPageSource());
pc.addPageSource(page.getPageSource(),true);
long currTime=pc.getExecutionTime();
long exeTime=0;
long time=System.nanoTime();
try {
debugEntry.updateFileLoadTime((int)(System.nanoTime()-time));
exeTime=System.nanoTime();
rtn=initComponent(pc,page,callPath,isRealPath,isExtendedComponent,executeConstr);
}
finally {
if(rtn!=null)rtn.setLoaded(true);
long diff= ((System.nanoTime()-exeTime)-(pc.getExecutionTime()-currTime));
pc.setExecutionTime(pc.getExecutionTime()+(System.nanoTime()-time));
debugEntry.updateExeTime(diff);
pc.removeLastPageSource(true);
}
}
// no debug
else {
pc.addPageSource(page.getPageSource(),true);
try {
rtn=initComponent(pc,page,callPath,isRealPath,isExtendedComponent,executeConstr);
}
finally {
if(rtn!=null)rtn.setLoaded(true);
pc.removeLastPageSource(true);
}
}
return rtn;
}
public static InterfaceImpl loadInterface(PageContext pc,Page page, PageSource ps,String callPath,
boolean isRealPath) throws PageException {
InterfaceImpl rtn=null;
if(pc.getConfig().debug()) {
DebugEntryTemplate debugEntry=pc.getDebugger().getEntry(pc,ps);
pc.addPageSource(ps,true);
long currTime=pc.getExecutionTime();
long exeTime=0;
long time=System.nanoTime();
try {
debugEntry.updateFileLoadTime((int)(System.nanoTime()-time));
exeTime=System.nanoTime();
if(page==null)page=ps.loadPage(pc,false);
rtn=initInterface(pc,page,callPath,isRealPath);
}
finally {
long diff= ((System.nanoTime()-exeTime)-(pc.getExecutionTime()-currTime));
pc.setExecutionTime(pc.getExecutionTime()+(System.nanoTime()-time));
debugEntry.updateExeTime(diff);
pc.removeLastPageSource(true);
}
}
// no debug
else {
pc.addPageSource(ps,true);
try {
if(page==null)page=ps.loadPage(pc,false);
rtn=initInterface(pc,page,callPath,isRealPath);
}
finally {
pc.removeLastPageSource(true);
}
}
return rtn;
}
private static InterfaceImpl initInterface(PageContext pc,Page page,String callPath,boolean isRealPath) throws PageException {
if(!(page instanceof InterfacePageImpl))
throw new ApplicationException("invalid interface definition ["+callPath+"]");
InterfacePageImpl ip=(InterfacePageImpl)page;
InterfaceImpl i = ip.newInstance(pc,callPath,isRealPath);
return i;
}
private static ComponentImpl initComponent(PageContext pc,CIPage page,String callPath,boolean isRealPath,final boolean isExtendedComponent, boolean executeConstr) throws PageException {
// is not a component, then it has to be a interface
if(!(page instanceof ComponentPageImpl))
throw new ApplicationException("you cannot instantiate the interface ["+
page.getPageSource().getDisplayPath()+"] as a component ("+page.getClass().getName()+""+(page instanceof InterfacePageImpl)+")");
ComponentPageImpl cp = (ComponentPageImpl)page;
ComponentImpl c = cp.newInstance(pc,callPath,isRealPath, isExtendedComponent,executeConstr);
// abstract/final check
if(!isExtendedComponent) {
if(c.getModifier()==Component.MODIFIER_ABSTRACT)
throw new ApplicationException("you cannot instantiate an abstract component ["+page.getPageSource().getDisplayPath()+"], this component can only be extended by other components");
}
else if(c.getModifier()==Component.MODIFIER_FINAL)
throw new ApplicationException("you cannot extend a final component ["+page.getPageSource().getDisplayPath()+"]");
c.setInitalized(true);
return c;
}
private static CIPage toCIPage(Page p, String callPath) throws PageException {
if(p instanceof CIPage) return (CIPage) p;
if(p!=null)
throw new ApplicationException("invalid component definition ["+callPath+"] in template ["+p.getPageSource().getDisplayPath()+"]");
throw new ApplicationException("invalid component definition ["+callPath+"] ");
}
private static CIPage toCIPage(Page p) {
if(p instanceof CIPage) return (CIPage) p;
return null;
}
}