001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime; 020 021import java.io.IOException; 022import java.net.MalformedURLException; 023import java.util.Map; 024 025import javax.servlet.ServletContext; 026 027import lucee.commons.io.FileUtil; 028import lucee.commons.io.res.Resource; 029import lucee.commons.lang.ArchiveClassLoader; 030import lucee.commons.lang.ExceptionUtil; 031import lucee.commons.lang.MappingUtil; 032import lucee.commons.lang.PCLCollection; 033import lucee.commons.lang.StringUtil; 034import lucee.runtime.config.Config; 035import lucee.runtime.config.ConfigImpl; 036import lucee.runtime.config.ConfigWebImpl; 037import lucee.runtime.config.ConfigWebUtil; 038import lucee.runtime.dump.DumpData; 039import lucee.runtime.dump.DumpProperties; 040import lucee.runtime.dump.DumpTable; 041import lucee.runtime.dump.DumpUtil; 042import lucee.runtime.dump.SimpleDumpData; 043import lucee.runtime.listener.ApplicationListener; 044import lucee.runtime.op.Caster; 045import lucee.runtime.type.util.ArrayUtil; 046 047import org.apache.commons.collections.map.ReferenceMap; 048 049/** 050 * Mapping class 051 */ 052public final class MappingImpl implements Mapping { 053 054 private static final long serialVersionUID = 6431380676262041196L; 055 056 //private static final Object NULL = new Object(); 057 private String virtual; 058 private String lcVirtual; 059 private boolean topLevel; 060 private short inspect; 061 private boolean physicalFirst; 062 private ArchiveClassLoader archiveClassLoader; 063 //private PhysicalClassLoader physicalClassLoader; 064 private PCLCollection pclCollection; 065 private Resource archive; 066 067 private boolean hasArchive; 068 private Config config; 069 private Resource classRootDirectory; 070 private PageSourcePool pageSourcePool=new PageSourcePool(); 071 072 private boolean readonly=false; 073 private boolean hidden=false; 074 private String strArchive; 075 076 private String strPhysical; 077 private Resource physical; 078 //private boolean hasPhysical; 079 080 private String lcVirtualWithSlash; 081 //private Resource classRoot; 082 private Map<String,Object> customTagPath=new ReferenceMap(ReferenceMap.SOFT,ReferenceMap.SOFT); 083 //private final Map<String,Object> customTagPath=new HashMap<String, Object>(); 084 private int classLoaderMaxElements=1000; 085 /** 086 * @return the classLoaderMaxElements 087 */ 088 public int getClassLoaderMaxElements() { 089 return classLoaderMaxElements; 090 } 091 092 private boolean appMapping; 093 private boolean ignoreVirtual; 094 095 private ApplicationListener appListener; 096 097 public MappingImpl(Config config, String virtual, String strPhysical,String strArchive, short inspect, 098 boolean physicalFirst, boolean hidden, boolean readonly,boolean topLevel, boolean appMapping,boolean ignoreVirtual,ApplicationListener appListener) { 099 this(config, virtual, strPhysical, strArchive, inspect, physicalFirst, hidden, readonly,topLevel,appMapping,ignoreVirtual,appListener,5000); 100 101 } 102 103 /** 104 * @param configServer 105 * @param config 106 * @param virtual 107 * @param strPhysical 108 * @param strArchive 109 * @param trusted 110 * @param physicalFirst 111 * @param hidden 112 * @param readonly 113 * @throws IOException 114 */ 115 public MappingImpl(Config config, String virtual, String strPhysical,String strArchive, short inspect, 116 boolean physicalFirst, boolean hidden, boolean readonly,boolean topLevel, boolean appMapping, boolean ignoreVirtual,ApplicationListener appListener, int classLoaderMaxElements) { 117 this.ignoreVirtual=ignoreVirtual; 118 this.config=config; 119 this.hidden=hidden; 120 this.readonly=readonly; 121 this.strPhysical=StringUtil.isEmpty(strPhysical)?null:strPhysical; 122 this.strArchive=StringUtil.isEmpty(strArchive)?null:strArchive; 123 this.inspect=inspect; 124 this.topLevel=topLevel; 125 this.appMapping=appMapping; 126 this.physicalFirst=physicalFirst; 127 this.appListener=appListener; 128 this.classLoaderMaxElements=classLoaderMaxElements; 129 130 // virtual 131 if(virtual.length()==0)virtual="/"; 132 if(!virtual.equals("/") && virtual.endsWith("/"))this.virtual=virtual.substring(0,virtual.length()-1); 133 else this.virtual=virtual; 134 this.lcVirtual=this.virtual.toLowerCase(); 135 this.lcVirtualWithSlash=lcVirtual.endsWith("/")?this.lcVirtual:this.lcVirtual+'/'; 136 137 //if(!(config instanceof ConfigWebImpl)) return; 138 //ConfigWebImpl cw=(ConfigWebImpl) config; 139 ServletContext cs = (config instanceof ConfigWebImpl)?((ConfigWebImpl)config).getServletContext():null; 140 141 142 // Physical 143 physical=ConfigWebUtil.getExistingResource(cs,strPhysical,null,config.getConfigDir(),FileUtil.TYPE_DIR, 144 config); 145 // Archive 146 archive=ConfigWebUtil.getExistingResource(cs,strArchive,null,config.getConfigDir(),FileUtil.TYPE_FILE, 147 config); 148 if(archive!=null) { 149 try { 150 archiveClassLoader = new ArchiveClassLoader(archive,getClass().getClassLoader()); 151 } 152 catch (Throwable t) { 153 ExceptionUtil.rethrowIfNecessary(t); 154 archive=null; 155 } 156 } 157 hasArchive=archive!=null; 158 159 if(archive==null) this.physicalFirst=true; 160 else if(physical==null) this.physicalFirst=false; 161 else this.physicalFirst=physicalFirst; 162 163 164 //if(!hasArchive && !hasPhysical) throw new IOException("missing physical and archive path, one of them must be defined"); 165 } 166 167 @Override 168 public ClassLoader getClassLoaderForArchive() { 169 return archiveClassLoader; 170 } 171 172 173 public Class<?> loadClass(String className) { 174 Class<?> clazz; 175 if(isPhysicalFirst()) { 176 clazz=loadClassPhysical(className); 177 if(clazz!=null) return clazz; 178 clazz=loadClassArchive(className); 179 if(clazz!=null) return clazz; 180 } 181 182 clazz=loadClassArchive(className); 183 if(clazz!=null) return clazz; 184 clazz=loadClassPhysical(className); 185 if(clazz!=null) return clazz; 186 187 return null; 188 } 189 190 private Class<?> loadClassArchive(String className) { 191 if(archiveClassLoader==null) return null; 192 try{ 193 return archiveClassLoader.loadClass(className); 194 } 195 catch(Throwable t){ 196 ExceptionUtil.rethrowIfNecessary(t);} 197 return null; 198 } 199 200 private Class<?> loadClassPhysical(String className) { 201 if(pclCollection==null) return null; 202 // first we check 203 try{ 204 return pclCollection.loadClass(className); 205 } 206 catch(Throwable t){ 207 ExceptionUtil.rethrowIfNecessary(t);} 208 209 return null; 210 } 211 212 public PCLCollection touchPCLCollection() throws IOException { 213 214 if(pclCollection==null){ 215 pclCollection=new PCLCollection(this,getClassRootDirectory(),getConfig().getClassLoader(),classLoaderMaxElements); 216 } 217 return pclCollection; 218 } 219 220 public PCLCollection getPCLCollection() { 221 return pclCollection; 222 } 223 224 225 226 227 /** 228 * remove all Page from Pool using this classloader 229 * @param cl 230 */ 231 public void clearPages(ClassLoader cl){ 232 pageSourcePool.clearPages(cl); 233 } 234 235 @Override 236 public Resource getPhysical() { 237 return physical; 238 } 239 240 @Override 241 public String getVirtualLowerCase() { 242 return lcVirtual; 243 } 244 @Override 245 public String getVirtualLowerCaseWithSlash() { 246 return lcVirtualWithSlash; 247 } 248 249 @Override 250 public Resource getArchive() { 251 //initArchive(); 252 return archive; 253 } 254 255 @Override 256 public boolean hasArchive() { 257 return hasArchive; 258 } 259 260 @Override 261 public boolean hasPhysical() { 262 return physical!=null; 263 } 264 265 @Override 266 public Resource getClassRootDirectory() { 267 if(classRootDirectory==null) { 268 String path=getPhysical()!=null? 269 getPhysical().getAbsolutePath(): 270 getArchive().getAbsolutePath(); 271 272 classRootDirectory=config.getDeployDirectory().getRealResource( 273 StringUtil.toIdentityVariableName( 274 path) 275 ); 276 } 277 return classRootDirectory; 278 } 279 280 /** 281 * clones a mapping and make it readOnly 282 * @param config 283 * @return cloned mapping 284 * @throws IOException 285 */ 286 public MappingImpl cloneReadOnly(ConfigImpl config) { 287 return new MappingImpl(config,virtual,strPhysical,strArchive,inspect,physicalFirst,hidden,true,topLevel,appMapping,ignoreVirtual,appListener,classLoaderMaxElements); 288 } 289 290 @Override 291 public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) { 292 maxlevel--; 293 294 295 296 DumpTable htmlBox = new DumpTable("mapping","#ff6600","#ffcc99","#000000"); 297 htmlBox.setTitle("Mapping"); 298 htmlBox.appendRow(1,new SimpleDumpData("virtual"),new SimpleDumpData(virtual)); 299 htmlBox.appendRow(1,new SimpleDumpData("physical"),DumpUtil.toDumpData(strPhysical,pageContext,maxlevel,dp)); 300 htmlBox.appendRow(1,new SimpleDumpData("archive"),DumpUtil.toDumpData(strArchive,pageContext,maxlevel,dp)); 301 htmlBox.appendRow(1,new SimpleDumpData("inspect"),new SimpleDumpData(ConfigWebUtil.inspectTemplate(getInspectTemplateRaw(),""))); 302 htmlBox.appendRow(1,new SimpleDumpData("physicalFirst"),new SimpleDumpData(Caster.toString(physicalFirst))); 303 htmlBox.appendRow(1,new SimpleDumpData("readonly"),new SimpleDumpData(Caster.toString(readonly))); 304 htmlBox.appendRow(1,new SimpleDumpData("hidden"),new SimpleDumpData(Caster.toString(hidden))); 305 htmlBox.appendRow(1,new SimpleDumpData("appmapping"),new SimpleDumpData(Caster.toBoolean(appMapping))); 306 htmlBox.appendRow(1,new SimpleDumpData("toplevel"),new SimpleDumpData(Caster.toString(topLevel))); 307 htmlBox.appendRow(1,new SimpleDumpData("ClassLoaderMaxElements"),new SimpleDumpData(Caster.toString(classLoaderMaxElements))); 308 return htmlBox; 309 } 310 311 /** 312 * inspect template setting (Config.INSPECT_*), if not defined with the mapping the config setting is returned 313 * @return 314 */ 315 public short getInspectTemplate() { 316 if(inspect==ConfigImpl.INSPECT_UNDEFINED) return config.getInspectTemplate(); 317 return inspect; 318 } 319 320 /** 321 * inspect template setting (Config.INSPECT_*), if not defined with the mapping, Config.INSPECT_UNDEFINED is returned 322 * @return 323 */ 324 public short getInspectTemplateRaw() { 325 return inspect; 326 } 327 328 329 330 331 @Override 332 public PageSource getPageSource(String relPath) { 333 boolean isOutSide = false; 334 relPath=relPath.replace('\\','/'); 335 if(relPath.indexOf('/')!=0) { 336 if(relPath.startsWith("../")) { 337 isOutSide=true; 338 } 339 else if(relPath.startsWith("./")) { 340 relPath=relPath.substring(1); 341 } 342 else { 343 relPath="/"+relPath; 344 } 345 } 346 return getPageSource(relPath,isOutSide); 347 } 348 349 @Override 350 public PageSource getPageSource(String path, boolean isOut) { 351 PageSource source=pageSourcePool.getPageSource(path,true); 352 if(source!=null) return source; 353 354 PageSourceImpl newSource = new PageSourceImpl(this,path,isOut); 355 pageSourcePool.setPage(path,newSource); 356 357 return newSource;//new PageSource(this,path); 358 } 359 360 /** 361 * @return Returns the pageSourcePool. 362 */ 363 public PageSourcePool getPageSourcePool() { 364 return pageSourcePool; 365 } 366 367 @Override 368 public void check() { 369 //if(config instanceof ConfigServer) return; 370 //ConfigWebImpl cw=(ConfigWebImpl) config; 371 ServletContext cs = (config instanceof ConfigWebImpl)?((ConfigWebImpl)config).getServletContext():null; 372 373 374 // Physical 375 if(getPhysical()==null && strPhysical!=null && strPhysical.length()>0) { 376 physical=ConfigWebUtil.getExistingResource(cs,strPhysical,null,config.getConfigDir(),FileUtil.TYPE_DIR,config); 377 378 } 379 // Archive 380 if(getArchive()==null && strArchive!=null && strArchive.length()>0) { 381 try { 382 archive=ConfigWebUtil.getExistingResource(cs,strArchive,null,config.getConfigDir(),FileUtil.TYPE_FILE, 383 config); 384 if(archive!=null) { 385 try { 386 archiveClassLoader = new ArchiveClassLoader(archive,getClass().getClassLoader()); 387 } 388 catch (MalformedURLException e) { 389 archive=null; 390 } 391 } 392 hasArchive=archive!=null; 393 } 394 catch (IOException e) {} 395 } 396 } 397 398 @Override 399 public Config getConfig() { 400 return config; 401 } 402 403 @Override 404 public boolean isHidden() { 405 return hidden; 406 } 407 408 @Override 409 public boolean isPhysicalFirst() { 410 return physicalFirst; 411 } 412 413 @Override 414 public boolean isReadonly() { 415 return readonly; 416 } 417 418 @Override 419 public String getStrArchive() { 420 return strArchive; 421 } 422 423 @Override 424 public String getStrPhysical() { 425 return strPhysical; 426 } 427 428 @Override 429 @Deprecated 430 public boolean isTrusted() { 431 return getInspectTemplate()==ConfigImpl.INSPECT_NEVER; 432 } 433 434 @Override 435 public String getVirtual() { 436 return virtual; 437 } 438 439 public boolean isAppMapping() { 440 return appMapping; 441 } 442 443 444 public boolean isTopLevel() { 445 return topLevel; 446 } 447 448 public PageSource getCustomTagPath(String name, boolean doCustomTagDeepSearch) { 449 return searchFor(name, name.toLowerCase().trim(), doCustomTagDeepSearch); 450 } 451 452 public boolean ignoreVirtual(){ 453 return ignoreVirtual; 454 } 455 456 457 private PageSource searchFor(String filename, String lcName, boolean doCustomTagDeepSearch) { 458 PageSource source=getPageSource(filename); 459 if(isOK(source)) { 460 return source; 461 } 462 customTagPath.remove(lcName); 463 if(doCustomTagDeepSearch){ 464 source = MappingUtil.searchMappingRecursive(this, filename, false); 465 if(isOK(source)) return source; 466 } 467 return null; 468 } 469 470 public static boolean isOK(PageSource ps) { 471 if(ps==null) return false; 472 return (ps.getMapping().isTrusted() && ((PageSourceImpl)ps).isLoad()) || ps.exists(); 473 } 474 475 public static PageSource isOK(PageSource[] arr) { 476 if(ArrayUtil.isEmpty(arr)) return null; 477 for(int i=0;i<arr.length;i++) { 478 if(isOK(arr[i])) return arr[i]; 479 } 480 return null; 481 } 482 483 @Override 484 public int hashCode() { 485 return toString().hashCode(); 486 } 487 488 @Override 489 public String toString() { 490 return "StrPhysical:"+getStrPhysical()+";"+ 491 "StrArchive:"+getStrArchive()+";"+ 492 "Virtual:"+getVirtual()+";"+ 493 "Archive:"+getArchive()+";"+ 494 "Physical:"+getPhysical()+";"+ 495 "topLevel:"+topLevel+";"+ 496 "inspect:"+ConfigWebUtil.inspectTemplate(getInspectTemplateRaw(),"")+";"+ 497 "physicalFirst:"+physicalFirst+";"+ 498 "readonly:"+readonly+";"+ 499 "hidden:"+hidden+";"; 500 } 501 502 public ApplicationListener getApplicationListener() { 503 if(appListener!=null) return appListener; 504 return config.getApplicationListener(); 505 } 506 507 public boolean getDotNotationUpperCase(){ 508 return ((ConfigImpl)config).getDotNotationUpperCase(); 509 } 510 511}