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 }