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.loader.classloader;
020
021
022import java.io.BufferedInputStream;
023import java.io.ByteArrayInputStream;
024import java.io.ByteArrayOutputStream;
025import java.io.File;
026import java.io.FileInputStream;
027import java.io.IOException;
028import java.io.InputStream;
029import java.util.Hashtable;
030import java.util.zip.ZipEntry;
031import java.util.zip.ZipInputStream;
032
033import lucee.loader.util.Util;
034
035
036/**
037 *
038 * This class implements a simple class loader 
039 * that can be used to load at runtime 
040 * classes contained in a JAR file.
041 */
042public final class LuceeClassLoader extends ClassLoader {
043    private Hashtable classes = new Hashtable();
044    private Hashtable resources = new Hashtable();
045    private ClassLoader pcl;
046 
047  /**
048   * Creates a new JarClassLoader that will allow the loading
049   * of classes stored in a jar file.
050   *
051   * @param jarFile   the name of the jar file
052   * @param parent parent class loader
053 * @throws IOException 
054   * @exception IOException   an error happened while reading
055   * the contents of the jar file
056   */
057    public LuceeClassLoader(File jarFile, ClassLoader parent) throws IOException {  
058        this(new FileInputStream(jarFile),parent,isSecure(jarFile));
059    }
060    private static boolean isSecure(File jarFile) {
061                if(jarFile.getName().toLowerCase().endsWith(".lco")) return false;
062        return true;
063        }
064        public LuceeClassLoader(InputStream jar, ClassLoader parent, boolean secured) throws IOException {  
065        super(parent);
066        
067        if(secured)
068                throw new IOException("secured core files are not supported");
069        
070        
071        this.pcl=parent;
072        ZipInputStream zis = new ZipInputStream(new BufferedInputStream(jar));
073  
074        try {
075            byte[] buffer = new byte[0xffff];
076            int bytes_read;
077          
078            ZipEntry ze;
079            byte[] barr;
080            while((ze = zis.getNextEntry()) != null) {
081                if (!ze.isDirectory()) {
082                    ByteArrayOutputStream baos=new ByteArrayOutputStream();
083                    while((bytes_read = zis.read(buffer)) != -1)
084                        baos.write(buffer, 0, bytes_read);
085                    String name = ze.getName().replace('\\', '/');
086                    barr=baos.toByteArray();
087                    if(name.endsWith(".class")) {
088                        String className=name.substring(0,name.length()-6);
089                        className=className.replace('/','.');
090                        classes.put(className,barr);
091                    }
092                    resources.put(name, barr);
093                    zis.closeEntry();
094                    baos.close();
095                }
096            }
097        }
098        finally {
099            Util.closeEL(zis);
100        }   
101    }
102
103        /**
104     * Looks among the contents of the jar file (cached in memory)
105     * and tries to find and define a class, given its name.
106     *
107     * @param className   the name of the class
108     * @return   a Class object representing our class
109     * @exception ClassNotFoundException   the jar file did not contain
110     * a class named <code>className</code>
111     */
112    public Class findClass(String className) throws ClassNotFoundException {
113        byte[] classBytes = (byte[])classes.get(className);
114        if (classBytes == null) throw new ClassNotFoundException("class ["+className+"] not found");
115        return defineClass(className, classBytes, 0, classBytes.length);
116    }
117    
118    private Class findClassEL(String className) {
119        byte[] classBytes = (byte[])classes.get(className);
120        if (classBytes == null) return null;
121        return defineClass(className, classBytes, 0, classBytes.length);
122    }
123     
124     
125     
126     /**
127      * Loads the class with the specified name. This method searches for 
128      * classes in the same manner as the "loadClass(String, boolean)" 
129      * method. It is called by the Java virtual machine to resolve class 
130      * references. Calling this method is equivalent to calling 
131      * <code>loadClass(name, false)</code>.
132      *
133      * @param     name the name of the class
134      * @return    the resulting <code>Class</code> object
135      * @exception ClassNotFoundException if the class was not found
136      */
137    public Class loadClass(String name) throws ClassNotFoundException   {
138        return loadClass(name, false);
139    }
140
141     /**
142      * Loads the class with the specified name.  The default implementation of
143      * this method searches for classes in the following order:<p>
144      *
145      * <ol>
146      * <li> Call {@link #findLoadedClass(String)} to check if the class has
147      *      already been loaded. <p>
148      * <li> Call the <code>loadClass</code> method on the parent class
149      *      loader.  If the parent is <code>null</code> the class loader
150      *      built-in to the virtual machine is used, instead. <p>
151      * <li> Call the {@link #findClass(String)} method to find the class. <p>
152      * </ol>
153      *
154      * If the class was found using the above steps, and the
155      * <code>resolve</code> flag is true, this method will then call the
156      * {@link #resolveClass(Class)} method on the resulting class object.
157      * <p>
158      * From the Java 2 SDK, v1.2, subclasses of ClassLoader are 
159      * encouraged to override
160      * {@link #findClass(String)}, rather than this method.<p>
161      *
162      * @param     name the name of the class
163      * @param     resolve if <code>true</code> then resolve the class
164      * @return   the resulting <code>Class</code> object
165      * @exception ClassNotFoundException if the class could not be found
166      */
167     protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
168         // First, check if the class has already been loaded
169         Class c = findLoadedClass(name);
170         if (c == null) {
171             c=findClassEL(name);
172             if(c==null) {
173                 c = pcl.loadClass(name);
174             }
175         }
176         if (resolve) {
177             resolveClass(c);
178         }
179         return c;
180    }
181     
182    /**
183     * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String)
184     */
185   public InputStream getResourceAsStream(String name) {
186       name = name.replace('\\', '/');
187       
188       byte[] bytes = (byte[])resources.get(name);
189       if (bytes == null) return super.getResourceAsStream(name);
190       return new ByteArrayInputStream(bytes);
191   }
192}
193