1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.webapp;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.lang.instrument.ClassFileTransformer;
25 import java.lang.instrument.IllegalClassFormatException;
26 import java.lang.instrument.Instrumentation;
27 import java.net.URL;
28 import java.net.URLClassLoader;
29 import java.security.CodeSource;
30 import java.security.PermissionCollection;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.Enumeration;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Set;
38 import java.util.StringTokenizer;
39 import java.util.concurrent.CopyOnWriteArrayList;
40
41 import org.eclipse.jetty.util.IO;
42 import org.eclipse.jetty.util.StringUtil;
43 import org.eclipse.jetty.util.log.Log;
44 import org.eclipse.jetty.util.log.Logger;
45 import org.eclipse.jetty.util.resource.Resource;
46 import org.eclipse.jetty.util.resource.ResourceCollection;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 public class WebAppClassLoader extends URLClassLoader
68 {
69 private static final Logger LOG = Log.getLogger(WebAppClassLoader.class);
70
71 private final Context _context;
72 private final ClassLoader _parent;
73 private final Set<String> _extensions=new HashSet<String>();
74 private String _name=String.valueOf(hashCode());
75 private final List<ClassFileTransformer> _transformers = new CopyOnWriteArrayList<>();
76
77
78
79
80 public interface Context
81 {
82
83
84
85
86
87
88
89
90 Resource newResource(String urlOrPath) throws IOException;
91
92
93
94
95
96 PermissionCollection getPermissions();
97
98
99
100
101
102
103
104
105
106 boolean isSystemClass(String clazz);
107
108
109
110
111
112
113
114
115
116
117 boolean isServerClass(String clazz);
118
119
120
121
122
123
124
125
126 boolean isParentLoaderPriority();
127
128
129 String getExtraClasspath();
130
131 }
132
133
134
135
136 public WebAppClassLoader(Context context)
137 throws IOException
138 {
139 this(null,context);
140 }
141
142
143
144
145 public WebAppClassLoader(ClassLoader parent, Context context)
146 throws IOException
147 {
148 super(new URL[]{},parent!=null?parent
149 :(Thread.currentThread().getContextClassLoader()!=null?Thread.currentThread().getContextClassLoader()
150 :(WebAppClassLoader.class.getClassLoader()!=null?WebAppClassLoader.class.getClassLoader()
151 :ClassLoader.getSystemClassLoader())));
152 _parent=getParent();
153 _context=context;
154 if (_parent==null)
155 throw new IllegalArgumentException("no parent classloader!");
156
157 _extensions.add(".jar");
158 _extensions.add(".zip");
159
160
161 String extensions = System.getProperty(WebAppClassLoader.class.getName() + ".extensions");
162 if(extensions!=null)
163 {
164 StringTokenizer tokenizer = new StringTokenizer(extensions, ",;");
165 while(tokenizer.hasMoreTokens())
166 _extensions.add(tokenizer.nextToken().trim());
167 }
168
169 if (context.getExtraClasspath()!=null)
170 addClassPath(context.getExtraClasspath());
171 }
172
173
174
175
176
177 public String getName()
178 {
179 return _name;
180 }
181
182
183
184
185
186 public void setName(String name)
187 {
188 _name=name;
189 }
190
191
192
193 public Context getContext()
194 {
195 return _context;
196 }
197
198
199
200
201
202
203
204 public void addClassPath(Resource resource)
205 throws IOException
206 {
207 if (resource instanceof ResourceCollection)
208 {
209 for (Resource r : ((ResourceCollection)resource).getResources())
210 addClassPath(r);
211 }
212 else
213 {
214 addClassPath(resource.toString());
215 }
216 }
217
218
219
220
221
222
223
224 public void addClassPath(String classPath)
225 throws IOException
226 {
227 if (classPath == null)
228 return;
229
230 StringTokenizer tokenizer= new StringTokenizer(classPath, ",;");
231 while (tokenizer.hasMoreTokens())
232 {
233 Resource resource= _context.newResource(tokenizer.nextToken().trim());
234 if (LOG.isDebugEnabled())
235 LOG.debug("Path resource=" + resource);
236
237
238 if (resource.isDirectory() && resource instanceof ResourceCollection)
239 addClassPath(resource);
240 else
241 {
242
243 File file= resource.getFile();
244 if (file != null)
245 {
246 URL url= resource.getURL();
247 addURL(url);
248 }
249 else if (resource.isDirectory())
250 addURL(resource.getURL());
251 else
252 {
253 if (LOG.isDebugEnabled())
254 LOG.debug("Check file exists and is not nested jar: "+resource);
255 throw new IllegalArgumentException("File not resolvable or incompatible with URLClassloader: "+resource);
256 }
257 }
258 }
259 }
260
261
262
263
264
265 private boolean isFileSupported(String file)
266 {
267 int dot = file.lastIndexOf('.');
268 return dot!=-1 && _extensions.contains(file.substring(dot));
269 }
270
271
272
273
274
275
276 public void addJars(Resource lib)
277 {
278 if (lib.exists() && lib.isDirectory())
279 {
280 String[] files=lib.list();
281 for (int f=0;files!=null && f<files.length;f++)
282 {
283 try
284 {
285 Resource fn=lib.addPath(files[f]);
286 String fnlc=fn.getName().toLowerCase(Locale.ENGLISH);
287
288 if (isFileSupported(fnlc))
289 {
290 String jar=fn.toString();
291 jar=StringUtil.replace(jar, ",", "%2C");
292 jar=StringUtil.replace(jar, ";", "%3B");
293 addClassPath(jar);
294 }
295 }
296 catch (Exception ex)
297 {
298 LOG.warn(Log.EXCEPTION,ex);
299 }
300 }
301 }
302 }
303
304
305 @Override
306 public PermissionCollection getPermissions(CodeSource cs)
307 {
308 PermissionCollection permissions=_context.getPermissions();
309 PermissionCollection pc= (permissions == null) ? super.getPermissions(cs) : permissions;
310 return pc;
311 }
312
313
314 @Override
315 public Enumeration<URL> getResources(String name) throws IOException
316 {
317 boolean system_class=_context.isSystemClass(name);
318 boolean server_class=_context.isServerClass(name);
319
320 List<URL> from_parent = toList(server_class?null:_parent.getResources(name));
321 List<URL> from_webapp = toList((system_class&&!from_parent.isEmpty())?null:this.findResources(name));
322
323 if (_context.isParentLoaderPriority())
324 {
325 from_parent.addAll(from_webapp);
326 return Collections.enumeration(from_parent);
327 }
328 from_webapp.addAll(from_parent);
329 return Collections.enumeration(from_webapp);
330 }
331
332
333 private List<URL> toList(Enumeration<URL> e)
334 {
335 if (e==null)
336 return new ArrayList<URL>();
337 return Collections.list(e);
338 }
339
340
341
342
343
344
345
346
347
348 @Override
349 public URL getResource(String name)
350 {
351 URL url= null;
352 boolean tried_parent= false;
353
354
355
356 String tmp = name;
357 if (tmp != null && tmp.endsWith(".class"))
358 tmp = tmp.substring(0, tmp.length()-6);
359
360 boolean system_class=_context.isSystemClass(tmp);
361 boolean server_class=_context.isServerClass(tmp);
362
363 if (system_class && server_class)
364 return null;
365
366 if (_parent!=null &&(_context.isParentLoaderPriority() || system_class ) && !server_class)
367 {
368 tried_parent= true;
369
370 if (_parent!=null)
371 url= _parent.getResource(name);
372 }
373
374 if (url == null)
375 {
376 url= this.findResource(name);
377
378 if (url == null && name.startsWith("/"))
379 {
380 if (LOG.isDebugEnabled())
381 LOG.debug("HACK leading / off " + name);
382 url= this.findResource(name.substring(1));
383 }
384 }
385
386 if (url == null && !tried_parent && !server_class )
387 {
388 if (_parent!=null)
389 url= _parent.getResource(name);
390 }
391
392 if (url != null)
393 if (LOG.isDebugEnabled())
394 LOG.debug("getResource("+name+")=" + url);
395
396 return url;
397 }
398
399
400 @Override
401 public Class<?> loadClass(String name) throws ClassNotFoundException
402 {
403 return loadClass(name, false);
404 }
405
406
407 @Override
408 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
409 {
410 Class<?> c= findLoadedClass(name);
411 ClassNotFoundException ex= null;
412 boolean tried_parent= false;
413
414 boolean system_class=_context.isSystemClass(name);
415 boolean server_class=_context.isServerClass(name);
416
417 if (system_class && server_class)
418 {
419 return null;
420 }
421
422 if (c == null && _parent!=null && (_context.isParentLoaderPriority() || system_class) && !server_class)
423 {
424 tried_parent= true;
425 try
426 {
427 c= _parent.loadClass(name);
428 if (LOG.isDebugEnabled())
429 LOG.debug("loaded " + c);
430 }
431 catch (ClassNotFoundException e)
432 {
433 ex= e;
434 }
435 }
436
437 if (c == null)
438 {
439 try
440 {
441 c= this.findClass(name);
442 }
443 catch (ClassNotFoundException e)
444 {
445 ex= e;
446 }
447 }
448
449 if (c == null && _parent!=null && !tried_parent && !server_class )
450 c= _parent.loadClass(name);
451
452 if (c == null && ex!=null)
453 throw ex;
454
455 if (resolve)
456 resolveClass(c);
457
458 if (LOG.isDebugEnabled())
459 LOG.debug("loaded {} from {}",c,c==null?null:c.getClassLoader());
460
461 return c;
462 }
463
464
465
466
467
468
469 public void addClassFileTransformer(ClassFileTransformer transformer)
470 {
471 _transformers.add(transformer);
472 }
473
474
475
476
477
478
479 public boolean removeClassFileTransformer(ClassFileTransformer transformer)
480 {
481 return _transformers.remove(transformer);
482 }
483
484
485
486
487
488 public void addTransformer(ClassFileTransformer transformer)
489 {
490 _transformers.add(transformer);
491 }
492
493
494
495
496
497 public boolean removeTransformer(ClassFileTransformer transformer)
498 {
499 return _transformers.remove(transformer);
500 }
501
502
503
504 @Override
505 protected Class<?> findClass(final String name) throws ClassNotFoundException
506 {
507 Class<?> clazz=null;
508
509 if (_transformers.isEmpty())
510 clazz = super.findClass(name);
511 else
512 {
513 String path = name.replace('.', '/').concat(".class");
514 URL url = getResource(path);
515 if (url==null)
516 throw new ClassNotFoundException(name);
517
518 InputStream content=null;
519 try
520 {
521 content = url.openStream();
522 byte[] bytes =IO.readBytes(content);
523
524 for (ClassFileTransformer transformer : _transformers)
525 bytes=transformer.transform(this,name,null,null,bytes);
526
527 clazz=defineClass(name,bytes,0,bytes.length);
528 }
529 catch (IOException e)
530 {
531 throw new ClassNotFoundException(name,e);
532 }
533 catch (IllegalClassFormatException e)
534 {
535 throw new ClassNotFoundException(name,e);
536 }
537 finally
538 {
539 if (content!=null)
540 {
541 try
542 {
543 content.close();
544 }
545 catch (IOException e)
546 {
547 throw new ClassNotFoundException(name,e);
548 }
549 }
550 }
551 }
552
553 return clazz;
554 }
555
556
557 @Override
558 public String toString()
559 {
560 return "WebAppClassLoader=" + _name+"@"+Long.toHexString(hashCode());
561 }
562 }