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