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