1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.catalina.loader;
18
19 import java.io.File;
20 import java.io.FilePermission;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.lang.instrument.ClassFileTransformer;
24 import java.lang.instrument.IllegalClassFormatException;
25 import java.lang.ref.Reference;
26 import java.lang.reflect.Field;
27 import java.lang.reflect.Method;
28 import java.net.URI;
29 import java.net.URISyntaxException;
30 import java.net.URL;
31 import java.net.URLClassLoader;
32 import java.security.AccessControlException;
33 import java.security.AccessController;
34 import java.security.CodeSource;
35 import java.security.Permission;
36 import java.security.PermissionCollection;
37 import java.security.Policy;
38 import java.security.PrivilegedAction;
39 import java.security.ProtectionDomain;
40 import java.security.cert.Certificate;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.ConcurrentModificationException;
46 import java.util.Date;
47 import java.util.Enumeration;
48 import java.util.HashMap;
49 import java.util.Iterator;
50 import java.util.LinkedHashSet;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Map.Entry;
54 import java.util.NoSuchElementException;
55 import java.util.concurrent.ConcurrentHashMap;
56 import java.util.concurrent.CopyOnWriteArrayList;
57 import java.util.concurrent.ThreadPoolExecutor;
58 import java.util.jar.Attributes;
59 import java.util.jar.Attributes.Name;
60 import java.util.jar.Manifest;
61
62 import org.apache.catalina.Container;
63 import org.apache.catalina.Globals;
64 import org.apache.catalina.Lifecycle;
65 import org.apache.catalina.LifecycleException;
66 import org.apache.catalina.LifecycleListener;
67 import org.apache.catalina.LifecycleState;
68 import org.apache.catalina.WebResource;
69 import org.apache.catalina.WebResourceRoot;
70 import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
71 import org.apache.juli.WebappProperties;
72 import org.apache.juli.logging.Log;
73 import org.apache.juli.logging.LogFactory;
74 import org.apache.tomcat.InstrumentableClassLoader;
75 import org.apache.tomcat.util.ExceptionUtils;
76 import org.apache.tomcat.util.IntrospectionUtils;
77 import org.apache.tomcat.util.compat.JreCompat;
78 import org.apache.tomcat.util.res.StringManager;
79 import org.apache.tomcat.util.security.PermissionCheck;
80
81 /**
82 * Specialized web application class loader.
83 * <p>
84 * This class loader is a full reimplementation of the
85 * <code>URLClassLoader</code> from the JDK. It is designed to be fully
86 * compatible with a normal <code>URLClassLoader</code>, although its internal
87 * behavior may be completely different.
88 * <p>
89 * <strong>IMPLEMENTATION NOTE</strong> - By default, this class loader follows
90 * the delegation model required by the specification. The system class
91 * loader will be queried first, then the local repositories, and only then
92 * delegation to the parent class loader will occur. This allows the web
93 * application to override any shared class except the classes from J2SE.
94 * Special handling is provided from the JAXP XML parser interfaces, the JNDI
95 * interfaces, and the classes from the servlet API, which are never loaded
96 * from the webapp repositories. The <code>delegate</code> property
97 * allows an application to modify this behavior to move the parent class loader
98 * ahead of the local repositories.
99 * <p>
100 * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper
101 * compilation technology, any repository which contains classes from
102 * the servlet API will be ignored by the class loader.
103 * <p>
104 * <strong>IMPLEMENTATION NOTE</strong> - The class loader generates source
105 * URLs which include the full JAR URL when a class is loaded from a JAR file,
106 * which allows setting security permission at the class level, even when a
107 * class is contained inside a JAR.
108 * <p>
109 * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in
110 * the order they are added via the initial constructor.
111 * <p>
112 * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
113 * security is made unless a security manager is present.
114 * <p>
115 * <strong>IMPLEMENTATION NOTE</strong> - As of 8.0, this class
116 * loader implements {@link InstrumentableClassLoader}, permitting web
117 * application classes to instrument other classes in the same web
118 * application. It does not permit instrumentation of system or container
119 * classes or classes in other web apps.
120 *
121 * @author Remy Maucherat
122 * @author Craig R. McClanahan
123 */
124 public abstract class WebappClassLoaderBase extends URLClassLoader
125 implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
126
127 private static final Log log = LogFactory.getLog(WebappClassLoaderBase.class);
128
129 /**
130 * List of ThreadGroup names to ignore when scanning for web application
131 * started threads that need to be shut down.
132 */
133 private static final List<String> JVM_THREAD_GROUP_NAMES = new ArrayList<>();
134
135 private static final String JVM_THREAD_GROUP_SYSTEM = "system";
136
137 private static final String CLASS_FILE_SUFFIX = ".class";
138
139 static {
140 if (!JreCompat.isGraalAvailable()) {
141 ClassLoader.registerAsParallelCapable();
142 }
143 JVM_THREAD_GROUP_NAMES.add(JVM_THREAD_GROUP_SYSTEM);
144 JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
145 }
146
147 protected class PrivilegedFindClassByName implements PrivilegedAction<Class<?>> {
148
149 private final String name;
150
151 PrivilegedFindClassByName(String name) {
152 this.name = name;
153 }
154
155 @Override
156 public Class<?> run() {
157 return findClassInternal(name);
158 }
159 }
160
161
162 protected static final class PrivilegedGetClassLoader implements PrivilegedAction<ClassLoader> {
163
164 private final Class<?> clazz;
165
166 public PrivilegedGetClassLoader(Class<?> clazz){
167 this.clazz = clazz;
168 }
169
170 @Override
171 public ClassLoader run() {
172 return clazz.getClassLoader();
173 }
174 }
175
176
177 protected final class PrivilegedJavaseGetResource implements PrivilegedAction<URL> {
178
179 private final String name;
180
181 public PrivilegedJavaseGetResource(String name) {
182 this.name = name;
183 }
184
185 @Override
186 public URL run() {
187 return javaseClassLoader.getResource(name);
188 }
189 }
190
191
192 // ------------------------------------------------------- Static Variables
193
194 /**
195 * The string manager for this package.
196 */
197 protected static final StringManager sm =
198 StringManager.getManager(Constants.Package);
199
200
201 // ----------------------------------------------------------- Constructors
202
203 /**
204 * Construct a new ClassLoader with no defined repositories and no
205 * parent ClassLoader.
206 */
207 protected WebappClassLoaderBase() {
208
209 super(new URL[0]);
210
211 ClassLoader p = getParent();
212 if (p == null) {
213 p = getSystemClassLoader();
214 }
215 this.parent = p;
216
217 ClassLoader j = String.class.getClassLoader();
218 if (j == null) {
219 j = getSystemClassLoader();
220 while (j.getParent() != null) {
221 j = j.getParent();
222 }
223 }
224 this.javaseClassLoader = j;
225
226 securityManager = System.getSecurityManager();
227 if (securityManager != null) {
228 refreshPolicy();
229 }
230 }
231
232
233 /**
234 * Construct a new ClassLoader with no defined repositories and the given
235 * parent ClassLoader.
236 * <p>
237 * Method is used via reflection -
238 * see {@link WebappLoader#createClassLoader()}
239 *
240 * @param parent Our parent class loader
241 */
242 protected WebappClassLoaderBase(ClassLoader parent) {
243
244 super(new URL[0], parent);
245
246 ClassLoader p = getParent();
247 if (p == null) {
248 p = getSystemClassLoader();
249 }
250 this.parent = p;
251
252 ClassLoader j = String.class.getClassLoader();
253 if (j == null) {
254 j = getSystemClassLoader();
255 while (j.getParent() != null) {
256 j = j.getParent();
257 }
258 }
259 this.javaseClassLoader = j;
260
261 securityManager = System.getSecurityManager();
262 if (securityManager != null) {
263 refreshPolicy();
264 }
265 }
266
267
268 // ----------------------------------------------------- Instance Variables
269
270 /**
271 * Associated web resources for this webapp.
272 */
273 protected WebResourceRoot resources = null;
274
275
276 /**
277 * The cache of ResourceEntry for classes and resources we have loaded,
278 * keyed by resource path, not binary name. Path is used as the key since
279 * resources may be requested by binary name (classes) or path (other
280 * resources such as property files) and the mapping from binary name to
281 * path is unambiguous but the reverse mapping is ambiguous.
282 */
283 protected final Map<String, ResourceEntry> resourceEntries =
284 new ConcurrentHashMap<>();
285
286
287 /**
288 * Should this class loader delegate to the parent class loader
289 * <strong>before</strong> searching its own repositories (i.e. the
290 * usual Java2 delegation model)? If set to <code>false</code>,
291 * this class loader will search its own repositories first, and
292 * delegate to the parent only if the class or resource is not
293 * found locally. Note that the default, <code>false</code>, is
294 * the behavior called for by the servlet specification.
295 */
296 protected boolean delegate = false;
297
298
299 private final Map<String,Long> jarModificationTimes = new HashMap<>();
300
301
302 /**
303 * A list of read File Permission's required if this loader is for a web
304 * application context.
305 */
306 protected final ArrayList<Permission> permissionList = new ArrayList<>();
307
308
309 /**
310 * The PermissionCollection for each CodeSource for a web
311 * application context.
312 */
313 protected final HashMap<String, PermissionCollection> loaderPC = new HashMap<>();
314
315
316 /**
317 * Instance of the SecurityManager installed.
318 */
319 protected final SecurityManager securityManager;
320
321
322 /**
323 * The parent class loader.
324 */
325 protected final ClassLoader parent;
326
327
328 /**
329 * The bootstrap class loader used to load the JavaSE classes. In some
330 * implementations this class loader is always <code>null</code> and in
331 * those cases {@link ClassLoader#getParent()} will be called recursively on
332 * the system class loader and the last non-null result used.
333 */
334 private ClassLoader javaseClassLoader;
335
336
337 /**
338 * Enables the RMI Target memory leak detection to be controlled. This is
339 * necessary since the detection can only work on Java 9 if some of the
340 * modularity checks are disabled.
341 */
342 private boolean clearReferencesRmiTargets = true;
343
344 /**
345 * Should Tomcat attempt to terminate threads that have been started by the
346 * web application? Stopping threads is performed via the deprecated (for
347 * good reason) <code>Thread.stop()</code> method and is likely to result in
348 * instability. As such, enabling this should be viewed as an option of last
349 * resort in a development environment and is not recommended in a
350 * production environment. If not specified, the default value of
351 * <code>false</code> will be used.
352 */
353 private boolean clearReferencesStopThreads = false;
354
355 /**
356 * Should Tomcat attempt to terminate any {@link java.util.TimerThread}s
357 * that have been started by the web application? If not specified, the
358 * default value of <code>false</code> will be used.
359 */
360 private boolean clearReferencesStopTimerThreads = false;
361
362 /**
363 * Should Tomcat call
364 * {@link org.apache.juli.logging.LogFactory#release(ClassLoader)}
365 * when the class loader is stopped? If not specified, the default value
366 * of <code>true</code> is used. Changing the default setting is likely to
367 * lead to memory leaks and other issues.
368 */
369 private boolean clearReferencesLogFactoryRelease = true;
370
371 /**
372 * If an HttpClient keep-alive timer thread has been started by this web
373 * application and is still running, should Tomcat change the context class
374 * loader from the current {@link ClassLoader} to
375 * {@link ClassLoader#getParent()} to prevent a memory leak? Note that the
376 * keep-alive timer thread will stop on its own once the keep-alives all
377 * expire however, on a busy system that might not happen for some time.
378 */
379 private boolean clearReferencesHttpClientKeepAliveThread = true;
380
381 /**
382 * Should Tomcat attempt to clear references to classes loaded by this class
383 * loader from the ObjectStreamClass caches?
384 */
385 private boolean clearReferencesObjectStreamClassCaches = true;
386
387 /**
388 * Should Tomcat attempt to clear references to classes loaded by this class
389 * loader from ThreadLocals?
390 */
391 private boolean clearReferencesThreadLocals = true;
392
393 /**
394 * Should Tomcat skip the memory leak checks when the web application is
395 * stopped as part of the process of shutting down the JVM?
396 */
397 private boolean skipMemoryLeakChecksOnJvmShutdown = false;
398
399 /**
400 * Holds the class file transformers decorating this class loader. The
401 * CopyOnWriteArrayList is thread safe. It is expensive on writes, but
402 * those should be rare. It is very fast on reads, since synchronization
403 * is not actually used. Importantly, the ClassLoader will never block
404 * iterating over the transformers while loading a class.
405 */
406 private final List<ClassFileTransformer> transformers = new CopyOnWriteArrayList<>();
407
408
409 /**
410 * Flag that indicates that {@link #addURL(URL)} has been called which
411 * creates a requirement to check the super class when searching for
412 * resources.
413 */
414 private boolean hasExternalRepositories = false;
415
416
417 /**
418 * Repositories managed by this class rather than the super class.
419 */
420 private List<URL> localRepositories = new ArrayList<>();
421
422
423 private volatile LifecycleState state = LifecycleState.NEW;
424
425
426 // ------------------------------------------------------------- Properties
427
428 /**
429 * @return associated resources.
430 */
431 public WebResourceRoot getResources() {
432 return this.resources;
433 }
434
435
436 /**
437 * Set associated resources.
438 * @param resources the resources from which the classloader will
439 * load the classes
440 */
441 public void setResources(WebResourceRoot resources) {
442 this.resources = resources;
443 }
444
445
446 /**
447 * @return the context name for this class loader.
448 */
449 public String getContextName() {
450 if (resources == null) {
451 return "Unknown";
452 } else {
453 return resources.getContext().getBaseName();
454 }
455 }
456
457
458 /**
459 * Return the "delegate first" flag for this class loader.
460 * @return <code>true</code> if the class lookup will delegate to
461 * the parent first. The default in Tomcat is <code>false</code>.
462 */
463 public boolean getDelegate() {
464 return this.delegate;
465 }
466
467
468 /**
469 * Set the "delegate first" flag for this class loader.
470 * If this flag is true, this class loader delegates
471 * to the parent class loader
472 * <strong>before</strong> searching its own repositories, as
473 * in an ordinary (non-servlet) chain of Java class loaders.
474 * If set to <code>false</code> (the default),
475 * this class loader will search its own repositories first, and
476 * delegate to the parent only if the class or resource is not
477 * found locally, as per the servlet specification.
478 *
479 * @param delegate The new "delegate first" flag
480 */
481 public void setDelegate(boolean delegate) {
482 this.delegate = delegate;
483 }
484
485
486 /**
487 * If there is a Java SecurityManager create a read permission for the
488 * target of the given URL as appropriate.
489 *
490 * @param url URL for a file or directory on local system
491 */
492 void addPermission(URL url) {
493 if (url == null) {
494 return;
495 }
496 if (securityManager != null) {
497 String protocol = url.getProtocol();
498 if ("file".equalsIgnoreCase(protocol)) {
499 URI uri;
500 File f;
501 String path;
502 try {
503 uri = url.toURI();
504 f = new File(uri);
505 path = f.getCanonicalPath();
506 } catch (IOException | URISyntaxException e) {
507 log.warn(sm.getString(
508 "webappClassLoader.addPermisionNoCanonicalFile",
509 url.toExternalForm()));
510 return;
511 }
512 if (f.isFile()) {
513 // Allow the file to be read
514 addPermission(new FilePermission(path, "read"));
515 } else if (f.isDirectory()) {
516 addPermission(new FilePermission(path, "read"));
517 addPermission(new FilePermission(
518 path + File.separator + "-", "read"));
519 } else {
520 // File does not exist - ignore (shouldn't happen)
521 }
522 } else {
523 // Unsupported URL protocol
524 log.warn(sm.getString(
525 "webappClassLoader.addPermisionNoProtocol",
526 protocol, url.toExternalForm()));
527 }
528 }
529 }
530
531
532 /**
533 * If there is a Java SecurityManager create a Permission.
534 *
535 * @param permission The permission
536 */
537 void addPermission(Permission permission) {
538 if ((securityManager != null) && (permission != null)) {
539 permissionList.add(permission);
540 }
541 }
542
543
544 public boolean getClearReferencesRmiTargets() {
545 return this.clearReferencesRmiTargets;
546 }
547
548
549 public void setClearReferencesRmiTargets(boolean clearReferencesRmiTargets) {
550 this.clearReferencesRmiTargets = clearReferencesRmiTargets;
551 }
552
553
554 /**
555 * @return the clearReferencesStopThreads flag for this Context.
556 */
557 public boolean getClearReferencesStopThreads() {
558 return this.clearReferencesStopThreads;
559 }
560
561
562 /**
563 * Set the clearReferencesStopThreads feature for this Context.
564 *
565 * @param clearReferencesStopThreads The new flag value
566 */
567 public void setClearReferencesStopThreads(
568 boolean clearReferencesStopThreads) {
569 this.clearReferencesStopThreads = clearReferencesStopThreads;
570 }
571
572
573 /**
574 * @return the clearReferencesStopTimerThreads flag for this Context.
575 */
576 public boolean getClearReferencesStopTimerThreads() {
577 return this.clearReferencesStopTimerThreads;
578 }
579
580
581 /**
582 * Set the clearReferencesStopTimerThreads feature for this Context.
583 *
584 * @param clearReferencesStopTimerThreads The new flag value
585 */
586 public void setClearReferencesStopTimerThreads(
587 boolean clearReferencesStopTimerThreads) {
588 this.clearReferencesStopTimerThreads = clearReferencesStopTimerThreads;
589 }
590
591
592 /**
593 * @return the clearReferencesLogFactoryRelease flag for this Context.
594 */
595 public boolean getClearReferencesLogFactoryRelease() {
596 return this.clearReferencesLogFactoryRelease;
597 }
598
599
600 /**
601 * Set the clearReferencesLogFactoryRelease feature for this Context.
602 *
603 * @param clearReferencesLogFactoryRelease The new flag value
604 */
605 public void setClearReferencesLogFactoryRelease(
606 boolean clearReferencesLogFactoryRelease) {
607 this.clearReferencesLogFactoryRelease =
608 clearReferencesLogFactoryRelease;
609 }
610
611
612 /**
613 * @return the clearReferencesHttpClientKeepAliveThread flag for this
614 * Context.
615 */
616 public boolean getClearReferencesHttpClientKeepAliveThread() {
617 return this.clearReferencesHttpClientKeepAliveThread;
618 }
619
620
621 /**
622 * Set the clearReferencesHttpClientKeepAliveThread feature for this
623 * Context.
624 *
625 * @param clearReferencesHttpClientKeepAliveThread The new flag value
626 */
627 public void setClearReferencesHttpClientKeepAliveThread(
628 boolean clearReferencesHttpClientKeepAliveThread) {
629 this.clearReferencesHttpClientKeepAliveThread =
630 clearReferencesHttpClientKeepAliveThread;
631 }
632
633
634 public boolean getClearReferencesObjectStreamClassCaches() {
635 return clearReferencesObjectStreamClassCaches;
636 }
637
638
639 public void setClearReferencesObjectStreamClassCaches(
640 boolean clearReferencesObjectStreamClassCaches) {
641 this.clearReferencesObjectStreamClassCaches = clearReferencesObjectStreamClassCaches;
642 }
643
644
645 public boolean getClearReferencesThreadLocals() {
646 return clearReferencesThreadLocals;
647 }
648
649
650 public void setClearReferencesThreadLocals(boolean clearReferencesThreadLocals) {
651 this.clearReferencesThreadLocals = clearReferencesThreadLocals;
652 }
653
654
655 public boolean getSkipMemoryLeakChecksOnJvmShutdown() {
656 return skipMemoryLeakChecksOnJvmShutdown;
657 }
658
659
660 public void setSkipMemoryLeakChecksOnJvmShutdown(boolean skipMemoryLeakChecksOnJvmShutdown) {
661 this.skipMemoryLeakChecksOnJvmShutdown = skipMemoryLeakChecksOnJvmShutdown;
662 }
663
664
665 // ------------------------------------------------------- Reloader Methods
666
667 /**
668 * Adds the specified class file transformer to this class loader. The
669 * transformer will then be able to modify the bytecode of any classes
670 * loaded by this class loader after the invocation of this method.
671 *
672 * @param transformer The transformer to add to the class loader
673 */
674 @Override
675 public void addTransformer(ClassFileTransformer transformer) {
676
677 if (transformer == null) {
678 throw new IllegalArgumentException(sm.getString(
679 "webappClassLoader.addTransformer.illegalArgument", getContextName()));
680 }
681
682 if (this.transformers.contains(transformer)) {
683 // if the same instance of this transformer was already added, bail out
684 log.warn(sm.getString("webappClassLoader.addTransformer.duplicate",
685 transformer, getContextName()));
686 return;
687 }
688 this.transformers.add(transformer);
689
690 log.info(sm.getString("webappClassLoader.addTransformer", transformer, getContextName()));
691 }
692
693 /**
694 * Removes the specified class file transformer from this class loader.
695 * It will no longer be able to modify the byte code of any classes
696 * loaded by the class loader after the invocation of this method.
697 * However, any classes already modified by this transformer will
698 * remain transformed.
699 *
700 * @param transformer The transformer to remove
701 */
702 @Override
703 public void removeTransformer(ClassFileTransformer transformer) {
704
705 if (transformer == null) {
706 return;
707 }
708
709 if (this.transformers.remove(transformer)) {
710 log.info(sm.getString("webappClassLoader.removeTransformer",
711 transformer, getContextName()));
712 }
713 }
714
715 protected void copyStateWithoutTransformers(WebappClassLoaderBase base) {
716 base.resources = this.resources;
717 base.delegate = this.delegate;
718 base.state = LifecycleState.NEW;
719 base.clearReferencesStopThreads = this.clearReferencesStopThreads;
720 base.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads;
721 base.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease;
722 base.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread;
723 base.jarModificationTimes.putAll(this.jarModificationTimes);
724 base.permissionList.addAll(this.permissionList);
725 base.loaderPC.putAll(this.loaderPC);
726 }
727
728 /**
729 * Have one or more classes or resources been modified so that a reload
730 * is appropriate?
731 * @return <code>true</code> if there's been a modification
732 */
733 public boolean modified() {
734
735 if (log.isDebugEnabled())
736 log.debug("modified()");
737
738 for (Entry<String,ResourceEntry> entry : resourceEntries.entrySet()) {
739 long cachedLastModified = entry.getValue().lastModified;
740 long lastModified = resources.getClassLoaderResource(
741 entry.getKey()).getLastModified();
742 if (lastModified != cachedLastModified) {
743 if( log.isDebugEnabled() )
744 log.debug(sm.getString("webappClassLoader.resourceModified",
745 entry.getKey(),
746 new Date(cachedLastModified),
747 new Date(lastModified)));
748 return true;
749 }
750 }
751
752 // Check if JARs have been added or removed
753 WebResource[] jars = resources.listResources("/WEB-INF/lib");
754 // Filter out non-JAR resources
755
756 int jarCount = 0;
757 for (WebResource jar : jars) {
758 if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
759 jarCount++;
760 Long recordedLastModified = jarModificationTimes.get(jar.getName());
761 if (recordedLastModified == null) {
762 // Jar has been added
763 log.info(sm.getString("webappClassLoader.jarsAdded",
764 resources.getContext().getName()));
765 return true;
766 }
767 if (recordedLastModified.longValue() != jar.getLastModified()) {
768 // Jar has been changed
769 log.info(sm.getString("webappClassLoader.jarsModified",
770 resources.getContext().getName()));
771 return true;
772 }
773 }
774 }
775
776 if (jarCount < jarModificationTimes.size()){
777 log.info(sm.getString("webappClassLoader.jarsRemoved",
778 resources.getContext().getName()));
779 return true;
780 }
781
782
783 // No classes have been modified
784 return false;
785 }
786
787
788 @Override
789 public String toString() {
790
791 StringBuilder sb = new StringBuilder(this.getClass().getSimpleName());
792 sb.append("\r\n context: ");
793 sb.append(getContextName());
794 sb.append("\r\n delegate: ");
795 sb.append(delegate);
796 sb.append("\r\n");
797 if (this.parent != null) {
798 sb.append("----------> Parent Classloader:\r\n");
799 sb.append(this.parent.toString());
800 sb.append("\r\n");
801 }
802 if (this.transformers.size() > 0) {
803 sb.append("----------> Class file transformers:\r\n");
804 for (ClassFileTransformer transformer : this.transformers) {
805 sb.append(transformer).append("\r\n");
806 }
807 }
808 return sb.toString();
809 }
810
811
812 // ---------------------------------------------------- ClassLoader Methods
813
814
815 // Note: exposed for use by tests
816 protected final Class<?> doDefineClass(String name, byte[] b, int off, int len,
817 ProtectionDomain protectionDomain) {
818 return super.defineClass(name, b, off, len, protectionDomain);
819 }
820
821 /**
822 * Find the specified class in our local repositories, if possible. If
823 * not found, throw <code>ClassNotFoundException</code>.
824 *
825 * @param name The binary name of the class to be loaded
826 *
827 * @exception ClassNotFoundException if the class was not found
828 */
829 @Override
830 public Class<?> findClass(String name) throws ClassNotFoundException {
831
832 if (log.isDebugEnabled())
833 log.debug(" findClass(" + name + ")");
834
835 checkStateForClassLoading(name);
836
837 // (1) Permission to define this class when using a SecurityManager
838 if (securityManager != null) {
839 int i = name.lastIndexOf('.');
840 if (i >= 0) {
841 try {
842 if (log.isTraceEnabled())
843 log.trace(" securityManager.checkPackageDefinition");
844 securityManager.checkPackageDefinition(name.substring(0,i));
845 } catch (Exception se) {
846 if (log.isTraceEnabled())
847 log.trace(" -->Exception-->ClassNotFoundException", se);
848 throw new ClassNotFoundException(name, se);
849 }
850 }
851 }
852
853 // Ask our superclass to locate this class, if possible
854 // (throws ClassNotFoundException if it is not found)
855 Class<?> clazz = null;
856 try {
857 if (log.isTraceEnabled())
858 log.trace(" findClassInternal(" + name + ")");
859 try {
860 if (securityManager != null) {
861 PrivilegedAction<Class<?>> dp =
862 new PrivilegedFindClassByName(name);
863 clazz = AccessController.doPrivileged(dp);
864 } else {
865 clazz = findClassInternal(name);
866 }
867 } catch(AccessControlException ace) {
868 log.warn(sm.getString("webappClassLoader.securityException", name,
869 ace.getMessage()), ace);
870 throw new ClassNotFoundException(name, ace);
871 } catch (RuntimeException e) {
872 if (log.isTraceEnabled())
873 log.trace(" -->RuntimeException Rethrown", e);
874 throw e;
875 }
876 if ((clazz == null) && hasExternalRepositories) {
877 try {
878 clazz = super.findClass(name);
879 } catch(AccessControlException ace) {
880 log.warn(sm.getString("webappClassLoader.securityException", name,
881 ace.getMessage()), ace);
882 throw new ClassNotFoundException(name, ace);
883 } catch (RuntimeException e) {
884 if (log.isTraceEnabled())
885 log.trace(" -->RuntimeException Rethrown", e);
886 throw e;
887 }
888 }
889 if (clazz == null) {
890 if (log.isDebugEnabled())
891 log.debug(" --> Returning ClassNotFoundException");
892 throw new ClassNotFoundException(name);
893 }
894 } catch (ClassNotFoundException e) {
895 if (log.isTraceEnabled())
896 log.trace(" --> Passing on ClassNotFoundException");
897 throw e;
898 }
899
900 // Return the class we have located
901 if (log.isTraceEnabled())
902 log.debug(" Returning class " + clazz);
903
904 if (log.isTraceEnabled()) {
905 ClassLoader cl;
906 if (Globals.IS_SECURITY_ENABLED){
907 cl = AccessController.doPrivileged(
908 new PrivilegedGetClassLoader(clazz));
909 } else {
910 cl = clazz.getClassLoader();
911 }
912 log.debug(" Loaded by " + cl.toString());
913 }
914 return clazz;
915
916 }
917
918
919 /**
920 * Find the specified resource in our local repository, and return a
921 * <code>URL</code> referring to it, or <code>null</code> if this resource
922 * cannot be found.
923 *
924 * @param name Name of the resource to be found
925 */
926 @Override
927 public URL findResource(final String name) {
928
929 if (log.isDebugEnabled())
930 log.debug(" findResource(" + name + ")");
931
932 checkStateForResourceLoading(name);
933
934 URL url = null;
935
936 String path = nameToPath(name);
937
938 WebResource resource = resources.getClassLoaderResource(path);
939 if (resource.exists()) {
940 url = resource.getURL();
941 trackLastModified(path, resource);
942 }
943
944 if ((url == null) && hasExternalRepositories) {
945 url = super.findResource(name);
946 }
947
948 if (log.isDebugEnabled()) {
949 if (url != null)
950 log.debug(" --> Returning '" + url.toString() + "'");
951 else
952 log.debug(" --> Resource not found, returning null");
953 }
954 return url;
955 }
956
957
958 private void trackLastModified(String path, WebResource resource) {
959 if (resourceEntries.containsKey(path)) {
960 return;
961 }
962 ResourceEntry entry = new ResourceEntry();
963 entry.lastModified = resource.getLastModified();
964 synchronized(resourceEntries) {
965 resourceEntries.putIfAbsent(path, entry);
966 }
967 }
968
969
970 /**
971 * Return an enumeration of <code>URLs</code> representing all of the
972 * resources with the given name. If no resources with this name are
973 * found, return an empty enumeration.
974 *
975 * @param name Name of the resources to be found
976 *
977 * @exception IOException if an input/output error occurs
978 */
979 @Override
980 public Enumeration<URL> findResources(String name) throws IOException {
981
982 if (log.isDebugEnabled())
983 log.debug(" findResources(" + name + ")");
984
985 checkStateForResourceLoading(name);
986
987 LinkedHashSet<URL> result = new LinkedHashSet<>();
988
989 String path = nameToPath(name);
990
991 WebResource[] webResources = resources.getClassLoaderResources(path);
992 for (WebResource webResource : webResources) {
993 if (webResource.exists()) {
994 result.add(webResource.getURL());
995 }
996 }
997
998 // Adding the results of a call to the superclass
999 if (hasExternalRepositories) {
1000 Enumeration<URL> otherResourcePaths = super.findResources(name);
1001 while (otherResourcePaths.hasMoreElements()) {
1002 result.add(otherResourcePaths.nextElement());
1003 }
1004 }
1005
1006 return Collections.enumeration(result);
1007 }
1008
1009
1010 /**
1011 * Find the resource with the given name. A resource is some data
1012 * (images, audio, text, etc.) that can be accessed by class code in a
1013 * way that is independent of the location of the code. The name of a
1014 * resource is a "/"-separated path name that identifies the resource.
1015 * If the resource cannot be found, return <code>null</code>.
1016 * <p>
1017 * This method searches according to the following algorithm, returning
1018 * as soon as it finds the appropriate URL. If the resource cannot be
1019 * found, returns <code>null</code>.
1020 * <ul>
1021 * <li>If the <code>delegate</code> property is set to <code>true</code>,
1022 * call the <code>getResource()</code> method of the parent class
1023 * loader, if any.</li>
1024 * <li>Call <code>findResource()</code> to find this resource in our
1025 * locally defined repositories.</li>
1026 * <li>Call the <code>getResource()</code> method of the parent class
1027 * loader, if any.</li>
1028 * </ul>
1029 *
1030 * @param name Name of the resource to return a URL for
1031 */
1032 @Override
1033 public URL getResource(String name) {
1034
1035 if (log.isDebugEnabled())
1036 log.debug("getResource(" + name + ")");
1037
1038 checkStateForResourceLoading(name);
1039
1040 URL url = null;
1041
1042 boolean delegateFirst = delegate || filter(name, false);
1043
1044 // (1) Delegate to parent if requested
1045 if (delegateFirst) {
1046 if (log.isDebugEnabled())
1047 log.debug(" Delegating to parent classloader " + parent);
1048 url = parent.getResource(name);
1049 if (url != null) {
1050 if (log.isDebugEnabled())
1051 log.debug(" --> Returning '" + url.toString() + "'");
1052 return url;
1053 }
1054 }
1055
1056 // (2) Search local repositories
1057 url = findResource(name);
1058 if (url != null) {
1059 if (log.isDebugEnabled())
1060 log.debug(" --> Returning '" + url.toString() + "'");
1061 return url;
1062 }
1063
1064 // (3) Delegate to parent unconditionally if not already attempted
1065 if (!delegateFirst) {
1066 url = parent.getResource(name);
1067 if (url != null) {
1068 if (log.isDebugEnabled())
1069 log.debug(" --> Returning '" + url.toString() + "'");
1070 return url;
1071 }
1072 }
1073
1074 // (4) Resource was not found
1075 if (log.isDebugEnabled())
1076 log.debug(" --> Resource not found, returning null");
1077 return null;
1078
1079 }
1080
1081
1082 @Override
1083 public Enumeration<URL> getResources(String name) throws IOException {
1084
1085 Enumeration<URL> parentResources = getParent().getResources(name);
1086 Enumeration<URL> localResources = findResources(name);
1087
1088 // Need to combine these enumerations. The order in which the
1089 // Enumerations are combined depends on how delegation is configured
1090 boolean delegateFirst = delegate || filter(name, false);
1091
1092 if (delegateFirst) {
1093 return new CombinedEnumeration(parentResources, localResources);
1094 } else {
1095 return new CombinedEnumeration(localResources, parentResources);
1096 }
1097 }
1098
1099
1100 /**
1101 * Find the resource with the given name, and return an input stream
1102 * that can be used for reading it. The search order is as described
1103 * for <code>getResource()</code>, after checking to see if the resource
1104 * data has been previously cached. If the resource cannot be found,
1105 * return <code>null</code>.
1106 *
1107 * @param name Name of the resource to return an input stream for
1108 */
1109 @Override
1110 public InputStream getResourceAsStream(String name) {
1111
1112 if (log.isDebugEnabled())
1113 log.debug("getResourceAsStream(" + name + ")");
1114
1115 checkStateForResourceLoading(name);
1116
1117 InputStream stream = null;
1118
1119 boolean delegateFirst = delegate || filter(name, false);
1120
1121 // (1) Delegate to parent if requested
1122 if (delegateFirst) {
1123 if (log.isDebugEnabled())
1124 log.debug(" Delegating to parent classloader " + parent);
1125 stream = parent.getResourceAsStream(name);
1126 if (stream != null) {
1127 if (log.isDebugEnabled())
1128 log.debug(" --> Returning stream from parent");
1129 return stream;
1130 }
1131 }
1132
1133 // (2) Search local repositories
1134 if (log.isDebugEnabled())
1135 log.debug(" Searching local repositories");
1136 String path = nameToPath(name);
1137 WebResource resource = resources.getClassLoaderResource(path);
1138 if (resource.exists()) {
1139 stream = resource.getInputStream();
1140 trackLastModified(path, resource);
1141 }
1142 try {
1143 if (hasExternalRepositories && stream == null) {
1144 URL url = super.findResource(name);
1145 if (url != null) {
1146 stream = url.openStream();
1147 }
1148 }
1149 } catch (IOException e) {
1150 // Ignore
1151 }
1152 if (stream != null) {
1153 if (log.isDebugEnabled())
1154 log.debug(" --> Returning stream from local");
1155 return stream;
1156 }
1157
1158 // (3) Delegate to parent unconditionally
1159 if (!delegateFirst) {
1160 if (log.isDebugEnabled())
1161 log.debug(" Delegating to parent classloader unconditionally " + parent);
1162 stream = parent.getResourceAsStream(name);
1163 if (stream != null) {
1164 if (log.isDebugEnabled())
1165 log.debug(" --> Returning stream from parent");
1166 return stream;
1167 }
1168 }
1169
1170 // (4) Resource was not found
1171 if (log.isDebugEnabled())
1172 log.debug(" --> Resource not found, returning null");
1173 return null;
1174 }
1175
1176
1177 /**
1178 * Load the class with the specified name. This method searches for
1179 * classes in the same manner as <code>loadClass(String, boolean)</code>
1180 * with <code>false</code> as the second argument.
1181 *
1182 * @param name The binary name of the class to be loaded
1183 *
1184 * @exception ClassNotFoundException if the class was not found
1185 */
1186 @Override
1187 public Class<?> loadClass(String name) throws ClassNotFoundException {
1188 return loadClass(name, false);
1189 }
1190
1191
1192 /**
1193 * Load the class with the specified name, searching using the following
1194 * algorithm until it finds and returns the class. If the class cannot
1195 * be found, returns <code>ClassNotFoundException</code>.
1196 * <ul>
1197 * <li>Call <code>findLoadedClass(String)</code> to check if the
1198 * class has already been loaded. If it has, the same
1199 * <code>Class</code> object is returned.</li>
1200 * <li>If the <code>delegate</code> property is set to <code>true</code>,
1201 * call the <code>loadClass()</code> method of the parent class
1202 * loader, if any.</li>
1203 * <li>Call <code>findClass()</code> to find this class in our locally
1204 * defined repositories.</li>
1205 * <li>Call the <code>loadClass()</code> method of our parent
1206 * class loader, if any.</li>
1207 * </ul>
1208 * If the class was found using the above steps, and the
1209 * <code>resolve</code> flag is <code>true</code>, this method will then
1210 * call <code>resolveClass(Class)</code> on the resulting Class object.
1211 *
1212 * @param name The binary name of the class to be loaded
1213 * @param resolve If <code>true</code> then resolve the class
1214 *
1215 * @exception ClassNotFoundException if the class was not found
1216 */
1217 @Override
1218 public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
1219
1220 synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
1221 if (log.isDebugEnabled())
1222 log.debug("loadClass(" + name + ", " + resolve + ")");
1223 Class<?> clazz = null;
1224
1225 // Log access to stopped class loader
1226 checkStateForClassLoading(name);
1227
1228 // (0) Check our previously loaded local class cache
1229 clazz = findLoadedClass0(name);
1230 if (clazz != null) {
1231 if (log.isDebugEnabled())
1232 log.debug(" Returning class from cache");
1233 if (resolve)
1234 resolveClass(clazz);
1235 return clazz;
1236 }
1237
1238 // (0.1) Check our previously loaded class cache
1239 clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
1240 if (clazz != null) {
1241 if (log.isDebugEnabled())
1242 log.debug(" Returning class from cache");
1243 if (resolve)
1244 resolveClass(clazz);
1245 return clazz;
1246 }
1247
1248 // (0.2) Try loading the class with the system class loader, to prevent
1249 // the webapp from overriding Java SE classes. This implements
1250 // SRV.10.7.2
1251 String resourceName = binaryNameToPath(name, false);
1252
1253 ClassLoader javaseLoader = getJavaseClassLoader();
1254 boolean tryLoadingFromJavaseLoader;
1255 try {
1256 // Use getResource as it won't trigger an expensive
1257 // ClassNotFoundException if the resource is not available from
1258 // the Java SE class loader. However (see
1259 // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
1260 // details) when running under a security manager in rare cases
1261 // this call may trigger a ClassCircularityError.
1262 // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
1263 // details of how this may trigger a StackOverflowError
1264 // Given these reported errors, catch Throwable to ensure any
1265 // other edge cases are also caught
1266 URL url;
1267 if (securityManager != null) {
1268 PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
1269 url = AccessController.doPrivileged(dp);
1270 } else {
1271 url = javaseLoader.getResource(resourceName);
1272 }
1273 tryLoadingFromJavaseLoader = (url != null);
1274 } catch (Throwable t) {
1275 // Swallow all exceptions apart from those that must be re-thrown
1276 ExceptionUtils.handleThrowable(t);
1277 // The getResource() trick won't work for this class. We have to
1278 // try loading it directly and accept that we might get a
1279 // ClassNotFoundException.
1280 tryLoadingFromJavaseLoader = true;
1281 }
1282
1283 if (tryLoadingFromJavaseLoader) {
1284 try {
1285 clazz = javaseLoader.loadClass(name);
1286 if (clazz != null) {
1287 if (resolve)
1288 resolveClass(clazz);
1289 return clazz;
1290 }
1291 } catch (ClassNotFoundException e) {
1292 // Ignore
1293 }
1294 }
1295
1296 // (0.5) Permission to access this class when using a SecurityManager
1297 if (securityManager != null) {
1298 int i = name.lastIndexOf('.');
1299 if (i >= 0) {
1300 try {
1301 securityManager.checkPackageAccess(name.substring(0,i));
1302 } catch (SecurityException se) {
1303 String error = sm.getString("webappClassLoader.restrictedPackage", name);
1304 log.info(error, se);
1305 throw new ClassNotFoundException(error, se);
1306 }
1307 }
1308 }
1309
1310 boolean delegateLoad = delegate || filter(name, true);
1311
1312 // (1) Delegate to our parent if requested
1313 if (delegateLoad) {
1314 if (log.isDebugEnabled())
1315 log.debug(" Delegating to parent classloader1 " + parent);
1316 try {
1317 clazz = Class.forName(name, false, parent);
1318 if (clazz != null) {
1319 if (log.isDebugEnabled())
1320 log.debug(" Loading class from parent");
1321 if (resolve)
1322 resolveClass(clazz);
1323 return clazz;
1324 }
1325 } catch (ClassNotFoundException e) {
1326 // Ignore
1327 }
1328 }
1329
1330 // (2) Search local repositories
1331 if (log.isDebugEnabled())
1332 log.debug(" Searching local repositories");
1333 try {
1334 clazz = findClass(name);
1335 if (clazz != null) {
1336 if (log.isDebugEnabled())
1337 log.debug(" Loading class from local repository");
1338 if (resolve)
1339 resolveClass(clazz);
1340 return clazz;
1341 }
1342 } catch (ClassNotFoundException e) {
1343 // Ignore
1344 }
1345
1346 // (3) Delegate to parent unconditionally
1347 if (!delegateLoad) {
1348 if (log.isDebugEnabled())
1349 log.debug(" Delegating to parent classloader at end: " + parent);
1350 try {
1351 clazz = Class.forName(name, false, parent);
1352 if (clazz != null) {
1353 if (log.isDebugEnabled())
1354 log.debug(" Loading class from parent");
1355 if (resolve)
1356 resolveClass(clazz);
1357 return clazz;
1358 }
1359 } catch (ClassNotFoundException e) {
1360 // Ignore
1361 }
1362 }
1363 }
1364
1365 throw new ClassNotFoundException(name);
1366 }
1367
1368
1369 protected void checkStateForClassLoading(String className) throws ClassNotFoundException {
1370 // It is not permitted to load new classes once the web application has
1371 // been stopped.
1372 try {
1373 checkStateForResourceLoading(className);
1374 } catch (IllegalStateException ise) {
1375 throw new ClassNotFoundException(ise.getMessage(), ise);
1376 }
1377 }
1378
1379
1380 protected void checkStateForResourceLoading(String resource) throws IllegalStateException {
1381 // It is not permitted to load resources once the web application has
1382 // been stopped.
1383 if (!state.isAvailable()) {
1384 String msg = sm.getString("webappClassLoader.stopped", resource);
1385 IllegalStateException ise = new IllegalStateException(msg);
1386 log.info(msg, ise);
1387 throw ise;
1388 }
1389 }
1390
1391 /**
1392 * Get the Permissions for a CodeSource. If this instance
1393 * of WebappClassLoaderBase is for a web application context,
1394 * add read FilePermission for the appropriate resources.
1395 *
1396 * @param codeSource where the code was loaded from
1397 * @return PermissionCollection for CodeSource
1398 */
1399 @Override
1400 protected PermissionCollection getPermissions(CodeSource codeSource) {
1401 String codeUrl = codeSource.getLocation().toString();
1402 PermissionCollection pc;
1403 if ((pc = loaderPC.get(codeUrl)) == null) {
1404 pc = super.getPermissions(codeSource);
1405 if (pc != null) {
1406 for (Permission p : permissionList) {
1407 pc.add(p);
1408 }
1409 loaderPC.put(codeUrl,pc);
1410 }
1411 }
1412 return pc;
1413 }
1414
1415
1416 @Override
1417 public boolean check(Permission permission) {
1418 if (!Globals.IS_SECURITY_ENABLED) {
1419 return true;
1420 }
1421 Policy currentPolicy = Policy.getPolicy();
1422 if (currentPolicy != null) {
1423 URL contextRootUrl = resources.getResource("/").getCodeBase();
1424 CodeSource cs = new CodeSource(contextRootUrl, (Certificate[]) null);
1425 PermissionCollection pc = currentPolicy.getPermissions(cs);
1426 if (pc.implies(permission)) {
1427 return true;
1428 }
1429 }
1430 return false;
1431 }
1432
1433
1434 /**
1435 * {@inheritDoc}
1436 * <p>
1437 * Note that list of URLs returned by this method may not be complete. The
1438 * web application class loader accesses class loader resources via the
1439 * {@link WebResourceRoot} which supports the arbitrary mapping of
1440 * additional files, directories and contents of JAR files under
1441 * WEB-INF/classes. Any such resources will not be included in the URLs
1442 * returned here.
1443 */
1444 @Override
1445 public URL[] getURLs() {
1446 ArrayList<URL> result = new ArrayList<>();
1447 result.addAll(localRepositories);
1448 result.addAll(Arrays.asList(super.getURLs()));
1449 return result.toArray(new URL[result.size()]);
1450 }
1451
1452
1453 // ------------------------------------------------------ Lifecycle Methods
1454
1455
1456 /**
1457 * Add a lifecycle event listener to this component.
1458 *
1459 * @param listener The listener to add
1460 */
1461 @Override
1462 public void addLifecycleListener(LifecycleListener listener) {
1463 // NOOP
1464 }
1465
1466
1467 /**
1468 * Get the lifecycle listeners associated with this lifecycle. If this
1469 * Lifecycle has no listeners registered, a zero-length array is returned.
1470 */
1471 @Override
1472 public LifecycleListener[] findLifecycleListeners() {
1473 return new LifecycleListener[0];
1474 }
1475
1476
1477 /**
1478 * Remove a lifecycle event listener from this component.
1479 *
1480 * @param listener The listener to remove
1481 */
1482 @Override
1483 public void removeLifecycleListener(LifecycleListener listener) {
1484 // NOOP
1485 }
1486
1487
1488 /**
1489 * Obtain the current state of the source component.
1490 *
1491 * @return The current state of the source component.
1492 */
1493 @Override
1494 public LifecycleState getState() {
1495 return state;
1496 }
1497
1498
1499 /**
1500 * {@inheritDoc}
1501 */
1502 @Override
1503 public String getStateName() {
1504 return getState().toString();
1505 }
1506
1507
1508 @Override
1509 public void init() {
1510 state = LifecycleState.INITIALIZED;
1511 }
1512
1513
1514 /**
1515 * Start the class loader.
1516 *
1517 * @exception LifecycleException if a lifecycle error occurs
1518 */
1519 @Override
1520 public void start() throws LifecycleException {
1521
1522 state = LifecycleState.STARTING_PREP;
1523
1524 WebResource[] classesResources = resources.getResources("/WEB-INF/classes");
1525 for (WebResource classes : classesResources) {
1526 if (classes.isDirectory() && classes.canRead()) {
1527 localRepositories.add(classes.getURL());
1528 }
1529 }
1530 WebResource[] jars = resources.listResources("/WEB-INF/lib");
1531 for (WebResource jar : jars) {
1532 if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
1533 localRepositories.add(jar.getURL());
1534 jarModificationTimes.put(
1535 jar.getName(), Long.valueOf(jar.getLastModified()));
1536 }
1537 }
1538
1539 state = LifecycleState.STARTED;
1540 }
1541
1542
1543 /**
1544 * Stop the class loader.
1545 *
1546 * @exception LifecycleException if a lifecycle error occurs
1547 */
1548 @Override
1549 public void stop() throws LifecycleException {
1550
1551 state = LifecycleState.STOPPING_PREP;
1552
1553 // Clearing references should be done before setting started to
1554 // false, due to possible side effects
1555 clearReferences();
1556
1557 state = LifecycleState.STOPPING;
1558
1559 resourceEntries.clear();
1560 jarModificationTimes.clear();
1561 resources = null;
1562
1563 permissionList.clear();
1564 loaderPC.clear();
1565
1566 state = LifecycleState.STOPPED;
1567 }
1568
1569
1570 @Override
1571 public void destroy() {
1572 state = LifecycleState.DESTROYING;
1573
1574 try {
1575 super.close();
1576 } catch (IOException ioe) {
1577 log.warn(sm.getString("webappClassLoader.superCloseFail"), ioe);
1578 }
1579 state = LifecycleState.DESTROYED;
1580 }
1581
1582
1583 // ------------------------------------------------------ Protected Methods
1584
1585 protected ClassLoader getJavaseClassLoader() {
1586 return javaseClassLoader;
1587 }
1588
1589 protected void setJavaseClassLoader(ClassLoader classLoader) {
1590 if (classLoader == null) {
1591 throw new IllegalArgumentException(
1592 sm.getString("webappClassLoader.javaseClassLoaderNull"));
1593 }
1594 javaseClassLoader = classLoader;
1595 }
1596
1597 /**
1598 * Clear references.
1599 */
1600 protected void clearReferences() {
1601
1602 // If the JVM is shutting down, skip the memory leak checks
1603 if (skipMemoryLeakChecksOnJvmShutdown
1604 && !resources.getContext().getParent().getState().isAvailable()) {
1605 // During reloading / redeployment the parent is expected to be
1606 // available. Parent is not available so this might be a JVM
1607 // shutdown.
1608 try {
1609 Thread dummyHook = new Thread();
1610 Runtime.getRuntime().addShutdownHook(dummyHook);
1611 Runtime.getRuntime().removeShutdownHook(dummyHook);
1612 } catch (IllegalStateException ise) {
1613 return;
1614 }
1615 }
1616
1617 if (!JreCompat.isGraalAvailable()) {
1618 // De-register any remaining JDBC drivers
1619 clearReferencesJdbc();
1620 }
1621
1622 // Stop any threads the web application started
1623 clearReferencesThreads();
1624
1625 // Clear any references retained in the serialization caches
1626 if (clearReferencesObjectStreamClassCaches && !JreCompat.isGraalAvailable()) {
1627 clearReferencesObjectStreamClassCaches();
1628 }
1629
1630 // Check for leaks triggered by ThreadLocals loaded by this class loader
1631 if (clearReferencesThreadLocals && !JreCompat.isGraalAvailable()) {
1632 checkThreadLocalsForLeaks();
1633 }
1634
1635 // Clear RMI Targets loaded by this class loader
1636 if (clearReferencesRmiTargets) {
1637 clearReferencesRmiTargets();
1638 }
1639
1640 // Clear the IntrospectionUtils cache.
1641 IntrospectionUtils.clear();
1642
1643 // Clear the classloader reference in common-logging
1644 if (clearReferencesLogFactoryRelease) {
1645 org.apache.juli.logging.LogFactory.release(this);
1646 }
1647
1648 // Clear the classloader reference in the VM's bean introspector
1649 java.beans.Introspector.flushCaches();
1650
1651 // Clear any custom URLStreamHandlers
1652 TomcatURLStreamHandlerFactory.release(this);
1653 }
1654
1655
1656 /**
1657 * Deregister any JDBC drivers registered by the webapp that the webapp
1658 * forgot. This is made unnecessary complex because a) DriverManager
1659 * checks the class loader of the calling class (it would be much easier
1660 * if it checked the context class loader) b) using reflection would
1661 * create a dependency on the DriverManager implementation which can,
1662 * and has, changed.
1663 *
1664 * We can't just create an instance of JdbcLeakPrevention as it will be
1665 * loaded by the common class loader (since it's .class file is in the
1666 * $CATALINA_HOME/lib directory). This would fail DriverManager's check
1667 * on the class loader of the calling class. So, we load the bytes via
1668 * our parent class loader but define the class with this class loader
1669 * so the JdbcLeakPrevention looks like a webapp class to the
1670 * DriverManager.
1671 *
1672 * If only apps cleaned up after themselves...
1673 */
1674 private final void clearReferencesJdbc() {
1675 // We know roughly how big the class will be (~ 1K) so allow 2k as a
1676 // starting point
1677 byte[] classBytes = new byte[2048];
1678 int offset = 0;
1679 try (InputStream is = getResourceAsStream(
1680 "org/apache/catalina/loader/JdbcLeakPrevention.class")) {
1681 int read = is.read(classBytes, offset, classBytes.length-offset);
1682 while (read > -1) {
1683 offset += read;
1684 if (offset == classBytes.length) {
1685 // Buffer full - double size
1686 byte[] tmp = new byte[classBytes.length * 2];
1687 System.arraycopy(classBytes, 0, tmp, 0, classBytes.length);
1688 classBytes = tmp;
1689 }
1690 read = is.read(classBytes, offset, classBytes.length-offset);
1691 }
1692 Class<?> lpClass =
1693 defineClass("org.apache.catalina.loader.JdbcLeakPrevention",
1694 classBytes, 0, offset, this.getClass().getProtectionDomain());
1695 Object obj = lpClass.getConstructor().newInstance();
1696 @SuppressWarnings("unchecked")
1697 List<String> driverNames = (List<String>) obj.getClass().getMethod(
1698 "clearJdbcDriverRegistrations").invoke(obj);
1699 for (String name : driverNames) {
1700 log.warn(sm.getString("webappClassLoader.clearJdbc",
1701 getContextName(), name));
1702 }
1703 } catch (Exception e) {
1704 // So many things to go wrong above...
1705 Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
1706 ExceptionUtils.handleThrowable(t);
1707 log.warn(sm.getString(
1708 "webappClassLoader.jdbcRemoveFailed", getContextName()), t);
1709 }
1710 }
1711
1712
1713 @SuppressWarnings("deprecation") // thread.stop()
1714 private void clearReferencesThreads() {
1715 Thread[] threads = getThreads();
1716 List<Thread> executorThreadsToStop = new ArrayList<>();
1717
1718 // Iterate over the set of threads
1719 for (Thread thread : threads) {
1720 if (thread != null) {
1721 ClassLoader ccl = thread.getContextClassLoader();
1722 if (ccl == this) {
1723 // Don't warn about this thread
1724 if (thread == Thread.currentThread()) {
1725 continue;
1726 }
1727
1728 final String threadName = thread.getName();
1729
1730 // JVM controlled threads
1731 ThreadGroup tg = thread.getThreadGroup();
1732 if (tg != null && JVM_THREAD_GROUP_NAMES.contains(tg.getName())) {
1733 // HttpClient keep-alive threads
1734 if (clearReferencesHttpClientKeepAliveThread &&
1735 threadName.equals("Keep-Alive-Timer")) {
1736 thread.setContextClassLoader(parent);
1737 log.debug(sm.getString("webappClassLoader.checkThreadsHttpClient"));
1738 }
1739
1740 // Don't warn about remaining JVM controlled threads
1741 continue;
1742 }
1743
1744 // Skip threads that have already died
1745 if (!thread.isAlive()) {
1746 continue;
1747 }
1748
1749 // TimerThread can be stopped safely so treat separately
1750 // "java.util.TimerThread" in Sun/Oracle JDK
1751 // "java.util.Timer$TimerImpl" in Apache Harmony and in IBM JDK
1752 if (thread.getClass().getName().startsWith("java.util.Timer") &&
1753 clearReferencesStopTimerThreads) {
1754 clearReferencesStopTimerThread(thread);
1755 continue;
1756 }
1757
1758 if (isRequestThread(thread)) {
1759 log.warn(sm.getString("webappClassLoader.stackTraceRequestThread",
1760 getContextName(), threadName, getStackTrace(thread)));
1761 } else {
1762 log.warn(sm.getString("webappClassLoader.stackTrace",
1763 getContextName(), threadName, getStackTrace(thread)));
1764 }
1765
1766 // Don't try and stop the threads unless explicitly
1767 // configured to do so
1768 if (!clearReferencesStopThreads) {
1769 continue;
1770 }
1771
1772 // If the thread has been started via an executor, try
1773 // shutting down the executor
1774 boolean usingExecutor = false;
1775 try {
1776
1777 // Runnable wrapped by Thread
1778 // "target" in Sun/Oracle JDK
1779 // "runnable" in IBM JDK
1780 // "action" in Apache Harmony
1781 Object target = null;
1782 for (String fieldName : new String[] { "target", "runnable", "action" }) {
1783 try {
1784 Field targetField = thread.getClass().getDeclaredField(fieldName);
1785 targetField.setAccessible(true);
1786 target = targetField.get(thread);
1787 break;
1788 } catch (NoSuchFieldException nfe) {
1789 continue;
1790 }
1791 }
1792
1793 // "java.util.concurrent" code is in public domain,
1794 // so all implementations are similar
1795 if (target != null && target.getClass().getCanonicalName() != null &&
1796 target.getClass().getCanonicalName().equals(
1797 "java.util.concurrent.ThreadPoolExecutor.Worker")) {
1798 Field executorField = target.getClass().getDeclaredField("this$0");
1799 executorField.setAccessible(true);
1800 Object executor = executorField.get(target);
1801 if (executor instanceof ThreadPoolExecutor) {
1802 ((ThreadPoolExecutor) executor).shutdownNow();
1803 usingExecutor = true;
1804 }
1805 }
1806 } catch (SecurityException | NoSuchFieldException | IllegalArgumentException |
1807 IllegalAccessException e) {
1808 log.warn(sm.getString("webappClassLoader.stopThreadFail",
1809 thread.getName(), getContextName()), e);
1810 }
1811
1812 if (usingExecutor) {
1813 // Executor may take a short time to stop all the
1814 // threads. Make a note of threads that should be
1815 // stopped and check them at the end of the method.
1816 executorThreadsToStop.add(thread);
1817 } else {
1818 // This method is deprecated and for good reason. This
1819 // is very risky code but is the only option at this
1820 // point. A *very* good reason for apps to do this
1821 // clean-up themselves.
1822 thread.stop();
1823 }
1824 }
1825 }
1826 }
1827
1828 // If thread stopping is enabled, executor threads should have been
1829 // stopped above when the executor was shut down but that depends on the
1830 // thread correctly handling the interrupt. Give all the executor
1831 // threads a few seconds shutdown and if they are still running
1832 // Give threads up to 2 seconds to shutdown
1833 int count = 0;
1834 for (Thread t : executorThreadsToStop) {
1835 while (t.isAlive() && count < 100) {
1836 try {
1837 Thread.sleep(20);
1838 } catch (InterruptedException e) {
1839 // Quit the while loop
1840 break;
1841 }
1842 count++;
1843 }
1844 if (t.isAlive()) {
1845 // This method is deprecated and for good reason. This is
1846 // very risky code but is the only option at this point.
1847 // A *very* good reason for apps to do this clean-up
1848 // themselves.
1849 t.stop();
1850 }
1851 }
1852 }
1853
1854
1855 /*
1856 * Look at a threads stack trace to see if it is a request thread or not. It
1857 * isn't perfect, but it should be good-enough for most cases.
1858 */
1859 private boolean isRequestThread(Thread thread) {
1860
1861 StackTraceElement[] elements = thread.getStackTrace();
1862
1863 if (elements == null || elements.length == 0) {
1864 // Must have stopped already. Too late to ignore it. Assume not a
1865 // request processing thread.
1866 return false;
1867 }
1868
1869 // Step through the methods in reverse order looking for calls to any
1870 // CoyoteAdapter method. All request threads will have this unless
1871 // Tomcat has been heavily modified - in which case there isn't much we
1872 // can do.
1873 for (int i = 0; i < elements.length; i++) {
1874 StackTraceElement element = elements[elements.length - (i+1)];
1875 if ("org.apache.catalina.connector.CoyoteAdapter".equals(
1876 element.getClassName())) {
1877 return true;
1878 }
1879 }
1880 return false;
1881 }
1882
1883
1884 private void clearReferencesStopTimerThread(Thread thread) {
1885
1886 // Need to get references to:
1887 // in Sun/Oracle JDK:
1888 // - newTasksMayBeScheduled field (in java.util.TimerThread)
1889 // - queue field
1890 // - queue.clear()
1891 // in IBM JDK, Apache Harmony:
1892 // - cancel() method (in java.util.Timer$TimerImpl)
1893
1894 try {
1895
1896 try {
1897 Field newTasksMayBeScheduledField =
1898 thread.getClass().getDeclaredField("newTasksMayBeScheduled");
1899 newTasksMayBeScheduledField.setAccessible(true);
1900 Field queueField = thread.getClass().getDeclaredField("queue");
1901 queueField.setAccessible(true);
1902
1903 Object queue = queueField.get(thread);
1904
1905 Method clearMethod = queue.getClass().getDeclaredMethod("clear");
1906 clearMethod.setAccessible(true);
1907
1908 synchronized(queue) {
1909 newTasksMayBeScheduledField.setBoolean(thread, false);
1910 clearMethod.invoke(queue);
1911 // In case queue was already empty. Should only be one
1912 // thread waiting but use notifyAll() to be safe.
1913 queue.notifyAll();
1914 }
1915
1916 }catch (NoSuchFieldException nfe){
1917 Method cancelMethod = thread.getClass().getDeclaredMethod("cancel");
1918 synchronized(thread) {
1919 cancelMethod.setAccessible(true);
1920 cancelMethod.invoke(thread);
1921 }
1922 }
1923
1924 log.warn(sm.getString("webappClassLoader.warnTimerThread",
1925 getContextName(), thread.getName()));
1926
1927 } catch (Exception e) {
1928 // So many things to go wrong above...
1929 Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
1930 ExceptionUtils.handleThrowable(t);
1931 log.warn(sm.getString(
1932 "webappClassLoader.stopTimerThreadFail",
1933 thread.getName(), getContextName()), t);
1934 }
1935 }
1936
1937 private void checkThreadLocalsForLeaks() {
1938 Thread[] threads = getThreads();
1939
1940 try {
1941 // Make the fields in the Thread class that store ThreadLocals
1942 // accessible
1943 Field threadLocalsField =
1944 Thread.class.getDeclaredField("threadLocals");
1945 threadLocalsField.setAccessible(true);
1946 Field inheritableThreadLocalsField =
1947 Thread.class.getDeclaredField("inheritableThreadLocals");
1948 inheritableThreadLocalsField.setAccessible(true);
1949 // Make the underlying array of ThreadLoad.ThreadLocalMap.Entry objects
1950 // accessible
1951 Class<?> tlmClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
1952 Field tableField = tlmClass.getDeclaredField("table");
1953 tableField.setAccessible(true);
1954 Method expungeStaleEntriesMethod = tlmClass.getDeclaredMethod("expungeStaleEntries");
1955 expungeStaleEntriesMethod.setAccessible(true);
1956
1957 for (int i = 0; i < threads.length; i++) {
1958 Object threadLocalMap;
1959 if (threads[i] != null) {
1960
1961 // Clear the first map
1962 threadLocalMap = threadLocalsField.get(threads[i]);
1963 if (null != threadLocalMap){
1964 expungeStaleEntriesMethod.invoke(threadLocalMap);
1965 checkThreadLocalMapForLeaks(threadLocalMap, tableField);
1966 }
1967
1968 // Clear the second map
1969 threadLocalMap =inheritableThreadLocalsField.get(threads[i]);
1970 if (null != threadLocalMap){
1971 expungeStaleEntriesMethod.invoke(threadLocalMap);
1972 checkThreadLocalMapForLeaks(threadLocalMap, tableField);
1973 }
1974 }
1975 }
1976 } catch (Throwable t) {
1977 JreCompat jreCompat = JreCompat.getInstance();
1978 if (jreCompat.isInstanceOfInaccessibleObjectException(t)) {
1979 // Must be running on Java 9 without the necessary command line
1980 // options.
1981 log.warn(sm.getString("webappClassLoader.addExportsThreadLocal"));
1982 } else {
1983 ExceptionUtils.handleThrowable(t);
1984 log.warn(sm.getString(
1985 "webappClassLoader.checkThreadLocalsForLeaksFail",
1986 getContextName()), t);
1987 }
1988 }
1989 }
1990
1991
1992 /**
1993 * Analyzes the given thread local map object. Also pass in the field that
1994 * points to the internal table to save re-calculating it on every
1995 * call to this method.
1996 */
1997 private void checkThreadLocalMapForLeaks(Object map,
1998 Field internalTableField) throws IllegalAccessException,
1999 NoSuchFieldException {
2000 if (map != null) {
2001 Object[] table = (Object[]) internalTableField.get(map);
2002 if (table != null) {
2003 for (int j =0; j < table.length; j++) {
2004 Object obj = table[j];
2005 if (obj != null) {
2006 boolean keyLoadedByWebapp = false;
2007 boolean valueLoadedByWebapp = false;
2008 // Check the key
2009 Object key = ((Reference<?>) obj).get();
2010 if (this.equals(key) || loadedByThisOrChild(key)) {
2011 keyLoadedByWebapp = true;
2012 }
2013 // Check the value
2014 Field valueField =
2015 obj.getClass().getDeclaredField("value");
2016 valueField.setAccessible(true);
2017 Object value = valueField.get(obj);
2018 if (this.equals(value) || loadedByThisOrChild(value)) {
2019 valueLoadedByWebapp = true;
2020 }
2021 if (keyLoadedByWebapp || valueLoadedByWebapp) {
2022 Object[] args = new Object[5];
2023 args[0] = getContextName();
2024 if (key != null) {
2025 args[1] = getPrettyClassName(key.getClass());
2026 try {
2027 args[2] = key.toString();
2028 } catch (Exception e) {
2029 log.warn(sm.getString(
2030 "webappClassLoader.checkThreadLocalsForLeaks.badKey",
2031 args[1]), e);
2032 args[2] = sm.getString(
2033 "webappClassLoader.checkThreadLocalsForLeaks.unknown");
2034 }
2035 }
2036 if (value != null) {
2037 args[3] = getPrettyClassName(value.getClass());
2038 try {
2039 args[4] = value.toString();
2040 } catch (Exception e) {
2041 log.warn(sm.getString(
2042 "webappClassLoader.checkThreadLocalsForLeaks.badValue",
2043 args[3]), e);
2044 args[4] = sm.getString(
2045 "webappClassLoader.checkThreadLocalsForLeaks.unknown");
2046 }
2047 }
2048 if (valueLoadedByWebapp) {
2049 log.error(sm.getString(
2050 "webappClassLoader.checkThreadLocalsForLeaks",
2051 args));
2052 } else if (value == null) {
2053 if (log.isDebugEnabled()) {
2054 log.debug(sm.getString(
2055 "webappClassLoader.checkThreadLocalsForLeaksNull",
2056 args));
2057 }
2058 } else {
2059 if (log.isDebugEnabled()) {
2060 log.debug(sm.getString(
2061 "webappClassLoader.checkThreadLocalsForLeaksNone",
2062 args));
2063 }
2064 }
2065 }
2066 }
2067 }
2068 }
2069 }
2070 }
2071
2072 private String getPrettyClassName(Class<?> clazz) {
2073 String name = clazz.getCanonicalName();
2074 if (name==null){
2075 name = clazz.getName();
2076 }
2077 return name;
2078 }
2079
2080 private String getStackTrace(Thread thread) {
2081 StringBuilder builder = new StringBuilder();
2082 for (StackTraceElement ste : thread.getStackTrace()) {
2083 builder.append("\n ").append(ste);
2084 }
2085 return builder.toString();
2086 }
2087
2088 /**
2089 * @param o object to test, may be null
2090 * @return <code>true</code> if o has been loaded by the current classloader
2091 * or one of its descendants.
2092 */
2093 private boolean loadedByThisOrChild(Object o) {
2094 if (o == null) {
2095 return false;
2096 }
2097
2098 Class<?> clazz;
2099 if (o instanceof Class) {
2100 clazz = (Class<?>) o;
2101 } else {
2102 clazz = o.getClass();
2103 }
2104
2105 ClassLoader cl = clazz.getClassLoader();
2106 while (cl != null) {
2107 if (cl == this) {
2108 return true;
2109 }
2110 cl = cl.getParent();
2111 }
2112
2113 if (o instanceof Collection<?>) {
2114 Iterator<?> iter = ((Collection<?>) o).iterator();
2115 try {
2116 while (iter.hasNext()) {
2117 Object entry = iter.next();
2118 if (loadedByThisOrChild(entry)) {
2119 return true;
2120 }
2121 }
2122 } catch (ConcurrentModificationException e) {
2123 log.warn(sm.getString(
2124 "webappClassLoader.loadedByThisOrChildFail", clazz.getName(), getContextName()),
2125 e);
2126 }
2127 }
2128 return false;
2129 }
2130
2131 /*
2132 * Get the set of current threads as an array.
2133 */
2134 private Thread[] getThreads() {
2135 // Get the current thread group
2136 ThreadGroup tg = Thread.currentThread().getThreadGroup();
2137 // Find the root thread group
2138 try {
2139 while (tg.getParent() != null) {
2140 tg = tg.getParent();
2141 }
2142 } catch (SecurityException se) {
2143 String msg = sm.getString(
2144 "webappClassLoader.getThreadGroupError", tg.getName());
2145 if (log.isDebugEnabled()) {
2146 log.debug(msg, se);
2147 } else {
2148 log.warn(msg);
2149 }
2150 }
2151
2152 int threadCountGuess = tg.activeCount() + 50;
2153 Thread[] threads = new Thread[threadCountGuess];
2154 int threadCountActual = tg.enumerate(threads);
2155 // Make sure we don't miss any threads
2156 while (threadCountActual == threadCountGuess) {
2157 threadCountGuess *=2;
2158 threads = new Thread[threadCountGuess];
2159 // Note tg.enumerate(Thread[]) silently ignores any threads that
2160 // can't fit into the array
2161 threadCountActual = tg.enumerate(threads);
2162 }
2163
2164 return threads;
2165 }
2166
2167
2168 /**
2169 * This depends on the internals of the Sun JVM so it does everything by
2170 * reflection.
2171 */
2172 private void clearReferencesRmiTargets() {
2173 try {
2174 // Need access to the ccl field of sun.rmi.transport.Target to find
2175 // the leaks
2176 Class<?> objectTargetClass =
2177 Class.forName("sun.rmi.transport.Target");
2178 Field cclField = objectTargetClass.getDeclaredField("ccl");
2179 cclField.setAccessible(true);
2180 // Need access to the stub field to report the leaks
2181 Field stubField = objectTargetClass.getDeclaredField("stub");
2182 stubField.setAccessible(true);
2183
2184 // Clear the objTable map
2185 Class<?> objectTableClass = Class.forName("sun.rmi.transport.ObjectTable");
2186 Field objTableField = objectTableClass.getDeclaredField("objTable");
2187 objTableField.setAccessible(true);
2188 Object objTable = objTableField.get(null);
2189 if (objTable == null) {
2190 return;
2191 }
2192 Field tableLockField = objectTableClass.getDeclaredField("tableLock");
2193 tableLockField.setAccessible(true);
2194 Object tableLock = tableLockField.get(null);
2195
2196 synchronized (tableLock) {
2197 // Iterate over the values in the table
2198 if (objTable instanceof Map<?,?>) {
2199 Iterator<?> iter = ((Map<?,?>) objTable).values().iterator();
2200 while (iter.hasNext()) {
2201 Object obj = iter.next();
2202 Object cclObject = cclField.get(obj);
2203 if (this == cclObject) {
2204 iter.remove();
2205 Object stubObject = stubField.get(obj);
2206 log.error(sm.getString("webappClassLoader.clearRmi",
2207 stubObject.getClass().getName(), stubObject));
2208 }
2209 }
2210 }
2211
2212 // Clear the implTable map
2213 Field implTableField = objectTableClass.getDeclaredField("implTable");
2214 implTableField.setAccessible(true);
2215 Object implTable = implTableField.get(null);
2216 if (implTable == null) {
2217 return;
2218 }
2219
2220 // Iterate over the values in the table
2221 if (implTable instanceof Map<?,?>) {
2222 Iterator<?> iter = ((Map<?,?>) implTable).values().iterator();
2223 while (iter.hasNext()) {
2224 Object obj = iter.next();
2225 Object cclObject = cclField.get(obj);
2226 if (this == cclObject) {
2227 iter.remove();
2228 }
2229 }
2230 }
2231 }
2232 } catch (ClassNotFoundException e) {
2233 log.info(sm.getString("webappClassLoader.clearRmiInfo",
2234 getContextName()), e);
2235 } catch (SecurityException | NoSuchFieldException | IllegalArgumentException |
2236 IllegalAccessException e) {
2237 log.warn(sm.getString("webappClassLoader.clearRmiFail",
2238 getContextName()), e);
2239 } catch (Exception e) {
2240 JreCompat jreCompat = JreCompat.getInstance();
2241 if (jreCompat.isInstanceOfInaccessibleObjectException(e)) {
2242 // Must be running on Java 9 without the necessary command line
2243 // options.
2244 log.warn(sm.getString("webappClassLoader.addExportsRmi"));
2245 } else {
2246 // Re-throw all other exceptions
2247 throw e;
2248 }
2249 }
2250 }
2251
2252
2253 private void clearReferencesObjectStreamClassCaches() {
2254 try {
2255 Class<?> clazz = Class.forName("java.io.ObjectStreamClass$Caches");
2256 clearCache(clazz, "localDescs");
2257 clearCache(clazz, "reflectors");
2258 } catch (ReflectiveOperationException | SecurityException | ClassCastException e) {
2259 log.warn(sm.getString(
2260 "webappClassLoader.clearObjectStreamClassCachesFail", getContextName()), e);
2261 }
2262 }
2263
2264
2265 private void clearCache(Class<?> target, String mapName)
2266 throws ReflectiveOperationException, SecurityException, ClassCastException {
2267 Field f = target.getDeclaredField(mapName);
2268 f.setAccessible(true);
2269 Map<?,?> map = (Map<?,?>) f.get(null);
2270 Iterator<?> keys = map.keySet().iterator();
2271 while (keys.hasNext()) {
2272 Object key = keys.next();
2273 if (key instanceof Reference) {
2274 Object clazz = ((Reference<?>) key).get();
2275 if (loadedByThisOrChild(clazz)) {
2276 keys.remove();
2277 }
2278 }
2279 }
2280 }
2281
2282
2283 /**
2284 * Find specified class in local repositories.
2285 *
2286 * @param name The binary name of the class to be loaded
2287 *
2288 * @return the loaded class, or null if the class isn't found
2289 */
2290 protected Class<?> findClassInternal(String name) {
2291
2292 checkStateForResourceLoading(name);
2293
2294 if (name == null) {
2295 return null;
2296 }
2297 String path = binaryNameToPath(name, true);
2298
2299 ResourceEntry entry = resourceEntries.get(path);
2300 WebResource resource = null;
2301
2302 if (entry == null) {
2303 resource = resources.getClassLoaderResource(path);
2304
2305 if (!resource.exists()) {
2306 return null;
2307 }
2308
2309 entry = new ResourceEntry();
2310 entry.lastModified = resource.getLastModified();
2311
2312 // Add the entry in the local resource repository
2313 synchronized (resourceEntries) {
2314 // Ensures that all the threads which may be in a race to load
2315 // a particular class all end up with the same ResourceEntry
2316 // instance
2317 ResourceEntry entry2 = resourceEntries.get(path);
2318 if (entry2 == null) {
2319 resourceEntries.put(path, entry);
2320 } else {
2321 entry = entry2;
2322 }
2323 }
2324 }
2325
2326 Class<?> clazz = entry.loadedClass;
2327 if (clazz != null)
2328 return clazz;
2329
2330 synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
2331 clazz = entry.loadedClass;
2332 if (clazz != null)
2333 return clazz;
2334
2335 if (resource == null) {
2336 resource = resources.getClassLoaderResource(path);
2337 }
2338
2339 if (!resource.exists()) {
2340 return null;
2341 }
2342
2343 byte[] binaryContent = resource.getContent();
2344 if (binaryContent == null) {
2345 // Something went wrong reading the class bytes (and will have
2346 // been logged at debug level).
2347 return null;
2348 }
2349 Manifest manifest = resource.getManifest();
2350 URL codeBase = resource.getCodeBase();
2351 Certificate[] certificates = resource.getCertificates();
2352
2353 if (transformers.size() > 0) {
2354 // If the resource is a class just being loaded, decorate it
2355 // with any attached transformers
2356
2357 // Ignore leading '/' and trailing CLASS_FILE_SUFFIX
2358 // Should be cheaper than replacing '.' by '/' in class name.
2359 String internalName = path.substring(1, path.length() - CLASS_FILE_SUFFIX.length());
2360
2361 for (ClassFileTransformer transformer : this.transformers) {
2362 try {
2363 byte[] transformed = transformer.transform(
2364 this, internalName, null, null, binaryContent);
2365 if (transformed != null) {
2366 binaryContent = transformed;
2367 }
2368 } catch (IllegalClassFormatException e) {
2369 log.error(sm.getString("webappClassLoader.transformError", name), e);
2370 return null;
2371 }
2372 }
2373 }
2374
2375 // Looking up the package
2376 String packageName = null;
2377 int pos = name.lastIndexOf('.');
2378 if (pos != -1)
2379 packageName = name.substring(0, pos);
2380
2381 Package pkg = null;
2382
2383 if (packageName != null) {
2384 pkg = getPackage(packageName);
2385 // Define the package (if null)
2386 if (pkg == null) {
2387 try {
2388 if (manifest == null) {
2389 definePackage(packageName, null, null, null, null, null, null, null);
2390 } else {
2391 definePackage(packageName, manifest, codeBase);
2392 }
2393 } catch (IllegalArgumentException e) {
2394 // Ignore: normal error due to dual definition of package
2395 }
2396 pkg = getPackage(packageName);
2397 }
2398 }
2399
2400 if (securityManager != null) {
2401
2402 // Checking sealing
2403 if (pkg != null) {
2404 boolean sealCheck = true;
2405 if (pkg.isSealed()) {
2406 sealCheck = pkg.isSealed(codeBase);
2407 } else {
2408 sealCheck = (manifest == null) || !isPackageSealed(packageName, manifest);
2409 }
2410 if (!sealCheck)
2411 throw new SecurityException
2412 ("Sealing violation loading " + name + " : Package "
2413 + packageName + " is sealed.");
2414 }
2415
2416 }
2417
2418 try {
2419 clazz = defineClass(name, binaryContent, 0,
2420 binaryContent.length, new CodeSource(codeBase, certificates));
2421 } catch (UnsupportedClassVersionError ucve) {
2422 throw new UnsupportedClassVersionError(
2423 ucve.getLocalizedMessage() + " " +
2424 sm.getString("webappClassLoader.wrongVersion",
2425 name));
2426 }
2427 entry.loadedClass = clazz;
2428 }
2429
2430 return clazz;
2431 }
2432
2433
2434 private String binaryNameToPath(String binaryName, boolean withLeadingSlash) {
2435 // 1 for leading '/', 6 for ".class"
2436 StringBuilder path = new StringBuilder(7 + binaryName.length());
2437 if (withLeadingSlash) {
2438 path.append('/');
2439 }
2440 path.append(binaryName.replace('.', '/'));
2441 path.append(CLASS_FILE_SUFFIX);
2442 return path.toString();
2443 }
2444
2445
2446 private String nameToPath(String name) {
2447 if (name.startsWith("/")) {
2448 return name;
2449 }
2450 StringBuilder path = new StringBuilder(
2451 1 + name.length());
2452 path.append('/');
2453 path.append(name);
2454 return path.toString();
2455 }
2456
2457
2458 /**
2459 * Returns true if the specified package name is sealed according to the
2460 * given manifest.
2461 *
2462 * @param name Path name to check
2463 * @param man Associated manifest
2464 * @return <code>true</code> if the manifest associated says it is sealed
2465 */
2466 protected boolean isPackageSealed(String name, Manifest man) {
2467
2468 String path = name.replace('.', '/') + '/';
2469 Attributes attr = man.getAttributes(path);
2470 String sealed = null;
2471 if (attr != null) {
2472 sealed = attr.getValue(Name.SEALED);
2473 }
2474 if (sealed == null) {
2475 if ((attr = man.getMainAttributes()) != null) {
2476 sealed = attr.getValue(Name.SEALED);
2477 }
2478 }
2479 return "true".equalsIgnoreCase(sealed);
2480
2481 }
2482
2483
2484 /**
2485 * Finds the class with the given name if it has previously been
2486 * loaded and cached by this class loader, and return the Class object.
2487 * If this class has not been cached, return <code>null</code>.
2488 *
2489 * @param name The binary name of the resource to return
2490 * @return a loaded class
2491 */
2492 protected Class<?> findLoadedClass0(String name) {
2493
2494 String path = binaryNameToPath(name, true);
2495
2496 ResourceEntry entry = resourceEntries.get(path);
2497 if (entry != null) {
2498 return entry.loadedClass;
2499 }
2500 return null;
2501 }
2502
2503
2504 /**
2505 * Refresh the system policy file, to pick up eventual changes.
2506 */
2507 protected void refreshPolicy() {
2508
2509 try {
2510 // The policy file may have been modified to adjust
2511 // permissions, so we're reloading it when loading or
2512 // reloading a Context
2513 Policy policy = Policy.getPolicy();
2514 policy.refresh();
2515 } catch (AccessControlException e) {
2516 // Some policy files may restrict this, even for the core,
2517 // so this exception is ignored
2518 }
2519
2520 }
2521
2522
2523 /**
2524 * Filter classes.
2525 *
2526 * @param name class name
2527 * @param isClassName <code>true</code> if name is a class name,
2528 * <code>false</code> if name is a resource name
2529 * @return <code>true</code> if the class should be filtered
2530 */
2531 protected boolean filter(String name, boolean isClassName) {
2532
2533 if (name == null)
2534 return false;
2535
2536 char ch;
2537 if (name.startsWith("javax")) {
2538 /* 5 == length("javax") */
2539 if (name.length() == 5) {
2540 return false;
2541 }
2542 ch = name.charAt(5);
2543 if (isClassName && ch == '.') {
2544 /* 6 == length("javax.") */
2545 if (name.startsWith("servlet.jsp.jstl.", 6)) {
2546 return false;
2547 }
2548 if (name.startsWith("el.", 6) ||
2549 name.startsWith("servlet.", 6) ||
2550 name.startsWith("websocket.", 6) ||
2551 name.startsWith("security.auth.message.", 6)) {
2552 return true;
2553 }
2554 } else if (!isClassName && ch == '/') {
2555 /* 6 == length("javax/") */
2556 if (name.startsWith("servlet/jsp/jstl/", 6)) {
2557 return false;
2558 }
2559 if (name.startsWith("el/", 6) ||
2560 name.startsWith("servlet/", 6) ||
2561 name.startsWith("websocket/", 6) ||
2562 name.startsWith("security/auth/message/", 6)) {
2563 return true;
2564 }
2565 }
2566 } else if (name.startsWith("org")) {
2567 /* 3 == length("org") */
2568 if (name.length() == 3) {
2569 return false;
2570 }
2571 ch = name.charAt(3);
2572 if (isClassName && ch == '.') {
2573 /* 4 == length("org.") */
2574 if (name.startsWith("apache.", 4)) {
2575 /* 11 == length("org.apache.") */
2576 if (name.startsWith("tomcat.jdbc.", 11)) {
2577 return false;
2578 }
2579 if (name.startsWith("el.", 11) ||
2580 name.startsWith("catalina.", 11) ||
2581 name.startsWith("jasper.", 11) ||
2582 name.startsWith("juli.", 11) ||
2583 name.startsWith("tomcat.", 11) ||
2584 name.startsWith("naming.", 11) ||
2585 name.startsWith("coyote.", 11)) {
2586 return true;
2587 }
2588 }
2589 } else if (!isClassName && ch == '/') {
2590 /* 4 == length("org/") */
2591 if (name.startsWith("apache/", 4)) {
2592 /* 11 == length("org/apache/") */
2593 if (name.startsWith("tomcat/jdbc/", 11)) {
2594 return false;
2595 }
2596 if (name.startsWith("el/", 11) ||
2597 name.startsWith("catalina/", 11) ||
2598 name.startsWith("jasper/", 11) ||
2599 name.startsWith("juli/", 11) ||
2600 name.startsWith("tomcat/", 11) ||
2601 name.startsWith("naming/", 11) ||
2602 name.startsWith("coyote/", 11)) {
2603 return true;
2604 }
2605 }
2606 }
2607 }
2608 return false;
2609 }
2610
2611
2612 @Override
2613 protected void addURL(URL url) {
2614 super.addURL(url);
2615 hasExternalRepositories = true;
2616 }
2617
2618
2619 @Override
2620 public String getWebappName() {
2621 return getContextName();
2622 }
2623
2624
2625 @Override
2626 public String getHostName() {
2627 if (resources != null) {
2628 Container host = resources.getContext().getParent();
2629 if (host != null) {
2630 return host.getName();
2631 }
2632 }
2633 return null;
2634 }
2635
2636
2637 @Override
2638 public String getServiceName() {
2639 if (resources != null) {
2640 Container host = resources.getContext().getParent();
2641 if (host != null) {
2642 Container engine = host.getParent();
2643 if (engine != null) {
2644 return engine.getName();
2645 }
2646 }
2647 }
2648 return null;
2649 }
2650
2651
2652 @Override
2653 public boolean hasLoggingConfig() {
2654 if (Globals.IS_SECURITY_ENABLED) {
2655 Boolean result = AccessController.doPrivileged(new PrivilegedHasLoggingConfig());
2656 return result.booleanValue();
2657 } else {
2658 return findResource("logging.properties") != null;
2659 }
2660 }
2661
2662
2663 private class PrivilegedHasLoggingConfig implements PrivilegedAction<Boolean> {
2664
2665 @Override
2666 public Boolean run() {
2667 return Boolean.valueOf(findResource("logging.properties") != null);
2668 }
2669 }
2670
2671
2672 private static class CombinedEnumeration implements Enumeration<URL> {
2673
2674 private final Enumeration<URL>[] sources;
2675 private int index = 0;
2676
2677 public CombinedEnumeration(Enumeration<URL> enum1, Enumeration<URL> enum2) {
2678 @SuppressWarnings("unchecked")
2679 Enumeration<URL>[] sources = new Enumeration[] { enum1, enum2 };
2680 this.sources = sources;
2681 }
2682
2683
2684 @Override
2685 public boolean hasMoreElements() {
2686 return inc();
2687 }
2688
2689
2690 @Override
2691 public URL nextElement() {
2692 if (inc()) {
2693 return sources[index].nextElement();
2694 }
2695 throw new NoSuchElementException();
2696 }
2697
2698
2699 private boolean inc() {
2700 while (index < sources.length) {
2701 if (sources[index].hasMoreElements()) {
2702 return true;
2703 }
2704 index++;
2705 }
2706 return false;
2707 }
2708 }
2709 }
2710