View Javadoc

1   // ========================================================================
2   // Copyright 1999-2005 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty.webapp;
16  
17  import java.io.File;
18  import java.io.FileOutputStream;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.net.URL;
22  import java.net.URLClassLoader;
23  import java.security.CodeSource;
24  import java.security.PermissionCollection;
25  import java.util.HashSet;
26  import java.util.StringTokenizer;
27  
28  import org.mortbay.jetty.handler.ContextHandler;
29  import org.mortbay.log.Log;
30  import org.mortbay.resource.Resource;
31  import org.mortbay.util.IO;
32  import org.mortbay.util.LazyList;
33  import org.mortbay.util.StringUtil;
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 behaviour 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.mortbay.jetty.webapp.WebAppContext#setParentLoaderPriority(boolean)} method.
46   *
47   * If no parent class loader is provided, then the current thread context classloader will
48   * be used.  If that is null then the classloader that loaded this class is used as the parent.
49   * 
50   * @author Greg Wilkins (gregw)
51   */
52  public class WebAppClassLoader extends URLClassLoader 
53  {
54      private String _name;
55      private WebAppContext _context;
56      private ClassLoader _parent;
57      private HashSet _extensions;
58      
59      /* ------------------------------------------------------------ */
60      /** Constructor.
61       */
62      public WebAppClassLoader(WebAppContext context)
63          throws IOException
64      {
65          this(null,context);
66      }
67      
68      /* ------------------------------------------------------------ */
69      /** Constructor.
70       */
71      public WebAppClassLoader(ClassLoader parent, WebAppContext context)
72          throws IOException
73      {
74          super(new URL[]{},parent!=null?parent
75                  :(Thread.currentThread().getContextClassLoader()!=null?Thread.currentThread().getContextClassLoader()
76                          :(WebAppClassLoader.class.getClassLoader()!=null?WebAppClassLoader.class.getClassLoader()
77                                  :ClassLoader.getSystemClassLoader())));
78          _parent=getParent();
79          _context=context;
80          if (_parent==null)
81              throw new IllegalArgumentException("no parent classloader!");
82          
83          _extensions = new HashSet();
84          _extensions.add(".jar");
85          _extensions.add(".zip");
86          
87          String extensions = System.getProperty(WebAppClassLoader.class.getName() + ".extensions");
88          if(extensions!=null)
89          {
90              StringTokenizer tokenizer = new StringTokenizer(extensions, ",;");
91              while(tokenizer.hasMoreTokens())
92                  _extensions.add(tokenizer.nextToken().trim());
93          }
94          
95          if (context.getExtraClasspath()!=null)
96              addClassPath(context.getExtraClasspath());
97      }
98      
99      /* ------------------------------------------------------------ */
100     /**
101      * @return the name of the classloader
102      */
103     public String getName()
104     {
105         return _name;
106     }
107 
108     /* ------------------------------------------------------------ */
109     /**
110      * @param name the name of the classloader
111      */
112     public void setName(String name)
113     {
114         _name=name;
115     }
116     
117 
118     /* ------------------------------------------------------------ */
119     public ContextHandler getContext()
120     {
121         return _context;
122     }
123     
124     /* ------------------------------------------------------------ */
125     /**
126      * @param classPath Comma or semicolon separated path of filenames or URLs
127      * pointing to directories or jar files. Directories should end
128      * with '/'.
129      */
130     public void addClassPath(String classPath)
131     	throws IOException
132     {
133         if (classPath == null)
134             return;
135             
136         StringTokenizer tokenizer= new StringTokenizer(classPath, ",;");
137         while (tokenizer.hasMoreTokens())
138         {
139             Resource resource= Resource.newResource(tokenizer.nextToken());
140             if (Log.isDebugEnabled())
141                 Log.debug("Path resource=" + resource);
142 
143             // Resolve file path if possible
144             File file= resource.getFile();
145             if (file != null)
146             {
147                 URL url= resource.getURL();
148                 addURL(url);
149             }
150             else
151             {
152                 // Add resource or expand jar/
153                 if (!resource.isDirectory() && file == null)
154                 {
155                     InputStream in= resource.getInputStream();
156                     File tmp_dir=_context.getTempDirectory();
157                     if (tmp_dir==null)
158                     {
159                         tmp_dir = File.createTempFile("jetty.cl.lib",null);
160                         tmp_dir.mkdir();
161                         tmp_dir.deleteOnExit();
162                     }
163                     File lib= new File(tmp_dir, "lib");
164                     if (!lib.exists())
165                     {
166                         lib.mkdir();
167                         lib.deleteOnExit();
168                     }
169                     File jar= File.createTempFile("Jetty-", ".jar", lib);
170                     
171                     jar.deleteOnExit();
172                     if (Log.isDebugEnabled())
173                         Log.debug("Extract " + resource + " to " + jar);
174                     FileOutputStream out = null;
175                     try
176                     {
177                         out= new FileOutputStream(jar);
178                         IO.copy(in, out);
179                     }
180                     finally
181                     {
182                         IO.close(out);
183                     }
184                     
185                     URL url= jar.toURL();
186                     addURL(url);
187                 }
188                 else
189                 {
190                     URL url= resource.getURL();
191                     addURL(url);
192                 }
193             }
194         }
195     }
196 
197     /* ------------------------------------------------------------ */
198     /**
199      * @param file Checks if this file type can be added to the classpath.
200      */
201     private boolean isFileSupported(String file)
202     {
203         int dot = file.lastIndexOf('.');
204         return dot!=-1 && _extensions.contains(file.substring(dot));
205     }
206     
207     /* ------------------------------------------------------------ */
208     /** Add elements to the class path for the context from the jar and zip files found
209      *  in the specified resource.
210      * @param lib the resource that contains the jar and/or zip files.
211      * @param append true if the classpath entries are to be appended to any
212      * existing classpath, or false if they replace the existing classpath.
213      * @see #setClassPath(String)
214      */
215     public void addJars(Resource lib)
216     {
217         if (lib.exists() && lib.isDirectory())
218         {
219             String[] files=lib.list();
220             for (int f=0;files!=null && f<files.length;f++)
221             {
222                 try {
223                     Resource fn=lib.addPath(files[f]);
224                     String fnlc=fn.getName().toLowerCase();
225                     if (isFileSupported(fnlc))
226                     {
227                     	String jar=fn.toString();
228                     	jar=StringUtil.replace(jar, ",", "%2C");
229                     	jar=StringUtil.replace(jar, ";", "%3B");
230                         addClassPath(jar);
231                     }
232                 }
233                 catch (Exception ex)
234                 {
235                     Log.warn(Log.EXCEPTION,ex);
236                 }
237             }
238         }
239     }
240     /* ------------------------------------------------------------ */
241     public void destroy()
242     {
243         this._parent=null;
244     }
245     
246 
247     /* ------------------------------------------------------------ */
248     public PermissionCollection getPermissions(CodeSource cs)
249     {
250         // TODO check CodeSource
251         PermissionCollection permissions=_context.getPermissions();
252         PermissionCollection pc= (permissions == null) ? super.getPermissions(cs) : permissions;
253         return pc;
254     }
255 
256     /* ------------------------------------------------------------ */
257     public URL getResource(String name)
258     {
259         URL url= null;
260         boolean tried_parent= false;
261         if (_context.isParentLoaderPriority() || isSystemPath(name))
262         {
263             tried_parent= true;
264             
265             if (_parent!=null)
266                 url= _parent.getResource(name);
267         }
268 
269         if (url == null)
270         {
271             url= this.findResource(name);
272 
273             if (url == null && name.startsWith("/"))
274             {
275                 if (Log.isDebugEnabled())
276                     Log.debug("HACK leading / off " + name);
277                 url= this.findResource(name.substring(1));
278             }
279         }
280 
281         if (url == null && !tried_parent)
282         {
283             if (_parent!=null)
284                 url= _parent.getResource(name);
285         }
286 
287         if (url != null)
288             if (Log.isDebugEnabled())
289                 Log.debug("getResource("+name+")=" + url);
290 
291         return url;
292     }
293     
294     /* ------------------------------------------------------------ */
295     public boolean isServerPath(String name)
296     {
297         name=name.replace('/','.');
298         while(name.startsWith("."))
299             name=name.substring(1);
300 
301         String[] server_classes = _context.getServerClasses();
302         if (server_classes!=null)
303         {
304             for (int i=0;i<server_classes.length;i++)
305             {
306                 boolean result=true;
307                 String c=server_classes[i];
308                 if (c.startsWith("-"))
309                 {
310                     c=c.substring(1); // TODO cache
311                     result=false;
312                 }
313                 
314                 if (c.endsWith("."))
315                 {
316                     if (name.startsWith(c))
317                         return result;
318                 }
319                 else if (name.equals(c))
320                     return result;
321             }
322         }
323         return false;
324     }
325 
326     /* ------------------------------------------------------------ */
327     public boolean isSystemPath(String name)
328     {
329         name=name.replace('/','.');
330         while(name.startsWith("."))
331             name=name.substring(1);
332         String[] system_classes = _context.getSystemClasses();
333         if (system_classes!=null)
334         {
335             for (int i=0;i<system_classes.length;i++)
336             {
337                 boolean result=true;
338                 String c=system_classes[i];
339                 
340                 if (c.startsWith("-"))
341                 {
342                     c=c.substring(1); // TODO cache
343                     result=false;
344                 }
345                 
346                 if (c.endsWith("."))
347                 {
348                     if (name.startsWith(c))
349                         return result;
350                 }
351                 else if (name.equals(c))
352                     return result;
353             }
354         }
355         
356         return false;
357         
358     }
359 
360     /* ------------------------------------------------------------ */
361     public Class loadClass(String name) throws ClassNotFoundException
362     {
363         return loadClass(name, false);
364     }
365 
366     /* ------------------------------------------------------------ */
367     protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException
368     {
369         Class c= findLoadedClass(name);
370         ClassNotFoundException ex= null;
371         boolean tried_parent= false;
372         
373         if (c == null && _parent!=null && (_context.isParentLoaderPriority() || isSystemPath(name)) )
374         {
375             tried_parent= true;
376             try
377             {
378                 c= _parent.loadClass(name);
379                 if (Log.isDebugEnabled())
380                     Log.debug("loaded " + c);
381             }
382             catch (ClassNotFoundException e)
383             {
384                 ex= e;
385             }
386         }
387 
388         if (c == null)
389         {
390             try
391             {
392                 c= this.findClass(name);
393             }
394             catch (ClassNotFoundException e)
395             {
396                 ex= e;
397             }
398         }
399 
400         if (c == null && _parent!=null && !tried_parent && !isServerPath(name) )
401             c= _parent.loadClass(name);
402 
403         if (c == null)
404             throw ex;
405 
406         if (resolve)
407             resolveClass(c);
408 
409         if (Log.isDebugEnabled())
410             Log.debug("loaded " + c+ " from "+c.getClassLoader());
411         
412         return c;
413     }
414 
415     /* ------------------------------------------------------------ */
416     public String toString()
417     {
418         if (Log.isDebugEnabled())
419             return "ContextLoader@" + _name + "(" + LazyList.array2List(getURLs()) + ") / " + _parent;
420         return "ContextLoader@" + _name;
421     }
422     
423 }