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 defaultthis 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 truethis 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 classif 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, nullnull, 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, nullnullnullnullnullnullnull);
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