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