001    package railo.runtime;
002    
003    import java.io.FileNotFoundException;
004    import java.io.IOException;
005    import java.io.InputStream;
006    import java.lang.reflect.Constructor;
007    import java.lang.reflect.InvocationTargetException;
008    
009    import railo.print;
010    import railo.commons.io.IOUtil;
011    import railo.commons.io.res.Resource;
012    import railo.commons.io.res.util.ResourceUtil;
013    import railo.commons.lang.SizeOf;
014    import railo.commons.lang.StringUtil;
015    import railo.commons.lang.types.RefBoolean;
016    import railo.commons.lang.types.RefBooleanImpl;
017    import railo.runtime.config.ConfigImpl;
018    import railo.runtime.config.ConfigWeb;
019    import railo.runtime.config.ConfigWebImpl;
020    import railo.runtime.engine.ThreadLocalPageContext;
021    import railo.runtime.engine.ThreadLocalPageSource;
022    import railo.runtime.exp.ExpressionException;
023    import railo.runtime.exp.MissingIncludeException;
024    import railo.runtime.exp.PageException;
025    import railo.runtime.exp.TemplateException;
026    import railo.runtime.functions.system.GetDirectoryFromPath;
027    import railo.runtime.op.Caster;
028    import railo.runtime.type.Sizeable;
029    import railo.runtime.type.util.ArrayUtil;
030    import railo.runtime.type.util.ListUtil;
031    
032    /**
033     * represent a cfml file on the runtime system
034     */
035    public final class PageSourceImpl implements SourceFile, PageSource, Sizeable {
036    
037            private static final long serialVersionUID = -7661676586215092539L;
038            //public static final byte LOAD_NONE=1;
039        public static final byte LOAD_ARCHIVE=2;
040        public static final byte LOAD_PHYSICAL=3;
041        
042        //private byte load=LOAD_NONE;
043    
044            private final MappingImpl mapping;
045        private final String realPath;
046        
047        private boolean isOutSide;
048        
049        private String className;
050        private String packageName;
051        private String javaName;
052    
053        private Resource physcalSource;
054        private Resource archiveSource;
055        private String fileName;
056        private String compName;
057        private Page page;
058            private long lastAccess;        
059            private int accessCount=0;
060        //private boolean recompileAlways;
061        //private boolean recompileAfterStartUp;
062        
063        private PageSourceImpl() {
064            mapping=null;
065            realPath=null;
066        }
067        
068        
069        /**
070             * constructor of the class
071         * @param mapping
072         * @param realPath
073             */
074            PageSourceImpl(MappingImpl mapping,String realPath) {
075                    this.mapping=mapping;
076            //recompileAlways=mapping.getConfig().getCompileType()==Config.RECOMPILE_ALWAYS;
077            //recompileAfterStartUp=mapping.getConfig().getCompileType()==Config.RECOMPILE_AFTER_STARTUP || recompileAlways;
078            realPath=realPath.replace('\\','/');
079                    if(realPath.indexOf('/')!=0) {
080                        if(realPath.startsWith("../")) {
081                                    isOutSide=true;
082                            }
083                            else if(realPath.startsWith("./")) {
084                                    realPath=realPath.substring(1);
085                            }
086                            else {
087                                    realPath="/"+realPath;
088                            }
089                    }
090                    this.realPath=realPath;
091                
092            }
093            
094            
095            
096            /**
097             * private constructor of the class
098             * @param mapping
099             * @param realPath
100             * @param isOutSide
101             */
102        PageSourceImpl(MappingImpl mapping, String realPath, boolean isOutSide) {
103            //recompileAlways=mapping.getConfig().getCompileType()==Config.RECOMPILE_ALWAYS;
104            //recompileAfterStartUp=mapping.getConfig().getCompileType()==Config.RECOMPILE_AFTER_STARTUP || recompileAlways;
105            this.mapping=mapping;
106                this.isOutSide=isOutSide;
107                    this.realPath=realPath;
108                    
109            }
110            
111            /**
112             * return page when already loaded, otherwise null
113             * @param pc
114             * @param config
115             * @return
116             * @throws PageException
117             */
118            public Page getPage() {
119                    return page;
120            }
121            
122            public PageSource getParent(){
123                    if(realPath.equals("/")) return null;
124                    if(StringUtil.endsWith(realPath, '/'))
125                            return new PageSourceImpl(mapping, GetDirectoryFromPath.invoke(realPath.substring(0, realPath.length()-1)));
126                    return new PageSourceImpl(mapping, GetDirectoryFromPath.invoke(realPath));
127            }
128    
129            
130            @Override
131            public Page loadPage(ConfigWeb config) throws PageException {
132                    return loadPage(ThreadLocalPageContext.get());
133            }
134    
135            @Override
136            public Page loadPage(ConfigWeb config, Page defaultValue) throws PageException {
137                    return loadPage(ThreadLocalPageContext.get(), defaultValue);
138            }
139            
140    
141            public Page loadPage(PageContext pc, boolean forceReload) throws PageException {
142                    if(forceReload) page=null;
143                    return loadPage(pc);
144            }
145            
146            public Page loadPage(PageContext pc) throws PageException {
147                    Page page=this.page;
148                    if(mapping.isPhysicalFirst()) {
149                            page=loadPhysical(pc,page);
150                            if(page==null) page=loadArchive(page); 
151                    if(page!=null) return page;
152                }
153                else {
154                    page=loadArchive(page);
155                    if(page==null)page=loadPhysical(pc,page);
156                    if(page!=null) return page;
157                }
158                    throw new MissingIncludeException(this);
159                
160            }
161            
162            @Override
163            public Page loadPage(PageContext pc, Page defaultValue) throws PageException {
164                    Page page=this.page;
165                    if(mapping.isPhysicalFirst()) {
166                    page=loadPhysical(pc,page);
167                    if(page==null) page=loadArchive(page); 
168                    if(page!=null) return page;
169                }
170                else {
171                    page=loadArchive(page);
172                    if(page==null)page=loadPhysical(pc,page);
173                    if(page!=null) return page;
174                }
175                return defaultValue;
176            }
177            
178        private Page loadArchive(Page page) {
179            if(!mapping.hasArchive()) return null;
180                    if(page!=null) return page;
181            
182            try {
183                synchronized(this) {
184                    Class clazz=mapping.getClassLoaderForArchive().loadClass(getClazz());
185                    this.page=page=newInstance(clazz);
186                    page.setPageSource(this);
187                    //page.setTimeCreated(System.currentTimeMillis());
188                    page.setLoadType(LOAD_ARCHIVE);
189                            ////load=LOAD_ARCHIVE;
190                            return page;
191                }
192            } 
193            catch (Exception e) {
194                return null;
195            }
196        }
197        
198    
199        private Page loadPhysical(PageContext pc,Page page) throws PageException {
200            if(!mapping.hasPhysical()) return null;
201            
202            ConfigWeb config=pc.getConfig();
203            PageContextImpl pci=(PageContextImpl) pc;
204            if((mapping.getInspectTemplate()==ConfigImpl.INSPECT_NEVER || pci.isTrusted(page)) && isLoad(LOAD_PHYSICAL)) return page;
205            Resource srcFile = getPhyscalFile();
206            
207            /*{
208                    String dp = getDisplayPath();
209                    String cn = getClassName();
210                    if(dp.endsWith(".cfc") && cn.startsWith("cfc")) {
211                            print.ds("->"+dp);
212                            print.e("trusted:"+mapping.isTrusted());
213                            print.e(mapping.getVirtual());
214                            print.e("mod:"+srcFile.lastModified());
215                    }
216            }*/
217            
218            
219                    long srcLastModified = srcFile.lastModified();
220            if(srcLastModified==0L) return null;
221            
222                    // Page exists    
223                            if(page!=null) {
224                            //if(page!=null && !recompileAlways) {
225                                    // java file is newer !mapping.isTrusted() && 
226                                    if(srcLastModified!=page.getSourceLastModified()) {
227                                            this.page=page=compile(config,mapping.getClassRootDirectory(),Boolean.TRUE);
228                            page.setPageSource(this);
229                                            page.setLoadType(LOAD_PHYSICAL);
230                                    }
231                            
232                            }
233                    // page doesn't exist
234                            else {
235                    ///synchronized(this) {
236                        Resource classRootDir=mapping.getClassRootDirectory();
237                        Resource classFile=classRootDir.getRealResource(getJavaName()+".class");
238                        boolean isNew=false;
239                        // new class
240                        if(!classFile.exists()) {
241                        //if(!classFile.exists() || recompileAfterStartUp) {
242                            this.page=page= compile(config,classRootDir,Boolean.FALSE);
243                            isNew=true;
244                        }
245                        // load page
246                        else {
247                            try {
248                                                            this.page=page=newInstance(mapping.touchPCLCollection().getClass(this));
249                                                    } catch (Throwable t) {t.printStackTrace();
250                                                            this.page=page=null;
251                                                    }
252                            if(page==null) this.page=page=compile(config,classRootDir,Boolean.TRUE);
253                                  
254                        }
255                        
256                        // check if there is a newwer version
257                        if(!isNew && srcLastModified!=page.getSourceLastModified()) {
258                            isNew=true;
259                            this.page=page=compile(config,classRootDir,null);
260                                    }
261                        
262                        // check version
263                        if(!isNew && page.getVersion()!=Info.getFullVersionInfo()) {
264                            isNew=true;
265                            this.page=page=compile(config,classRootDir,null);
266                        }
267                        
268                        page.setPageSource(this);
269                                    page.setLoadType(LOAD_PHYSICAL);
270    
271                            }
272                            pci.setPageUsed(page);
273                            return page;
274        }
275    
276        private boolean isLoad(byte load) {
277                    return page!=null && load==page.getLoadType();
278            }
279        
280    
281            private synchronized Page compile(ConfigWeb config,Resource classRootDir, Boolean resetCL) throws PageException {
282                    try {
283                            return _compile(config, classRootDir, resetCL);
284            }
285            catch(ClassFormatError e) {
286                    String msg=StringUtil.emptyIfNull(e.getMessage());
287                    if(StringUtil.indexOfIgnoreCase(msg, "Invalid method Code length")!=-1) {
288                            throw new TemplateException("There is too much code inside the template ["+getDisplayPath()+"], Railo was not able to break it into pieces, move parts of your code to an include or a external component/function",msg);
289                    }
290                    throw Caster.toPageException(e);
291            }
292            catch(Throwable t) {
293                    throw Caster.toPageException(t);
294            }
295            }
296    
297            private synchronized Page _compile(ConfigWeb config,Resource classRootDir, Boolean resetCL) throws IOException, SecurityException, IllegalArgumentException, PageException {
298            ConfigWebImpl cwi=(ConfigWebImpl) config;
299            
300            
301            byte[] barr = cwi.getCompiler().
302                    compile(cwi,this,cwi.getTLDs(),cwi.getFLDs(),classRootDir,getJavaName());
303            Class<?> clazz = mapping.touchPCLCollection().loadClass(getClazz(), barr,isComponent());
304            try{
305                    return  newInstance(clazz);
306            }
307            catch(Throwable t){
308                    PageException pe = Caster.toPageException(t);
309                    pe.setExtendedInfo("failed to load template "+getDisplayPath());
310                    throw pe;
311            }
312        }
313    
314        private Page newInstance(Class clazz) throws SecurityException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
315            try{
316                            Constructor c = clazz.getConstructor(new Class[]{PageSource.class});
317                            return (Page) c.newInstance(new Object[]{this});
318                    }
319            // this only happens with old code from ra files
320                    catch(NoSuchMethodException e){
321                            ThreadLocalPageSource.register(this);
322                            try{
323                                    return (Page) clazz.newInstance();
324                            }
325                            finally {
326                                    ThreadLocalPageSource.release();
327                            }
328                            
329                            
330                    }
331            }
332    
333    
334            /**
335         * return source path as String 
336         * @return source path as String
337         */
338        public String getDisplayPath() {
339            if(!mapping.hasArchive())       {
340                    return StringUtil.toString(getPhyscalFile(), null);
341            }
342            else if(isLoad(LOAD_PHYSICAL))  {
343                    return StringUtil.toString(getPhyscalFile(), null);
344            }
345            else if(isLoad(LOAD_ARCHIVE))   {
346                    return StringUtil.toString(getArchiveSourcePath(), null);
347            }
348            else {
349                boolean pse = physcalExists();
350                boolean ase = archiveExists();
351                
352                if(mapping.isPhysicalFirst()) {
353                    if(pse)return getPhyscalFile().toString();
354                    else if(ase)return getArchiveSourcePath();
355                    return getPhyscalFile().toString();
356                }
357                if(ase)return getArchiveSourcePath();
358                else if(pse)return getPhyscalFile().toString();
359                return getArchiveSourcePath();
360            }
361        }
362        
363        public boolean isComponent() {
364            return ResourceUtil.getExtension(getRealpath(), "").equalsIgnoreCase(mapping.getConfig().getCFCExtension());
365        }
366        
367        /**
368             * return file object, based on physical path and realpath
369             * @return file Object
370             */
371            private String getArchiveSourcePath() {
372                return "zip://"+mapping.getArchive().getAbsolutePath()+"!"+realPath; 
373            }
374    
375        /**
376             * return file object, based on physical path and realpath
377             * @return file Object
378             */
379        public Resource getPhyscalFile() {
380            if(physcalSource==null) {
381                if(!mapping.hasPhysical()) {
382                    return null;
383                }
384                            physcalSource=ResourceUtil.toExactResource(mapping.getPhysical().getRealResource(realPath));
385            }
386            return physcalSource;
387            }
388        
389        public Resource getArchiveFile() {
390            if(archiveSource==null) {
391                    if(!mapping.hasArchive()) return null;
392                    String path="zip://"+mapping.getArchive().getAbsolutePath()+"!"+realPath;
393                    archiveSource = ThreadLocalPageContext.getConfig().getResource(path);
394            }
395            return archiveSource;
396            }
397        
398    
399        /**
400             * merge to realpath to one
401             * @param mapping 
402             * @param parentRealPath 
403             * @param newRealPath
404             * @param isOutSide 
405             * @return merged realpath
406             */
407            private static String mergeRealPathes(Mapping mapping,String parentRealPath, String newRealPath, RefBoolean isOutSide) {
408                    parentRealPath=pathRemoveLast(parentRealPath,isOutSide);
409                    while(newRealPath.startsWith("../")) {
410                            parentRealPath=pathRemoveLast(parentRealPath,isOutSide);
411                            newRealPath=newRealPath.substring(3);
412                    }
413                    
414                    // check if come back
415                    String path=parentRealPath.concat("/").concat(newRealPath);
416                    
417                    if(path.startsWith("../")) {
418                            int count=0;
419                            do {
420                                    count++;
421                                    path=path.substring(3);
422                            }while(path.startsWith("../"));
423                            
424                            String strRoot=mapping.getPhysical().getAbsolutePath().replace('\\','/');
425                            if(!StringUtil.endsWith(strRoot,'/')) {
426                                    strRoot+='/';
427                            }
428                            int rootLen=strRoot.length();
429                            String[] arr=ListUtil.toStringArray(ListUtil.listToArray(path,'/'),"");//path.split("/");
430                            int tmpLen;
431                            for(int i=count;i>0;i--) {
432                                    if(arr.length>i) {
433                                            String tmp='/'+list(arr,0,i);
434                                            tmpLen=rootLen-tmp.length();
435                                            if(strRoot.lastIndexOf(tmp)==tmpLen && tmpLen>=0) {
436                                                    StringBuffer rtn=new StringBuffer();
437                                                    while(i<count-i) {
438                                                            count--;
439                                                            rtn.append("../");
440                                                    }
441                                                    isOutSide.setValue(rtn.length()!=0);
442                                                    return (rtn.length()==0?"/":rtn.toString())+list(arr,i,arr.length);
443                                            }
444                                    }
445                            }
446                    }
447                    return parentRealPath.concat("/").concat(newRealPath);
448            }
449    
450            /**
451             * convert a String array to a string list, but only part of it 
452             * @param arr String Array
453             * @param from start from here
454             * @param len how many element
455             * @return String list
456             */
457            private static String list(String[] arr,int from, int len) {
458                    StringBuffer sb=new StringBuffer();
459                    for(int i=from;i<len;i++) {
460                            sb.append(arr[i]);
461                            if(i+1!=arr.length)sb.append('/');
462                    }
463                    return sb.toString();
464            }
465    
466            
467            
468            /**
469             * remove the last elemtn of a path
470             * @param path path to remove last element from it
471             * @param isOutSide 
472             * @return path with removed element
473             */
474            private static String pathRemoveLast(String path, RefBoolean isOutSide) {
475                    if(path.length()==0) {
476                            isOutSide.setValue(true);
477                            return "..";
478                    }
479                    else if(path.endsWith("..")){
480                        isOutSide.setValue(true);
481                            return path.concat("/..");//path+"/..";
482                    }
483                    return path.substring(0,path.lastIndexOf('/'));
484            }
485            
486            @Override
487            public String getRealpath() {
488                    return realPath;
489            }       
490            @Override
491            public String getFullRealpath() {
492                    if(mapping.getVirtual().length()==1 || mapping.ignoreVirtual())
493                            return realPath;
494                    return mapping.getVirtual()+realPath;
495            }
496            
497            /**
498             * @return returns a variable string based on realpath and return it
499             */
500            public String getRealPathAsVariableString() {
501                    return StringUtil.toIdentityVariableName(realPath);
502            }
503            
504            @Override
505            public String getClazz() {
506                    if(className==null) createClassAndPackage();
507                    if(packageName.length()>0) return packageName+'.'+className;
508                    return className;
509            }
510            
511            /**
512             * @return returns the a classname matching to filename (Example: test_cfm)
513             */
514            public String getClassName() {
515                    if(className==null) createClassAndPackage();
516                    return className;
517            }
518    
519        @Override
520        public String getFileName() {
521                    if(fileName==null) createClassAndPackage();
522            return fileName;
523        }
524            
525            @Override
526            public String getJavaName() {
527                    if(javaName==null) createClassAndPackage();
528                    return javaName;
529            }
530    
531            /**
532             * @return returns the a package matching to file (Example: railo.web)
533             */
534            public String getPackageName() {
535                    if(packageName==null) createClassAndPackage();
536                    return packageName;
537            }
538            @Override
539            public String getComponentName() {
540                    if(compName==null) createComponentName();
541                    return compName;
542            }
543            
544            
545            private synchronized void createClassAndPackage() {
546                    String str=realPath;
547                    StringBuffer packageName=new StringBuffer();
548                    StringBuffer javaName=new StringBuffer();
549                    
550                    String[] arr=ListUtil.toStringArrayEL(ListUtil.listToArrayRemoveEmpty(str,'/'));
551                    
552                    String varName;
553                    for(int i=0;i<arr.length;i++) {
554                            if(i==(arr.length-1)) {
555                                    int index=arr[i].lastIndexOf('.');
556                                    if(index!=-1){
557                                            String ext=arr[i].substring(index+1);
558                                            varName=StringUtil.toVariableName(arr[i].substring(0,index)+"_"+ext);
559                                    }
560                                    else varName=StringUtil.toVariableName(arr[i]);
561                                    varName=varName+"$cf";
562                                    className=varName.toLowerCase();
563                                    fileName=arr[i];
564                            }
565                            else {
566                                    varName=StringUtil.toVariableName(arr[i]);
567                                    if(i!=0) {
568                                        packageName.append('.');
569                                    }
570                                    packageName.append(varName);
571                            }
572                            javaName.append('/');
573                            javaName.append(varName);
574                    }
575                    
576                    this.packageName=packageName.toString().toLowerCase();
577                    this.javaName=javaName.toString().toLowerCase();
578    
579                    
580                    
581            }
582            
583            
584    
585            private synchronized void createComponentName() {
586                    Resource res = this.getPhyscalFile();
587                String str=null;
588                    if(res!=null) {
589                            
590                            str=res.getAbsolutePath();
591                            str=str.substring(str.length()-realPath.length());
592                            if(!str.equalsIgnoreCase(realPath)) {
593                                    str=realPath;
594                            }
595                    }
596                    else str=realPath;
597                
598                    StringBuffer compName=new StringBuffer();
599                    String[] arr;
600                    
601                    // virtual part
602                    if(!mapping.ignoreVirtual()) {
603                            arr=ListUtil.toStringArrayEL(ListUtil.listToArrayRemoveEmpty(mapping.getVirtual(),"\\/"));
604                            for(int i=0;i<arr.length;i++) {
605                                    if(compName.length()>0) compName.append('.');
606                                    compName.append(arr[i]);
607                            }
608                    }
609                    
610                    // physical part
611                    arr=ListUtil.toStringArrayEL(ListUtil.listToArrayRemoveEmpty(str,'/')); 
612                    for(int i=0;i<arr.length;i++) {
613                        if(compName.length()>0) compName.append('.');
614                            if(i==(arr.length-1)) {
615                                compName.append(arr[i].substring(0,arr[i].length()-4));
616                            }
617                            else compName.append(arr[i]);
618                    }
619                    this.compName=compName.toString();
620            }
621    
622        @Override
623        public Mapping getMapping() {
624            return mapping;
625        }
626    
627        @Override
628        public synchronized boolean exists() {
629            if(mapping.isPhysicalFirst())
630                    return physcalExists() || archiveExists();
631                return archiveExists() || physcalExists();
632        }
633    
634        @Override
635        public boolean physcalExists() {
636            return ResourceUtil.exists(getPhyscalFile());
637        }
638        
639        private boolean archiveExists() {
640            if(!mapping.hasArchive())return false;
641            try {
642                    String clazz = getClazz();
643                    if(clazz==null) return getArchiveFile().exists();
644                    mapping.getClassLoaderForArchive().loadClass(clazz);
645                    return true;
646            } 
647            catch(ClassNotFoundException cnfe){
648                    return false;
649            }
650            catch (Exception e) {
651                return getArchiveFile().exists();
652            }
653        }
654    
655        /**
656         * return the inputstream of the source file
657         * @return return the inputstream for the source from ohysical or archive
658         * @throws FileNotFoundException
659         */
660        private InputStream getSourceAsInputStream() throws IOException {
661            if(!mapping.hasArchive())               return IOUtil.toBufferedInputStream(getPhyscalFile().getInputStream());
662            else if(isLoad(LOAD_PHYSICAL))  return IOUtil.toBufferedInputStream(getPhyscalFile().getInputStream());
663            else if(isLoad(LOAD_ARCHIVE))   {
664                StringBuffer name=new StringBuffer(getPackageName().replace('.','/'));
665                if(name.length()>0)name.append("/");
666                name.append(getFileName());
667                
668                return mapping.getClassLoaderForArchive().getResourceAsStream(name.toString());
669            }
670            else {
671                return null;
672            }
673        }
674        @Override
675        public String[] getSource() throws IOException {
676            //if(source!=null) return source;
677            InputStream is = getSourceAsInputStream();
678            if(is==null) return null;
679            try {
680                    return IOUtil.toStringArray(IOUtil.getReader(is,getMapping().getConfig().getTemplateCharset()));
681            }
682            finally {
683                    IOUtil.closeEL(is);
684            }
685        }
686    
687        @Override
688        public boolean equals(Object obj) {
689            if(this==obj) return true;  
690            if(!(obj instanceof PageSource)) return false;
691            return getClassName().equals(((PageSource)obj).getClassName());
692            //return equals((PageSource)obj);
693        }
694        
695        /**
696         * is given object equal to this
697         * @param other
698         * @return is same
699         */
700        public boolean equals(PageSource other) {
701            if(this==other) return true;  
702            return getClassName().equals(other.getClassName());
703        }
704    
705            @Override
706            public PageSource getRealPage(String realPath) {
707                if(realPath.equals(".") || realPath.equals(".."))realPath+='/';
708                else realPath=realPath.replace('\\','/');
709                RefBoolean _isOutSide=new RefBooleanImpl(isOutSide);
710                
711                
712                    if(realPath.indexOf('/')==0) {
713                        _isOutSide.setValue(false);
714                    }
715                    else if(realPath.startsWith("./")) {
716                            realPath=mergeRealPathes(mapping,this.realPath, realPath.substring(2),_isOutSide);
717                    }
718                    else {
719                            realPath=mergeRealPathes(mapping,this.realPath, realPath,_isOutSide);
720                    }
721                    return mapping.getPageSource(realPath,_isOutSide.toBooleanValue());
722            }
723            
724            @Override
725            public final void setLastAccessTime(long lastAccess) {
726                    this.lastAccess=lastAccess;
727            }       
728            
729            @Override
730            public final long getLastAccessTime() {
731                    return lastAccess;
732            }
733    
734            @Override
735            public synchronized final void setLastAccessTime() {
736                    accessCount++;
737                    this.lastAccess=System.currentTimeMillis();
738            }       
739            
740            @Override
741            public final int getAccessCount() {
742                    return accessCount;
743            }
744    
745        @Override
746        public Resource getResource() {
747            Resource p = getPhyscalFile();
748            Resource a = getArchiveFile();
749            if(mapping.isPhysicalFirst()){
750                    if(a==null) return p;
751                    if(p==null) return a;
752                    
753                    if(p.exists()) return p;
754                    if(a.exists()) return a;
755                    return p;
756            }
757            if(p==null) return a;
758            if(a==null) return p;
759            
760            if(a.exists()) return a;
761            if(p.exists()) return p;
762            return a;
763            
764            //return getArchiveFile();
765        }
766        
767        @Override
768        public Resource getResourceTranslated(PageContext pc) throws ExpressionException {
769            Resource res = null;
770            if(!isLoad(LOAD_ARCHIVE)) res=getPhyscalFile();
771            
772            // there is no physical resource
773                    if(res==null){
774                    String path=getDisplayPath();
775                    if(path!=null){
776                            if(path.startsWith("ra://"))
777                                    path="zip://"+path.substring(5);
778                            res=ResourceUtil.toResourceNotExisting(pc, path,false);
779                    }
780            }
781                    return res;
782        }
783    
784    
785        public void clear() {
786            if(page!=null){
787                    page=null;
788            }
789        }
790        
791        /**
792         * clear page, but only when page use the same clasloader as provided
793         * @param cl
794         */
795        public void clear(ClassLoader cl) {
796            if(page!=null && page.getClass().getClassLoader().equals(cl)){
797                    page=null;
798            }
799        }
800    
801            @Override
802        public String getFullClassName() {
803            String s=_getFullClassName();
804            return s;
805        }
806        
807            public String _getFullClassName() {
808                    String p=getPackageName();
809                    if(p.length()==0) return getClassName();
810                    return p.concat(".").concat(getClassName());
811            }
812            
813            public boolean isLoad() {
814                    return page!=null;////load!=LOAD_NONE;
815            }
816    
817            @Override
818            public String toString() {
819                    return getDisplayPath();
820            }
821            
822            @Override
823            public long sizeOf() {
824                    return SizeOf.size(page,0)+
825                    SizeOf.size(className)+
826                    SizeOf.size(packageName)+
827                    SizeOf.size(javaName)+
828                    SizeOf.size(fileName)+
829                    SizeOf.size(compName)+
830                    SizeOf.size(lastAccess)+
831                    SizeOf.size(accessCount);
832            }
833    
834            public static PageSource best(PageSource[] arr) {
835                    if(ArrayUtil.isEmpty(arr)) return null;
836                    if(arr.length==1)return arr[0];
837                    for(int i=0;i<arr.length;i++) {
838                            if(pageExist(arr[i])) return arr[i];
839                    }
840                    /*// get the best none existing
841                    for(int i=0;i<arr.length;i++) {
842                            if(arr[i].getPhyscalFile()!=null) return arr[i];
843                    }
844                    for(int i=0;i<arr.length;i++) {
845                            if(arr[i].getDisplayPath()!=null) return arr[i];
846                    }
847                    */
848                    return arr[0];
849            }
850    
851            public static boolean pageExist(PageSource ps) {
852                    return (ps.getMapping().isTrusted() && ((PageSourceImpl)ps).isLoad()) || ps.exists();
853            }
854    
855            public static Page loadPage(PageContext pc,PageSource[] arr,Page defaultValue) throws PageException {
856                    if(ArrayUtil.isEmpty(arr)) return null;
857                    Page p;
858                    for(int i=0;i<arr.length;i++) {
859                            p=arr[i].loadPage(pc,(Page)null);
860                            if(p!=null) return p;
861                    }
862                    return defaultValue;
863            }
864    
865            public static Page loadPage(PageContext pc,PageSource[] arr) throws PageException {
866                    if(ArrayUtil.isEmpty(arr)) return null;
867                    
868                    Page p;
869                    for(int i=0;i<arr.length;i++) {
870                            p=arr[i].loadPage(pc,null);
871                            if(p!=null) return p;
872                    }
873                    throw new MissingIncludeException(arr[0]);
874            }
875            
876            
877            
878    }