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