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