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.StringTokenizer;
28  
29  import org.eclipse.jetty.server.handler.ContextHandler;
30  import org.eclipse.jetty.util.LazyList;
31  import org.eclipse.jetty.util.StringUtil;
32  import org.eclipse.jetty.util.log.Log;
33  import org.eclipse.jetty.util.resource.Resource;
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   */
55  public class WebAppClassLoader extends URLClassLoader 
56  {
57      private String _name;
58      private WebAppContext _context;
59      private ClassLoader _parent;
60      private HashSet<String> _extensions;
61      
62      /* ------------------------------------------------------------ */
63      /** Constructor.
64       */
65      public WebAppClassLoader(WebAppContext context)
66          throws IOException
67      {
68          this(null,context);
69      }
70      
71      /* ------------------------------------------------------------ */
72      /** Constructor.
73       */
74      public WebAppClassLoader(ClassLoader parent, WebAppContext context)
75          throws IOException
76      {
77          super(new URL[]{},parent!=null?parent
78                  :(Thread.currentThread().getContextClassLoader()!=null?Thread.currentThread().getContextClassLoader()
79                          :(WebAppClassLoader.class.getClassLoader()!=null?WebAppClassLoader.class.getClassLoader()
80                                  :ClassLoader.getSystemClassLoader())));
81          _parent=getParent();
82          _context=context;
83          if (_parent==null)
84              throw new IllegalArgumentException("no parent classloader!");
85          
86          _extensions = new HashSet<String>();
87          _extensions.add(".jar");
88          _extensions.add(".zip");
89          
90          String extensions = System.getProperty(WebAppClassLoader.class.getName() + ".extensions");
91          if(extensions!=null)
92          {
93              StringTokenizer tokenizer = new StringTokenizer(extensions, ",;");
94              while(tokenizer.hasMoreTokens())
95                  _extensions.add(tokenizer.nextToken().trim());
96          }
97          
98          if (context.getExtraClasspath()!=null)
99              addClassPath(context.getExtraClasspath());
100     }
101     
102     /* ------------------------------------------------------------ */
103     /**
104      * @return the name of the classloader
105      */
106     public String getName()
107     {
108         return _name;
109     }
110 
111     /* ------------------------------------------------------------ */
112     /**
113      * @param name the name of the classloader
114      */
115     public void setName(String name)
116     {
117         _name=name;
118     }
119     
120 
121     /* ------------------------------------------------------------ */
122     public ContextHandler getContext()
123     {
124         return _context;
125     }
126     
127     /* ------------------------------------------------------------ */
128     /**
129      * @param classPath Comma or semicolon separated path of filenames or URLs
130      * pointing to directories or jar files. Directories should end
131      * with '/'.
132      */
133     public void addClassPath(String classPath)
134     	throws IOException
135     {
136         if (classPath == null)
137             return;
138             
139         StringTokenizer tokenizer= new StringTokenizer(classPath, ",;");
140         while (tokenizer.hasMoreTokens())
141         {
142             Resource resource= _context.newResource(tokenizer.nextToken());
143             if (Log.isDebugEnabled())
144                 Log.debug("Path resource=" + resource);
145 
146             // Resolve file path if possible
147             File file= resource.getFile();
148             if (file != null)
149             {
150                 URL url= resource.getURL();
151                 addURL(url);
152             }
153             else
154             {
155                 // Add resource or expand jar/
156                 if (!resource.isDirectory() && file == null)
157                 {
158                     throw new IllegalArgumentException("!file: "+resource);
159                 }
160                 else
161                 {
162                     URL url= resource.getURL();
163                     addURL(url);
164                 }
165             }
166         }
167     }
168 
169     /* ------------------------------------------------------------ */
170     /**
171      * @param file Checks if this file type can be added to the classpath.
172      */
173     private boolean isFileSupported(String file)
174     {
175         int dot = file.lastIndexOf('.');
176         return dot!=-1 && _extensions.contains(file.substring(dot));
177     }
178     
179     /* ------------------------------------------------------------ */
180     /** Add elements to the class path for the context from the jar and zip files found
181      *  in the specified resource.
182      * @param lib the resource that contains the jar and/or zip files.
183      */
184     public void addJars(Resource lib)
185     {
186         if (lib.exists() && lib.isDirectory())
187         {
188             String[] files=lib.list();
189             for (int f=0;files!=null && f<files.length;f++)
190             {
191                 try {
192                     Resource fn=lib.addPath(files[f]);
193                     String fnlc=fn.getName().toLowerCase();
194                     if (isFileSupported(fnlc))
195                     {
196                         String jar=fn.toString();
197                         jar=StringUtil.replace(jar, ",", "%2C");
198                         jar=StringUtil.replace(jar, ";", "%3B");
199                         addClassPath(jar);
200                     }
201                 }
202                 catch (Exception ex)
203                 {
204                     Log.warn(Log.EXCEPTION,ex);
205                 }
206             }
207         }
208     }
209     /* ------------------------------------------------------------ */
210     public void destroy()
211     {
212         this._parent=null;
213     }
214     
215 
216     /* ------------------------------------------------------------ */
217     public PermissionCollection getPermissions(CodeSource cs)
218     {
219         // TODO check CodeSource
220         PermissionCollection permissions=_context.getPermissions();
221         PermissionCollection pc= (permissions == null) ? super.getPermissions(cs) : permissions;
222         return pc;
223     }
224 
225     /* ------------------------------------------------------------ */
226     public Enumeration<URL> getResources(String name) throws IOException
227     {
228         boolean system_class=_context.isSystemClass(name);
229         boolean server_class=_context.isServerClass(name);
230         
231         
232         List<URL> from_parent = toList(server_class?null:_parent.getResources(name));
233         List<URL> from_webapp = toList((system_class&&!from_parent.isEmpty())?null:this.findResources(name));
234             
235         if (_context.isParentLoaderPriority())
236         {
237             from_parent.addAll(from_webapp);
238             return Collections.enumeration(from_parent);
239         }
240         from_webapp.addAll(from_parent);
241         return Collections.enumeration(from_webapp);
242     }
243     
244     private List<URL> toList(Enumeration<URL> e)
245     {
246         List<URL> list = new ArrayList<URL>();
247         while (e!=null && e.hasMoreElements())
248             list.add(e.nextElement());
249         return list;
250     }
251     
252     /* ------------------------------------------------------------ */
253     public URL getResource(String name)
254     {
255         URL url= null;
256         boolean tried_parent= false;
257         boolean system_class=_context.isSystemClass(name);
258         boolean server_class=_context.isServerClass(name);
259         
260         if (system_class && server_class)
261             return null;
262         
263         if (_parent!=null &&(_context.isParentLoaderPriority() || system_class ) && !server_class)
264         {
265             tried_parent= true;
266             
267             if (_parent!=null)
268                 url= _parent.getResource(name);
269         }
270 
271         if (url == null)
272         {
273             url= this.findResource(name);
274 
275             if (url == null && name.startsWith("/"))
276             {
277                 if (Log.isDebugEnabled())
278                     Log.debug("HACK leading / off " + name);
279                 url= this.findResource(name.substring(1));
280             }
281         }
282 
283         if (url == null && !tried_parent && !server_class )
284         {
285             if (_parent!=null)
286                 url= _parent.getResource(name);
287         }
288 
289         if (url != null)
290             if (Log.isDebugEnabled())
291                 Log.debug("getResource("+name+")=" + url);
292 
293         return url;
294     }
295 
296     /* ------------------------------------------------------------ */
297     @Override
298     public Class<?> loadClass(String name) throws ClassNotFoundException
299     {
300         return loadClass(name, false);
301     }
302 
303     /* ------------------------------------------------------------ */
304     @Override
305     protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
306     {
307         Class<?> c= findLoadedClass(name);
308         ClassNotFoundException ex= null;
309         boolean tried_parent= false;
310         
311         boolean system_class=_context.isSystemClass(name);
312         boolean server_class=_context.isServerClass(name);
313         
314         if (system_class && server_class)
315         {
316             return null;
317         }
318         
319         if (c == null && _parent!=null && (_context.isParentLoaderPriority() || system_class) && !server_class)
320         {
321             tried_parent= true;
322             try
323             {
324                 c= _parent.loadClass(name);
325                 if (Log.isDebugEnabled())
326                     Log.debug("loaded " + c);
327             }
328             catch (ClassNotFoundException e)
329             {
330                 ex= e;
331             }
332         }
333 
334         if (c == null)
335         {
336             try
337             {
338                 c= this.findClass(name);
339             }
340             catch (ClassNotFoundException e)
341             {
342                 ex= e;
343             }
344         }
345 
346         if (c == null && _parent!=null && !tried_parent && !server_class )
347             c= _parent.loadClass(name);
348 
349         if (c == null)
350             throw ex;
351 
352         if (resolve)
353             resolveClass(c);
354 
355         if (Log.isDebugEnabled())
356             Log.debug("loaded " + c+ " from "+c.getClassLoader());
357         
358         return c;
359     }
360 
361     /* ------------------------------------------------------------ */
362     public String toString()
363     {
364         if (Log.isDebugEnabled())
365             return "ContextLoader@" + _name + "(" + LazyList.array2List(getURLs()) + ") / " + _parent;
366         return "ContextLoader@" + _name;
367     }
368     
369 }