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