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.IOException;
18 import java.net.URL;
19 import java.net.URLClassLoader;
20 import java.security.CodeSource;
21 import java.security.PermissionCollection;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.Enumeration;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Set;
29 import java.util.StringTokenizer;
30
31 import org.eclipse.jetty.server.handler.ContextHandler;
32 import org.eclipse.jetty.util.LazyList;
33 import org.eclipse.jetty.util.StringUtil;
34 import org.eclipse.jetty.util.log.Log;
35 import org.eclipse.jetty.util.resource.Resource;
36 import org.eclipse.jetty.util.resource.ResourceCollection;
37
38
39 /* ------------------------------------------------------------ */
40 /** ClassLoader for HttpContext.
41 * Specializes URLClassLoader with some utility and file mapping
42 * methods.
43 *
44 * This loader defaults to the 2.3 servlet spec behavior where non
45 * system classes are loaded from the classpath in preference to the
46 * parent loader. Java2 compliant loading, where the parent loader
47 * always has priority, can be selected with the
48 * {@link org.eclipse.jetty.webapp.WebAppContext#setParentLoaderPriority(boolean)}
49 * method and influenced with {@link WebAppContext#isServerClass(String)} and
50 * {@link WebAppContext#isSystemClass(String)}.
51 *
52 * If no parent class loader is provided, then the current thread
53 * context classloader will be used. If that is null then the
54 * classloader that loaded this class is used as the parent.
55 *
56 */
57 public class WebAppClassLoader extends URLClassLoader
58 {
59 private final Context _context;
60 private final ClassLoader _parent;
61 private final Set<String> _extensions=new HashSet<String>();
62 private String _name=String.valueOf(hashCode());
63
64 /* ------------------------------------------------------------ */
65 /** The Context in which the classloader operates.
66 */
67 public interface Context
68 {
69 /* ------------------------------------------------------------ */
70 /** Convert a URL or path to a Resource.
71 * The default implementation
72 * is a wrapper for {@link Resource#newResource(String)}.
73 * @param urlOrPath The URL or path to convert
74 * @return The Resource for the URL/path
75 * @throws IOException The Resource could not be created.
76 */
77 Resource newResource(String urlOrPath) throws IOException;
78
79 /* ------------------------------------------------------------ */
80 /**
81 * @return Returns the permissions.
82 */
83 PermissionCollection getPermissions();
84
85 /* ------------------------------------------------------------ */
86 /** Is the class a System Class.
87 * A System class is a class that is visible to a webapplication,
88 * but that cannot be overridden by the contents of WEB-INF/lib or
89 * WEB-INF/classes
90 * @param clazz The fully qualified name of the class.
91 * @return True if the class is a system class.
92 */
93 boolean isSystemClass(String clazz);
94
95 /* ------------------------------------------------------------ */
96 /** Is the class a Server Class.
97 * A Server class is a class that is part of the implementation of
98 * the server and is NIT visible to a webapplication. The web
99 * application may provide it's own implementation of the class,
100 * to be loaded from WEB-INF/lib or WEB-INF/classes
101 * @param clazz The fully qualified name of the class.
102 * @return True if the class is a server class.
103 */
104 boolean isServerClass(String clazz);
105
106 /* ------------------------------------------------------------ */
107 /**
108 * @return True if the classloader should delegate first to the parent
109 * classloader (standard java behaviour) or false if the classloader
110 * should first try to load from WEB-INF/lib or WEB-INF/classes (servlet
111 * spec recommendation).
112 */
113 boolean isParentLoaderPriority();
114
115 /* ------------------------------------------------------------ */
116 String getExtraClasspath();
117
118 }
119
120 /* ------------------------------------------------------------ */
121 /** Constructor.
122 */
123 public WebAppClassLoader(Context context)
124 throws IOException
125 {
126 this(null,context);
127 }
128
129 /* ------------------------------------------------------------ */
130 /** Constructor.
131 */
132 public WebAppClassLoader(ClassLoader parent, Context context)
133 throws IOException
134 {
135 super(new URL[]{},parent!=null?parent
136 :(Thread.currentThread().getContextClassLoader()!=null?Thread.currentThread().getContextClassLoader()
137 :(WebAppClassLoader.class.getClassLoader()!=null?WebAppClassLoader.class.getClassLoader()
138 :ClassLoader.getSystemClassLoader())));
139 _parent=getParent();
140 _context=context;
141 if (_parent==null)
142 throw new IllegalArgumentException("no parent classloader!");
143
144 _extensions.add(".jar");
145 _extensions.add(".zip");
146
147 // TODO remove this system property
148 String extensions = System.getProperty(WebAppClassLoader.class.getName() + ".extensions");
149 if(extensions!=null)
150 {
151 StringTokenizer tokenizer = new StringTokenizer(extensions, ",;");
152 while(tokenizer.hasMoreTokens())
153 _extensions.add(tokenizer.nextToken().trim());
154 }
155
156 if (context.getExtraClasspath()!=null)
157 addClassPath(context.getExtraClasspath());
158 }
159
160 /* ------------------------------------------------------------ */
161 /**
162 * @return the name of the classloader
163 */
164 public String getName()
165 {
166 return _name;
167 }
168
169 /* ------------------------------------------------------------ */
170 /**
171 * @param name the name of the classloader
172 */
173 public void setName(String name)
174 {
175 _name=name;
176 }
177
178
179 /* ------------------------------------------------------------ */
180 public Context getContext()
181 {
182 return _context;
183 }
184
185 /* ------------------------------------------------------------ */
186 /**
187 * @param resource Comma or semicolon separated path of filenames or URLs
188 * pointing to directories or jar files. Directories should end
189 * with '/'.
190 */
191 public void addClassPath(Resource resource)
192 throws IOException
193 {
194 if (resource instanceof ResourceCollection)
195 {
196 for (Resource r : ((ResourceCollection)resource).getResources())
197 addClassPath(r);
198 }
199 else
200 {
201 addClassPath(resource.toString());
202 }
203 }
204
205 /* ------------------------------------------------------------ */
206 /**
207 * @param classPath Comma or semicolon separated path of filenames or URLs
208 * pointing to directories or jar files. Directories should end
209 * with '/'.
210 */
211 public void addClassPath(String classPath)
212 throws IOException
213 {
214 if (classPath == null)
215 return;
216
217 StringTokenizer tokenizer= new StringTokenizer(classPath, ",;");
218 while (tokenizer.hasMoreTokens())
219 {
220 Resource resource= _context.newResource(tokenizer.nextToken());
221 if (Log.isDebugEnabled())
222 Log.debug("Path resource=" + resource);
223
224 // Add the resource
225 if (resource.isDirectory() && resource instanceof ResourceCollection)
226 addClassPath(resource);
227 else
228 {
229 // Resolve file path if possible
230 File file= resource.getFile();
231 if (file != null)
232 {
233 URL url= resource.getURL();
234 addURL(url);
235 }
236 else if (resource.isDirectory())
237 addURL(resource.getURL());
238 else
239 throw new IllegalArgumentException("!file: "+resource);
240 }
241 }
242 }
243
244 /* ------------------------------------------------------------ */
245 /**
246 * @param file Checks if this file type can be added to the classpath.
247 */
248 private boolean isFileSupported(String file)
249 {
250 int dot = file.lastIndexOf('.');
251 return dot!=-1 && _extensions.contains(file.substring(dot));
252 }
253
254 /* ------------------------------------------------------------ */
255 /** Add elements to the class path for the context from the jar and zip files found
256 * in the specified resource.
257 * @param lib the resource that contains the jar and/or zip files.
258 */
259 public void addJars(Resource lib)
260 {
261 if (lib.exists() && lib.isDirectory())
262 {
263 String[] files=lib.list();
264 for (int f=0;files!=null && f<files.length;f++)
265 {
266 try
267 {
268 Resource fn=lib.addPath(files[f]);
269 String fnlc=fn.getName().toLowerCase();
270 if (!fn.isDirectory() && isFileSupported(fnlc))
271 {
272 String jar=fn.toString();
273 jar=StringUtil.replace(jar, ",", "%2C");
274 jar=StringUtil.replace(jar, ";", "%3B");
275 addClassPath(jar);
276 }
277 }
278 catch (Exception ex)
279 {
280 Log.warn(Log.EXCEPTION,ex);
281 }
282 }
283 }
284 }
285
286 /* ------------------------------------------------------------ */
287 public PermissionCollection getPermissions(CodeSource cs)
288 {
289 // TODO check CodeSource
290 PermissionCollection permissions=_context.getPermissions();
291 PermissionCollection pc= (permissions == null) ? super.getPermissions(cs) : permissions;
292 return pc;
293 }
294
295 /* ------------------------------------------------------------ */
296 public Enumeration<URL> getResources(String name) throws IOException
297 {
298 boolean system_class=_context.isSystemClass(name);
299 boolean server_class=_context.isServerClass(name);
300
301 List<URL> from_parent = toList(server_class?null:_parent.getResources(name));
302 List<URL> from_webapp = toList((system_class&&!from_parent.isEmpty())?null:this.findResources(name));
303
304 if (_context.isParentLoaderPriority())
305 {
306 from_parent.addAll(from_webapp);
307 return Collections.enumeration(from_parent);
308 }
309 from_webapp.addAll(from_parent);
310 return Collections.enumeration(from_webapp);
311 }
312
313 /* ------------------------------------------------------------ */
314 private List<URL> toList(Enumeration<URL> e)
315 {
316 List<URL> list = new ArrayList<URL>();
317 while (e!=null && e.hasMoreElements())
318 list.add(e.nextElement());
319 return list;
320 }
321
322 /* ------------------------------------------------------------ */
323 public URL getResource(String name)
324 {
325 URL url= null;
326 boolean tried_parent= false;
327 boolean system_class=_context.isSystemClass(name);
328 boolean server_class=_context.isServerClass(name);
329
330 if (system_class && server_class)
331 return null;
332
333 if (_parent!=null &&(_context.isParentLoaderPriority() || system_class ) && !server_class)
334 {
335 tried_parent= true;
336
337 if (_parent!=null)
338 url= _parent.getResource(name);
339 }
340
341 if (url == null)
342 {
343 url= this.findResource(name);
344
345 if (url == null && name.startsWith("/"))
346 {
347 if (Log.isDebugEnabled())
348 Log.debug("HACK leading / off " + name);
349 url= this.findResource(name.substring(1));
350 }
351 }
352
353 if (url == null && !tried_parent && !server_class )
354 {
355 if (_parent!=null)
356 url= _parent.getResource(name);
357 }
358
359 if (url != null)
360 if (Log.isDebugEnabled())
361 Log.debug("getResource("+name+")=" + url);
362
363 return url;
364 }
365
366 /* ------------------------------------------------------------ */
367 @Override
368 public Class<?> loadClass(String name) throws ClassNotFoundException
369 {
370 return loadClass(name, false);
371 }
372
373 /* ------------------------------------------------------------ */
374 @Override
375 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
376 {
377 Class<?> c= findLoadedClass(name);
378 ClassNotFoundException ex= null;
379 boolean tried_parent= false;
380
381 boolean system_class=_context.isSystemClass(name);
382 boolean server_class=_context.isServerClass(name);
383
384 if (system_class && server_class)
385 {
386 return null;
387 }
388
389 if (c == null && _parent!=null && (_context.isParentLoaderPriority() || system_class) && !server_class)
390 {
391 tried_parent= true;
392 try
393 {
394 c= _parent.loadClass(name);
395 if (Log.isDebugEnabled())
396 Log.debug("loaded " + c);
397 }
398 catch (ClassNotFoundException e)
399 {
400 ex= e;
401 }
402 }
403
404 if (c == null)
405 {
406 try
407 {
408 c= this.findClass(name);
409 }
410 catch (ClassNotFoundException e)
411 {
412 ex= e;
413 }
414 }
415
416 if (c == null && _parent!=null && !tried_parent && !server_class )
417 c= _parent.loadClass(name);
418
419 if (c == null)
420 throw ex;
421
422 if (resolve)
423 resolveClass(c);
424
425 if (Log.isDebugEnabled())
426 Log.debug("loaded " + c+ " from "+c.getClassLoader());
427
428 return c;
429 }
430
431 /* ------------------------------------------------------------ */
432 public String toString()
433 {
434 return "WebAppClassLoader@" + _name;
435 }
436 }