001    package railo.commons.lang;
002    
003    import java.io.IOException;
004    import java.io.InputStream;
005    import java.util.HashMap;
006    import java.util.LinkedList;
007    import java.util.Map;
008    
009    import railo.commons.io.res.Resource;
010    import railo.commons.io.res.util.ResourceClassLoader;
011    import railo.runtime.MappingImpl;
012    import railo.runtime.PageSourceImpl;
013    import railo.runtime.instrumentation.InstrumentationUtil;
014    import railo.runtime.type.util.StructUtil;
015    
016    /**
017     * Directory ClassLoader
018     */
019    public final class PCLCollection {
020        
021        private final Resource directory;
022        private final ClassLoader resourceCL;
023    
024            private final int maxBlockSize;
025            private final MappingImpl mapping;
026            private final LinkedList<PCLBlock> cfcs=new LinkedList<PCLBlock>();
027            private LinkedList<PCLBlock> cfms=new LinkedList<PCLBlock>();
028            private PCLBlock cfc;
029            private PCLBlock cfm;
030            private Map<String,PCLBlock> index=new HashMap<String, PCLBlock>();
031    
032        /**
033         * Constructor of the class
034         * @param directory
035         * @param parent
036         * @throws IOException
037         */
038        public PCLCollection(MappingImpl mapping,Resource directory, ClassLoader resourceCL, int maxBlockSize) throws IOException {
039            // check directory
040            if(!directory.exists())
041                directory.mkdirs();
042            
043            if(!directory.isDirectory())
044                throw new IOException("resource "+directory+" is not a directory");
045            if(!directory.canRead())
046                throw new IOException("no access to "+directory+" directory");
047            
048            this.directory=directory;
049            this.mapping=mapping;
050            //this.pcl=systemCL;
051            this.resourceCL=resourceCL;
052            cfc=new PCLBlock(directory, resourceCL);
053            cfcs.add(cfc);
054            cfm=new PCLBlock(directory, resourceCL);
055            cfms.add(cfm);
056            this.maxBlockSize=100;//maxBlockSize;
057        }
058        
059    
060        private PCLBlock current(boolean isCFC) {
061            if((isCFC?cfc.count():cfm.count())>=maxBlockSize) {
062                    synchronized (isCFC?cfcs:cfms) {
063                            if(isCFC) {
064                                    cfc=new PCLBlock(directory, resourceCL);
065                                    cfcs.add(cfc);
066                            }
067                            else {
068                                    cfm=new PCLBlock(directory, resourceCL);
069                                    cfms.add(cfm);
070                            }
071                            }
072            }
073                    return isCFC?cfc:cfm;
074            }
075        
076        
077    
078        public synchronized Class<?> loadClass(String name, byte[] barr, boolean isCFC)   {
079            // if class is already loaded flush the classloader and do new classloader
080            PCLBlock cl = index.get(name);
081            if(cl!=null) {
082                    // if can upate class
083                    if(InstrumentationUtil.isSupported()){
084                            try{
085                                    Class<?> old = cl.loadClass(name);
086                            InstrumentationUtil.redefineClass(old, barr);
087                            return old;
088                            }
089                            catch(Throwable t){
090                                    t.printStackTrace();
091                            }
092                    }
093                    
094                    // flush classloader when update is not possible
095                    mapping.clearPages(cl);
096                    StructUtil.removeValue(index,cl);
097                    if(isCFC){
098                    cfcs.remove(cl);
099                            if(cl==cfc) cfc=new PCLBlock(directory, resourceCL);
100                    }
101                    else {
102                    cfms.remove(cl);
103                            if(cl==cfm) cfm=new PCLBlock(directory, resourceCL);
104                    }
105            }
106            
107            // load class from byte array
108            PCLBlock c = current(isCFC);
109            index.put(name, c);
110            return c.loadClass(name, barr);
111        }
112    
113        public synchronized Class<?> getClass(PageSourceImpl ps) throws ClassNotFoundException {
114            String name=ps.getClazz();
115            PCLBlock cl = index.get(name);
116            if(cl==null) {
117                    cl=current(ps.isComponent());
118                    Class<?> clazz = cl.loadClass(name);
119                    index.put(name, cl);
120                    return clazz;
121            }
122            return cl.loadClass(name);      
123        }
124    
125        public synchronized InputStream getResourceAsStream(String name) {
126            return current(false).getResourceAsStream(name);
127        }
128    
129            public long count() {
130                    return index.size();
131            }
132            
133            /**
134             * shrink the classloader elements
135             * @return how many page have removed from classloaders
136             */
137    
138            public synchronized int shrink(boolean force){
139                    int before=index.size();
140                    
141                    // CFM
142                    int flushCFM=0;
143                    while(cfms.size()>1) {
144                            flush(cfms.poll());
145                            flushCFM++;
146                    }
147            
148                    // CFC
149                    if(force && flushCFM<2 && cfcs.size()>1) {
150                            flush(oldest(cfcs));
151                            if(cfcs.size()>1)flush(cfcs.poll());
152                    }
153                    //print.o("shrink("+mapping.getVirtual()+"):"+(before-index.size())+">"+force+";"+(flushCFM));
154            return before-index.size();
155            }
156    
157            private static PCLBlock oldest(LinkedList<PCLBlock> queue) {
158                    int index=NumberUtil.randomRange(0,queue.size()-2);
159                    return queue.remove(index);
160                    //return queue.poll();
161            }
162    
163    
164            private void flush(PCLBlock cl) {
165                    mapping.clearPages(cl);
166                    StructUtil.removeValue(index,cl);
167                    //System.gc(); gc is in Controller call, to make sure gc is only called once 
168            }
169    }