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.FileOutputStream;
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.net.URL;
21  import java.net.URLClassLoader;
22  import java.security.CodeSource;
23  import java.security.PermissionCollection;
24  import java.util.HashSet;
25  import java.util.StringTokenizer;
26  
27  import org.eclipse.jetty.server.handler.ContextHandler;
28  import org.eclipse.jetty.util.IO;
29  import org.eclipse.jetty.util.LazyList;
30  import org.eclipse.jetty.util.StringUtil;
31  import org.eclipse.jetty.util.log.Log;
32  import org.eclipse.jetty.util.resource.Resource;
33  
34  
35  /* ------------------------------------------------------------ */
36  /** ClassLoader for HttpContext.
37   * Specializes URLClassLoader with some utility and file mapping
38   * methods.
39   *
40   * This loader defaults to the 2.3 servlet spec behaviour where non
41   * system classes are loaded from the classpath in preference to the
42   * parent loader.  Java2 compliant loading, where the parent loader
43   * always has priority, can be selected with the 
44   * {@link org.eclipse.jetty.server.server.webapp.WebAppContext#setParentLoaderPriority(boolean)} 
45   * method and influenced with {@link WebAppContext#isServerClass(String)} and 
46   * {@link WebAppContext#isSystemClass(String)}.
47   *
48   * If no parent class loader is provided, then the current thread 
49   * context classloader will be used.  If that is null then the 
50   * classloader that loaded this class is used as the parent.
51   * 
52   * 
53   */
54  public class WebAppClassLoader extends URLClassLoader 
55  {
56      private String _name;
57      private WebAppContext _context;
58      private ClassLoader _parent;
59      private HashSet<String> _extensions;
60      
61      /* ------------------------------------------------------------ */
62      /** Constructor.
63       */
64      public WebAppClassLoader(WebAppContext context)
65          throws IOException
66      {
67          this(null,context);
68      }
69      
70      /* ------------------------------------------------------------ */
71      /** Constructor.
72       */
73      public WebAppClassLoader(ClassLoader parent, WebAppContext context)
74          throws IOException
75      {
76          super(new URL[]{},parent!=null?parent
77                  :(Thread.currentThread().getContextClassLoader()!=null?Thread.currentThread().getContextClassLoader()
78                          :(WebAppClassLoader.class.getClassLoader()!=null?WebAppClassLoader.class.getClassLoader()
79                                  :ClassLoader.getSystemClassLoader())));
80          _parent=getParent();
81          _context=context;
82          if (_parent==null)
83              throw new IllegalArgumentException("no parent classloader!");
84          
85          _extensions = new HashSet<String>();
86          _extensions.add(".jar");
87          _extensions.add(".zip");
88          
89          String extensions = System.getProperty(WebAppClassLoader.class.getName() + ".extensions");
90          if(extensions!=null)
91          {
92              StringTokenizer tokenizer = new StringTokenizer(extensions, ",;");
93              while(tokenizer.hasMoreTokens())
94                  _extensions.add(tokenizer.nextToken().trim());
95          }
96          
97          if (context.getExtraClasspath()!=null)
98              addClassPath(context.getExtraClasspath());
99      }
100     
101     /* ------------------------------------------------------------ */
102     /**
103      * @return the name of the classloader
104      */
105     public String getName()
106     {
107         return _name;
108     }
109 
110     /* ------------------------------------------------------------ */
111     /**
112      * @param name the name of the classloader
113      */
114     public void setName(String name)
115     {
116         _name=name;
117     }
118     
119 
120     /* ------------------------------------------------------------ */
121     public ContextHandler getContext()
122     {
123         return _context;
124     }
125     
126     /* ------------------------------------------------------------ */
127     /**
128      * @param classPath Comma or semicolon separated path of filenames or URLs
129      * pointing to directories or jar files. Directories should end
130      * with '/'.
131      */
132     public void addClassPath(String classPath)
133     	throws IOException
134     {
135         if (classPath == null)
136             return;
137             
138         StringTokenizer tokenizer= new StringTokenizer(classPath, ",;");
139         while (tokenizer.hasMoreTokens())
140         {
141             Resource resource= _context.newResource(tokenizer.nextToken());
142             if (Log.isDebugEnabled())
143                 Log.debug("Path resource=" + resource);
144 
145             // Resolve file path if possible
146             File file= resource.getFile();
147             if (file != null)
148             {
149                 URL url= resource.getURL();
150                 addURL(url);
151             }
152             else
153             {
154                 // Add resource or expand jar/
155                 if (!resource.isDirectory() && file == null)
156                 {
157                     throw new IllegalArgumentException("!file: "+resource);
158                 }
159                 else
160                 {
161                     URL url= resource.getURL();
162                     addURL(url);
163                 }
164             }
165         }
166     }
167 
168     /* ------------------------------------------------------------ */
169     /**
170      * @param file Checks if this file type can be added to the classpath.
171      */
172     private boolean isFileSupported(String file)
173     {
174         int dot = file.lastIndexOf('.');
175         return dot!=-1 && _extensions.contains(file.substring(dot));
176     }
177     
178     /* ------------------------------------------------------------ */
179     /** Add elements to the class path for the context from the jar and zip files found
180      *  in the specified resource.
181      * @param lib the resource that contains the jar and/or zip files.
182      * @param append true if the classpath entries are to be appended to any
183      * existing classpath, or false if they replace the existing classpath.
184      * @see #setClassPath(String)
185      */
186     public void addJars(Resource lib)
187     {
188         if (lib.exists() && lib.isDirectory())
189         {
190             String[] files=lib.list();
191             for (int f=0;files!=null && f<files.length;f++)
192             {
193                 try {
194                     Resource fn=lib.addPath(files[f]);
195                     String fnlc=fn.getName().toLowerCase();
196                     if (isFileSupported(fnlc))
197                     {
198                         String jar=fn.toString();
199                         jar=StringUtil.replace(jar, ",", "%2C");
200                         jar=StringUtil.replace(jar, ";", "%3B");
201                         addClassPath(jar);
202                     }
203                 }
204                 catch (Exception ex)
205                 {
206                     Log.warn(Log.EXCEPTION,ex);
207                 }
208             }
209         }
210     }
211     /* ------------------------------------------------------------ */
212     public void destroy()
213     {
214         this._parent=null;
215     }
216     
217 
218     /* ------------------------------------------------------------ */
219     public PermissionCollection getPermissions(CodeSource cs)
220     {
221         // TODO check CodeSource
222         PermissionCollection permissions=_context.getPermissions();
223         PermissionCollection pc= (permissions == null) ? super.getPermissions(cs) : permissions;
224         return pc;
225     }
226 
227     /* ------------------------------------------------------------ */
228     public synchronized URL getResource(String name)
229     {
230         URL url= null;
231         boolean tried_parent= false;
232         if (_context.isParentLoaderPriority() || _context.isSystemClass(name))
233         {
234             tried_parent= true;
235             
236             if (_parent!=null)
237                 url= _parent.getResource(name);
238         }
239 
240         if (url == null)
241         {
242             url= this.findResource(name);
243 
244             if (url == null && name.startsWith("/"))
245             {
246                 if (Log.isDebugEnabled())
247                     Log.debug("HACK leading / off " + name);
248                 url= this.findResource(name.substring(1));
249             }
250         }
251 
252         if (url == null && !tried_parent && !_context.isServerClass(name) )
253         {
254             if (_parent!=null)
255                 url= _parent.getResource(name);
256         }
257 
258         if (url != null)
259             if (Log.isDebugEnabled())
260                 Log.debug("getResource("+name+")=" + url);
261 
262         return url;
263     }
264 
265     /* ------------------------------------------------------------ */
266     @Override
267     public synchronized Class<?> loadClass(String name) throws ClassNotFoundException
268     {
269         return loadClass(name, false);
270     }
271 
272     /* ------------------------------------------------------------ */
273     @Override
274     protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
275     {
276         Class<?> c= findLoadedClass(name);
277         ClassNotFoundException ex= null;
278         boolean tried_parent= false;
279         
280         boolean system_class=_context.isSystemClass(name);
281         boolean server_class=_context.isServerClass(name);
282         
283         if (system_class && server_class)
284         {
285             return null;
286         }
287         
288         if (c == null && _parent!=null && (_context.isParentLoaderPriority() || system_class) )
289         {
290             tried_parent= true;
291             try
292             {
293                 c= _parent.loadClass(name);
294                 if (Log.isDebugEnabled())
295                     Log.debug("loaded " + c);
296             }
297             catch (ClassNotFoundException e)
298             {
299                 ex= e;
300             }
301         }
302 
303         if (c == null)
304         {
305             try
306             {
307                 c= this.findClass(name);
308             }
309             catch (ClassNotFoundException e)
310             {
311                 ex= e;
312             }
313         }
314 
315         if (c == null && _parent!=null && !tried_parent && !server_class )
316             c= _parent.loadClass(name);
317 
318         if (c == null)
319             throw ex;
320 
321         if (resolve)
322             resolveClass(c);
323 
324         if (Log.isDebugEnabled())
325             Log.debug("loaded " + c+ " from "+c.getClassLoader());
326         
327         return c;
328     }
329 
330     /* ------------------------------------------------------------ */
331     public String toString()
332     {
333         if (Log.isDebugEnabled())
334             return "ContextLoader@" + _name + "(" + LazyList.array2List(getURLs()) + ") / " + _parent;
335         return "ContextLoader@" + _name;
336     }
337     
338 }