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}