/** * Copyright (c) 2014, the Railo Company Ltd. * Copyright (c) 2015, Lucee Assosication Switzerland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ package lucee.commons.lang; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.instrument.ClassDefinition; import java.lang.instrument.UnmodifiableClassException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import lucee.commons.digest.HashUtil; import lucee.commons.io.IOUtil; import lucee.commons.io.SystemUtil; import lucee.commons.io.res.Resource; import lucee.commons.io.res.util.ResourceClassLoader; import lucee.commons.io.res.util.ResourceUtil; import lucee.runtime.config.Config; import lucee.runtime.config.ConfigImpl; import lucee.runtime.functions.other.CreateObject; import lucee.runtime.instrumentation.InstrumentationFactory; import lucee.runtime.type.util.ArrayUtil; import org.apache.commons.collections4.map.ReferenceMap; /** * Directory ClassLoader */ public final class PhysicalClassLoader extends ExtendableClassLoader { private Resource directory; private ConfigImpl config; private final ClassLoader[] parents; Set<String> loadedClasses = new HashSet<>(); Set<String> unavaiClasses = new HashSet<>(); private Map<String,PhysicalClassLoader> customCLs; /** * Constructor of the class * @param directory * @param parent * @throws IOException */ public PhysicalClassLoader(Config c,Resource directory) throws IOException { this(c,directory,(ClassLoader[])null,true); } public PhysicalClassLoader(Config c,Resource directory, ClassLoader[] parentClassLoaders, boolean includeCoreCL) throws IOException { super(parentClassLoaders==null || parentClassLoaders.length==0?c.getClassLoader():parentClassLoaders[0]); config = (ConfigImpl)c; //ClassLoader resCL = parent!=null?parent:config.getResourceClassLoader(null); List<ClassLoader> tmp = new ArrayList<ClassLoader>(); if(parentClassLoaders==null || parentClassLoaders.length==0) { ResourceClassLoader _cl = config.getResourceClassLoader(null); if(_cl!=null) tmp.add(_cl); } else { for(ClassLoader p:parentClassLoaders) { tmp.add(p); } } if(includeCoreCL) tmp.add(config.getClassLoaderCore()); parents= tmp.toArray(new ClassLoader[tmp.size()]); // check directory if(!directory.exists()) directory.mkdirs(); if(!directory.isDirectory()) throw new IOException("resource "+directory+" is not a directory"); if(!directory.canRead()) throw new IOException("no access to "+directory+" directory"); this.directory=directory; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (loadedClasses.contains(name) || unavaiClasses.contains(name)) { return super.loadClass(name,false); // Use default CL cache } // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { for(ClassLoader p:parents) { try { c = p.loadClass(name); break; } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} } if(c==null) { c = findClass(name); /*try { c = findClass(name); } catch(ClassNotFoundException cnf) { return SystemUtil.getLoaderClassLoader().loadClass(name); } catch(NoClassDefFoundError cfdfe) { return SystemUtil.getLoaderClassLoader().loadClass(name); }*/ } } if (resolve)resolveClass(c); return c; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException {//if(name.indexOf("sub")!=-1)print.ds(name); Resource res=directory .getRealResource( name.replace('.','/') .concat(".class")); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { IOUtil.copy(res,baos,false); } catch (IOException e) { this.unavaiClasses.add(name); throw new ClassNotFoundException("class "+name+" is invalid or doesn't exist"); } byte[] barr=baos.toByteArray(); IOUtil.closeEL(baos); return _loadClass(name, barr); } @Override public synchronized Class<?> loadClass(String name, byte[] barr) throws UnmodifiableClassException { Class<?> clazz=null; try { clazz = loadClass(name); } catch (ClassNotFoundException cnf) {} // if class already exists if(clazz!=null) { try { InstrumentationFactory.getInstrumentation(config).redefineClasses(new ClassDefinition(clazz,barr)); } catch (ClassNotFoundException e) { // the documentation clearly sais that this exception only exists for backward compatibility and never happen } return clazz; } // class not exists yet return _loadClass(name, barr); } private synchronized Class<?> _loadClass(String name, byte[] barr) { Class<?> clazz = defineClass(name,barr,0,barr.length); if (clazz != null) { loadedClasses.add(name); resolveClass(clazz); } return clazz; } @Override public URL getResource(String name) { return null; } @Override public InputStream getResourceAsStream(String name) { InputStream is = super.getResourceAsStream(name); if(is!=null) return is; Resource f = _getResource(name); if(f!=null) { try { return IOUtil.toBufferedInputStream(f.getInputStream()); } catch (IOException e) {} } return null; } /** * returns matching File Object or null if file not exust * @param name * @return matching file */ public Resource _getResource(String name) { Resource f = directory.getRealResource(name); if(f!=null && f.exists() && f.isFile()) return f; return null; } public boolean hasClass(String className) { return hasResource(className.replace('.','/').concat(".class")); } public boolean isClassLoaded(String className) { //print.o("isClassLoaded:"+className+"-"+(findLoadedClass(className)!=null)); return findLoadedClass(className)!=null; } public boolean hasResource(String name) { return _getResource(name)!=null; } /** * @return the directory */ public Resource getDirectory() { return directory; } public PhysicalClassLoader getCustomClassLoader(Resource[] resources, boolean reload) throws IOException{ if(ArrayUtil.isEmpty(resources)) return this; String key = hash(resources); if(reload && customCLs!=null) customCLs.remove(key); PhysicalClassLoader pcl=customCLs==null?null:customCLs.get(key); if(pcl!=null) return pcl; pcl=new PhysicalClassLoader(config,getDirectory(),new ClassLoader[]{new ResourceClassLoader(resources,getParent())},true); if(customCLs==null)customCLs=new ReferenceMap<String,PhysicalClassLoader>(); customCLs.put(key, pcl); return pcl; } private String hash(Resource[] resources) { Arrays.sort(resources); StringBuilder sb=new StringBuilder(); for(int i=0;i<resources.length;i++){ sb.append(ResourceUtil.getCanonicalPathEL(resources[i])); sb.append(';'); } return HashUtil.create64BitHashAsString(sb.toString(),Character.MAX_RADIX); } }