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.ByteArrayOutputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.net.URL; 025import java.util.Arrays; 026import java.util.Map; 027 028import lucee.commons.digest.MD5; 029import lucee.commons.io.IOUtil; 030import lucee.commons.io.SystemUtil; 031import lucee.commons.io.res.Resource; 032import lucee.commons.io.res.util.ResourceClassLoader; 033import lucee.commons.io.res.util.ResourceUtil; 034import lucee.runtime.config.Config; 035import lucee.runtime.engine.ThreadLocalPageContext; 036import lucee.runtime.type.Sizeable; 037import lucee.runtime.type.util.ArrayUtil; 038 039import org.apache.commons.collections.map.ReferenceMap; 040 041/** 042 * Directory ClassLoader 043 */ 044public final class PhysicalClassLoader extends ExtendableClassLoader implements Sizeable { 045 046 private Resource directory; 047 private ClassLoader parent; 048 private int size=0; 049 private int count; 050 private Map<String,PhysicalClassLoader> customCLs; 051 052 /** 053 * Constructor of the class 054 * @param directory 055 * @throws IOException 056 */ 057 public PhysicalClassLoader(Resource directory) throws IOException { 058 this(directory,getParentCL()); 059 } 060 private static ClassLoader getParentCL() { 061 Config config = ThreadLocalPageContext.getConfig(); 062 if(config!=null) return config.getClassLoader(); 063 return new ClassLoaderHelper().getClass().getClassLoader(); 064 } 065 066 /** 067 * Constructor of the class 068 * @param directory 069 * @param parent 070 * @throws IOException 071 */ 072 public PhysicalClassLoader(Resource directory, ClassLoader parent) throws IOException { 073 super(parent); 074 this.parent=parent; 075 if(!directory.isDirectory()) { 076 if(!directory.exists()) directory.mkdirs(); 077 else throw new IOException("resource "+directory+" is not a directory"); 078 } 079 if(!directory.canRead()) 080 throw new IOException("no access to "+directory+" directory"); 081 this.directory=directory; 082 } 083 084 /** 085 * Loads the class with the specified name. This method searches for 086 * classes in the same manner as the {@link #loadClass(String, boolean)} 087 * method. It is called by the Java virtual machine to resolve class 088 * references. Calling this method is equivalent to calling 089 * <code>loadClass(name, false)</code>. 090 * 091 * @param name the name of the class 092 * @return the resulting <code>Class</code> object 093 * @exception ClassNotFoundException if the class was not found 094 */ 095 public Class<?> loadClass(String name) throws ClassNotFoundException { 096 return loadClass(name, false); 097 }//15075171 098 099 /** 100 * Loads the class with the specified name. The default implementation of 101 * this method searches for classes in the following order:<p> 102 * 103 * <ol> 104 * <li> Call {@link #findLoadedClass(String)} to check if the class has 105 * already been loaded. <p> 106 * <li> Call the <code>loadClass</code> method on the parent class 107 * loader. If the parent is <code>null</code> the class loader 108 * built-in to the virtual machine is used, instead. <p> 109 * <li> Call the {@link #findClass(String)} method to find the class. <p> 110 * </ol> 111 * 112 * If the class was found using the above steps, and the 113 * <code>resolve</code> flag is true, this method will then call the 114 * {@link #resolveClass(Class)} method on the resulting class object. 115 * <p> 116 * From the Java 2 SDK, v1.2, subclasses of ClassLoader are 117 * encouraged to override 118 * {@link #findClass(String)}, rather than this method.<p> 119 * 120 * @param name the name of the class 121 * @param resolve if <code>true</code> then resolve the class 122 * @return the resulting <code>Class</code> object 123 * @exception ClassNotFoundException if the class could not be found 124 */ 125 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 126 //if(!name.endsWith("$cf")) return super.loadClass(name, resolve); this break Webervices 127 // First, check if the class has already been loaded 128 Class<?> c = findLoadedClass(name); 129 //print.o("load:"+name+" -> "+c); 130 if (c == null) { 131 try { 132 c =parent.loadClass(name); 133 } 134 catch (Throwable t) { 135 ExceptionUtil.rethrowIfNecessary(t); 136 c = findClass(name); 137 } 138 } 139 if (resolve) { 140 resolveClass(c); 141 } 142 return c; 143 } 144 145 146 147 148 public static long lastModified(Resource res, long defaultValue) { 149 InputStream in = null; 150 try{ 151 in=res.getInputStream(); 152 byte[] buffer = new byte[10]; 153 in.read(buffer); 154 if(!ClassUtil.isEncryptedBytecode(buffer)) return defaultValue; 155 156 byte[] _buffer = new byte[]{ 157 buffer[2], 158 buffer[3], 159 buffer[4], 160 buffer[5], 161 buffer[6], 162 buffer[7], 163 buffer[8], 164 buffer[9], 165 }; 166 167 168 return NumberUtil.byteArrayToLong(_buffer); 169 } 170 catch(IOException ioe){ 171 return defaultValue; 172 } 173 finally { 174 IOUtil.closeEL(in); 175 } 176 177 } 178 179 @Override 180 protected Class<?> findClass(String name) throws ClassNotFoundException { 181 Resource res=directory.getRealResource(name.replace('.','/').concat(".class")); 182 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 183 try { 184 IOUtil.copy(res,baos,false); 185 } 186 catch (IOException e) {//e.printStackTrace(); 187 throw new ClassNotFoundException("class "+name+" is invalid or doesn't exist"); 188 } 189 190 byte[] barr=baos.toByteArray(); 191 size+=barr.length; 192 count++; 193 //print.o(name+":"+count+" -> "+(size/1024)); 194 IOUtil.closeEL(baos); 195 return loadClass(name, barr); 196 //return defineClass(name,barr,0,barr.length); 197 } 198 199 200 public Class<?> loadClass(String name, byte[] barr) { 201 int start=0; 202 if(ClassUtil.isEncryptedBytecode(barr)) start=10; 203 size+=barr.length-start; 204 count++; 205 try { 206 return defineClass(name,barr,start,barr.length-start); 207 } 208 catch (Throwable t) { 209 ExceptionUtil.rethrowIfNecessary(t); 210 SystemUtil.sleep(1); 211 return defineClass(name,barr,start,barr.length-start); 212 } 213 //return loadClass(name,false); 214 } 215 216 @Override 217 public URL getResource(String name) { 218 /*URL url=super.getResource(name); 219 if(url!=null) return url; 220 221 Resource f =_getResource(name); 222 if(f!=null) { 223 try { 224 return f.toURL(); 225 } 226 catch (MalformedURLException e) {} 227 }*/ 228 return null; 229 } 230 231 @Override 232 public InputStream getResourceAsStream(String name) { 233 InputStream is = super.getResourceAsStream(name); 234 if(is!=null) return is; 235 236 Resource f = _getResource(name); 237 if(f!=null) { 238 try { 239 return IOUtil.toBufferedInputStream(f.getInputStream()); 240 } 241 catch (IOException e) {} 242 } 243 return null; 244 } 245 246 /** 247 * returns matching File Object or null if file not exust 248 * @param name 249 * @return matching file 250 */ 251 public Resource _getResource(String name) { 252 Resource f = directory.getRealResource(name); 253 if(f!=null && f.exists() && f.isFile()) return f; 254 return null; 255 } 256 257 public boolean hasClass(String className) { 258 return hasResource(className.replace('.','/').concat(".class")); 259 } 260 261 public boolean isClassLoaded(String className) { 262 //print.o("isClassLoaded:"+className+"-"+(findLoadedClass(className)!=null)); 263 return findLoadedClass(className)!=null; 264 } 265 266 public boolean hasResource(String name) { 267 return _getResource(name)!=null; 268 } 269 270 /** 271 * @return the directory 272 */ 273 public Resource getDirectory() { 274 return directory; 275 } 276 277 @Override 278 public long sizeOf() { 279 return 0; 280 } 281 282 public int count() { 283 return count; 284 } 285 286 // FUTURE add to interface 287 public PhysicalClassLoader getCustomClassLoader(Resource[] resources, boolean reload) throws IOException{ 288 if(ArrayUtil.isEmpty(resources)) return this; 289 String key = hash(resources); 290 291 if(reload && customCLs!=null) customCLs.remove(key); 292 293 294 PhysicalClassLoader pcl=customCLs==null?null:customCLs.get(key); 295 if(pcl!=null) return pcl; 296 pcl=new PhysicalClassLoader(this.getDirectory(),new ResourceClassLoader(resources,getParent())); 297 if(customCLs==null)customCLs=new ReferenceMap(); 298 customCLs.put(key, pcl); 299 return pcl; 300 } 301 302 private String hash(Resource[] resources) { 303 Arrays.sort(resources); 304 StringBuilder sb=new StringBuilder(); 305 for(int i=0;i<resources.length;i++){ 306 sb.append(ResourceUtil.getCanonicalPathEL(resources[i])); 307 sb.append(';'); 308 } 309 return MD5.getDigestAsString(sb.toString(),null); 310 } 311 312}