View Javadoc

1   // ========================================================================
2   // Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.webapp;
15  
16  import java.io.File;
17  import java.io.IOException;
18  import java.net.URL;
19  import java.net.URLClassLoader;
20  import java.security.CodeSource;
21  import java.security.PermissionCollection;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.Enumeration;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Set;
29  import java.util.StringTokenizer;
30  
31  import org.eclipse.jetty.server.handler.ContextHandler;
32  import org.eclipse.jetty.util.LazyList;
33  import org.eclipse.jetty.util.StringUtil;
34  import org.eclipse.jetty.util.TypeUtil;
35  import org.eclipse.jetty.util.component.AggregateLifeCycle;
36  import org.eclipse.jetty.util.component.Dumpable;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.resource.Resource;
39  import org.eclipse.jetty.util.resource.ResourceCollection;
40  
41  
42  /* ------------------------------------------------------------ */
43  /** ClassLoader for HttpContext.
44   * Specializes URLClassLoader with some utility and file mapping
45   * methods.
46   *
47   * This loader defaults to the 2.3 servlet spec behavior where non
48   * system classes are loaded from the classpath in preference to the
49   * parent loader.  Java2 compliant loading, where the parent loader
50   * always has priority, can be selected with the 
51   * {@link org.eclipse.jetty.webapp.WebAppContext#setParentLoaderPriority(boolean)} 
52   * method and influenced with {@link WebAppContext#isServerClass(String)} and 
53   * {@link WebAppContext#isSystemClass(String)}.
54   *
55   * If no parent class loader is provided, then the current thread 
56   * context classloader will be used.  If that is null then the 
57   * classloader that loaded this class is used as the parent.
58   * 
59   */
60  public class WebAppClassLoader extends URLClassLoader
61  {
62      private final Context _context;
63      private final ClassLoader _parent;
64      private final Set<String> _extensions=new HashSet<String>();
65      private String _name=String.valueOf(hashCode());
66      
67      /* ------------------------------------------------------------ */
68      /** The Context in which the classloader operates.
69       */
70      public interface Context
71      {
72          /* ------------------------------------------------------------ */
73          /** Convert a URL or path to a Resource.
74           * The default implementation
75           * is a wrapper for {@link Resource#newResource(String)}.
76           * @param urlOrPath The URL or path to convert
77           * @return The Resource for the URL/path
78           * @throws IOException The Resource could not be created.
79           */
80          Resource newResource(String urlOrPath) throws IOException;
81  
82          /* ------------------------------------------------------------ */
83          /**
84           * @return Returns the permissions.
85           */
86          PermissionCollection getPermissions();
87  
88          /* ------------------------------------------------------------ */
89          /** Is the class a System Class.
90           * A System class is a class that is visible to a webapplication,
91           * but that cannot be overridden by the contents of WEB-INF/lib or
92           * WEB-INF/classes 
93           * @param clazz The fully qualified name of the class.
94           * @return True if the class is a system class.
95           */
96          boolean isSystemClass(String clazz);
97  
98          /* ------------------------------------------------------------ */
99          /** Is the class a Server Class.
100          * A Server class is a class that is part of the implementation of 
101          * the server and is NIT visible to a webapplication. The web
102          * application may provide it's own implementation of the class,
103          * to be loaded from WEB-INF/lib or WEB-INF/classes 
104          * @param clazz The fully qualified name of the class.
105          * @return True if the class is a server class.
106          */
107         boolean isServerClass(String clazz);
108 
109         /* ------------------------------------------------------------ */
110         /**
111          * @return True if the classloader should delegate first to the parent 
112          * classloader (standard java behaviour) or false if the classloader 
113          * should first try to load from WEB-INF/lib or WEB-INF/classes (servlet 
114          * spec recommendation).
115          */
116         boolean isParentLoaderPriority();
117         
118         /* ------------------------------------------------------------ */
119         String getExtraClasspath();
120         
121     }
122     
123     /* ------------------------------------------------------------ */
124     /** Constructor.
125      */
126     public WebAppClassLoader(Context context)
127         throws IOException
128     {
129         this(null,context);
130     }
131     
132     /* ------------------------------------------------------------ */
133     /** Constructor.
134      */
135     public WebAppClassLoader(ClassLoader parent, Context context)
136         throws IOException
137     {
138         super(new URL[]{},parent!=null?parent
139                 :(Thread.currentThread().getContextClassLoader()!=null?Thread.currentThread().getContextClassLoader()
140                         :(WebAppClassLoader.class.getClassLoader()!=null?WebAppClassLoader.class.getClassLoader()
141                                 :ClassLoader.getSystemClassLoader())));
142         _parent=getParent();
143         _context=context;
144         if (_parent==null)
145             throw new IllegalArgumentException("no parent classloader!");
146         
147         _extensions.add(".jar");
148         _extensions.add(".zip");
149         
150         // TODO remove this system property
151         String extensions = System.getProperty(WebAppClassLoader.class.getName() + ".extensions");
152         if(extensions!=null)
153         {
154             StringTokenizer tokenizer = new StringTokenizer(extensions, ",;");
155             while(tokenizer.hasMoreTokens())
156                 _extensions.add(tokenizer.nextToken().trim());
157         }
158         
159         if (context.getExtraClasspath()!=null)
160             addClassPath(context.getExtraClasspath());
161     }
162     
163     /* ------------------------------------------------------------ */
164     /**
165      * @return the name of the classloader
166      */
167     public String getName()
168     {
169         return _name;
170     }
171 
172     /* ------------------------------------------------------------ */
173     /**
174      * @param name the name of the classloader
175      */
176     public void setName(String name)
177     {
178         _name=name;
179     }
180     
181 
182     /* ------------------------------------------------------------ */
183     public Context getContext()
184     {
185         return _context;
186     }
187 
188     /* ------------------------------------------------------------ */
189     /**
190      * @param resource Comma or semicolon separated path of filenames or URLs
191      * pointing to directories or jar files. Directories should end
192      * with '/'.
193      */
194     public void addClassPath(Resource resource)
195         throws IOException
196     {
197         if (resource instanceof ResourceCollection)
198         {
199             for (Resource r : ((ResourceCollection)resource).getResources())
200                 addClassPath(r);
201         }
202         else
203         {
204             addClassPath(resource.toString());
205         }
206     }
207     
208     /* ------------------------------------------------------------ */
209     /**
210      * @param classPath Comma or semicolon separated path of filenames or URLs
211      * pointing to directories or jar files. Directories should end
212      * with '/'.
213      */
214     public void addClassPath(String classPath)
215     	throws IOException
216     {
217         if (classPath == null)
218             return;
219             
220         StringTokenizer tokenizer= new StringTokenizer(classPath, ",;");
221         while (tokenizer.hasMoreTokens())
222         {
223             Resource resource= _context.newResource(tokenizer.nextToken());
224             if (Log.isDebugEnabled())
225                 Log.debug("Path resource=" + resource);
226 
227             // Add the resource
228             if (resource.isDirectory() && resource instanceof ResourceCollection)
229                 addClassPath(resource);
230             else
231             {
232                 // Resolve file path if possible
233                 File file= resource.getFile();
234                 if (file != null)
235                 {
236                     URL url= resource.getURL();
237                     addURL(url);
238                 }
239                 else if (resource.isDirectory())
240                     addURL(resource.getURL());
241                 else
242                     throw new IllegalArgumentException("!file: "+resource);
243             }
244         }
245     }
246 
247     /* ------------------------------------------------------------ */
248     /**
249      * @param file Checks if this file type can be added to the classpath.
250      */
251     private boolean isFileSupported(String file)
252     {
253         int dot = file.lastIndexOf('.');
254         return dot!=-1 && _extensions.contains(file.substring(dot));
255     }
256     
257     /* ------------------------------------------------------------ */
258     /** Add elements to the class path for the context from the jar and zip files found
259      *  in the specified resource.
260      * @param lib the resource that contains the jar and/or zip files.
261      */
262     public void addJars(Resource lib)
263     {
264         if (lib.exists() && lib.isDirectory())
265         {
266             String[] files=lib.list();
267             for (int f=0;files!=null && f<files.length;f++)
268             {
269                 try 
270                 {
271                     Resource fn=lib.addPath(files[f]);
272                     String fnlc=fn.getName().toLowerCase();
273                     if (!fn.isDirectory() && isFileSupported(fnlc))
274                     {
275                         String jar=fn.toString();
276                         jar=StringUtil.replace(jar, ",", "%2C");
277                         jar=StringUtil.replace(jar, ";", "%3B");
278                         addClassPath(jar);
279                     }
280                 }
281                 catch (Exception ex)
282                 {
283                     Log.warn(Log.EXCEPTION,ex);
284                 }
285             }
286         }
287     }
288 
289     /* ------------------------------------------------------------ */
290     public PermissionCollection getPermissions(CodeSource cs)
291     {
292         // TODO check CodeSource
293         PermissionCollection permissions=_context.getPermissions();
294         PermissionCollection pc= (permissions == null) ? super.getPermissions(cs) : permissions;
295         return pc;
296     }
297 
298     /* ------------------------------------------------------------ */
299     public Enumeration<URL> getResources(String name) throws IOException
300     {
301         boolean system_class=_context.isSystemClass(name);
302         boolean server_class=_context.isServerClass(name);
303         
304         List<URL> from_parent = toList(server_class?null:_parent.getResources(name));
305         List<URL> from_webapp = toList((system_class&&!from_parent.isEmpty())?null:this.findResources(name));
306             
307         if (_context.isParentLoaderPriority())
308         {
309             from_parent.addAll(from_webapp);
310             return Collections.enumeration(from_parent);
311         }
312         from_webapp.addAll(from_parent);
313         return Collections.enumeration(from_webapp);
314     }
315 
316     /* ------------------------------------------------------------ */
317     private List<URL> toList(Enumeration<URL> e)
318     {
319         List<URL> list = new ArrayList<URL>();
320         while (e!=null && e.hasMoreElements())
321             list.add(e.nextElement());
322         return list;
323     }
324     
325     /* ------------------------------------------------------------ */
326     public URL getResource(String name)
327     {
328         URL url= null;
329         boolean tried_parent= false;
330         boolean system_class=_context.isSystemClass(name);
331         boolean server_class=_context.isServerClass(name);
332         
333         if (system_class && server_class)
334             return null;
335         
336         if (_parent!=null &&(_context.isParentLoaderPriority() || system_class ) && !server_class)
337         {
338             tried_parent= true;
339             
340             if (_parent!=null)
341                 url= _parent.getResource(name);
342         }
343 
344         if (url == null)
345         {
346             url= this.findResource(name);
347 
348             if (url == null && name.startsWith("/"))
349             {
350                 if (Log.isDebugEnabled())
351                     Log.debug("HACK leading / off " + name);
352                 url= this.findResource(name.substring(1));
353             }
354         }
355 
356         if (url == null && !tried_parent && !server_class )
357         {
358             if (_parent!=null)
359                 url= _parent.getResource(name);
360         }
361 
362         if (url != null)
363             if (Log.isDebugEnabled())
364                 Log.debug("getResource("+name+")=" + url);
365 
366         return url;
367     }
368 
369     /* ------------------------------------------------------------ */
370     @Override
371     public Class<?> loadClass(String name) throws ClassNotFoundException
372     {
373         return loadClass(name, false);
374     }
375 
376     /* ------------------------------------------------------------ */
377     @Override
378     protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
379     {
380         Class<?> c= findLoadedClass(name);
381         ClassNotFoundException ex= null;
382         boolean tried_parent= false;
383         
384         boolean system_class=_context.isSystemClass(name);
385         boolean server_class=_context.isServerClass(name);
386         
387         if (system_class && server_class)
388         {
389             return null;
390         }
391         
392         if (c == null && _parent!=null && (_context.isParentLoaderPriority() || system_class) && !server_class)
393         {
394             tried_parent= true;
395             try
396             {
397                 c= _parent.loadClass(name);
398                 if (Log.isDebugEnabled())
399                     Log.debug("loaded " + c);
400             }
401             catch (ClassNotFoundException e)
402             {
403                 ex= e;
404             }
405         }
406 
407         if (c == null)
408         {
409             try
410             {
411                 c= this.findClass(name);
412             }
413             catch (ClassNotFoundException e)
414             {
415                 ex= e;
416             }
417         }
418 
419         if (c == null && _parent!=null && !tried_parent && !server_class )
420             c= _parent.loadClass(name);
421 
422         if (c == null)
423             throw ex;
424 
425         if (resolve)
426             resolveClass(c);
427 
428         if (Log.isDebugEnabled())
429             Log.debug("loaded " + c+ " from "+c.getClassLoader());
430         
431         return c;
432     }
433 
434     /* ------------------------------------------------------------ */
435     public String toString()
436     {
437         return "WebAppClassLoader=" + _name+"@"+Long.toHexString(hashCode());
438     }
439 
440 }