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