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
18
19 package org.apache.catalina.loader;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.File;
23 import java.io.FileOutputStream;
24 import java.io.FilePermission;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.lang.ref.Reference;
28 import java.lang.ref.WeakReference;
29 import java.lang.reflect.Field;
30 import java.lang.reflect.InvocationTargetException;
31 import java.lang.reflect.Method;
32 import java.lang.reflect.Modifier;
33 import java.net.MalformedURLException;
34 import java.net.URL;
35 import java.net.URLClassLoader;
36 import java.security.AccessControlException;
37 import java.security.AccessController;
38 import java.security.CodeSource;
39 import java.security.Permission;
40 import java.security.PermissionCollection;
41 import java.security.Policy;
42 import java.security.PrivilegedAction;
43 import java.util.ArrayList;
44 import java.util.Collection;
45 import java.util.Enumeration;
46 import java.util.HashMap;
47 import java.util.Iterator;
48 import java.util.LinkedHashMap;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.ResourceBundle;
52 import java.util.Set;
53 import java.util.Vector;
54 import java.util.concurrent.ThreadPoolExecutor;
55 import java.util.jar.Attributes;
56 import java.util.jar.JarEntry;
57 import java.util.jar.JarFile;
58 import java.util.jar.Manifest;
59 import java.util.jar.Attributes.Name;
60
61 import javax.naming.NameClassPair;
62 import javax.naming.NamingEnumeration;
63 import javax.naming.NamingException;
64 import javax.naming.directory.DirContext;
65
66 import org.apache.catalina.Globals;
67 import org.apache.catalina.Lifecycle;
68 import org.apache.catalina.LifecycleException;
69 import org.apache.catalina.LifecycleListener;
70 import org.apache.catalina.util.StringManager;
71 import org.apache.jasper.servlet.JasperLoader;
72 import org.apache.naming.JndiPermission;
73 import org.apache.naming.resources.Resource;
74 import org.apache.naming.resources.ResourceAttributes;
75 import org.apache.tomcat.util.IntrospectionUtils;
76
77 /**
78 * Specialized web application class loader.
79 * <p>
80 * This class loader is a full reimplementation of the
81 * <code>URLClassLoader</code> from the JDK. It is designed to be fully
82 * compatible with a normal <code>URLClassLoader</code>, although its internal
83 * behavior may be completely different.
84 * <p>
85 * <strong>IMPLEMENTATION NOTE</strong> - This class loader faithfully follows
86 * the delegation model recommended in the specification. The system class
87 * loader will be queried first, then the local repositories, and only then
88 * delegation to the parent class loader will occur. This allows the web
89 * application to override any shared class except the classes from J2SE.
90 * Special handling is provided from the JAXP XML parser interfaces, the JNDI
91 * interfaces, and the classes from the servlet API, which are never loaded
92 * from the webapp repository.
93 * <p>
94 * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper
95 * compilation technology, any repository which contains classes from
96 * the servlet API will be ignored by the class loader.
97 * <p>
98 * <strong>IMPLEMENTATION NOTE</strong> - The class loader generates source
99 * URLs which include the full JAR URL when a class is loaded from a JAR file,
100 * which allows setting security permission at the class level, even when a
101 * class is contained inside a JAR.
102 * <p>
103 * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in
104 * the order they are added via the initial constructor and/or any subsequent
105 * calls to <code>addRepository()</code> or <code>addJar()</code>.
106 * <p>
107 * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
108 * security is made unless a security manager is present.
109 *
110 * @author Remy Maucherat
111 * @author Craig R. McClanahan
112 * @version $Revision: 915603 $ $Date: 2010-02-24 01:07:06 +0100 (Wed, 24 Feb 2010) $
113 */
114 public class WebappClassLoader
115 extends URLClassLoader
116 implements Reloader, Lifecycle
117 {
118
119 protected static org.apache.juli.logging.Log log=
120 org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
121
122 /**
123 * List of ThreadGroup names to ignore when scanning for web application
124 * started threads that need to be shut down.
125 */
126 private static final List<String> JVM_THREAD_GROUP_NAMES =
127 new ArrayList<String>();
128
129 public static final boolean ENABLE_CLEAR_REFERENCES =
130 Boolean.valueOf(System.getProperty("org.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES", "true")).booleanValue();
131
132 /**
133 * @deprecated Not used
134 */
135 protected class PrivilegedFindResource
136 implements PrivilegedAction {
137
138 protected File file;
139 protected String path;
140
141 PrivilegedFindResource(File file, String path) {
142 this.file = file;
143 this.path = path;
144 }
145
146 public Object run() {
147 return findResourceInternal(file, path);
148 }
149
150 }
151
152 static {
153 JVM_THREAD_GROUP_NAMES.add("system");
154 JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
155 }
156
157 protected class PrivilegedFindResourceByName
158 implements PrivilegedAction<ResourceEntry> {
159
160 protected String name;
161 protected String path;
162
163 PrivilegedFindResourceByName(String name, String path) {
164 this.name = name;
165 this.path = path;
166 }
167
168 public ResourceEntry run() {
169 return findResourceInternal(name, path);
170 }
171
172 }
173
174
175 protected final class PrivilegedGetClassLoader
176 implements PrivilegedAction<ClassLoader> {
177
178 public Class<?> clazz;
179
180 public PrivilegedGetClassLoader(Class<?> clazz){
181 this.clazz = clazz;
182 }
183
184 public ClassLoader run() {
185 return clazz.getClassLoader();
186 }
187 }
188
189
190
191
192 // ------------------------------------------------------- Static Variables
193
194
195 /**
196 * The set of trigger classes that will cause a proposed repository not
197 * to be added if this class is visible to the class loader that loaded
198 * this factory class. Typically, trigger classes will be listed for
199 * components that have been integrated into the JDK for later versions,
200 * but where the corresponding JAR files are required to run on
201 * earlier versions.
202 */
203 protected static final String[] triggers = {
204 "javax.servlet.Servlet" // Servlet API
205 };
206
207
208 /**
209 * Set of package names which are not allowed to be loaded from a webapp
210 * class loader without delegating first.
211 */
212 protected static final String[] packageTriggers = {
213 };
214
215
216 /**
217 * The string manager for this package.
218 */
219 protected static final StringManager sm =
220 StringManager.getManager(Constants.Package);
221
222
223 /**
224 * Use anti JAR locking code, which does URL rerouting when accessing
225 * resources.
226 */
227 boolean antiJARLocking = false;
228
229
230 // ----------------------------------------------------------- Constructors
231
232
233 /**
234 * Construct a new ClassLoader with no defined repositories and no
235 * parent ClassLoader.
236 */
237 public WebappClassLoader() {
238
239 super(new URL[0]);
240 this.parent = getParent();
241 system = getSystemClassLoader();
242 securityManager = System.getSecurityManager();
243
244 if (securityManager != null) {
245 refreshPolicy();
246 }
247
248 }
249
250
251 /**
252 * Construct a new ClassLoader with no defined repositories and no
253 * parent ClassLoader.
254 */
255 public WebappClassLoader(ClassLoader parent) {
256
257 super(new URL[0], parent);
258
259 this.parent = getParent();
260
261 system = getSystemClassLoader();
262 securityManager = System.getSecurityManager();
263
264 if (securityManager != null) {
265 refreshPolicy();
266 }
267 }
268
269
270 // ----------------------------------------------------- Instance Variables
271
272
273 /**
274 * Associated directory context giving access to the resources in this
275 * webapp.
276 */
277 protected DirContext resources = null;
278
279
280 /**
281 * The cache of ResourceEntry for classes and resources we have loaded,
282 * keyed by resource name.
283 */
284 protected HashMap resourceEntries = new HashMap();
285
286
287 /**
288 * The list of not found resources.
289 */
290 protected HashMap<String, String> notFoundResources =
291 new LinkedHashMap<String, String>() {
292 private static final long serialVersionUID = 1L;
293 protected boolean removeEldestEntry(
294 Map.Entry<String, String> eldest) {
295 return size() > 1000;
296 }
297 };
298
299
300 /**
301 * Should this class loader delegate to the parent class loader
302 * <strong>before</strong> searching its own repositories (i.e. the
303 * usual Java2 delegation model)? If set to <code>false</code>,
304 * this class loader will search its own repositories first, and
305 * delegate to the parent only if the class or resource is not
306 * found locally.
307 */
308 protected boolean delegate = false;
309
310
311 /**
312 * Last time a JAR was accessed.
313 */
314 protected long lastJarAccessed = 0L;
315
316
317 /**
318 * The list of local repositories, in the order they should be searched
319 * for locally loaded classes or resources.
320 */
321 protected String[] repositories = new String[0];
322
323
324 /**
325 * Repositories URLs, used to cache the result of getURLs.
326 */
327 protected URL[] repositoryURLs = null;
328
329
330 /**
331 * Repositories translated as path in the work directory (for Jasper
332 * originally), but which is used to generate fake URLs should getURLs be
333 * called.
334 */
335 protected File[] files = new File[0];
336
337
338 /**
339 * The list of JARs, in the order they should be searched
340 * for locally loaded classes or resources.
341 */
342 protected JarFile[] jarFiles = new JarFile[0];
343
344
345 /**
346 * The list of JARs, in the order they should be searched
347 * for locally loaded classes or resources.
348 */
349 protected File[] jarRealFiles = new File[0];
350
351
352 /**
353 * The path which will be monitored for added Jar files.
354 */
355 protected String jarPath = null;
356
357
358 /**
359 * The list of JARs, in the order they should be searched
360 * for locally loaded classes or resources.
361 */
362 protected String[] jarNames = new String[0];
363
364
365 /**
366 * The list of JARs last modified dates, in the order they should be
367 * searched for locally loaded classes or resources.
368 */
369 protected long[] lastModifiedDates = new long[0];
370
371
372 /**
373 * The list of resources which should be checked when checking for
374 * modifications.
375 */
376 protected String[] paths = new String[0];
377
378
379 /**
380 * A list of read File and Jndi Permission's required if this loader
381 * is for a web application context.
382 */
383 protected ArrayList permissionList = new ArrayList();
384
385
386 /**
387 * Path where resources loaded from JARs will be extracted.
388 */
389 protected File loaderDir = null;
390 protected String canonicalLoaderDir = null;
391
392 /**
393 * The PermissionCollection for each CodeSource for a web
394 * application context.
395 */
396 protected HashMap loaderPC = new HashMap();
397
398
399 /**
400 * Instance of the SecurityManager installed.
401 */
402 protected SecurityManager securityManager = null;
403
404
405 /**
406 * The parent class loader.
407 */
408 protected ClassLoader parent = null;
409
410
411 /**
412 * The system class loader.
413 */
414 protected ClassLoader system = null;
415
416
417 /**
418 * Has this component been started?
419 */
420 protected boolean started = false;
421
422
423 /**
424 * Has external repositories.
425 */
426 protected boolean hasExternalRepositories = false;
427
428 /**
429 * need conversion for properties files
430 */
431 protected boolean needConvert = false;
432
433
434 /**
435 * All permission.
436 */
437 protected Permission allPermission = new java.security.AllPermission();
438
439
440 /**
441 * Should Tomcat attempt to terminate threads that have been started by the
442 * web application? Stopping threads is performed via the deprecated (for
443 * good reason) <code>Thread.stop()</code> method and is likely to result in
444 * instability. As such, enabling this should be viewed as an option of last
445 * resort in a development environment and is not recommended in a
446 * production environment. If not specified, the default value of
447 * <code>false</code> will be used. Note that instances of
448 * java.util.TimerThread will always be terminate since a safe method exists
449 * to do so.
450 */
451 private boolean clearReferencesStopThreads = false;
452
453 /**
454 * Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()}
455 * when the class loader is stopped? If not specified, the default value
456 * of <code>true</code> is used. Changing the default setting is likely to
457 * lead to memory leaks and other issues.
458 */
459 private boolean clearReferencesLogFactoryRelease = true;
460
461 // ------------------------------------------------------------- Properties
462
463
464 /**
465 * Get associated resources.
466 */
467 public DirContext getResources() {
468
469 return this.resources;
470
471 }
472
473
474 /**
475 * Set associated resources.
476 */
477 public void setResources(DirContext resources) {
478
479 this.resources = resources;
480
481 }
482
483
484 /**
485 * Return the "delegate first" flag for this class loader.
486 */
487 public boolean getDelegate() {
488
489 return (this.delegate);
490
491 }
492
493
494 /**
495 * Set the "delegate first" flag for this class loader.
496 *
497 * @param delegate The new "delegate first" flag
498 */
499 public void setDelegate(boolean delegate) {
500
501 this.delegate = delegate;
502
503 }
504
505
506 /**
507 * @return Returns the antiJARLocking.
508 */
509 public boolean getAntiJARLocking() {
510 return antiJARLocking;
511 }
512
513
514 /**
515 * @param antiJARLocking The antiJARLocking to set.
516 */
517 public void setAntiJARLocking(boolean antiJARLocking) {
518 this.antiJARLocking = antiJARLocking;
519 }
520
521
522 /**
523 * If there is a Java SecurityManager create a read FilePermission
524 * or JndiPermission for the file directory path.
525 *
526 * @param path file directory path
527 */
528 public void addPermission(String path) {
529 if (path == null) {
530 return;
531 }
532
533 if (securityManager != null) {
534 Permission permission = null;
535 if( path.startsWith("jndi:") || path.startsWith("jar:jndi:") ) {
536 if (!path.endsWith("/")) {
537 path = path + "/";
538 }
539 permission = new JndiPermission(path + "*");
540 addPermission(permission);
541 } else {
542 if (!path.endsWith(File.separator)) {
543 permission = new FilePermission(path, "read");
544 addPermission(permission);
545 path = path + File.separator;
546 }
547 permission = new FilePermission(path + "-", "read");
548 addPermission(permission);
549 }
550 }
551 }
552
553
554 /**
555 * If there is a Java SecurityManager create a read FilePermission
556 * or JndiPermission for URL.
557 *
558 * @param url URL for a file or directory on local system
559 */
560 public void addPermission(URL url) {
561 if (url != null) {
562 addPermission(url.toString());
563 }
564 }
565
566
567 /**
568 * If there is a Java SecurityManager create a Permission.
569 *
570 * @param permission The permission
571 */
572 public void addPermission(Permission permission) {
573 if ((securityManager != null) && (permission != null)) {
574 permissionList.add(permission);
575 }
576 }
577
578
579 /**
580 * Return the JAR path.
581 */
582 public String getJarPath() {
583
584 return this.jarPath;
585
586 }
587
588
589 /**
590 * Change the Jar path.
591 */
592 public void setJarPath(String jarPath) {
593
594 this.jarPath = jarPath;
595
596 }
597
598
599 /**
600 * Change the work directory.
601 */
602 public void setWorkDir(File workDir) {
603 this.loaderDir = new File(workDir, "loader");
604 if (loaderDir == null) {
605 canonicalLoaderDir = null;
606 } else {
607 try {
608 canonicalLoaderDir = loaderDir.getCanonicalPath();
609 if (!canonicalLoaderDir.endsWith(File.separator)) {
610 canonicalLoaderDir += File.separator;
611 }
612 } catch (IOException ioe) {
613 canonicalLoaderDir = null;
614 }
615 }
616 }
617
618 /**
619 * Utility method for use in subclasses.
620 * Must be called before Lifecycle methods to have any effect.
621 */
622 protected void setParentClassLoader(ClassLoader pcl) {
623 parent = pcl;
624 }
625
626 /**
627 * Return the clearReferencesStopThreads flag for this Context.
628 */
629 public boolean getClearReferencesStopThreads() {
630 return (this.clearReferencesStopThreads);
631 }
632
633
634 /**
635 * Set the clearReferencesStopThreads feature for this Context.
636 *
637 * @param clearReferencesStopThreads The new flag value
638 */
639 public void setClearReferencesStopThreads(
640 boolean clearReferencesStopThreads) {
641 this.clearReferencesStopThreads = clearReferencesStopThreads;
642 }
643
644
645 /**
646 * Return the clearReferencesLogFactoryRelease flag for this Context.
647 */
648 public boolean getClearReferencesLogFactoryRelease() {
649 return (this.clearReferencesLogFactoryRelease);
650 }
651
652
653 /**
654 * Set the clearReferencesLogFactoryRelease feature for this Context.
655 *
656 * @param clearReferencesLogFactoryRelease The new flag value
657 */
658 public void setClearReferencesLogFactoryRelease(
659 boolean clearReferencesLogFactoryRelease) {
660 this.clearReferencesLogFactoryRelease =
661 clearReferencesLogFactoryRelease;
662 }
663
664
665 // ------------------------------------------------------- Reloader Methods
666
667
668 /**
669 * Add a new repository to the set of places this ClassLoader can look for
670 * classes to be loaded.
671 *
672 * @param repository Name of a source of classes to be loaded, such as a
673 * directory pathname, a JAR file pathname, or a ZIP file pathname
674 *
675 * @exception IllegalArgumentException if the specified repository is
676 * invalid or does not exist
677 */
678 public void addRepository(String repository) {
679
680 // Ignore any of the standard repositories, as they are set up using
681 // either addJar or addRepository
682 if (repository.startsWith("/WEB-INF/lib")
683 || repository.startsWith("/WEB-INF/classes"))
684 return;
685
686 // Add this repository to our underlying class loader
687 try {
688 URL url = new URL(repository);
689 super.addURL(url);
690 hasExternalRepositories = true;
691 repositoryURLs = null;
692 } catch (MalformedURLException e) {
693 IllegalArgumentException iae = new IllegalArgumentException
694 ("Invalid repository: " + repository);
695 iae.initCause(e);
696 throw iae;
697 }
698
699 }
700
701
702 /**
703 * Add a new repository to the set of places this ClassLoader can look for
704 * classes to be loaded.
705 *
706 * @param repository Name of a source of classes to be loaded, such as a
707 * directory pathname, a JAR file pathname, or a ZIP file pathname
708 *
709 * @exception IllegalArgumentException if the specified repository is
710 * invalid or does not exist
711 */
712 synchronized void addRepository(String repository, File file) {
713
714 // Note : There should be only one (of course), but I think we should
715 // keep this a bit generic
716
717 if (repository == null)
718 return;
719
720 if (log.isDebugEnabled())
721 log.debug("addRepository(" + repository + ")");
722
723 int i;
724
725 // Add this repository to our internal list
726 String[] result = new String[repositories.length + 1];
727 for (i = 0; i < repositories.length; i++) {
728 result[i] = repositories[i];
729 }
730 result[repositories.length] = repository;
731 repositories = result;
732
733 // Add the file to the list
734 File[] result2 = new File[files.length + 1];
735 for (i = 0; i < files.length; i++) {
736 result2[i] = files[i];
737 }
738 result2[files.length] = file;
739 files = result2;
740
741 }
742
743
744 synchronized void addJar(String jar, JarFile jarFile, File file)
745 throws IOException {
746
747 if (jar == null)
748 return;
749 if (jarFile == null)
750 return;
751 if (file == null)
752 return;
753
754 if (log.isDebugEnabled())
755 log.debug("addJar(" + jar + ")");
756
757 int i;
758
759 if ((jarPath != null) && (jar.startsWith(jarPath))) {
760
761 String jarName = jar.substring(jarPath.length());
762 while (jarName.startsWith("/"))
763 jarName = jarName.substring(1);
764
765 String[] result = new String[jarNames.length + 1];
766 for (i = 0; i < jarNames.length; i++) {
767 result[i] = jarNames[i];
768 }
769 result[jarNames.length] = jarName;
770 jarNames = result;
771
772 }
773
774 try {
775
776 // Register the JAR for tracking
777
778 long lastModified =
779 ((ResourceAttributes) resources.getAttributes(jar))
780 .getLastModified();
781
782 String[] result = new String[paths.length + 1];
783 for (i = 0; i < paths.length; i++) {
784 result[i] = paths[i];
785 }
786 result[paths.length] = jar;
787 paths = result;
788
789 long[] result3 = new long[lastModifiedDates.length + 1];
790 for (i = 0; i < lastModifiedDates.length; i++) {
791 result3[i] = lastModifiedDates[i];
792 }
793 result3[lastModifiedDates.length] = lastModified;
794 lastModifiedDates = result3;
795
796 } catch (NamingException e) {
797 // Ignore
798 }
799
800 // If the JAR currently contains invalid classes, don't actually use it
801 // for classloading
802 if (!validateJarFile(file))
803 return;
804
805 JarFile[] result2 = new JarFile[jarFiles.length + 1];
806 for (i = 0; i < jarFiles.length; i++) {
807 result2[i] = jarFiles[i];
808 }
809 result2[jarFiles.length] = jarFile;
810 jarFiles = result2;
811
812 // Add the file to the list
813 File[] result4 = new File[jarRealFiles.length + 1];
814 for (i = 0; i < jarRealFiles.length; i++) {
815 result4[i] = jarRealFiles[i];
816 }
817 result4[jarRealFiles.length] = file;
818 jarRealFiles = result4;
819 }
820
821
822 /**
823 * Return a String array of the current repositories for this class
824 * loader. If there are no repositories, a zero-length array is
825 * returned.For security reason, returns a clone of the Array (since
826 * String are immutable).
827 */
828 public String[] findRepositories() {
829
830 return ((String[])repositories.clone());
831
832 }
833
834
835 /**
836 * Have one or more classes or resources been modified so that a reload
837 * is appropriate?
838 */
839 public boolean modified() {
840
841 if (log.isDebugEnabled())
842 log.debug("modified()");
843
844 // Checking for modified loaded resources
845 int length = paths.length;
846
847 // A rare race condition can occur in the updates of the two arrays
848 // It's totally ok if the latest class added is not checked (it will
849 // be checked the next time
850 int length2 = lastModifiedDates.length;
851 if (length > length2)
852 length = length2;
853
854 for (int i = 0; i < length; i++) {
855 try {
856 long lastModified =
857 ((ResourceAttributes) resources.getAttributes(paths[i]))
858 .getLastModified();
859 if (lastModified != lastModifiedDates[i]) {
860 if( log.isDebugEnabled() )
861 log.debug(" Resource '" + paths[i]
862 + "' was modified; Date is now: "
863 + new java.util.Date(lastModified) + " Was: "
864 + new java.util.Date(lastModifiedDates[i]));
865 return (true);
866 }
867 } catch (NamingException e) {
868 log.error(" Resource '" + paths[i] + "' is missing");
869 return (true);
870 }
871 }
872
873 length = jarNames.length;
874
875 // Check if JARs have been added or removed
876 if (getJarPath() != null) {
877
878 try {
879 NamingEnumeration enumeration = resources.listBindings(getJarPath());
880 int i = 0;
881 while (enumeration.hasMoreElements() && (i < length)) {
882 NameClassPair ncPair = (NameClassPair) enumeration.nextElement();
883 String name = ncPair.getName();
884 // Ignore non JARs present in the lib folder
885 if (!name.endsWith(".jar"))
886 continue;
887 if (!name.equals(jarNames[i])) {
888 // Missing JAR
889 log.info(" Additional JARs have been added : '"
890 + name + "'");
891 return (true);
892 }
893 i++;
894 }
895 if (enumeration.hasMoreElements()) {
896 while (enumeration.hasMoreElements()) {
897 NameClassPair ncPair =
898 (NameClassPair) enumeration.nextElement();
899 String name = ncPair.getName();
900 // Additional non-JAR files are allowed
901 if (name.endsWith(".jar")) {
902 // There was more JARs
903 log.info(" Additional JARs have been added");
904 return (true);
905 }
906 }
907 } else if (i < jarNames.length) {
908 // There was less JARs
909 log.info(" Additional JARs have been added");
910 return (true);
911 }
912 } catch (NamingException e) {
913 if (log.isDebugEnabled())
914 log.debug(" Failed tracking modifications of '"
915 + getJarPath() + "'");
916 } catch (ClassCastException e) {
917 log.error(" Failed tracking modifications of '"
918 + getJarPath() + "' : " + e.getMessage());
919 }
920
921 }
922
923 // No classes have been modified
924 return (false);
925
926 }
927
928
929 /**
930 * Render a String representation of this object.
931 */
932 public String toString() {
933
934 StringBuffer sb = new StringBuffer("WebappClassLoader\r\n");
935 sb.append(" delegate: ");
936 sb.append(delegate);
937 sb.append("\r\n");
938 sb.append(" repositories:\r\n");
939 if (repositories != null) {
940 for (int i = 0; i < repositories.length; i++) {
941 sb.append(" ");
942 sb.append(repositories[i]);
943 sb.append("\r\n");
944 }
945 }
946 if (this.parent != null) {
947 sb.append("----------> Parent Classloader:\r\n");
948 sb.append(this.parent.toString());
949 sb.append("\r\n");
950 }
951 return (sb.toString());
952
953 }
954
955
956 // ---------------------------------------------------- ClassLoader Methods
957
958
959 /**
960 * Add the specified URL to the classloader.
961 */
962 protected void addURL(URL url) {
963 super.addURL(url);
964 hasExternalRepositories = true;
965 repositoryURLs = null;
966 }
967
968
969 /**
970 * Find the specified class in our local repositories, if possible. If
971 * not found, throw <code>ClassNotFoundException</code>.
972 *
973 * @param name Name of the class to be loaded
974 *
975 * @exception ClassNotFoundException if the class was not found
976 */
977 public Class findClass(String name) throws ClassNotFoundException {
978
979 if (log.isDebugEnabled())
980 log.debug(" findClass(" + name + ")");
981
982 // Cannot load anything from local repositories if class loader is stopped
983 if (!started) {
984 throw new ClassNotFoundException(name);
985 }
986
987 // (1) Permission to define this class when using a SecurityManager
988 if (securityManager != null) {
989 int i = name.lastIndexOf('.');
990 if (i >= 0) {
991 try {
992 if (log.isTraceEnabled())
993 log.trace(" securityManager.checkPackageDefinition");
994 securityManager.checkPackageDefinition(name.substring(0,i));
995 } catch (Exception se) {
996 if (log.isTraceEnabled())
997 log.trace(" -->Exception-->ClassNotFoundException", se);
998 throw new ClassNotFoundException(name, se);
999 }
1000 }
1001 }
1002
1003 // Ask our superclass to locate this class, if possible
1004 // (throws ClassNotFoundException if it is not found)
1005 Class clazz = null;
1006 try {
1007 if (log.isTraceEnabled())
1008 log.trace(" findClassInternal(" + name + ")");
1009 try {
1010 clazz = findClassInternal(name);
1011 } catch(ClassNotFoundException cnfe) {
1012 if (!hasExternalRepositories) {
1013 throw cnfe;
1014 }
1015 } catch(AccessControlException ace) {
1016 log.warn("WebappClassLoader.findClassInternal(" + name
1017 + ") security exception: " + ace.getMessage(), ace);
1018 throw new ClassNotFoundException(name, ace);
1019 } catch (RuntimeException e) {
1020 if (log.isTraceEnabled())
1021 log.trace(" -->RuntimeException Rethrown", e);
1022 throw e;
1023 }
1024 if ((clazz == null) && hasExternalRepositories) {
1025 try {
1026 clazz = super.findClass(name);
1027 } catch(AccessControlException ace) {
1028 log.warn("WebappClassLoader.findClassInternal(" + name
1029 + ") security exception: " + ace.getMessage(), ace);
1030 throw new ClassNotFoundException(name, ace);
1031 } catch (RuntimeException e) {
1032 if (log.isTraceEnabled())
1033 log.trace(" -->RuntimeException Rethrown", e);
1034 throw e;
1035 }
1036 }
1037 if (clazz == null) {
1038 if (log.isDebugEnabled())
1039 log.debug(" --> Returning ClassNotFoundException");
1040 throw new ClassNotFoundException(name);
1041 }
1042 } catch (ClassNotFoundException e) {
1043 if (log.isTraceEnabled())
1044 log.trace(" --> Passing on ClassNotFoundException");
1045 throw e;
1046 }
1047
1048 // Return the class we have located
1049 if (log.isTraceEnabled())
1050 log.debug(" Returning class " + clazz);
1051
1052 if ((log.isTraceEnabled()) && (clazz != null)) {
1053 ClassLoader cl;
1054 if (Globals.IS_SECURITY_ENABLED){
1055 cl = AccessController.doPrivileged(
1056 new PrivilegedGetClassLoader(clazz));
1057 } else {
1058 cl = clazz.getClassLoader();
1059 }
1060 log.debug(" Loaded by " + cl.toString());
1061 }
1062 return (clazz);
1063
1064 }
1065
1066
1067 /**
1068 * Find the specified resource in our local repository, and return a
1069 * <code>URL</code> refering to it, or <code>null</code> if this resource
1070 * cannot be found.
1071 *
1072 * @param name Name of the resource to be found
1073 */
1074 public URL findResource(final String name) {
1075
1076 if (log.isDebugEnabled())
1077 log.debug(" findResource(" + name + ")");
1078
1079 URL url = null;
1080
1081 ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
1082 if (entry == null) {
1083 if (securityManager != null) {
1084 PrivilegedAction<ResourceEntry> dp =
1085 new PrivilegedFindResourceByName(name, name);
1086 entry = AccessController.doPrivileged(dp);
1087 } else {
1088 entry = findResourceInternal(name, name);
1089 }
1090 }
1091 if (entry != null) {
1092 url = entry.source;
1093 }
1094
1095 if ((url == null) && hasExternalRepositories)
1096 url = super.findResource(name);
1097
1098 if (log.isDebugEnabled()) {
1099 if (url != null)
1100 log.debug(" --> Returning '" + url.toString() + "'");
1101 else
1102 log.debug(" --> Resource not found, returning null");
1103 }
1104 return (url);
1105
1106 }
1107
1108
1109 /**
1110 * Return an enumeration of <code>URLs</code> representing all of the
1111 * resources with the given name. If no resources with this name are
1112 * found, return an empty enumeration.
1113 *
1114 * @param name Name of the resources to be found
1115 *
1116 * @exception IOException if an input/output error occurs
1117 */
1118 public Enumeration findResources(String name) throws IOException {
1119
1120 if (log.isDebugEnabled())
1121 log.debug(" findResources(" + name + ")");
1122
1123 Vector result = new Vector();
1124
1125 int jarFilesLength = jarFiles.length;
1126 int repositoriesLength = repositories.length;
1127
1128 int i;
1129
1130 // Looking at the repositories
1131 for (i = 0; i < repositoriesLength; i++) {
1132 try {
1133 String fullPath = repositories[i] + name;
1134 resources.lookup(fullPath);
1135 // Note : Not getting an exception here means the resource was
1136 // found
1137 try {
1138 result.addElement(getURI(new File(files[i], name)));
1139 } catch (MalformedURLException e) {
1140 // Ignore
1141 }
1142 } catch (NamingException e) {
1143 }
1144 }
1145
1146 // Looking at the JAR files
1147 synchronized (jarFiles) {
1148 if (openJARs()) {
1149 for (i = 0; i < jarFilesLength; i++) {
1150 JarEntry jarEntry = jarFiles[i].getJarEntry(name);
1151 if (jarEntry != null) {
1152 try {
1153 String jarFakeUrl = getURI(jarRealFiles[i]).toString();
1154 jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name;
1155 result.addElement(new URL(jarFakeUrl));
1156 } catch (MalformedURLException e) {
1157 // Ignore
1158 }
1159 }
1160 }
1161 }
1162 }
1163
1164 // Adding the results of a call to the superclass
1165 if (hasExternalRepositories) {
1166
1167 Enumeration otherResourcePaths = super.findResources(name);
1168
1169 while (otherResourcePaths.hasMoreElements()) {
1170 result.addElement(otherResourcePaths.nextElement());
1171 }
1172
1173 }
1174
1175 return result.elements();
1176
1177 }
1178
1179
1180 /**
1181 * Find the resource with the given name. A resource is some data
1182 * (images, audio, text, etc.) that can be accessed by class code in a
1183 * way that is independent of the location of the code. The name of a
1184 * resource is a "/"-separated path name that identifies the resource.
1185 * If the resource cannot be found, return <code>null</code>.
1186 * <p>
1187 * This method searches according to the following algorithm, returning
1188 * as soon as it finds the appropriate URL. If the resource cannot be
1189 * found, returns <code>null</code>.
1190 * <ul>
1191 * <li>If the <code>delegate</code> property is set to <code>true</code>,
1192 * call the <code>getResource()</code> method of the parent class
1193 * loader, if any.</li>
1194 * <li>Call <code>findResource()</code> to find this resource in our
1195 * locally defined repositories.</li>
1196 * <li>Call the <code>getResource()</code> method of the parent class
1197 * loader, if any.</li>
1198 * </ul>
1199 *
1200 * @param name Name of the resource to return a URL for
1201 */
1202 public URL getResource(String name) {
1203
1204 if (log.isDebugEnabled())
1205 log.debug("getResource(" + name + ")");
1206 URL url = null;
1207
1208 // (1) Delegate to parent if requested
1209 if (delegate) {
1210 if (log.isDebugEnabled())
1211 log.debug(" Delegating to parent classloader " + parent);
1212 ClassLoader loader = parent;
1213 if (loader == null)
1214 loader = system;
1215 url = loader.getResource(name);
1216 if (url != null) {
1217 if (log.isDebugEnabled())
1218 log.debug(" --> Returning '" + url.toString() + "'");
1219 return (url);
1220 }
1221 }
1222
1223 // (2) Search local repositories
1224 url = findResource(name);
1225 if (url != null) {
1226 // Locating the repository for special handling in the case
1227 // of a JAR
1228 if (antiJARLocking) {
1229 ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
1230 try {
1231 String repository = entry.codeBase.toString();
1232 if ((repository.endsWith(".jar"))
1233 && (!(name.endsWith(".class")))) {
1234 // Copy binary content to the work directory if not present
1235 File resourceFile = new File(loaderDir, name);
1236 url = getURI(resourceFile);
1237 }
1238 } catch (Exception e) {
1239 // Ignore
1240 }
1241 }
1242 if (log.isDebugEnabled())
1243 log.debug(" --> Returning '" + url.toString() + "'");
1244 return (url);
1245 }
1246
1247 // (3) Delegate to parent unconditionally if not already attempted
1248 if( !delegate ) {
1249 ClassLoader loader = parent;
1250 if (loader == null)
1251 loader = system;
1252 url = loader.getResource(name);
1253 if (url != null) {
1254 if (log.isDebugEnabled())
1255 log.debug(" --> Returning '" + url.toString() + "'");
1256 return (url);
1257 }
1258 }
1259
1260 // (4) Resource was not found
1261 if (log.isDebugEnabled())
1262 log.debug(" --> Resource not found, returning null");
1263 return (null);
1264
1265 }
1266
1267
1268 /**
1269 * Find the resource with the given name, and return an input stream
1270 * that can be used for reading it. The search order is as described
1271 * for <code>getResource()</code>, after checking to see if the resource
1272 * data has been previously cached. If the resource cannot be found,
1273 * return <code>null</code>.
1274 *
1275 * @param name Name of the resource to return an input stream for
1276 */
1277 public InputStream getResourceAsStream(String name) {
1278
1279 if (log.isDebugEnabled())
1280 log.debug("getResourceAsStream(" + name + ")");
1281 InputStream stream = null;
1282
1283 // (0) Check for a cached copy of this resource
1284 stream = findLoadedResource(name);
1285 if (stream != null) {
1286 if (log.isDebugEnabled())
1287 log.debug(" --> Returning stream from cache");
1288 return (stream);
1289 }
1290
1291 // (1) Delegate to parent if requested
1292 if (delegate) {
1293 if (log.isDebugEnabled())
1294 log.debug(" Delegating to parent classloader " + parent);
1295 ClassLoader loader = parent;
1296 if (loader == null)
1297 loader = system;
1298 stream = loader.getResourceAsStream(name);
1299 if (stream != null) {
1300 // FIXME - cache???
1301 if (log.isDebugEnabled())
1302 log.debug(" --> Returning stream from parent");
1303 return (stream);
1304 }
1305 }
1306
1307 // (2) Search local repositories
1308 if (log.isDebugEnabled())
1309 log.debug(" Searching local repositories");
1310 URL url = findResource(name);
1311 if (url != null) {
1312 // FIXME - cache???
1313 if (log.isDebugEnabled())
1314 log.debug(" --> Returning stream from local");
1315 stream = findLoadedResource(name);
1316 try {
1317 if (hasExternalRepositories && (stream == null))
1318 stream = url.openStream();
1319 } catch (IOException e) {
1320 ; // Ignore
1321 }
1322 if (stream != null)
1323 return (stream);
1324 }
1325
1326 // (3) Delegate to parent unconditionally
1327 if (!delegate) {
1328 if (log.isDebugEnabled())
1329 log.debug(" Delegating to parent classloader unconditionally " + parent);
1330 ClassLoader loader = parent;
1331 if (loader == null)
1332 loader = system;
1333 stream = loader.getResourceAsStream(name);
1334 if (stream != null) {
1335 // FIXME - cache???
1336 if (log.isDebugEnabled())
1337 log.debug(" --> Returning stream from parent");
1338 return (stream);
1339 }
1340 }
1341
1342 // (4) Resource was not found
1343 if (log.isDebugEnabled())
1344 log.debug(" --> Resource not found, returning null");
1345 return (null);
1346
1347 }
1348
1349
1350 /**
1351 * Load the class with the specified name. This method searches for
1352 * classes in the same manner as <code>loadClass(String, boolean)</code>
1353 * with <code>false</code> as the second argument.
1354 *
1355 * @param name Name of the class to be loaded
1356 *
1357 * @exception ClassNotFoundException if the class was not found
1358 */
1359 public Class loadClass(String name) throws ClassNotFoundException {
1360
1361 return (loadClass(name, false));
1362
1363 }
1364
1365
1366 /**
1367 * Load the class with the specified name, searching using the following
1368 * algorithm until it finds and returns the class. If the class cannot
1369 * be found, returns <code>ClassNotFoundException</code>.
1370 * <ul>
1371 * <li>Call <code>findLoadedClass(String)</code> to check if the
1372 * class has already been loaded. If it has, the same
1373 * <code>Class</code> object is returned.</li>
1374 * <li>If the <code>delegate</code> property is set to <code>true</code>,
1375 * call the <code>loadClass()</code> method of the parent class
1376 * loader, if any.</li>
1377 * <li>Call <code>findClass()</code> to find this class in our locally
1378 * defined repositories.</li>
1379 * <li>Call the <code>loadClass()</code> method of our parent
1380 * class loader, if any.</li>
1381 * </ul>
1382 * If the class was found using the above steps, and the
1383 * <code>resolve</code> flag is <code>true</code>, this method will then
1384 * call <code>resolveClass(Class)</code> on the resulting Class object.
1385 *
1386 * @param name Name of the class to be loaded
1387 * @param resolve If <code>true</code> then resolve the class
1388 *
1389 * @exception ClassNotFoundException if the class was not found
1390 */
1391 public Class loadClass(String name, boolean resolve)
1392 throws ClassNotFoundException {
1393
1394 synchronized (name.intern()) {
1395 if (log.isDebugEnabled())
1396 log.debug("loadClass(" + name + ", " + resolve + ")");
1397 Class clazz = null;
1398
1399 // Log access to stopped classloader
1400 if (!started) {
1401 try {
1402 throw new IllegalStateException();
1403 } catch (IllegalStateException e) {
1404 log.info(sm.getString("webappClassLoader.stopped", name), e);
1405 }
1406 }
1407
1408 // (0) Check our previously loaded local class cache
1409 clazz = findLoadedClass0(name);
1410 if (clazz != null) {
1411 if (log.isDebugEnabled())
1412 log.debug(" Returning class from cache");
1413 if (resolve)
1414 resolveClass(clazz);
1415 return (clazz);
1416 }
1417
1418 // (0.1) Check our previously loaded class cache
1419 clazz = findLoadedClass(name);
1420 if (clazz != null) {
1421 if (log.isDebugEnabled())
1422 log.debug(" Returning class from cache");
1423 if (resolve)
1424 resolveClass(clazz);
1425 return (clazz);
1426 }
1427
1428 // (0.2) Try loading the class with the system class loader, to prevent
1429 // the webapp from overriding J2SE classes
1430 try {
1431 clazz = system.loadClass(name);
1432 if (clazz != null) {
1433 if (resolve)
1434 resolveClass(clazz);
1435 return (clazz);
1436 }
1437 } catch (ClassNotFoundException e) {
1438 // Ignore
1439 }
1440
1441 // (0.5) Permission to access this class when using a SecurityManager
1442 if (securityManager != null) {
1443 int i = name.lastIndexOf('.');
1444 if (i >= 0) {
1445 try {
1446 securityManager.checkPackageAccess(name.substring(0,i));
1447 } catch (SecurityException se) {
1448 String error = "Security Violation, attempt to use " +
1449 "Restricted Class: " + name;
1450 log.info(error, se);
1451 throw new ClassNotFoundException(error, se);
1452 }
1453 }
1454 }
1455
1456 boolean delegateLoad = delegate || filter(name);
1457
1458 // (1) Delegate to our parent if requested
1459 if (delegateLoad) {
1460 if (log.isDebugEnabled())
1461 log.debug(" Delegating to parent classloader1 " + parent);
1462 ClassLoader loader = parent;
1463 if (loader == null)
1464 loader = system;
1465 try {
1466 clazz = loader.loadClass(name);
1467 if (clazz != null) {
1468 if (log.isDebugEnabled())
1469 log.debug(" Loading class from parent");
1470 if (resolve)
1471 resolveClass(clazz);
1472 return (clazz);
1473 }
1474 } catch (ClassNotFoundException e) {
1475 ;
1476 }
1477 }
1478
1479 // (2) Search local repositories
1480 if (log.isDebugEnabled())
1481 log.debug(" Searching local repositories");
1482 try {
1483 clazz = findClass(name);
1484 if (clazz != null) {
1485 if (log.isDebugEnabled())
1486 log.debug(" Loading class from local repository");
1487 if (resolve)
1488 resolveClass(clazz);
1489 return (clazz);
1490 }
1491 } catch (ClassNotFoundException e) {
1492 ;
1493 }
1494
1495 // (3) Delegate to parent unconditionally
1496 if (!delegateLoad) {
1497 if (log.isDebugEnabled())
1498 log.debug(" Delegating to parent classloader at end: " + parent);
1499 ClassLoader loader = parent;
1500 if (loader == null)
1501 loader = system;
1502 try {
1503 clazz = loader.loadClass(name);
1504 if (clazz != null) {
1505 if (log.isDebugEnabled())
1506 log.debug(" Loading class from parent");
1507 if (resolve)
1508 resolveClass(clazz);
1509 return (clazz);
1510 }
1511 } catch (ClassNotFoundException e) {
1512 ;
1513 }
1514 }
1515
1516 throw new ClassNotFoundException(name);
1517 }
1518 }
1519
1520
1521 /**
1522 * Get the Permissions for a CodeSource. If this instance
1523 * of WebappClassLoader is for a web application context,
1524 * add read FilePermission or JndiPermissions for the base
1525 * directory (if unpacked),
1526 * the context URL, and jar file resources.
1527 *
1528 * @param codeSource where the code was loaded from
1529 * @return PermissionCollection for CodeSource
1530 */
1531 protected PermissionCollection getPermissions(CodeSource codeSource) {
1532
1533 String codeUrl = codeSource.getLocation().toString();
1534 PermissionCollection pc;
1535 if ((pc = (PermissionCollection)loaderPC.get(codeUrl)) == null) {
1536 pc = super.getPermissions(codeSource);
1537 if (pc != null) {
1538 Iterator perms = permissionList.iterator();
1539 while (perms.hasNext()) {
1540 Permission p = (Permission)perms.next();
1541 pc.add(p);
1542 }
1543 loaderPC.put(codeUrl,pc);
1544 }
1545 }
1546 return (pc);
1547
1548 }
1549
1550
1551 /**
1552 * Returns the search path of URLs for loading classes and resources.
1553 * This includes the original list of URLs specified to the constructor,
1554 * along with any URLs subsequently appended by the addURL() method.
1555 * @return the search path of URLs for loading classes and resources.
1556 */
1557 public URL[] getURLs() {
1558
1559 if (repositoryURLs != null) {
1560 return repositoryURLs;
1561 }
1562
1563 URL[] external = super.getURLs();
1564
1565 int filesLength = files.length;
1566 int jarFilesLength = jarRealFiles.length;
1567 int length = filesLength + jarFilesLength + external.length;
1568 int i;
1569
1570 try {
1571
1572 URL[] urls = new URL[length];
1573 for (i = 0; i < length; i++) {
1574 if (i < filesLength) {
1575 urls[i] = getURL(files[i], true);
1576 } else if (i < filesLength + jarFilesLength) {
1577 urls[i] = getURL(jarRealFiles[i - filesLength], true);
1578 } else {
1579 urls[i] = external[i - filesLength - jarFilesLength];
1580 }
1581 }
1582
1583 repositoryURLs = urls;
1584
1585 } catch (MalformedURLException e) {
1586 repositoryURLs = new URL[0];
1587 }
1588
1589 return repositoryURLs;
1590
1591 }
1592
1593
1594 // ------------------------------------------------------ Lifecycle Methods
1595
1596
1597 /**
1598 * Add a lifecycle event listener to this component.
1599 *
1600 * @param listener The listener to add
1601 */
1602 public void addLifecycleListener(LifecycleListener listener) {
1603 }
1604
1605
1606 /**
1607 * Get the lifecycle listeners associated with this lifecycle. If this
1608 * Lifecycle has no listeners registered, a zero-length array is returned.
1609 */
1610 public LifecycleListener[] findLifecycleListeners() {
1611 return new LifecycleListener[0];
1612 }
1613
1614
1615 /**
1616 * Remove a lifecycle event listener from this component.
1617 *
1618 * @param listener The listener to remove
1619 */
1620 public void removeLifecycleListener(LifecycleListener listener) {
1621 }
1622
1623
1624 /**
1625 * Start the class loader.
1626 *
1627 * @exception LifecycleException if a lifecycle error occurs
1628 */
1629 public void start() throws LifecycleException {
1630
1631 started = true;
1632 String encoding = null;
1633 try {
1634 encoding = System.getProperty("file.encoding");
1635 } catch (Exception e) {
1636 return;
1637 }
1638 if (encoding.indexOf("EBCDIC")!=-1) {
1639 needConvert = true;
1640 }
1641
1642 }
1643
1644
1645 public boolean isStarted() {
1646 return started;
1647 }
1648
1649 /**
1650 * Stop the class loader.
1651 *
1652 * @exception LifecycleException if a lifecycle error occurs
1653 */
1654 public void stop() throws LifecycleException {
1655
1656 // Clearing references should be done before setting started to
1657 // false, due to possible side effects
1658 clearReferences();
1659
1660 started = false;
1661
1662 int length = files.length;
1663 for (int i = 0; i < length; i++) {
1664 files[i] = null;
1665 }
1666
1667 length = jarFiles.length;
1668 for (int i = 0; i < length; i++) {
1669 try {
1670 if (jarFiles[i] != null) {
1671 jarFiles[i].close();
1672 }
1673 } catch (IOException e) {
1674 // Ignore
1675 }
1676 jarFiles[i] = null;
1677 }
1678
1679 notFoundResources.clear();
1680 resourceEntries.clear();
1681 resources = null;
1682 repositories = null;
1683 repositoryURLs = null;
1684 files = null;
1685 jarFiles = null;
1686 jarRealFiles = null;
1687 jarPath = null;
1688 jarNames = null;
1689 lastModifiedDates = null;
1690 paths = null;
1691 hasExternalRepositories = false;
1692 parent = null;
1693
1694 permissionList.clear();
1695 loaderPC.clear();
1696
1697 if (loaderDir != null) {
1698 deleteDir(loaderDir);
1699 }
1700
1701 }
1702
1703
1704 /**
1705 * Used to periodically signal to the classloader to release
1706 * JAR resources.
1707 */
1708 public void closeJARs(boolean force) {
1709 if (jarFiles.length > 0) {
1710 synchronized (jarFiles) {
1711 if (force || (System.currentTimeMillis()
1712 > (lastJarAccessed + 90000))) {
1713 for (int i = 0; i < jarFiles.length; i++) {
1714 try {
1715 if (jarFiles[i] != null) {
1716 jarFiles[i].close();
1717 jarFiles[i] = null;
1718 }
1719 } catch (IOException e) {
1720 if (log.isDebugEnabled()) {
1721 log.debug("Failed to close JAR", e);
1722 }
1723 }
1724 }
1725 }
1726 }
1727 }
1728 }
1729
1730
1731 // ------------------------------------------------------ Protected Methods
1732
1733
1734 /**
1735 * Clear references.
1736 */
1737 protected void clearReferences() {
1738
1739 // De-register any remaining JDBC drivers
1740 clearReferencesJdbc();
1741
1742 // Stop any threads the web application started
1743 clearReferencesThreads();
1744
1745 // Clear any ThreadLocals loaded by this class loader
1746 clearReferencesThreadLocals();
1747
1748 // Clear RMI Targets loaded by this class loader
1749 clearReferencesRmiTargets();
1750
1751 // Null out any static or final fields from loaded classes,
1752 // as a workaround for apparent garbage collection bugs
1753 if (ENABLE_CLEAR_REFERENCES) {
1754 clearReferencesStaticFinal();
1755 }
1756
1757 // Clear the IntrospectionUtils cache.
1758 IntrospectionUtils.clear();
1759
1760 // Clear the classloader reference in common-logging
1761 if (clearReferencesLogFactoryRelease) {
1762 org.apache.juli.logging.LogFactory.release(this);
1763 }
1764
1765 // Clear the resource bundle cache
1766 // This shouldn't be necessary, the cache uses weak references but
1767 // it has caused leaks. Oddly, using the leak detection code in
1768 // standard host allows the class loader to be GC'd. This has been seen
1769 // on Sun but not IBM JREs. Maybe a bug in Sun's GC impl?
1770 clearReferencesResourceBundles();
1771
1772 // Clear the classloader reference in the VM's bean introspector
1773 java.beans.Introspector.flushCaches();
1774
1775 }
1776
1777
1778 /**
1779 * Deregister any JDBC drivers registered by the webapp that the webapp
1780 * forgot. This is made unnecessary complex because a) DriverManager
1781 * checks the class loader of the calling class (it would be much easier
1782 * if it checked the context class loader) b) using reflection would
1783 * create a dependency on the DriverManager implementation which can,
1784 * and has, changed.
1785 *
1786 * We can't just create an instance of JdbcLeakPrevention as it will be
1787 * loaded by the common class loader (since it's .class file is in the
1788 * $CATALINA_HOME/lib directory). This would fail DriverManager's check
1789 * on the class loader of the calling class. So, we load the bytes via
1790 * our parent class loader but define the class with this class loader
1791 * so the JdbcLeakPrevention looks like a webapp class to the
1792 * DriverManager.
1793 *
1794 * If only apps cleaned up after themselves...
1795 */
1796 private final void clearReferencesJdbc() {
1797 InputStream is = getResourceAsStream(
1798 "org/apache/catalina/loader/JdbcLeakPrevention.class");
1799 // We know roughly how big the class will be (~ 1K) so allow 2k as a
1800 // starting point
1801 byte[] classBytes = new byte[2048];
1802 int offset = 0;
1803 try {
1804 int read = is.read(classBytes, offset, classBytes.length-offset);
1805 while (read > -1) {
1806 offset += read;
1807 if (offset == classBytes.length) {
1808 // Buffer full - double size
1809 byte[] tmp = new byte[classBytes.length * 2];
1810 System.arraycopy(classBytes, 0, tmp, 0, classBytes.length);
1811 classBytes = tmp;
1812 }
1813 read = is.read(classBytes, offset, classBytes.length-offset);
1814 }
1815 Class<?> lpClass =
1816 defineClass("org.apache.catalina.loader.JdbcLeakPrevention",
1817 classBytes, 0, offset);
1818 Object obj = lpClass.newInstance();
1819 @SuppressWarnings("unchecked")
1820 List<String> driverNames = (List<String>) obj.getClass().getMethod(
1821 "clearJdbcDriverRegistrations").invoke(obj);
1822 for (String name : driverNames) {
1823 log.error(sm.getString("webappClassLoader.clearJbdc", name));
1824 }
1825 } catch (Exception e) {
1826 // So many things to go wrong above...
1827 log.warn(sm.getString("webappClassLoader.jdbcRemoveFailed"), e);
1828 } finally {
1829 if (is != null) {
1830 try {
1831 is.close();
1832 } catch (IOException ioe) {
1833 log.warn(sm.getString(
1834 "webappClassLoader.jdbcRemoveStreamError"), ioe);
1835 }
1836 }
1837 }
1838 }
1839
1840
1841 private final void clearReferencesStaticFinal() {
1842
1843 @SuppressWarnings("unchecked")
1844 Collection<ResourceEntry> values =
1845 ((HashMap<String,ResourceEntry>) resourceEntries.clone()).values();
1846 Iterator<ResourceEntry> loadedClasses = values.iterator();
1847 //
1848 // walk through all loaded class to trigger initialization for
1849 // any uninitialized classes, otherwise initialization of
1850 // one class may call a previously cleared class.
1851 while(loadedClasses.hasNext()) {
1852 ResourceEntry entry = loadedClasses.next();
1853 if (entry.loadedClass != null) {
1854 Class<?> clazz = entry.loadedClass;
1855 try {
1856 Field[] fields = clazz.getDeclaredFields();
1857 for (int i = 0; i < fields.length; i++) {
1858 if(Modifier.isStatic(fields[i].getModifiers())) {
1859 fields[i].get(null);
1860 break;
1861 }
1862 }
1863 } catch(Throwable t) {
1864 // Ignore
1865 }
1866 }
1867 }
1868 loadedClasses = values.iterator();
1869 while (loadedClasses.hasNext()) {
1870 ResourceEntry entry = loadedClasses.next();
1871 if (entry.loadedClass != null) {
1872 Class<?> clazz = entry.loadedClass;
1873 try {
1874 Field[] fields = clazz.getDeclaredFields();
1875 for (int i = 0; i < fields.length; i++) {
1876 Field field = fields[i];
1877 int mods = field.getModifiers();
1878 if (field.getType().isPrimitive()
1879 || (field.getName().indexOf("$") != -1)) {
1880 continue;
1881 }
1882 if (Modifier.isStatic(mods)) {
1883 try {
1884 field.setAccessible(true);
1885 if (Modifier.isFinal(mods)) {
1886 if (!((field.getType().getName().startsWith("java."))
1887 || (field.getType().getName().startsWith("javax.")))) {
1888 nullInstance(field.get(null));
1889 }
1890 } else {
1891 field.set(null, null);
1892 if (log.isDebugEnabled()) {
1893 log.debug("Set field " + field.getName()
1894 + " to null in class " + clazz.getName());
1895 }
1896 }
1897 } catch (Throwable t) {
1898 if (log.isDebugEnabled()) {
1899 log.debug("Could not set field " + field.getName()
1900 + " to null in class " + clazz.getName(), t);
1901 }
1902 }
1903 }
1904 }
1905 } catch (Throwable t) {
1906 if (log.isDebugEnabled()) {
1907 log.debug("Could not clean fields for class " + clazz.getName(), t);
1908 }
1909 }
1910 }
1911 }
1912
1913 }
1914
1915
1916 private void nullInstance(Object instance) {
1917 if (instance == null) {
1918 return;
1919 }
1920 Field[] fields = instance.getClass().getDeclaredFields();
1921 for (int i = 0; i < fields.length; i++) {
1922 Field field = fields[i];
1923 int mods = field.getModifiers();
1924 if (field.getType().isPrimitive()
1925 || (field.getName().indexOf("$") != -1)) {
1926 continue;
1927 }
1928 try {
1929 field.setAccessible(true);
1930 if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) {
1931 // Doing something recursively is too risky
1932 continue;
1933 }
1934 Object value = field.get(instance);
1935 if (null != value) {
1936 Class<? extends Object> valueClass = value.getClass();
1937 if (!loadedByThisOrChild(valueClass)) {
1938 if (log.isDebugEnabled()) {
1939 log.debug("Not setting field " + field.getName() +
1940 " to null in object of class " +
1941 instance.getClass().getName() +
1942 " because the referenced object was of type " +
1943 valueClass.getName() +
1944 " which was not loaded by this WebappClassLoader.");
1945 }
1946 } else {
1947 field.set(instance, null);
1948 if (log.isDebugEnabled()) {
1949 log.debug("Set field " + field.getName()
1950 + " to null in class " + instance.getClass().getName());
1951 }
1952 }
1953 }
1954 } catch (Throwable t) {
1955 if (log.isDebugEnabled()) {
1956 log.debug("Could not set field " + field.getName()
1957 + " to null in object instance of class "
1958 + instance.getClass().getName(), t);
1959 }
1960 }
1961 }
1962 }
1963
1964
1965 @SuppressWarnings("deprecation")
1966 private void clearReferencesThreads() {
1967 Thread[] threads = getThreads();
1968
1969 // Iterate over the set of threads
1970 for (Thread thread : threads) {
1971 if (thread != null) {
1972 ClassLoader ccl = thread.getContextClassLoader();
1973 if (ccl != null && ccl == this) {
1974 // Don't warn about this thread
1975 if (thread == Thread.currentThread()) {
1976 continue;
1977 }
1978
1979 // Skip threads that have already died
1980 if (!thread.isAlive()) {
1981 continue;
1982 }
1983
1984 // Don't warn about JVM controlled threads
1985 ThreadGroup tg = thread.getThreadGroup();
1986 if (tg != null &&
1987 JVM_THREAD_GROUP_NAMES.contains(tg.getName())) {
1988 continue;
1989 }
1990
1991 // TimerThread is not normally visible
1992 if (thread.getClass().getName().equals(
1993 "java.util.TimerThread")) {
1994 clearReferencesStopTimerThread(thread);
1995 continue;
1996 }
1997
1998 log.error(sm.getString("webappClassLoader.warnThread",
1999 thread.getName()));
2000
2001 // Don't try an stop the threads unless explicitly
2002 // configured to do so
2003 if (!clearReferencesStopThreads) {
2004 continue;
2005 }
2006
2007 // If the thread has been started via an executor, try
2008 // shutting down the executor
2009 try {
2010 Field targetField =
2011 thread.getClass().getDeclaredField("target");
2012 targetField.setAccessible(true);
2013 Object target = targetField.get(thread);
2014
2015 if (target != null &&
2016 target.getClass().getCanonicalName().equals(
2017 "java.util.concurrent.ThreadPoolExecutor.Worker")) {
2018 Field executorField =
2019 target.getClass().getDeclaredField("this$0");
2020 executorField.setAccessible(true);
2021 Object executor = executorField.get(target);
2022 if (executor instanceof ThreadPoolExecutor) {
2023 ((ThreadPoolExecutor) executor).shutdownNow();
2024 }
2025 }
2026 } catch (SecurityException e) {
2027 log.warn(sm.getString(
2028 "webappClassLoader.stopThreadFail",
2029 thread.getName()), e);
2030 } catch (NoSuchFieldException e) {
2031 log.warn(sm.getString(
2032 "webappClassLoader.stopThreadFail",
2033 thread.getName()), e);
2034 } catch (IllegalArgumentException e) {
2035 log.warn(sm.getString(
2036 "webappClassLoader.stopThreadFail",
2037 thread.getName()), e);
2038 } catch (IllegalAccessException e) {
2039 log.warn(sm.getString(
2040 "webappClassLoader.stopThreadFail",
2041 thread.getName()), e);
2042 }
2043
2044 // This method is deprecated and for good reason. This is
2045 // very risky code but is the only option at this point.
2046 // A *very* good reason for apps to do this clean-up
2047 // themselves.
2048 thread.stop();
2049 }
2050 }
2051 }
2052 }
2053
2054
2055 private void clearReferencesStopTimerThread(Thread thread) {
2056
2057 // Need to get references to:
2058 // - newTasksMayBeScheduled field
2059 // - queue field
2060 // - queue.clear()
2061
2062 try {
2063 Field newTasksMayBeScheduledField =
2064 thread.getClass().getDeclaredField("newTasksMayBeScheduled");
2065 newTasksMayBeScheduledField.setAccessible(true);
2066 Field queueField = thread.getClass().getDeclaredField("queue");
2067 queueField.setAccessible(true);
2068
2069 Object queue = queueField.get(thread);
2070
2071 Method clearMethod = queue.getClass().getDeclaredMethod("clear");
2072 clearMethod.setAccessible(true);
2073
2074 synchronized(queue) {
2075 newTasksMayBeScheduledField.setBoolean(thread, false);
2076 clearMethod.invoke(queue);
2077 queue.notify(); // In case queue was already empty.
2078 }
2079
2080 log.error(sm.getString("webappClassLoader.warnTimerThread",
2081 thread.getName()));
2082
2083 } catch (NoSuchFieldException e) {
2084 log.warn(sm.getString(
2085 "webappClassLoader.stopTimerThreadFail",
2086 thread.getName()), e);
2087 } catch (IllegalAccessException e) {
2088 log.warn(sm.getString(
2089 "webappClassLoader.stopTimerThreadFail",
2090 thread.getName()), e);
2091 } catch (NoSuchMethodException e) {
2092 log.warn(sm.getString(
2093 "webappClassLoader.stopTimerThreadFail",
2094 thread.getName()), e);
2095 } catch (InvocationTargetException e) {
2096 log.warn(sm.getString(
2097 "webappClassLoader.stopTimerThreadFail",
2098 thread.getName()), e);
2099 }
2100 }
2101
2102 private void clearReferencesThreadLocals() {
2103 Thread[] threads = getThreads();
2104
2105 try {
2106 // Make the fields in the Thread class that store ThreadLocals
2107 // accessible
2108 Field threadLocalsField =
2109 Thread.class.getDeclaredField("threadLocals");
2110 threadLocalsField.setAccessible(true);
2111 Field inheritableThreadLocalsField =
2112 Thread.class.getDeclaredField("inheritableThreadLocals");
2113 inheritableThreadLocalsField.setAccessible(true);
2114 // Make the underlying array of ThreadLoad.ThreadLocalMap.Entry objects
2115 // accessible
2116 Class<?> tlmClass =
2117 Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
2118 Field tableField = tlmClass.getDeclaredField("table");
2119 tableField.setAccessible(true);
2120
2121 for (int i = 0; i < threads.length; i++) {
2122 Object threadLocalMap;
2123 if (threads[i] != null) {
2124 // Clear the first map
2125 threadLocalMap = threadLocalsField.get(threads[i]);
2126 clearThreadLocalMap(threadLocalMap, tableField);
2127 // Clear the second map
2128 threadLocalMap =
2129 inheritableThreadLocalsField.get(threads[i]);
2130 clearThreadLocalMap(threadLocalMap, tableField);
2131 }
2132 }
2133 } catch (SecurityException e) {
2134 log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
2135 } catch (NoSuchFieldException e) {
2136 log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
2137 } catch (ClassNotFoundException e) {
2138 log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
2139 } catch (IllegalArgumentException e) {
2140 log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
2141 } catch (IllegalAccessException e) {
2142 log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
2143 } catch (NoSuchMethodException e) {
2144 log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
2145 } catch (InvocationTargetException e) {
2146 log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
2147 }
2148 }
2149
2150
2151 /*
2152 * Clears the given thread local map object. Also pass in the field that
2153 * points to the internal table to save re-calculating it on every
2154 * call to this method.
2155 */
2156 private void clearThreadLocalMap(Object map, Field internalTableField)
2157 throws NoSuchMethodException, IllegalAccessException,
2158 NoSuchFieldException, InvocationTargetException {
2159 if (map != null) {
2160 Method mapRemove =
2161 map.getClass().getDeclaredMethod("remove",
2162 ThreadLocal.class);
2163 mapRemove.setAccessible(true);
2164 Object[] table = (Object[]) internalTableField.get(map);
2165 int staleEntriesCount = 0;
2166 if (table != null) {
2167 for (int j =0; j < table.length; j++) {
2168 if (table[j] != null) {
2169 boolean remove = false;
2170 // Check the key
2171 Object key = ((Reference<?>) table[j]).get();
2172 if (this.equals(key) || (key != null &&
2173 this == key.getClass().getClassLoader())) {
2174 remove = true;
2175 }
2176 // Check the value
2177 Field valueField =
2178 table[j].getClass().getDeclaredField("value");
2179 valueField.setAccessible(true);
2180 Object value = valueField.get(table[j]);
2181 if (this.equals(value) || (value != null &&
2182 this == value.getClass().getClassLoader())) {
2183 remove = true;
2184 }
2185 if (remove) {
2186 Object[] args = new Object[4];
2187 if (key != null) {
2188 args[0] = key.getClass().getCanonicalName();
2189 args[1] = key.toString();
2190 }
2191 if (value != null) {
2192 args[2] = value.getClass().getCanonicalName();
2193 args[3] = value.toString();
2194 }
2195 if (value == null) {
2196 if (log.isDebugEnabled()) {
2197 log.debug(sm.getString(
2198 "webappClassLoader.clearThreadLocalDebug",
2199 args));
2200 }
2201 } else {
2202 log.error(sm.getString(
2203 "webappClassLoader.clearThreadLocal",
2204 args));
2205 }
2206 if (key == null) {
2207 staleEntriesCount++;
2208 } else {
2209 mapRemove.invoke(map, key);
2210 }
2211 }
2212 }
2213 }
2214 }
2215 if (staleEntriesCount > 0) {
2216 Method mapRemoveStale =
2217 map.getClass().getDeclaredMethod("expungeStaleEntries");
2218 mapRemoveStale.setAccessible(true);
2219 mapRemoveStale.invoke(map);
2220 }
2221 }
2222 }
2223
2224 /*
2225 * Get the set of current threads as an array.
2226 */
2227 private Thread[] getThreads() {
2228 // Get the current thread group
2229 ThreadGroup tg = Thread.currentThread( ).getThreadGroup( );
2230 // Find the root thread group
2231 while (tg.getParent() != null) {
2232 tg = tg.getParent();
2233 }
2234
2235 int threadCountGuess = tg.activeCount() + 50;
2236 Thread[] threads = new Thread[threadCountGuess];
2237 int threadCountActual = tg.enumerate(threads);
2238 // Make sure we don't miss any threads
2239 while (threadCountActual == threadCountGuess) {
2240 threadCountGuess *=2;
2241 threads = new Thread[threadCountGuess];
2242 // Note tg.enumerate(Thread[]) silently ignores any threads that
2243 // can't fit into the array
2244 threadCountActual = tg.enumerate(threads);
2245 }
2246
2247 return threads;
2248 }
2249
2250
2251 /**
2252 * This depends on the internals of the Sun JVM so it does everything by
2253 * reflection.
2254 */
2255 private void clearReferencesRmiTargets() {
2256 try {
2257 // Need access to the ccl field of sun.rmi.transport.Target
2258 Class<?> objectTargetClass =
2259 Class.forName("sun.rmi.transport.Target");
2260 Field cclField = objectTargetClass.getDeclaredField("ccl");
2261 cclField.setAccessible(true);
2262
2263 // Clear the objTable map
2264 Class<?> objectTableClass =
2265 Class.forName("sun.rmi.transport.ObjectTable");
2266 Field objTableField = objectTableClass.getDeclaredField("objTable");
2267 objTableField.setAccessible(true);
2268 Object objTable = objTableField.get(null);
2269 if (objTable == null) {
2270 return;
2271 }
2272
2273 // Iterate over the values in the table
2274 if (objTable instanceof Map<?,?>) {
2275 Iterator<?> iter = ((Map<?,?>) objTable).values().iterator();
2276 while (iter.hasNext()) {
2277 Object obj = iter.next();
2278 Object cclObject = cclField.get(obj);
2279 if (this == cclObject) {
2280 iter.remove();
2281 }
2282 }
2283 }
2284
2285 // Clear the implTable map
2286 Field implTableField = objectTableClass.getDeclaredField("implTable");
2287 implTableField.setAccessible(true);
2288 Object implTable = implTableField.get(null);
2289 if (implTable == null) {
2290 return;
2291 }
2292
2293 // Iterate over the values in the table
2294 if (implTable instanceof Map<?,?>) {
2295 Iterator<?> iter = ((Map<?,?>) implTable).values().iterator();
2296 while (iter.hasNext()) {
2297 Object obj = iter.next();
2298 Object cclObject = cclField.get(obj);
2299 if (this == cclObject) {
2300 iter.remove();
2301 }
2302 }
2303 }
2304 } catch (ClassNotFoundException e) {
2305 log.info(sm.getString("webappClassLoader.clearRmiInfo"), e);
2306 } catch (SecurityException e) {
2307 log.warn(sm.getString("webappClassLoader.clearRmiFail"), e);
2308 } catch (NoSuchFieldException e) {
2309 log.warn(sm.getString("webappClassLoader.clearRmiFail"), e);
2310 } catch (IllegalArgumentException e) {
2311 log.warn(sm.getString("webappClassLoader.clearRmiFail"), e);
2312 } catch (IllegalAccessException e) {
2313 log.warn(sm.getString("webappClassLoader.clearRmiFail"), e);
2314 }
2315 }
2316
2317
2318 /**
2319 * Clear the {@link ResourceBundle} cache of any bundles loaded by this
2320 * class loader or any class loader where this loader is a parent class
2321 * loader. Whilst {@link ResourceBundle#clearCache()} could be used there
2322 * are complications around the {@link JasperLoader} that mean a reflection
2323 * based approach is more likely to be complete.
2324 *
2325 * The ResourceBundle is using WeakReferences so it shouldn't be pinning the
2326 * class loader in memory. However, it is. Therefore clear ou the
2327 * references.
2328 */
2329 private void clearReferencesResourceBundles() {
2330 // Get a reference to the cache
2331 try {
2332 Field cacheListField =
2333 ResourceBundle.class.getDeclaredField("cacheList");
2334 cacheListField.setAccessible(true);
2335
2336 // Java 6 uses ConcurrentMap
2337 // Java 5 uses SoftCache extends Abstract Map
2338 // So use Map and it *should* work with both
2339 Map<?,?> cacheList = (Map<?,?>) cacheListField.get(null);
2340
2341 // Get the keys (loader references are in the key)
2342 Set<?> keys = cacheList.keySet();
2343
2344 Field loaderRefField = null;
2345
2346 // Iterate over the keys looking at the loader instances
2347 Iterator<?> keysIter = keys.iterator();
2348
2349 int countRemoved = 0;
2350
2351 while (keysIter.hasNext()) {
2352 Object key = keysIter.next();
2353
2354 if (loaderRefField == null) {
2355 loaderRefField =
2356 key.getClass().getDeclaredField("loaderRef");
2357 loaderRefField.setAccessible(true);
2358 }
2359 WeakReference<?> loaderRef =
2360 (WeakReference<?>) loaderRefField.get(key);
2361
2362 ClassLoader loader = (ClassLoader) loaderRef.get();
2363
2364 while (loader != null && loader != this) {
2365 loader = loader.getParent();
2366 }
2367
2368 if (loader != null) {
2369 keysIter.remove();
2370 countRemoved++;
2371 }
2372 }
2373
2374 if (countRemoved > 0 && log.isDebugEnabled()) {
2375 log.debug(sm.getString(
2376 "webappClassLoader.clearReferencesResourceBundlesCount",
2377 Integer.valueOf(countRemoved)));
2378 }
2379 } catch (SecurityException e) {
2380 log.error(sm.getString(
2381 "webappClassLoader.clearReferencesResourceBundlesFail"), e);
2382 } catch (NoSuchFieldException e) {
2383 if (System.getProperty("java.vendor").startsWith("Sun")) {
2384 log.error(sm.getString(
2385 "webappClassLoader.clearReferencesResourceBundlesFail"), e);
2386 } else {
2387 log.debug(sm.getString(
2388 "webappClassLoader.clearReferencesResourceBundlesFail"), e);
2389 }
2390 } catch (IllegalArgumentException e) {
2391 log.error(sm.getString(
2392 "webappClassLoader.clearReferencesResourceBundlesFail"), e);
2393 } catch (IllegalAccessException e) {
2394 log.error(sm.getString(
2395 "webappClassLoader.clearReferencesResourceBundlesFail"), e);
2396 }
2397 }
2398
2399
2400 /**
2401 * Determine whether a class was loaded by this class loader or one of
2402 * its child class loaders.
2403 */
2404 protected boolean loadedByThisOrChild(Class clazz)
2405 {
2406 boolean result = false;
2407 for (ClassLoader classLoader = clazz.getClassLoader();
2408 null != classLoader; classLoader = classLoader.getParent()) {
2409 if (classLoader.equals(this)) {
2410 result = true;
2411 break;
2412 }
2413 }
2414 return result;
2415 }
2416
2417
2418 /**
2419 * Used to periodically signal to the classloader to release JAR resources.
2420 */
2421 protected boolean openJARs() {
2422 if (started && (jarFiles.length > 0)) {
2423 lastJarAccessed = System.currentTimeMillis();
2424 if (jarFiles[0] == null) {
2425 for (int i = 0; i < jarFiles.length; i++) {
2426 try {
2427 jarFiles[i] = new JarFile(jarRealFiles[i]);
2428 } catch (IOException e) {
2429 if (log.isDebugEnabled()) {
2430 log.debug("Failed to open JAR", e);
2431 }
2432 return false;
2433 }
2434 }
2435 }
2436 }
2437 return true;
2438 }
2439
2440
2441 /**
2442 * Find specified class in local repositories.
2443 *
2444 * @return the loaded class, or null if the class isn't found
2445 */
2446 protected Class findClassInternal(String name)
2447 throws ClassNotFoundException {
2448
2449 if (!validate(name))
2450 throw new ClassNotFoundException(name);
2451
2452 String tempPath = name.replace('.', '/');
2453 String classPath = tempPath + ".class";
2454
2455 ResourceEntry entry = null;
2456
2457 if (securityManager != null) {
2458 PrivilegedAction<ResourceEntry> dp =
2459 new PrivilegedFindResourceByName(name, classPath);
2460 entry = AccessController.doPrivileged(dp);
2461 } else {
2462 entry = findResourceInternal(name, classPath);
2463 }
2464
2465 if (entry == null)
2466 throw new ClassNotFoundException(name);
2467
2468 Class clazz = entry.loadedClass;
2469 if (clazz != null)
2470 return clazz;
2471
2472 synchronized (name.intern()) {
2473 clazz = entry.loadedClass;
2474 if (clazz != null)
2475 return clazz;
2476
2477 if (entry.binaryContent == null)
2478 throw new ClassNotFoundException(name);
2479
2480 // Looking up the package
2481 String packageName = null;
2482 int pos = name.lastIndexOf('.');
2483 if (pos != -1)
2484 packageName = name.substring(0, pos);
2485
2486 Package pkg = null;
2487
2488 if (packageName != null) {
2489 pkg = getPackage(packageName);
2490 // Define the package (if null)
2491 if (pkg == null) {
2492 try {
2493 if (entry.manifest == null) {
2494 definePackage(packageName, null, null, null, null,
2495 null, null, null);
2496 } else {
2497 definePackage(packageName, entry.manifest,
2498 entry.codeBase);
2499 }
2500 } catch (IllegalArgumentException e) {
2501 // Ignore: normal error due to dual definition of package
2502 }
2503 pkg = getPackage(packageName);
2504 }
2505 }
2506
2507 if (securityManager != null) {
2508
2509 // Checking sealing
2510 if (pkg != null) {
2511 boolean sealCheck = true;
2512 if (pkg.isSealed()) {
2513 sealCheck = pkg.isSealed(entry.codeBase);
2514 } else {
2515 sealCheck = (entry.manifest == null)
2516 || !isPackageSealed(packageName, entry.manifest);
2517 }
2518 if (!sealCheck)
2519 throw new SecurityException
2520 ("Sealing violation loading " + name + " : Package "
2521 + packageName + " is sealed.");
2522 }
2523
2524 }
2525
2526 try {
2527 clazz = defineClass(name, entry.binaryContent, 0,
2528 entry.binaryContent.length,
2529 new CodeSource(entry.codeBase, entry.certificates));
2530 } catch (UnsupportedClassVersionError ucve) {
2531 throw new UnsupportedClassVersionError(
2532 ucve.getLocalizedMessage() + " " +
2533 sm.getString("webappClassLoader.wrongVersion",
2534 name));
2535 }
2536 entry.loadedClass = clazz;
2537 entry.binaryContent = null;
2538 entry.source = null;
2539 entry.codeBase = null;
2540 entry.manifest = null;
2541 entry.certificates = null;
2542 }
2543
2544 return clazz;
2545
2546 }
2547
2548 /**
2549 * Find specified resource in local repositories.
2550 *
2551 * @return the loaded resource, or null if the resource isn't found
2552 */
2553 protected ResourceEntry findResourceInternal(File file, String path){
2554 ResourceEntry entry = new ResourceEntry();
2555 try {
2556 entry.source = getURI(new File(file, path));
2557 entry.codeBase = getURL(new File(file, path), false);
2558 } catch (MalformedURLException e) {
2559 return null;
2560 }
2561 return entry;
2562 }
2563
2564
2565 /**
2566 * Find specified resource in local repositories.
2567 *
2568 * @return the loaded resource, or null if the resource isn't found
2569 */
2570 protected ResourceEntry findResourceInternal(String name, String path) {
2571
2572 if (!started) {
2573 log.info(sm.getString("webappClassLoader.stopped", name));
2574 return null;
2575 }
2576
2577 if ((name == null) || (path == null))
2578 return null;
2579
2580 ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
2581 if (entry != null)
2582 return entry;
2583
2584 int contentLength = -1;
2585 InputStream binaryStream = null;
2586
2587 int jarFilesLength = jarFiles.length;
2588 int repositoriesLength = repositories.length;
2589
2590 int i;
2591
2592 Resource resource = null;
2593
2594 boolean fileNeedConvert = false;
2595
2596 for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
2597 try {
2598
2599 String fullPath = repositories[i] + path;
2600
2601 Object lookupResult = resources.lookup(fullPath);
2602 if (lookupResult instanceof Resource) {
2603 resource = (Resource) lookupResult;
2604 }
2605
2606 // Note : Not getting an exception here means the resource was
2607 // found
2608 entry = findResourceInternal(files[i], path);
2609
2610 ResourceAttributes attributes =
2611 (ResourceAttributes) resources.getAttributes(fullPath);
2612 contentLength = (int) attributes.getContentLength();
2613 entry.lastModified = attributes.getLastModified();
2614
2615 if (resource != null) {
2616
2617
2618 try {
2619 binaryStream = resource.streamContent();
2620 } catch (IOException e) {
2621 return null;
2622 }
2623
2624 if (needConvert) {
2625 if (path.endsWith(".properties")) {
2626 fileNeedConvert = true;
2627 }
2628 }
2629
2630 // Register the full path for modification checking
2631 // Note: Only syncing on a 'constant' object is needed
2632 synchronized (allPermission) {
2633
2634 int j;
2635
2636 long[] result2 =
2637 new long[lastModifiedDates.length + 1];
2638 for (j = 0; j < lastModifiedDates.length; j++) {
2639 result2[j] = lastModifiedDates[j];
2640 }
2641 result2[lastModifiedDates.length] = entry.lastModified;
2642 lastModifiedDates = result2;
2643
2644 String[] result = new String[paths.length + 1];
2645 for (j = 0; j < paths.length; j++) {
2646 result[j] = paths[j];
2647 }
2648 result[paths.length] = fullPath;
2649 paths = result;
2650
2651 }
2652
2653 }
2654
2655 } catch (NamingException e) {
2656 }
2657 }
2658
2659 if ((entry == null) && (notFoundResources.containsKey(name)))
2660 return null;
2661
2662 JarEntry jarEntry = null;
2663
2664 synchronized (jarFiles) {
2665
2666 try {
2667 if (!openJARs()) {
2668 return null;
2669 }
2670 for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
2671
2672 jarEntry = jarFiles[i].getJarEntry(path);
2673
2674 if (jarEntry != null) {
2675
2676 entry = new ResourceEntry();
2677 try {
2678 entry.codeBase = getURL(jarRealFiles[i], false);
2679 String jarFakeUrl = getURI(jarRealFiles[i]).toString();
2680 jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
2681 entry.source = new URL(jarFakeUrl);
2682 entry.lastModified = jarRealFiles[i].lastModified();
2683 } catch (MalformedURLException e) {
2684 return null;
2685 }
2686 contentLength = (int) jarEntry.getSize();
2687 try {
2688 entry.manifest = jarFiles[i].getManifest();
2689 binaryStream = jarFiles[i].getInputStream(jarEntry);
2690 } catch (IOException e) {
2691 return null;
2692 }
2693
2694 // Extract resources contained in JAR to the workdir
2695 if (antiJARLocking && !(path.endsWith(".class"))) {
2696 byte[] buf = new byte[1024];
2697 File resourceFile = new File
2698 (loaderDir, jarEntry.getName());
2699 if (!resourceFile.exists()) {
2700 Enumeration<JarEntry> entries =
2701 jarFiles[i].entries();
2702 while (entries.hasMoreElements()) {
2703 JarEntry jarEntry2 = entries.nextElement();
2704 if (!(jarEntry2.isDirectory())
2705 && (!jarEntry2.getName().endsWith
2706 (".class"))) {
2707 resourceFile = new File
2708 (loaderDir, jarEntry2.getName());
2709 try {
2710 if (!resourceFile.getCanonicalPath().startsWith(
2711 canonicalLoaderDir)) {
2712 throw new IllegalArgumentException(
2713 sm.getString("webappClassLoader.illegalJarPath",
2714 jarEntry2.getName()));
2715 }
2716 } catch (IOException ioe) {
2717 throw new IllegalArgumentException(
2718 sm.getString("webappClassLoader.validationErrorJarPath",
2719 jarEntry2.getName()), ioe);
2720 }
2721 resourceFile.getParentFile().mkdirs();
2722 FileOutputStream os = null;
2723 InputStream is = null;
2724 try {
2725 is = jarFiles[i].getInputStream
2726 (jarEntry2);
2727 os = new FileOutputStream
2728 (resourceFile);
2729 while (true) {
2730 int n = is.read(buf);
2731 if (n <= 0) {
2732 break;
2733 }
2734 os.write(buf, 0, n);
2735 }
2736 } catch (IOException e) {
2737 // Ignore
2738 } finally {
2739 try {
2740 if (is != null) {
2741 is.close();
2742 }
2743 } catch (IOException e) {
2744 }
2745 try {
2746 if (os != null) {
2747 os.close();
2748 }
2749 } catch (IOException e) {
2750 }
2751 }
2752 }
2753 }
2754 }
2755 }
2756
2757 }
2758
2759 }
2760
2761 if (entry == null) {
2762 synchronized (notFoundResources) {
2763 notFoundResources.put(name, name);
2764 }
2765 return null;
2766 }
2767
2768 if (binaryStream != null) {
2769
2770 byte[] binaryContent = new byte[contentLength];
2771
2772 int pos = 0;
2773 try {
2774
2775 while (true) {
2776 int n = binaryStream.read(binaryContent, pos,
2777 binaryContent.length - pos);
2778 if (n <= 0)
2779 break;
2780 pos += n;
2781 }
2782 } catch (IOException e) {
2783 log.error(sm.getString("webappClassLoader.readError", name), e);
2784 return null;
2785 }
2786 if (fileNeedConvert) {
2787 // Workaround for certain files on platforms that use
2788 // EBCDIC encoding, when they are read through FileInputStream.
2789 // See commit message of rev.303915 for details
2790 // http://svn.apache.org/viewvc?view=revision&revision=303915
2791 String str = new String(binaryContent,0,pos);
2792 try {
2793 binaryContent = str.getBytes("UTF-8");
2794 } catch (Exception e) {
2795 return null;
2796 }
2797 }
2798 entry.binaryContent = binaryContent;
2799
2800 // The certificates are only available after the JarEntry
2801 // associated input stream has been fully read
2802 if (jarEntry != null) {
2803 entry.certificates = jarEntry.getCertificates();
2804 }
2805
2806 }
2807 } finally {
2808 if (binaryStream != null) {
2809 try {
2810 binaryStream.close();
2811 } catch (IOException e) { /* Ignore */}
2812 }
2813 }
2814 }
2815
2816 // Add the entry in the local resource repository
2817 synchronized (resourceEntries) {
2818 // Ensures that all the threads which may be in a race to load
2819 // a particular class all end up with the same ResourceEntry
2820 // instance
2821 ResourceEntry entry2 = (ResourceEntry) resourceEntries.get(name);
2822 if (entry2 == null) {
2823 resourceEntries.put(name, entry);
2824 } else {
2825 entry = entry2;
2826 }
2827 }
2828
2829 return entry;
2830
2831 }
2832
2833
2834 /**
2835 * Returns true if the specified package name is sealed according to the
2836 * given manifest.
2837 */
2838 protected boolean isPackageSealed(String name, Manifest man) {
2839
2840 String path = name.replace('.', '/') + '/';
2841 Attributes attr = man.getAttributes(path);
2842 String sealed = null;
2843 if (attr != null) {
2844 sealed = attr.getValue(Name.SEALED);
2845 }
2846 if (sealed == null) {
2847 if ((attr = man.getMainAttributes()) != null) {
2848 sealed = attr.getValue(Name.SEALED);
2849 }
2850 }
2851 return "true".equalsIgnoreCase(sealed);
2852
2853 }
2854
2855
2856 /**
2857 * Finds the resource with the given name if it has previously been
2858 * loaded and cached by this class loader, and return an input stream
2859 * to the resource data. If this resource has not been cached, return
2860 * <code>null</code>.
2861 *
2862 * @param name Name of the resource to return
2863 */
2864 protected InputStream findLoadedResource(String name) {
2865
2866 ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
2867 if (entry != null) {
2868 if (entry.binaryContent != null)
2869 return new ByteArrayInputStream(entry.binaryContent);
2870 }
2871 return (null);
2872
2873 }
2874
2875
2876 /**
2877 * Finds the class with the given name if it has previously been
2878 * loaded and cached by this class loader, and return the Class object.
2879 * If this class has not been cached, return <code>null</code>.
2880 *
2881 * @param name Name of the resource to return
2882 */
2883 protected Class findLoadedClass0(String name) {
2884
2885 ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
2886 if (entry != null) {
2887 return entry.loadedClass;
2888 }
2889 return (null); // FIXME - findLoadedResource()
2890
2891 }
2892
2893
2894 /**
2895 * Refresh the system policy file, to pick up eventual changes.
2896 */
2897 protected void refreshPolicy() {
2898
2899 try {
2900 // The policy file may have been modified to adjust
2901 // permissions, so we're reloading it when loading or
2902 // reloading a Context
2903 Policy policy = Policy.getPolicy();
2904 policy.refresh();
2905 } catch (AccessControlException e) {
2906 // Some policy files may restrict this, even for the core,
2907 // so this exception is ignored
2908 }
2909
2910 }
2911
2912
2913 /**
2914 * Filter classes.
2915 *
2916 * @param name class name
2917 * @return true if the class should be filtered
2918 */
2919 protected boolean filter(String name) {
2920
2921 if (name == null)
2922 return false;
2923
2924 // Looking up the package
2925 String packageName = null;
2926 int pos = name.lastIndexOf('.');
2927 if (pos != -1)
2928 packageName = name.substring(0, pos);
2929 else
2930 return false;
2931
2932 for (int i = 0; i < packageTriggers.length; i++) {
2933 if (packageName.startsWith(packageTriggers[i]))
2934 return true;
2935 }
2936
2937 return false;
2938
2939 }
2940
2941
2942 /**
2943 * Validate a classname. As per SRV.9.7.2, we must restict loading of
2944 * classes from J2SE (java.*) and classes of the servlet API
2945 * (javax.servlet.*). That should enhance robustness and prevent a number
2946 * of user error (where an older version of servlet.jar would be present
2947 * in /WEB-INF/lib).
2948 *
2949 * @param name class name
2950 * @return true if the name is valid
2951 */
2952 protected boolean validate(String name) {
2953
2954 if (name == null)
2955 return false;
2956 if (name.startsWith("java."))
2957 return false;
2958
2959 return true;
2960
2961 }
2962
2963
2964 /**
2965 * Check the specified JAR file, and return <code>true</code> if it does
2966 * not contain any of the trigger classes.
2967 *
2968 * @param jarfile The JAR file to be checked
2969 *
2970 * @exception IOException if an input/output error occurs
2971 */
2972 protected boolean validateJarFile(File jarfile)
2973 throws IOException {
2974
2975 if (triggers == null)
2976 return (true);
2977 JarFile jarFile = new JarFile(jarfile);
2978 for (int i = 0; i < triggers.length; i++) {
2979 Class clazz = null;
2980 try {
2981 if (parent != null) {
2982 clazz = parent.loadClass(triggers[i]);
2983 } else {
2984 clazz = Class.forName(triggers[i]);
2985 }
2986 } catch (Throwable t) {
2987 clazz = null;
2988 }
2989 if (clazz == null)
2990 continue;
2991 String name = triggers[i].replace('.', '/') + ".class";
2992 if (log.isDebugEnabled())
2993 log.debug(" Checking for " + name);
2994 JarEntry jarEntry = jarFile.getJarEntry(name);
2995 if (jarEntry != null) {
2996 log.info("validateJarFile(" + jarfile +
2997 ") - jar not loaded. See Servlet Spec 2.3, "
2998 + "section 9.7.2. Offending class: " + name);
2999 jarFile.close();
3000 return (false);
3001 }
3002 }
3003 jarFile.close();
3004 return (true);
3005
3006 }
3007
3008
3009 /**
3010 * Get URL.
3011 */
3012 protected URL getURL(File file, boolean encoded)
3013 throws MalformedURLException {
3014
3015 File realFile = file;
3016 try {
3017 realFile = realFile.getCanonicalFile();
3018 } catch (IOException e) {
3019 // Ignore
3020 }
3021 if(encoded) {
3022 return getURI(realFile);
3023 } else {
3024 return realFile.toURL();
3025 }
3026
3027 }
3028
3029
3030 /**
3031 * Get URL.
3032 */
3033 protected URL getURI(File file)
3034 throws MalformedURLException {
3035
3036
3037 File realFile = file;
3038 try {
3039 realFile = realFile.getCanonicalFile();
3040 } catch (IOException e) {
3041 // Ignore
3042 }
3043 return realFile.toURI().toURL();
3044
3045 }
3046
3047
3048 /**
3049 * Delete the specified directory, including all of its contents and
3050 * subdirectories recursively.
3051 *
3052 * @param dir File object representing the directory to be deleted
3053 */
3054 protected static void deleteDir(File dir) {
3055
3056 String files[] = dir.list();
3057 if (files == null) {
3058 files = new String[0];
3059 }
3060 for (int i = 0; i < files.length; i++) {
3061 File file = new File(dir, files[i]);
3062 if (file.isDirectory()) {
3063 deleteDir(file);
3064 } else {
3065 file.delete();
3066 }
3067 }
3068 dir.delete();
3069
3070 }
3071
3072
3073 }
3074