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}