/**
* 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;
import static org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength.SOFT;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.UnmodifiableClassException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import lucee.commons.io.FileUtil;
import lucee.commons.io.log.Log;
import lucee.commons.io.res.Resource;
import lucee.commons.lang.ExceptionUtil;
import lucee.commons.lang.MappingUtil;
import lucee.commons.lang.PhysicalClassLoader;
import lucee.commons.lang.StringUtil;
import lucee.loader.engine.CFMLEngine;
import lucee.runtime.config.Config;
import lucee.runtime.config.ConfigImpl;
import lucee.runtime.config.ConfigWebImpl;
import lucee.runtime.config.ConfigWebUtil;
import lucee.runtime.listener.ApplicationListener;
import lucee.runtime.osgi.OSGiUtil;
import lucee.runtime.type.util.ArrayUtil;
import org.apache.commons.collections4.map.ReferenceMap;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
/**
* Mapping class
*/
public final class MappingImpl implements Mapping {
private static final long serialVersionUID = 6431380676262041196L;
private String virtual;
private String lcVirtual;
private boolean topLevel;
private short inspect;
private boolean physicalFirst;
private PhysicalClassLoader pcl;
//private Map<String,PhysicalClassLoader> pcls=new HashMap<String, PhysicalClassLoader>();
private Resource archive;
private boolean hasArchive;
private final Config config;
private Resource classRootDirectory;
private PageSourcePool pageSourcePool=new PageSourcePool();
private boolean readonly=false;
private boolean hidden=false;
private final String strArchive;
private final String strPhysical;
private Resource physical;
private String lcVirtualWithSlash;
private Map<String,Object> customTagPath=new ReferenceMap<String,Object>(SOFT,SOFT);
private boolean appMapping;
private boolean ignoreVirtual;
private ApplicationListener appListener;
private Bundle archiveBundle;
private long archMod;
private static int listenerMode;
private static int listenerType;
/**
* constructor of the class
* @param config
* @param virtual
* @param strPhysical
* @param strArchive
* @param inspect
* @param physicalFirst
* @param hidden
* @param readonly
* @param topLevel
* @param appMapping
* @param ignoreVirtual
* @param appListener
*/
public MappingImpl(Config config, String virtual, String strPhysical,String strArchive, short inspect,
boolean physicalFirst, boolean hidden, boolean readonly,boolean topLevel, boolean appMapping,
boolean ignoreVirtual,ApplicationListener appListener,int listenerMode,int listenerType) {
this.ignoreVirtual=ignoreVirtual;
this.config=config;
this.hidden=hidden;
this.readonly=readonly;
this.strPhysical=StringUtil.isEmpty(strPhysical)?null:strPhysical;
this.strArchive=StringUtil.isEmpty(strArchive)?null:strArchive;
this.inspect=inspect;
this.topLevel=topLevel;
this.appMapping=appMapping;
this.physicalFirst=physicalFirst;
this.appListener=appListener;
this.listenerMode=listenerMode;
this.listenerType=listenerType;
// virtual
if(virtual.length()==0)virtual="/";
if(!virtual.equals("/") && virtual.endsWith("/"))this.virtual=virtual.substring(0,virtual.length()-1);
else this.virtual=virtual;
this.lcVirtual=this.virtual.toLowerCase();
this.lcVirtualWithSlash=lcVirtual.endsWith("/")?this.lcVirtual:this.lcVirtual+'/';
ServletContext cs = (config instanceof ConfigWebImpl)?((ConfigWebImpl)config).getServletContext():null;
// Physical
physical=ConfigWebUtil.getExistingResource(cs,strPhysical,null,config.getConfigDir(),FileUtil.TYPE_DIR,
config);
// Archive
archive=ConfigWebUtil.getExistingResource(cs,strArchive,null,config.getConfigDir(),FileUtil.TYPE_FILE,config);
loadArchive();
hasArchive=archive!=null;
if(archive==null) this.physicalFirst=true;
else if(physical==null) this.physicalFirst=false;
else this.physicalFirst=physicalFirst;
//if(!hasArchive && !hasPhysical) throw new IOException("missing physical and archive path, one of them must be defined");
}
private void loadArchive() {
if(archive==null || archMod==archive.lastModified()) return;
CFMLEngine engine = ConfigWebUtil.getEngine(config);
BundleContext bc = engine.getBundleContext();
try {
archiveBundle=OSGiUtil.installBundle( bc, archive,true);
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
archMod=archive.lastModified();
config.getLog("application").log(Log.LEVEL_ERROR, "OSGi", t);
archive=null;
}
}
@Override
public Class<?> getArchiveClass(String className) throws ClassNotFoundException {
if(archiveBundle!=null) {
return archiveBundle.loadClass(className);
}
//else if(archiveClassLoader!=null) return archiveClassLoader.loadClass(className);
throw new ClassNotFoundException("there is no archive context to load "+className+" from it");
}
@Override
public Class<?> getArchiveClass(String className, Class<?> defaultValue) {
try {
if(archiveBundle!=null)
return archiveBundle.loadClass(className);
//else if(archiveClassLoader!=null) return archiveClassLoader.loadClass(className);
}
catch (ClassNotFoundException e) {}
return defaultValue;
}
@Override
public InputStream getArchiveResourceAsStream(String name) {
// MUST implement
return null;
}
public Class<?> loadClass(String className) {
Class<?> clazz;
if(isPhysicalFirst()) {
clazz=getPhysicalClass(className,(Class<?>)null);
if(clazz!=null) return clazz;
clazz=getArchiveClass(className, null);
if(clazz!=null) return clazz;
}
clazz=getArchiveClass(className, null);
if(clazz!=null) return clazz;
clazz=getPhysicalClass(className,(Class<?>)null);
if(clazz!=null) return clazz;
return null;
}
@Override
public Class<?> getPhysicalClass(String className) throws ClassNotFoundException,IOException {
//PhysicalClassLoader pcl = pcls.get(className);
if(pcl==null){
pcl=new PhysicalClassLoader(config,getClassRootDirectory());
//pcls.put(className, pcl);
}
return pcl.loadClass(className);
}
public Class<?> getPhysicalClass(String className, Class<?> defaultValue) {
//PhysicalClassLoader pcl = pcls.get(className);
if(pcl==null)return null;
try {
return pcl.loadClass(className);
} catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
return null;
}
}
@Override
public Class<?> getPhysicalClass(String className, byte[] code) throws IOException {
if(pcl==null) pcl=new PhysicalClassLoader(config,getClassRootDirectory());
/*PhysicalClassLoader pcl = pcls.get(className);
// flush
if(pcl!=null) {
pageSourcePool.remove(className);
//this.pageSourcePool.remove(key);
}
// MUST is that ok
pcl = new PhysicalClassLoader(config,getClassRootDirectory());
pcls.put(className, pcl);*/
try {
return pcl.loadClass(className,code);
} catch (UnmodifiableClassException e) {
throw new IOException(e);
}
}
/**
* remove all Page from Pool using this classloader
* @param cl
*/
public void clearPages(ClassLoader cl){
pageSourcePool.clearPages(cl);
}
@Override
public Resource getPhysical() {
return physical;
}
@Override
public String getVirtualLowerCase() {
return lcVirtual;
}
@Override
public String getVirtualLowerCaseWithSlash() {
return lcVirtualWithSlash;
}
@Override
public Resource getArchive() {
//initArchive();
return archive;
}
@Override
public boolean hasArchive() {
return hasArchive;
}
@Override
public boolean hasPhysical() {
return physical!=null;
}
@Override
public Resource getClassRootDirectory() {
if(classRootDirectory==null) {
String path=getPhysical()!=null?
getPhysical().getAbsolutePath():
getArchive().getAbsolutePath();
classRootDirectory=config.getClassDirectory().getRealResource(
StringUtil.toIdentityVariableName(
path)
);
}
return classRootDirectory;
}
/**
* clones a mapping and make it readOnly
* @param config
* @return cloned mapping
* @throws IOException
*/
public MappingImpl cloneReadOnly(ConfigImpl config) {
return new MappingImpl(config,virtual,strPhysical,strArchive,inspect,physicalFirst,hidden,true,topLevel,appMapping,ignoreVirtual,appListener,listenerMode,listenerType);
}
@Override
public short getInspectTemplate() {
if(inspect==Config.INSPECT_UNDEFINED) return config.getInspectTemplate();
return inspect;
}
/**
* inspect template setting (Config.INSPECT_*), if not defined with the mapping, Config.INSPECT_UNDEFINED is returned
* @return
*/
public short getInspectTemplateRaw() {
return inspect;
}
@Override
public PageSource getPageSource(String realPath) {
boolean isOutSide = false;
realPath=realPath.replace('\\','/');
if(realPath.indexOf('/')!=0) {
if(realPath.startsWith("../")) {
isOutSide=true;
}
else if(realPath.startsWith("./")) {
realPath=realPath.substring(1);
}
else {
realPath="/"+realPath;
}
}
return getPageSource(realPath,isOutSide);
}
@Override
public PageSource getPageSource(String path, boolean isOut) {
PageSource source=pageSourcePool.getPageSource(path,true);
if(source!=null) return source;
PageSourceImpl newSource = new PageSourceImpl(this,path,isOut);
pageSourcePool.setPage(path,newSource);
return newSource;//new PageSource(this,path);
}
/**
* @return Returns the pageSourcePool.
*/
public PageSourcePool getPageSourcePool() {
return pageSourcePool;
}
@Override
public void check() {
//if(config instanceof ConfigServer) return;
//ConfigWebImpl cw=(ConfigWebImpl) config;
ServletContext cs = (config instanceof ConfigWebImpl)?((ConfigWebImpl)config).getServletContext():null;
// Physical
if(getPhysical()==null && strPhysical!=null && strPhysical.length()>0) {
physical=ConfigWebUtil.getExistingResource(cs,strPhysical,null,config.getConfigDir(),FileUtil.TYPE_DIR,config);
}
// Archive
if(getArchive()==null && strArchive!=null && strArchive.length()>0) {
archive=ConfigWebUtil.getExistingResource(cs,strArchive,null,config.getConfigDir(),FileUtil.TYPE_FILE,config);
loadArchive();
hasArchive=archive!=null;
}
}
@Override
public Config getConfig() {
return config;
}
@Override
public boolean isHidden() {
return hidden;
}
@Override
public boolean isPhysicalFirst() {
return physicalFirst;
}
@Override
public boolean isReadonly() {
return readonly;
}
@Override
public String getStrArchive() {
return strArchive;
}
@Override
public String getStrPhysical() {
return strPhysical;
}
@Override
@Deprecated
public boolean isTrusted() {
return getInspectTemplate()==Config.INSPECT_NEVER;
}
@Override
public String getVirtual() {
return virtual;
}
public boolean isAppMapping() {
return appMapping;
}
@Override
public boolean isTopLevel() {
return topLevel;
}
public PageSource getCustomTagPath(String name, boolean doCustomTagDeepSearch) {
return searchFor(name, name.toLowerCase().trim(), doCustomTagDeepSearch);
}
public boolean ignoreVirtual(){
return ignoreVirtual;
}
private PageSource searchFor(String filename, String lcName, boolean doCustomTagDeepSearch) {
PageSource source=getPageSource(filename);
if(isOK(source)) {
return source;
}
customTagPath.remove(lcName);
if(doCustomTagDeepSearch){
source = MappingUtil.searchMappingRecursive(this, filename, false);
if(isOK(source)) return source;
}
return null;
}
public static boolean isOK(PageSource ps) {
if(ps==null) return false;
return ps.executable();
}
public static PageSource isOK(PageSource[] arr) {
if(ArrayUtil.isEmpty(arr)) return null;
for(int i=0;i<arr.length;i++) {
if(isOK(arr[i])) return arr[i];
}
return null;
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
return "StrPhysical:"+getStrPhysical()+";"+
"StrArchive:"+getStrArchive()+";"+
"Virtual:"+getVirtual()+";"+
"Archive:"+getArchive()+";"+
"Physical:"+getPhysical()+";"+
"topLevel:"+topLevel+";"+
"inspect:"+ConfigWebUtil.inspectTemplate(getInspectTemplateRaw(),"")+";"+
"physicalFirst:"+physicalFirst+";"+
"readonly:"+readonly+";"+
"hidden:"+hidden+";";
}
public ApplicationListener getApplicationListener() {
if(appListener!=null) return appListener;
return config.getApplicationListener();
}
public boolean getDotNotationUpperCase(){
return ((ConfigImpl)config).getDotNotationUpperCase();
}
public void shrink() {
// MUST implement
}
@Override
public int getListenerMode() {
return listenerMode;
}
@Override
public int getListenerType() {
return listenerType;
}
public void flush() {
getPageSourcePool().clear();
}
}