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