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