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                     InputStream in= resource.getInputStream();
158                     File tmp_dir=_context.getTempDirectory();
159                     if (tmp_dir==null)
160                     {
161                         tmp_dir = File.createTempFile("jetty.cl.lib",null);
162                         tmp_dir.mkdir();
163                         tmp_dir.deleteOnExit();
164                     }
165                     File lib= new File(tmp_dir, "lib");
166                     if (!lib.exists())
167                     {
168                         lib.mkdir();
169                         lib.deleteOnExit();
170                     }
171                     File jar= File.createTempFile("Jetty-", ".jar", lib);
172                     
173                     jar.deleteOnExit();
174                     if (Log.isDebugEnabled())
175                         Log.debug("Extract " + resource + " to " + jar);
176                     FileOutputStream out = null;
177                     try
178                     {
179                         out= new FileOutputStream(jar);
180                         IO.copy(in, out);
181                     }
182                     finally
183                     {
184                         IO.close(out);
185                     }
186                     
187                     URL url= jar.toURL();
188                     addURL(url);
189                 }
190                 else
191                 {
192                     URL url= resource.getURL();
193                     addURL(url);
194                 }
195             }
196         }
197     }
198 
199     /* ------------------------------------------------------------ */
200     /**
201      * @param file Checks if this file type can be added to the classpath.
202      */
203     private boolean isFileSupported(String file)
204     {
205         int dot = file.lastIndexOf('.');
206         return dot!=-1 && _extensions.contains(file.substring(dot));
207     }
208     
209     /* ------------------------------------------------------------ */
210     /** Add elements to the class path for the context from the jar and zip files found
211      *  in the specified resource.
212      * @param lib the resource that contains the jar and/or zip files.
213      * @param append true if the classpath entries are to be appended to any
214      * existing classpath, or false if they replace the existing classpath.
215      * @see #setClassPath(String)
216      */
217     public void addJars(Resource lib)
218     {
219         if (lib.exists() && lib.isDirectory())
220         {
221             String[] files=lib.list();
222             for (int f=0;files!=null && f<files.length;f++)
223             {
224                 try {
225                     Resource fn=lib.addPath(files[f]);
226                     String fnlc=fn.getName().toLowerCase();
227                     if (isFileSupported(fnlc))
228                     {
229                         String jar=fn.toString();
230                         jar=StringUtil.replace(jar, ",", "%2C");
231                         jar=StringUtil.replace(jar, ";", "%3B");
232                         addClassPath(jar);
233                     }
234                 }
235                 catch (Exception ex)
236                 {
237                     Log.warn(Log.EXCEPTION,ex);
238                 }
239             }
240         }
241     }
242     /* ------------------------------------------------------------ */
243     public void destroy()
244     {
245         this._parent=null;
246     }
247     
248 
249     /* ------------------------------------------------------------ */
250     public PermissionCollection getPermissions(CodeSource cs)
251     {
252         // TODO check CodeSource
253         PermissionCollection permissions=_context.getPermissions();
254         PermissionCollection pc= (permissions == null) ? super.getPermissions(cs) : permissions;
255         return pc;
256     }
257 
258     /* ------------------------------------------------------------ */
259     public synchronized URL getResource(String name)
260     {
261         URL url= null;
262         boolean tried_parent= false;
263         if (_context.isParentLoaderPriority() || _context.isSystemClass(name))
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 && !_context.isServerClass(name) )
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 synchronized 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) )
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 }