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.reflect.Field;
28 import java.lang.reflect.Modifier;
29 import java.net.MalformedURLException;
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.sql.Driver;
40 import java.sql.DriverManager;
41 import java.sql.SQLException;
42 import java.util.ArrayList;
43 import java.util.Enumeration;
44 import java.util.HashMap;
45 import java.util.Iterator;
46 import java.util.Vector;
47 import java.util.jar.Attributes;
48 import java.util.jar.JarEntry;
49 import java.util.jar.JarFile;
50 import java.util.jar.Manifest;
51 import java.util.jar.Attributes.Name;
52
53 import javax.naming.NameClassPair;
54 import javax.naming.NamingEnumeration;
55 import javax.naming.NamingException;
56 import javax.naming.directory.DirContext;
57
58 import org.apache.catalina.Lifecycle;
59 import org.apache.catalina.LifecycleException;
60 import org.apache.catalina.LifecycleListener;
61 import org.apache.catalina.util.StringManager;
62 import org.apache.naming.JndiPermission;
63 import org.apache.naming.resources.Resource;
64 import org.apache.naming.resources.ResourceAttributes;
65 import org.apache.tomcat.util.IntrospectionUtils;
66
67 /**
68 * Specialized web application class loader.
69 * <p>
70 * This class loader is a full reimplementation of the
71 * <code>URLClassLoader</code> from the JDK. It is desinged to be fully
72 * compatible with a normal <code>URLClassLoader</code>, although its internal
73 * behavior may be completely different.
74 * <p>
75 * <strong>IMPLEMENTATION NOTE</strong> - This class loader faithfully follows
76 * the delegation model recommended in the specification. The system class
77 * loader will be queried first, then the local repositories, and only then
78 * delegation to the parent class loader will occur. This allows the web
79 * application to override any shared class except the classes from J2SE.
80 * Special handling is provided from the JAXP XML parser interfaces, the JNDI
81 * interfaces, and the classes from the servlet API, which are never loaded
82 * from the webapp repository.
83 * <p>
84 * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper
85 * compilation technology, any repository which contains classes from
86 * the servlet API will be ignored by the class loader.
87 * <p>
88 * <strong>IMPLEMENTATION NOTE</strong> - The class loader generates source
89 * URLs which include the full JAR URL when a class is loaded from a JAR file,
90 * which allows setting security permission at the class level, even when a
91 * class is contained inside a JAR.
92 * <p>
93 * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in
94 * the order they are added via the initial constructor and/or any subsequent
95 * calls to <code>addRepository()</code> or <code>addJar()</code>.
96 * <p>
97 * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
98 * security is made unless a security manager is present.
99 *
100 * @author Remy Maucherat
101 * @author Craig R. McClanahan
102 * @version $Revision: 604974 $ $Date: 2007-12-17 20:36:44 +0100 (lun., 17 déc. 2007) $
103 */
104 public class WebappClassLoader
105 extends URLClassLoader
106 implements Reloader, Lifecycle
107 {
108
109 protected static org.apache.juli.logging.Log log=
110 org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
111
112 public static final boolean ENABLE_CLEAR_REFERENCES =
113 Boolean.valueOf(System.getProperty("org.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES", "true")).booleanValue();
114
115 protected class PrivilegedFindResource
116 implements PrivilegedAction {
117
118 protected File file;
119 protected String path;
120
121 PrivilegedFindResource(File file, String path) {
122 this.file = file;
123 this.path = path;
124 }
125
126 public Object run() {
127 return findResourceInternal(file, path);
128 }
129
130 }
131
132
133 // ------------------------------------------------------- Static Variables
134
135
136 /**
137 * The set of trigger classes that will cause a proposed repository not
138 * to be added if this class is visible to the class loader that loaded
139 * this factory class. Typically, trigger classes will be listed for
140 * components that have been integrated into the JDK for later versions,
141 * but where the corresponding JAR files are required to run on
142 * earlier versions.
143 */
144 protected static final String[] triggers = {
145 "javax.servlet.Servlet" // Servlet API
146 };
147
148
149 /**
150 * Set of package names which are not allowed to be loaded from a webapp
151 * class loader without delegating first.
152 */
153 protected static final String[] packageTriggers = {
154 };
155
156
157 /**
158 * The string manager for this package.
159 */
160 protected static final StringManager sm =
161 StringManager.getManager(Constants.Package);
162
163
164 /**
165 * Use anti JAR locking code, which does URL rerouting when accessing
166 * resources.
167 */
168 boolean antiJARLocking = false;
169
170
171 // ----------------------------------------------------------- Constructors
172
173
174 /**
175 * Construct a new ClassLoader with no defined repositories and no
176 * parent ClassLoader.
177 */
178 public WebappClassLoader() {
179
180 super(new URL[0]);
181 this.parent = getParent();
182 system = getSystemClassLoader();
183 securityManager = System.getSecurityManager();
184
185 if (securityManager != null) {
186 refreshPolicy();
187 }
188
189 }
190
191
192 /**
193 * Construct a new ClassLoader with no defined repositories and no
194 * parent ClassLoader.
195 */
196 public WebappClassLoader(ClassLoader parent) {
197
198 super(new URL[0], parent);
199
200 this.parent = getParent();
201
202 system = getSystemClassLoader();
203 securityManager = System.getSecurityManager();
204
205 if (securityManager != null) {
206 refreshPolicy();
207 }
208 }
209
210
211 // ----------------------------------------------------- Instance Variables
212
213
214 /**
215 * Associated directory context giving access to the resources in this
216 * webapp.
217 */
218 protected DirContext resources = null;
219
220
221 /**
222 * The cache of ResourceEntry for classes and resources we have loaded,
223 * keyed by resource name.
224 */
225 protected HashMap resourceEntries = new HashMap();
226
227
228 /**
229 * The list of not found resources.
230 */
231 protected HashMap notFoundResources = new HashMap();
232
233
234 /**
235 * Should this class loader delegate to the parent class loader
236 * <strong>before</strong> searching its own repositories (i.e. the
237 * usual Java2 delegation model)? If set to <code>false</code>,
238 * this class loader will search its own repositories first, and
239 * delegate to the parent only if the class or resource is not
240 * found locally.
241 */
242 protected boolean delegate = false;
243
244
245 /**
246 * Last time a JAR was accessed.
247 */
248 protected long lastJarAccessed = 0L;
249
250
251 /**
252 * The list of local repositories, in the order they should be searched
253 * for locally loaded classes or resources.
254 */
255 protected String[] repositories = new String[0];
256
257
258 /**
259 * Repositories URLs, used to cache the result of getURLs.
260 */
261 protected URL[] repositoryURLs = null;
262
263
264 /**
265 * Repositories translated as path in the work directory (for Jasper
266 * originally), but which is used to generate fake URLs should getURLs be
267 * called.
268 */
269 protected File[] files = new File[0];
270
271
272 /**
273 * The list of JARs, in the order they should be searched
274 * for locally loaded classes or resources.
275 */
276 protected JarFile[] jarFiles = new JarFile[0];
277
278
279 /**
280 * The list of JARs, in the order they should be searched
281 * for locally loaded classes or resources.
282 */
283 protected File[] jarRealFiles = new File[0];
284
285
286 /**
287 * The path which will be monitored for added Jar files.
288 */
289 protected String jarPath = null;
290
291
292 /**
293 * The list of JARs, in the order they should be searched
294 * for locally loaded classes or resources.
295 */
296 protected String[] jarNames = new String[0];
297
298
299 /**
300 * The list of JARs last modified dates, in the order they should be
301 * searched for locally loaded classes or resources.
302 */
303 protected long[] lastModifiedDates = new long[0];
304
305
306 /**
307 * The list of resources which should be checked when checking for
308 * modifications.
309 */
310 protected String[] paths = new String[0];
311
312
313 /**
314 * A list of read File and Jndi Permission's required if this loader
315 * is for a web application context.
316 */
317 protected ArrayList permissionList = new ArrayList();
318
319
320 /**
321 * Path where resources loaded from JARs will be extracted.
322 */
323 protected File loaderDir = null;
324
325
326 /**
327 * The PermissionCollection for each CodeSource for a web
328 * application context.
329 */
330 protected HashMap loaderPC = new HashMap();
331
332
333 /**
334 * Instance of the SecurityManager installed.
335 */
336 protected SecurityManager securityManager = null;
337
338
339 /**
340 * The parent class loader.
341 */
342 protected ClassLoader parent = null;
343
344
345 /**
346 * The system class loader.
347 */
348 protected ClassLoader system = null;
349
350
351 /**
352 * Has this component been started?
353 */
354 protected boolean started = false;
355
356
357 /**
358 * Has external repositories.
359 */
360 protected boolean hasExternalRepositories = false;
361
362 /**
363 * need conversion for properties files
364 */
365 protected boolean needConvert = false;
366
367
368 /**
369 * All permission.
370 */
371 protected Permission allPermission = new java.security.AllPermission();
372
373
374 // ------------------------------------------------------------- Properties
375
376
377 /**
378 * Get associated resources.
379 */
380 public DirContext getResources() {
381
382 return this.resources;
383
384 }
385
386
387 /**
388 * Set associated resources.
389 */
390 public void setResources(DirContext resources) {
391
392 this.resources = resources;
393
394 }
395
396
397 /**
398 * Return the "delegate first" flag for this class loader.
399 */
400 public boolean getDelegate() {
401
402 return (this.delegate);
403
404 }
405
406
407 /**
408 * Set the "delegate first" flag for this class loader.
409 *
410 * @param delegate The new "delegate first" flag
411 */
412 public void setDelegate(boolean delegate) {
413
414 this.delegate = delegate;
415
416 }
417
418
419 /**
420 * @return Returns the antiJARLocking.
421 */
422 public boolean getAntiJARLocking() {
423 return antiJARLocking;
424 }
425
426
427 /**
428 * @param antiJARLocking The antiJARLocking to set.
429 */
430 public void setAntiJARLocking(boolean antiJARLocking) {
431 this.antiJARLocking = antiJARLocking;
432 }
433
434
435 /**
436 * If there is a Java SecurityManager create a read FilePermission
437 * or JndiPermission for the file directory path.
438 *
439 * @param path file directory path
440 */
441 public void addPermission(String path) {
442 if (path == null) {
443 return;
444 }
445
446 if (securityManager != null) {
447 Permission permission = null;
448 if( path.startsWith("jndi:") || path.startsWith("jar:jndi:") ) {
449 if (!path.endsWith("/")) {
450 path = path + "/";
451 }
452 permission = new JndiPermission(path + "*");
453 addPermission(permission);
454 } else {
455 if (!path.endsWith(File.separator)) {
456 permission = new FilePermission(path, "read");
457 addPermission(permission);
458 path = path + File.separator;
459 }
460 permission = new FilePermission(path + "-", "read");
461 addPermission(permission);
462 }
463 }
464 }
465
466
467 /**
468 * If there is a Java SecurityManager create a read FilePermission
469 * or JndiPermission for URL.
470 *
471 * @param url URL for a file or directory on local system
472 */
473 public void addPermission(URL url) {
474 if (url != null) {
475 addPermission(url.toString());
476 }
477 }
478
479
480 /**
481 * If there is a Java SecurityManager create a Permission.
482 *
483 * @param permission The permission
484 */
485 public void addPermission(Permission permission) {
486 if ((securityManager != null) && (permission != null)) {
487 permissionList.add(permission);
488 }
489 }
490
491
492 /**
493 * Return the JAR path.
494 */
495 public String getJarPath() {
496
497 return this.jarPath;
498
499 }
500
501
502 /**
503 * Change the Jar path.
504 */
505 public void setJarPath(String jarPath) {
506
507 this.jarPath = jarPath;
508
509 }
510
511
512 /**
513 * Change the work directory.
514 */
515 public void setWorkDir(File workDir) {
516 this.loaderDir = new File(workDir, "loader");
517 }
518
519 /**
520 * Utility method for use in subclasses.
521 * Must be called before Lifecycle methods to have any effect.
522 */
523 protected void setParentClassLoader(ClassLoader pcl) {
524 parent = pcl;
525 }
526
527 // ------------------------------------------------------- Reloader Methods
528
529
530 /**
531 * Add a new repository to the set of places this ClassLoader can look for
532 * classes to be loaded.
533 *
534 * @param repository Name of a source of classes to be loaded, such as a
535 * directory pathname, a JAR file pathname, or a ZIP file pathname
536 *
537 * @exception IllegalArgumentException if the specified repository is
538 * invalid or does not exist
539 */
540 public void addRepository(String repository) {
541
542 // Ignore any of the standard repositories, as they are set up using
543 // either addJar or addRepository
544 if (repository.startsWith("/WEB-INF/lib")
545 || repository.startsWith("/WEB-INF/classes"))
546 return;
547
548 // Add this repository to our underlying class loader
549 try {
550 URL url = new URL(repository);
551 super.addURL(url);
552 hasExternalRepositories = true;
553 repositoryURLs = null;
554 } catch (MalformedURLException e) {
555 IllegalArgumentException iae = new IllegalArgumentException
556 ("Invalid repository: " + repository);
557 iae.initCause(e);
558 throw iae;
559 }
560
561 }
562
563
564 /**
565 * Add a new repository to the set of places this ClassLoader can look for
566 * classes to be loaded.
567 *
568 * @param repository Name of a source of classes to be loaded, such as a
569 * directory pathname, a JAR file pathname, or a ZIP file pathname
570 *
571 * @exception IllegalArgumentException if the specified repository is
572 * invalid or does not exist
573 */
574 synchronized void addRepository(String repository, File file) {
575
576 // Note : There should be only one (of course), but I think we should
577 // keep this a bit generic
578
579 if (repository == null)
580 return;
581
582 if (log.isDebugEnabled())
583 log.debug("addRepository(" + repository + ")");
584
585 int i;
586
587 // Add this repository to our internal list
588 String[] result = new String[repositories.length + 1];
589 for (i = 0; i < repositories.length; i++) {
590 result[i] = repositories[i];
591 }
592 result[repositories.length] = repository;
593 repositories = result;
594
595 // Add the file to the list
596 File[] result2 = new File[files.length + 1];
597 for (i = 0; i < files.length; i++) {
598 result2[i] = files[i];
599 }
600 result2[files.length] = file;
601 files = result2;
602
603 }
604
605
606 synchronized void addJar(String jar, JarFile jarFile, File file)
607 throws IOException {
608
609 if (jar == null)
610 return;
611 if (jarFile == null)
612 return;
613 if (file == null)
614 return;
615
616 if (log.isDebugEnabled())
617 log.debug("addJar(" + jar + ")");
618
619 int i;
620
621 if ((jarPath != null) && (jar.startsWith(jarPath))) {
622
623 String jarName = jar.substring(jarPath.length());
624 while (jarName.startsWith("/"))
625 jarName = jarName.substring(1);
626
627 String[] result = new String[jarNames.length + 1];
628 for (i = 0; i < jarNames.length; i++) {
629 result[i] = jarNames[i];
630 }
631 result[jarNames.length] = jarName;
632 jarNames = result;
633
634 }
635
636 try {
637
638 // Register the JAR for tracking
639
640 long lastModified =
641 ((ResourceAttributes) resources.getAttributes(jar))
642 .getLastModified();
643
644 String[] result = new String[paths.length + 1];
645 for (i = 0; i < paths.length; i++) {
646 result[i] = paths[i];
647 }
648 result[paths.length] = jar;
649 paths = result;
650
651 long[] result3 = new long[lastModifiedDates.length + 1];
652 for (i = 0; i < lastModifiedDates.length; i++) {
653 result3[i] = lastModifiedDates[i];
654 }
655 result3[lastModifiedDates.length] = lastModified;
656 lastModifiedDates = result3;
657
658 } catch (NamingException e) {
659 // Ignore
660 }
661
662 // If the JAR currently contains invalid classes, don't actually use it
663 // for classloading
664 if (!validateJarFile(file))
665 return;
666
667 JarFile[] result2 = new JarFile[jarFiles.length + 1];
668 for (i = 0; i < jarFiles.length; i++) {
669 result2[i] = jarFiles[i];
670 }
671 result2[jarFiles.length] = jarFile;
672 jarFiles = result2;
673
674 // Add the file to the list
675 File[] result4 = new File[jarRealFiles.length + 1];
676 for (i = 0; i < jarRealFiles.length; i++) {
677 result4[i] = jarRealFiles[i];
678 }
679 result4[jarRealFiles.length] = file;
680 jarRealFiles = result4;
681 }
682
683
684 /**
685 * Return a String array of the current repositories for this class
686 * loader. If there are no repositories, a zero-length array is
687 * returned.For security reason, returns a clone of the Array (since
688 * String are immutable).
689 */
690 public String[] findRepositories() {
691
692 return ((String[])repositories.clone());
693
694 }
695
696
697 /**
698 * Have one or more classes or resources been modified so that a reload
699 * is appropriate?
700 */
701 public boolean modified() {
702
703 if (log.isDebugEnabled())
704 log.debug("modified()");
705
706 // Checking for modified loaded resources
707 int length = paths.length;
708
709 // A rare race condition can occur in the updates of the two arrays
710 // It's totally ok if the latest class added is not checked (it will
711 // be checked the next time
712 int length2 = lastModifiedDates.length;
713 if (length > length2)
714 length = length2;
715
716 for (int i = 0; i < length; i++) {
717 try {
718 long lastModified =
719 ((ResourceAttributes) resources.getAttributes(paths[i]))
720 .getLastModified();
721 if (lastModified != lastModifiedDates[i]) {
722 if( log.isDebugEnabled() )
723 log.debug(" Resource '" + paths[i]
724 + "' was modified; Date is now: "
725 + new java.util.Date(lastModified) + " Was: "
726 + new java.util.Date(lastModifiedDates[i]));
727 return (true);
728 }
729 } catch (NamingException e) {
730 log.error(" Resource '" + paths[i] + "' is missing");
731 return (true);
732 }
733 }
734
735 length = jarNames.length;
736
737 // Check if JARs have been added or removed
738 if (getJarPath() != null) {
739
740 try {
741 NamingEnumeration enumeration = resources.listBindings(getJarPath());
742 int i = 0;
743 while (enumeration.hasMoreElements() && (i < length)) {
744 NameClassPair ncPair = (NameClassPair) enumeration.nextElement();
745 String name = ncPair.getName();
746 // Ignore non JARs present in the lib folder
747 if (!name.endsWith(".jar"))
748 continue;
749 if (!name.equals(jarNames[i])) {
750 // Missing JAR
751 log.info(" Additional JARs have been added : '"
752 + name + "'");
753 return (true);
754 }
755 i++;
756 }
757 if (enumeration.hasMoreElements()) {
758 while (enumeration.hasMoreElements()) {
759 NameClassPair ncPair =
760 (NameClassPair) enumeration.nextElement();
761 String name = ncPair.getName();
762 // Additional non-JAR files are allowed
763 if (name.endsWith(".jar")) {
764 // There was more JARs
765 log.info(" Additional JARs have been added");
766 return (true);
767 }
768 }
769 } else if (i < jarNames.length) {
770 // There was less JARs
771 log.info(" Additional JARs have been added");
772 return (true);
773 }
774 } catch (NamingException e) {
775 if (log.isDebugEnabled())
776 log.debug(" Failed tracking modifications of '"
777 + getJarPath() + "'");
778 } catch (ClassCastException e) {
779 log.error(" Failed tracking modifications of '"
780 + getJarPath() + "' : " + e.getMessage());
781 }
782
783 }
784
785 // No classes have been modified
786 return (false);
787
788 }
789
790
791 /**
792 * Render a String representation of this object.
793 */
794 public String toString() {
795
796 StringBuffer sb = new StringBuffer("WebappClassLoader\r\n");
797 sb.append(" delegate: ");
798 sb.append(delegate);
799 sb.append("\r\n");
800 sb.append(" repositories:\r\n");
801 if (repositories != null) {
802 for (int i = 0; i < repositories.length; i++) {
803 sb.append(" ");
804 sb.append(repositories[i]);
805 sb.append("\r\n");
806 }
807 }
808 if (this.parent != null) {
809 sb.append("----------> Parent Classloader:\r\n");
810 sb.append(this.parent.toString());
811 sb.append("\r\n");
812 }
813 return (sb.toString());
814
815 }
816
817
818 // ---------------------------------------------------- ClassLoader Methods
819
820
821 /**
822 * Add the specified URL to the classloader.
823 */
824 protected void addURL(URL url) {
825 super.addURL(url);
826 hasExternalRepositories = true;
827 repositoryURLs = null;
828 }
829
830
831 /**
832 * Find the specified class in our local repositories, if possible. If
833 * not found, throw <code>ClassNotFoundException</code>.
834 *
835 * @param name Name of the class to be loaded
836 *
837 * @exception ClassNotFoundException if the class was not found
838 */
839 public Class findClass(String name) throws ClassNotFoundException {
840
841 if (log.isDebugEnabled())
842 log.debug(" findClass(" + name + ")");
843
844 // Cannot load anything from local repositories if class loader is stopped
845 if (!started) {
846 throw new ClassNotFoundException(name);
847 }
848
849 // (1) Permission to define this class when using a SecurityManager
850 if (securityManager != null) {
851 int i = name.lastIndexOf('.');
852 if (i >= 0) {
853 try {
854 if (log.isTraceEnabled())
855 log.trace(" securityManager.checkPackageDefinition");
856 securityManager.checkPackageDefinition(name.substring(0,i));
857 } catch (Exception se) {
858 if (log.isTraceEnabled())
859 log.trace(" -->Exception-->ClassNotFoundException", se);
860 throw new ClassNotFoundException(name, se);
861 }
862 }
863 }
864
865 // Ask our superclass to locate this class, if possible
866 // (throws ClassNotFoundException if it is not found)
867 Class clazz = null;
868 try {
869 if (log.isTraceEnabled())
870 log.trace(" findClassInternal(" + name + ")");
871 try {
872 clazz = findClassInternal(name);
873 } catch(ClassNotFoundException cnfe) {
874 if (!hasExternalRepositories) {
875 throw cnfe;
876 }
877 } catch(AccessControlException ace) {
878 throw new ClassNotFoundException(name, ace);
879 } catch (RuntimeException e) {
880 if (log.isTraceEnabled())
881 log.trace(" -->RuntimeException Rethrown", e);
882 throw e;
883 }
884 if ((clazz == null) && hasExternalRepositories) {
885 try {
886 synchronized (this) {
887 clazz = super.findClass(name);
888 }
889 } catch(AccessControlException ace) {
890 throw new ClassNotFoundException(name, ace);
891 } catch (RuntimeException e) {
892 if (log.isTraceEnabled())
893 log.trace(" -->RuntimeException Rethrown", e);
894 throw e;
895 }
896 }
897 if (clazz == null) {
898 if (log.isDebugEnabled())
899 log.debug(" --> Returning ClassNotFoundException");
900 throw new ClassNotFoundException(name);
901 }
902 } catch (ClassNotFoundException e) {
903 if (log.isTraceEnabled())
904 log.trace(" --> Passing on ClassNotFoundException");
905 throw e;
906 }
907
908 // Return the class we have located
909 if (log.isTraceEnabled())
910 log.debug(" Returning class " + clazz);
911 if ((log.isTraceEnabled()) && (clazz != null))
912 log.debug(" Loaded by " + clazz.getClassLoader());
913 return (clazz);
914
915 }
916
917
918 /**
919 * Find the specified resource in our local repository, and return a
920 * <code>URL</code> refering to it, or <code>null</code> if this resource
921 * cannot be found.
922 *
923 * @param name Name of the resource to be found
924 */
925 public URL findResource(final String name) {
926
927 if (log.isDebugEnabled())
928 log.debug(" findResource(" + name + ")");
929
930 URL url = null;
931
932 ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
933 if (entry == null) {
934 entry = findResourceInternal(name, name);
935 }
936 if (entry != null) {
937 url = entry.source;
938 }
939
940 if ((url == null) && hasExternalRepositories)
941 url = super.findResource(name);
942
943 if (log.isDebugEnabled()) {
944 if (url != null)
945 log.debug(" --> Returning '" + url.toString() + "'");
946 else
947 log.debug(" --> Resource not found, returning null");
948 }
949 return (url);
950
951 }
952
953
954 /**
955 * Return an enumeration of <code>URLs</code> representing all of the
956 * resources with the given name. If no resources with this name are
957 * found, return an empty enumeration.
958 *
959 * @param name Name of the resources to be found
960 *
961 * @exception IOException if an input/output error occurs
962 */
963 public Enumeration findResources(String name) throws IOException {
964
965 if (log.isDebugEnabled())
966 log.debug(" findResources(" + name + ")");
967
968 Vector result = new Vector();
969
970 int jarFilesLength = jarFiles.length;
971 int repositoriesLength = repositories.length;
972
973 int i;
974
975 // Looking at the repositories
976 for (i = 0; i < repositoriesLength; i++) {
977 try {
978 String fullPath = repositories[i] + name;
979 resources.lookup(fullPath);
980 // Note : Not getting an exception here means the resource was
981 // found
982 try {
983 result.addElement(getURI(new File(files[i], name)));
984 } catch (MalformedURLException e) {
985 // Ignore
986 }
987 } catch (NamingException e) {
988 }
989 }
990
991 // Looking at the JAR files
992 synchronized (jarFiles) {
993 if (openJARs()) {
994 for (i = 0; i < jarFilesLength; i++) {
995 JarEntry jarEntry = jarFiles[i].getJarEntry(name);
996 if (jarEntry != null) {
997 try {
998 String jarFakeUrl = getURI(jarRealFiles[i]).toString();
999 jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name;
1000 result.addElement(new URL(jarFakeUrl));
1001 } catch (MalformedURLException e) {
1002 // Ignore
1003 }
1004 }
1005 }
1006 }
1007 }
1008
1009 // Adding the results of a call to the superclass
1010 if (hasExternalRepositories) {
1011
1012 Enumeration otherResourcePaths = super.findResources(name);
1013
1014 while (otherResourcePaths.hasMoreElements()) {
1015 result.addElement(otherResourcePaths.nextElement());
1016 }
1017
1018 }
1019
1020 return result.elements();
1021
1022 }
1023
1024
1025 /**
1026 * Find the resource with the given name. A resource is some data
1027 * (images, audio, text, etc.) that can be accessed by class code in a
1028 * way that is independent of the location of the code. The name of a
1029 * resource is a "/"-separated path name that identifies the resource.
1030 * If the resource cannot be found, return <code>null</code>.
1031 * <p>
1032 * This method searches according to the following algorithm, returning
1033 * as soon as it finds the appropriate URL. If the resource cannot be
1034 * found, returns <code>null</code>.
1035 * <ul>
1036 * <li>If the <code>delegate</code> property is set to <code>true</code>,
1037 * call the <code>getResource()</code> method of the parent class
1038 * loader, if any.</li>
1039 * <li>Call <code>findResource()</code> to find this resource in our
1040 * locally defined repositories.</li>
1041 * <li>Call the <code>getResource()</code> method of the parent class
1042 * loader, if any.</li>
1043 * </ul>
1044 *
1045 * @param name Name of the resource to return a URL for
1046 */
1047 public URL getResource(String name) {
1048
1049 if (log.isDebugEnabled())
1050 log.debug("getResource(" + name + ")");
1051 URL url = null;
1052
1053 // (1) Delegate to parent if requested
1054 if (delegate) {
1055 if (log.isDebugEnabled())
1056 log.debug(" Delegating to parent classloader " + parent);
1057 ClassLoader loader = parent;
1058 if (loader == null)
1059 loader = system;
1060 url = loader.getResource(name);
1061 if (url != null) {
1062 if (log.isDebugEnabled())
1063 log.debug(" --> Returning '" + url.toString() + "'");
1064 return (url);
1065 }
1066 }
1067
1068 // (2) Search local repositories
1069 url = findResource(name);
1070 if (url != null) {
1071 // Locating the repository for special handling in the case
1072 // of a JAR
1073 if (antiJARLocking) {
1074 ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
1075 try {
1076 String repository = entry.codeBase.toString();
1077 if ((repository.endsWith(".jar"))
1078 && (!(name.endsWith(".class")))) {
1079 // Copy binary content to the work directory if not present
1080 File resourceFile = new File(loaderDir, name);
1081 url = getURI(resourceFile);
1082 }
1083 } catch (Exception e) {
1084 // Ignore
1085 }
1086 }
1087 if (log.isDebugEnabled())
1088 log.debug(" --> Returning '" + url.toString() + "'");
1089 return (url);
1090 }
1091
1092 // (3) Delegate to parent unconditionally if not already attempted
1093 if( !delegate ) {
1094 ClassLoader loader = parent;
1095 if (loader == null)
1096 loader = system;
1097 url = loader.getResource(name);
1098 if (url != null) {
1099 if (log.isDebugEnabled())
1100 log.debug(" --> Returning '" + url.toString() + "'");
1101 return (url);
1102 }
1103 }
1104
1105 // (4) Resource was not found
1106 if (log.isDebugEnabled())
1107 log.debug(" --> Resource not found, returning null");
1108 return (null);
1109
1110 }
1111
1112
1113 /**
1114 * Find the resource with the given name, and return an input stream
1115 * that can be used for reading it. The search order is as described
1116 * for <code>getResource()</code>, after checking to see if the resource
1117 * data has been previously cached. If the resource cannot be found,
1118 * return <code>null</code>.
1119 *
1120 * @param name Name of the resource to return an input stream for
1121 */
1122 public InputStream getResourceAsStream(String name) {
1123
1124 if (log.isDebugEnabled())
1125 log.debug("getResourceAsStream(" + name + ")");
1126 InputStream stream = null;
1127
1128 // (0) Check for a cached copy of this resource
1129 stream = findLoadedResource(name);
1130 if (stream != null) {
1131 if (log.isDebugEnabled())
1132 log.debug(" --> Returning stream from cache");
1133 return (stream);
1134 }
1135
1136 // (1) Delegate to parent if requested
1137 if (delegate) {
1138 if (log.isDebugEnabled())
1139 log.debug(" Delegating to parent classloader " + parent);
1140 ClassLoader loader = parent;
1141 if (loader == null)
1142 loader = system;
1143 stream = loader.getResourceAsStream(name);
1144 if (stream != null) {
1145 // FIXME - cache???
1146 if (log.isDebugEnabled())
1147 log.debug(" --> Returning stream from parent");
1148 return (stream);
1149 }
1150 }
1151
1152 // (2) Search local repositories
1153 if (log.isDebugEnabled())
1154 log.debug(" Searching local repositories");
1155 URL url = findResource(name);
1156 if (url != null) {
1157 // FIXME - cache???
1158 if (log.isDebugEnabled())
1159 log.debug(" --> Returning stream from local");
1160 stream = findLoadedResource(name);
1161 try {
1162 if (hasExternalRepositories && (stream == null))
1163 stream = url.openStream();
1164 } catch (IOException e) {
1165 ; // Ignore
1166 }
1167 if (stream != null)
1168 return (stream);
1169 }
1170
1171 // (3) Delegate to parent unconditionally
1172 if (!delegate) {
1173 if (log.isDebugEnabled())
1174 log.debug(" Delegating to parent classloader unconditionally " + parent);
1175 ClassLoader loader = parent;
1176 if (loader == null)
1177 loader = system;
1178 stream = loader.getResourceAsStream(name);
1179 if (stream != null) {
1180 // FIXME - cache???
1181 if (log.isDebugEnabled())
1182 log.debug(" --> Returning stream from parent");
1183 return (stream);
1184 }
1185 }
1186
1187 // (4) Resource was not found
1188 if (log.isDebugEnabled())
1189 log.debug(" --> Resource not found, returning null");
1190 return (null);
1191
1192 }
1193
1194
1195 /**
1196 * Load the class with the specified name. This method searches for
1197 * classes in the same manner as <code>loadClass(String, boolean)</code>
1198 * with <code>false</code> as the second argument.
1199 *
1200 * @param name Name of the class to be loaded
1201 *
1202 * @exception ClassNotFoundException if the class was not found
1203 */
1204 public Class loadClass(String name) throws ClassNotFoundException {
1205
1206 return (loadClass(name, false));
1207
1208 }
1209
1210
1211 /**
1212 * Load the class with the specified name, searching using the following
1213 * algorithm until it finds and returns the class. If the class cannot
1214 * be found, returns <code>ClassNotFoundException</code>.
1215 * <ul>
1216 * <li>Call <code>findLoadedClass(String)</code> to check if the
1217 * class has already been loaded. If it has, the same
1218 * <code>Class</code> object is returned.</li>
1219 * <li>If the <code>delegate</code> property is set to <code>true</code>,
1220 * call the <code>loadClass()</code> method of the parent class
1221 * loader, if any.</li>
1222 * <li>Call <code>findClass()</code> to find this class in our locally
1223 * defined repositories.</li>
1224 * <li>Call the <code>loadClass()</code> method of our parent
1225 * class loader, if any.</li>
1226 * </ul>
1227 * If the class was found using the above steps, and the
1228 * <code>resolve</code> flag is <code>true</code>, this method will then
1229 * call <code>resolveClass(Class)</code> on the resulting Class object.
1230 *
1231 * @param name Name of the class to be loaded
1232 * @param resolve If <code>true</code> then resolve the class
1233 *
1234 * @exception ClassNotFoundException if the class was not found
1235 */
1236 public Class loadClass(String name, boolean resolve)
1237 throws ClassNotFoundException {
1238
1239 if (log.isDebugEnabled())
1240 log.debug("loadClass(" + name + ", " + resolve + ")");
1241 Class clazz = null;
1242
1243 // Log access to stopped classloader
1244 if (!started) {
1245 try {
1246 throw new IllegalStateException();
1247 } catch (IllegalStateException e) {
1248 log.info(sm.getString("webappClassLoader.stopped", name), e);
1249 }
1250 }
1251
1252 // (0) Check our previously loaded local class cache
1253 clazz = findLoadedClass0(name);
1254 if (clazz != null) {
1255 if (log.isDebugEnabled())
1256 log.debug(" Returning class from cache");
1257 if (resolve)
1258 resolveClass(clazz);
1259 return (clazz);
1260 }
1261
1262 // (0.1) Check our previously loaded class cache
1263 clazz = findLoadedClass(name);
1264 if (clazz != null) {
1265 if (log.isDebugEnabled())
1266 log.debug(" Returning class from cache");
1267 if (resolve)
1268 resolveClass(clazz);
1269 return (clazz);
1270 }
1271
1272 // (0.2) Try loading the class with the system class loader, to prevent
1273 // the webapp from overriding J2SE classes
1274 try {
1275 clazz = system.loadClass(name);
1276 if (clazz != null) {
1277 if (resolve)
1278 resolveClass(clazz);
1279 return (clazz);
1280 }
1281 } catch (ClassNotFoundException e) {
1282 // Ignore
1283 }
1284
1285 // (0.5) Permission to access this class when using a SecurityManager
1286 if (securityManager != null) {
1287 int i = name.lastIndexOf('.');
1288 if (i >= 0) {
1289 try {
1290 securityManager.checkPackageAccess(name.substring(0,i));
1291 } catch (SecurityException se) {
1292 String error = "Security Violation, attempt to use " +
1293 "Restricted Class: " + name;
1294 log.info(error, se);
1295 throw new ClassNotFoundException(error, se);
1296 }
1297 }
1298 }
1299
1300 boolean delegateLoad = delegate || filter(name);
1301
1302 // (1) Delegate to our parent if requested
1303 if (delegateLoad) {
1304 if (log.isDebugEnabled())
1305 log.debug(" Delegating to parent classloader1 " + parent);
1306 ClassLoader loader = parent;
1307 if (loader == null)
1308 loader = system;
1309 try {
1310 clazz = loader.loadClass(name);
1311 if (clazz != null) {
1312 if (log.isDebugEnabled())
1313 log.debug(" Loading class from parent");
1314 if (resolve)
1315 resolveClass(clazz);
1316 return (clazz);
1317 }
1318 } catch (ClassNotFoundException e) {
1319 ;
1320 }
1321 }
1322
1323 // (2) Search local repositories
1324 if (log.isDebugEnabled())
1325 log.debug(" Searching local repositories");
1326 try {
1327 clazz = findClass(name);
1328 if (clazz != null) {
1329 if (log.isDebugEnabled())
1330 log.debug(" Loading class from local repository");
1331 if (resolve)
1332 resolveClass(clazz);
1333 return (clazz);
1334 }
1335 } catch (ClassNotFoundException e) {
1336 ;
1337 }
1338
1339 // (3) Delegate to parent unconditionally
1340 if (!delegateLoad) {
1341 if (log.isDebugEnabled())
1342 log.debug(" Delegating to parent classloader at end: " + parent);
1343 ClassLoader loader = parent;
1344 if (loader == null)
1345 loader = system;
1346 try {
1347 clazz = loader.loadClass(name);
1348 if (clazz != null) {
1349 if (log.isDebugEnabled())
1350 log.debug(" Loading class from parent");
1351 if (resolve)
1352 resolveClass(clazz);
1353 return (clazz);
1354 }
1355 } catch (ClassNotFoundException e) {
1356 ;
1357 }
1358 }
1359
1360 throw new ClassNotFoundException(name);
1361 }
1362
1363
1364 /**
1365 * Get the Permissions for a CodeSource. If this instance
1366 * of WebappClassLoader is for a web application context,
1367 * add read FilePermission or JndiPermissions for the base
1368 * directory (if unpacked),
1369 * the context URL, and jar file resources.
1370 *
1371 * @param codeSource where the code was loaded from
1372 * @return PermissionCollection for CodeSource
1373 */
1374 protected PermissionCollection getPermissions(CodeSource codeSource) {
1375
1376 String codeUrl = codeSource.getLocation().toString();
1377 PermissionCollection pc;
1378 if ((pc = (PermissionCollection)loaderPC.get(codeUrl)) == null) {
1379 pc = super.getPermissions(codeSource);
1380 if (pc != null) {
1381 Iterator perms = permissionList.iterator();
1382 while (perms.hasNext()) {
1383 Permission p = (Permission)perms.next();
1384 pc.add(p);
1385 }
1386 loaderPC.put(codeUrl,pc);
1387 }
1388 }
1389 return (pc);
1390
1391 }
1392
1393
1394 /**
1395 * Returns the search path of URLs for loading classes and resources.
1396 * This includes the original list of URLs specified to the constructor,
1397 * along with any URLs subsequently appended by the addURL() method.
1398 * @return the search path of URLs for loading classes and resources.
1399 */
1400 public URL[] getURLs() {
1401
1402 if (repositoryURLs != null) {
1403 return repositoryURLs;
1404 }
1405
1406 URL[] external = super.getURLs();
1407
1408 int filesLength = files.length;
1409 int jarFilesLength = jarRealFiles.length;
1410 int length = filesLength + jarFilesLength + external.length;
1411 int i;
1412
1413 try {
1414
1415 URL[] urls = new URL[length];
1416 for (i = 0; i < length; i++) {
1417 if (i < filesLength) {
1418 urls[i] = getURL(files[i], true);
1419 } else if (i < filesLength + jarFilesLength) {
1420 urls[i] = getURL(jarRealFiles[i - filesLength], true);
1421 } else {
1422 urls[i] = external[i - filesLength - jarFilesLength];
1423 }
1424 }
1425
1426 repositoryURLs = urls;
1427
1428 } catch (MalformedURLException e) {
1429 repositoryURLs = new URL[0];
1430 }
1431
1432 return repositoryURLs;
1433
1434 }
1435
1436
1437 // ------------------------------------------------------ Lifecycle Methods
1438
1439
1440 /**
1441 * Add a lifecycle event listener to this component.
1442 *
1443 * @param listener The listener to add
1444 */
1445 public void addLifecycleListener(LifecycleListener listener) {
1446 }
1447
1448
1449 /**
1450 * Get the lifecycle listeners associated with this lifecycle. If this
1451 * Lifecycle has no listeners registered, a zero-length array is returned.
1452 */
1453 public LifecycleListener[] findLifecycleListeners() {
1454 return new LifecycleListener[0];
1455 }
1456
1457
1458 /**
1459 * Remove a lifecycle event listener from this component.
1460 *
1461 * @param listener The listener to remove
1462 */
1463 public void removeLifecycleListener(LifecycleListener listener) {
1464 }
1465
1466
1467 /**
1468 * Start the class loader.
1469 *
1470 * @exception LifecycleException if a lifecycle error occurs
1471 */
1472 public void start() throws LifecycleException {
1473
1474 started = true;
1475 String encoding = null;
1476 try {
1477 encoding = System.getProperty("file.encoding");
1478 } catch (Exception e) {
1479 return;
1480 }
1481 if (encoding.indexOf("EBCDIC")!=-1) {
1482 needConvert = true;
1483 }
1484
1485 }
1486
1487
1488 /**
1489 * Stop the class loader.
1490 *
1491 * @exception LifecycleException if a lifecycle error occurs
1492 */
1493 public void stop() throws LifecycleException {
1494
1495 // Clearing references should be done before setting started to
1496 // false, due to possible side effects
1497 clearReferences();
1498
1499 started = false;
1500
1501 int length = files.length;
1502 for (int i = 0; i < length; i++) {
1503 files[i] = null;
1504 }
1505
1506 length = jarFiles.length;
1507 for (int i = 0; i < length; i++) {
1508 try {
1509 if (jarFiles[i] != null) {
1510 jarFiles[i].close();
1511 }
1512 } catch (IOException e) {
1513 // Ignore
1514 }
1515 jarFiles[i] = null;
1516 }
1517
1518 notFoundResources.clear();
1519 resourceEntries.clear();
1520 resources = null;
1521 repositories = null;
1522 repositoryURLs = null;
1523 files = null;
1524 jarFiles = null;
1525 jarRealFiles = null;
1526 jarPath = null;
1527 jarNames = null;
1528 lastModifiedDates = null;
1529 paths = null;
1530 hasExternalRepositories = false;
1531 parent = null;
1532
1533 permissionList.clear();
1534 loaderPC.clear();
1535
1536 if (loaderDir != null) {
1537 deleteDir(loaderDir);
1538 }
1539
1540 }
1541
1542
1543 /**
1544 * Used to periodically signal to the classloader to release
1545 * JAR resources.
1546 */
1547 public void closeJARs(boolean force) {
1548 if (jarFiles.length > 0) {
1549 synchronized (jarFiles) {
1550 if (force || (System.currentTimeMillis()
1551 > (lastJarAccessed + 90000))) {
1552 for (int i = 0; i < jarFiles.length; i++) {
1553 try {
1554 if (jarFiles[i] != null) {
1555 jarFiles[i].close();
1556 jarFiles[i] = null;
1557 }
1558 } catch (IOException e) {
1559 if (log.isDebugEnabled()) {
1560 log.debug("Failed to close JAR", e);
1561 }
1562 }
1563 }
1564 }
1565 }
1566 }
1567 }
1568
1569
1570 // ------------------------------------------------------ Protected Methods
1571
1572
1573 /**
1574 * Clear references.
1575 */
1576 protected void clearReferences() {
1577
1578 // Unregister any JDBC drivers loaded by this classloader
1579 Enumeration drivers = DriverManager.getDrivers();
1580 while (drivers.hasMoreElements()) {
1581 Driver driver = (Driver) drivers.nextElement();
1582 if (driver.getClass().getClassLoader() == this) {
1583 try {
1584 DriverManager.deregisterDriver(driver);
1585 } catch (SQLException e) {
1586 log.warn("SQL driver deregistration failed", e);
1587 }
1588 }
1589 }
1590
1591 // Null out any static or final fields from loaded classes,
1592 // as a workaround for apparent garbage collection bugs
1593 if (ENABLE_CLEAR_REFERENCES) {
1594 Iterator loadedClasses = ((HashMap) resourceEntries.clone()).values().iterator();
1595 while (loadedClasses.hasNext()) {
1596 ResourceEntry entry = (ResourceEntry) loadedClasses.next();
1597 if (entry.loadedClass != null) {
1598 Class clazz = entry.loadedClass;
1599 try {
1600 Field[] fields = clazz.getDeclaredFields();
1601 for (int i = 0; i < fields.length; i++) {
1602 Field field = fields[i];
1603 int mods = field.getModifiers();
1604 if (field.getType().isPrimitive()
1605 || (field.getName().indexOf("$") != -1)) {
1606 continue;
1607 }
1608 if (Modifier.isStatic(mods)) {
1609 try {
1610 field.setAccessible(true);
1611 if (Modifier.isFinal(mods)) {
1612 if (!((field.getType().getName().startsWith("java."))
1613 || (field.getType().getName().startsWith("javax.")))) {
1614 nullInstance(field.get(null));
1615 }
1616 } else {
1617 field.set(null, null);
1618 if (log.isDebugEnabled()) {
1619 log.debug("Set field " + field.getName()
1620 + " to null in class " + clazz.getName());
1621 }
1622 }
1623 } catch (Throwable t) {
1624 if (log.isDebugEnabled()) {
1625 log.debug("Could not set field " + field.getName()
1626 + " to null in class " + clazz.getName(), t);
1627 }
1628 }
1629 }
1630 }
1631 } catch (Throwable t) {
1632 if (log.isDebugEnabled()) {
1633 log.debug("Could not clean fields for class " + clazz.getName(), t);
1634 }
1635 }
1636 }
1637 }
1638 }
1639
1640 // Clear the IntrospectionUtils cache.
1641 IntrospectionUtils.clear();
1642
1643 // Clear the classloader reference in common-logging
1644 org.apache.juli.logging.LogFactory.release(this);
1645
1646 // Clear the classloader reference in the VM's bean introspector
1647 java.beans.Introspector.flushCaches();
1648
1649 }
1650
1651
1652 protected void nullInstance(Object instance) {
1653 if (instance == null) {
1654 return;
1655 }
1656 Field[] fields = instance.getClass().getDeclaredFields();
1657 for (int i = 0; i < fields.length; i++) {
1658 Field field = fields[i];
1659 int mods = field.getModifiers();
1660 if (field.getType().isPrimitive()
1661 || (field.getName().indexOf("$") != -1)) {
1662 continue;
1663 }
1664 try {
1665 field.setAccessible(true);
1666 if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) {
1667 // Doing something recursively is too risky
1668 continue;
1669 } else {
1670 Object value = field.get(instance);
1671 if (null != value) {
1672 Class valueClass = value.getClass();
1673 if (!loadedByThisOrChild(valueClass)) {
1674 if (log.isDebugEnabled()) {
1675 log.debug("Not setting field " + field.getName() +
1676 " to null in object of class " +
1677 instance.getClass().getName() +
1678 " because the referenced object was of type " +
1679 valueClass.getName() +
1680 " which was not loaded by this WebappClassLoader.");
1681 }
1682 } else {
1683 field.set(instance, null);
1684 if (log.isDebugEnabled()) {
1685 log.debug("Set field " + field.getName()
1686 + " to null in class " + instance.getClass().getName());
1687 }
1688 }
1689 }
1690 }
1691 } catch (Throwable t) {
1692 if (log.isDebugEnabled()) {
1693 log.debug("Could not set field " + field.getName()
1694 + " to null in object instance of class "
1695 + instance.getClass().getName(), t);
1696 }
1697 }
1698 }
1699 }
1700
1701
1702 /**
1703 * Determine whether a class was loaded by this class loader or one of
1704 * its child class loaders.
1705 */
1706 protected boolean loadedByThisOrChild(Class clazz)
1707 {
1708 boolean result = false;
1709 for (ClassLoader classLoader = clazz.getClassLoader();
1710 null != classLoader; classLoader = classLoader.getParent()) {
1711 if (classLoader.equals(this)) {
1712 result = true;
1713 break;
1714 }
1715 }
1716 return result;
1717 }
1718
1719
1720 /**
1721 * Used to periodically signal to the classloader to release JAR resources.
1722 */
1723 protected boolean openJARs() {
1724 if (started && (jarFiles.length > 0)) {
1725 lastJarAccessed = System.currentTimeMillis();
1726 if (jarFiles[0] == null) {
1727 for (int i = 0; i < jarFiles.length; i++) {
1728 try {
1729 jarFiles[i] = new JarFile(jarRealFiles[i]);
1730 } catch (IOException e) {
1731 if (log.isDebugEnabled()) {
1732 log.debug("Failed to open JAR", e);
1733 }
1734 return false;
1735 }
1736 }
1737 }
1738 }
1739 return true;
1740 }
1741
1742
1743 /**
1744 * Find specified class in local repositories.
1745 *
1746 * @return the loaded class, or null if the class isn't found
1747 */
1748 protected Class findClassInternal(String name)
1749 throws ClassNotFoundException {
1750
1751 if (!validate(name))
1752 throw new ClassNotFoundException(name);
1753
1754 String tempPath = name.replace('.', '/');
1755 String classPath = tempPath + ".class";
1756
1757 ResourceEntry entry = null;
1758
1759 entry = findResourceInternal(name, classPath);
1760
1761 if (entry == null)
1762 throw new ClassNotFoundException(name);
1763
1764 Class clazz = entry.loadedClass;
1765 if (clazz != null)
1766 return clazz;
1767
1768 synchronized (this) {
1769 if (entry.binaryContent == null && entry.loadedClass == null)
1770 throw new ClassNotFoundException(name);
1771
1772 // Looking up the package
1773 String packageName = null;
1774 int pos = name.lastIndexOf('.');
1775 if (pos != -1)
1776 packageName = name.substring(0, pos);
1777
1778 Package pkg = null;
1779
1780 if (packageName != null) {
1781 pkg = getPackage(packageName);
1782 // Define the package (if null)
1783 if (pkg == null) {
1784 try {
1785 if (entry.manifest == null) {
1786 definePackage(packageName, null, null, null, null,
1787 null, null, null);
1788 } else {
1789 definePackage(packageName, entry.manifest,
1790 entry.codeBase);
1791 }
1792 } catch (IllegalArgumentException e) {
1793 // Ignore: normal error due to dual definition of package
1794 }
1795 pkg = getPackage(packageName);
1796 }
1797 }
1798
1799 if (securityManager != null) {
1800
1801 // Checking sealing
1802 if (pkg != null) {
1803 boolean sealCheck = true;
1804 if (pkg.isSealed()) {
1805 sealCheck = pkg.isSealed(entry.codeBase);
1806 } else {
1807 sealCheck = (entry.manifest == null)
1808 || !isPackageSealed(packageName, entry.manifest);
1809 }
1810 if (!sealCheck)
1811 throw new SecurityException
1812 ("Sealing violation loading " + name + " : Package "
1813 + packageName + " is sealed.");
1814 }
1815
1816 }
1817
1818 if (entry.loadedClass == null) {
1819 clazz = defineClass(name, entry.binaryContent, 0,
1820 entry.binaryContent.length,
1821 new CodeSource(entry.codeBase, entry.certificates));
1822 entry.loadedClass = clazz;
1823 entry.binaryContent = null;
1824 entry.source = null;
1825 entry.codeBase = null;
1826 entry.manifest = null;
1827 entry.certificates = null;
1828 } else {
1829 clazz = entry.loadedClass;
1830 }
1831 }
1832
1833 return clazz;
1834
1835 }
1836
1837 /**
1838 * Find specified resource in local repositories. This block
1839 * will execute under an AccessControl.doPrivilege block.
1840 *
1841 * @return the loaded resource, or null if the resource isn't found
1842 */
1843 protected ResourceEntry findResourceInternal(File file, String path){
1844 ResourceEntry entry = new ResourceEntry();
1845 try {
1846 entry.source = getURI(new File(file, path));
1847 entry.codeBase = getURL(new File(file, path), false);
1848 } catch (MalformedURLException e) {
1849 return null;
1850 }
1851 return entry;
1852 }
1853
1854
1855 /**
1856 * Find specified resource in local repositories.
1857 *
1858 * @return the loaded resource, or null if the resource isn't found
1859 */
1860 protected ResourceEntry findResourceInternal(String name, String path) {
1861
1862 if (!started) {
1863 log.info(sm.getString("webappClassLoader.stopped", name));
1864 return null;
1865 }
1866
1867 if ((name == null) || (path == null))
1868 return null;
1869
1870 ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
1871 if (entry != null)
1872 return entry;
1873
1874 int contentLength = -1;
1875 InputStream binaryStream = null;
1876
1877 int jarFilesLength = jarFiles.length;
1878 int repositoriesLength = repositories.length;
1879
1880 int i;
1881
1882 Resource resource = null;
1883
1884 boolean fileNeedConvert = false;
1885
1886 for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
1887 try {
1888
1889 String fullPath = repositories[i] + path;
1890
1891 Object lookupResult = resources.lookup(fullPath);
1892 if (lookupResult instanceof Resource) {
1893 resource = (Resource) lookupResult;
1894 }
1895
1896 // Note : Not getting an exception here means the resource was
1897 // found
1898 if (securityManager != null) {
1899 PrivilegedAction dp =
1900 new PrivilegedFindResource(files[i], path);
1901 entry = (ResourceEntry)AccessController.doPrivileged(dp);
1902 } else {
1903 entry = findResourceInternal(files[i], path);
1904 }
1905
1906 ResourceAttributes attributes =
1907 (ResourceAttributes) resources.getAttributes(fullPath);
1908 contentLength = (int) attributes.getContentLength();
1909 entry.lastModified = attributes.getLastModified();
1910
1911 if (resource != null) {
1912
1913
1914 try {
1915 binaryStream = resource.streamContent();
1916 } catch (IOException e) {
1917 return null;
1918 }
1919
1920 if (needConvert) {
1921 if (path.endsWith(".properties")) {
1922 fileNeedConvert = true;
1923 }
1924 }
1925
1926 // Register the full path for modification checking
1927 // Note: Only syncing on a 'constant' object is needed
1928 synchronized (allPermission) {
1929
1930 int j;
1931
1932 long[] result2 =
1933 new long[lastModifiedDates.length + 1];
1934 for (j = 0; j < lastModifiedDates.length; j++) {
1935 result2[j] = lastModifiedDates[j];
1936 }
1937 result2[lastModifiedDates.length] = entry.lastModified;
1938 lastModifiedDates = result2;
1939
1940 String[] result = new String[paths.length + 1];
1941 for (j = 0; j < paths.length; j++) {
1942 result[j] = paths[j];
1943 }
1944 result[paths.length] = fullPath;
1945 paths = result;
1946
1947 }
1948
1949 }
1950
1951 } catch (NamingException e) {
1952 }
1953 }
1954
1955 if ((entry == null) && (notFoundResources.containsKey(name)))
1956 return null;
1957
1958 JarEntry jarEntry = null;
1959
1960 synchronized (jarFiles) {
1961
1962 if (!openJARs()) {
1963 return null;
1964 }
1965 for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
1966
1967 jarEntry = jarFiles[i].getJarEntry(path);
1968
1969 if (jarEntry != null) {
1970
1971 entry = new ResourceEntry();
1972 try {
1973 entry.codeBase = getURL(jarRealFiles[i], false);
1974 String jarFakeUrl = getURI(jarRealFiles[i]).toString();
1975 jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
1976 entry.source = new URL(jarFakeUrl);
1977 entry.lastModified = jarRealFiles[i].lastModified();
1978 } catch (MalformedURLException e) {
1979 return null;
1980 }
1981 contentLength = (int) jarEntry.getSize();
1982 try {
1983 entry.manifest = jarFiles[i].getManifest();
1984 binaryStream = jarFiles[i].getInputStream(jarEntry);
1985 } catch (IOException e) {
1986 return null;
1987 }
1988
1989 // Extract resources contained in JAR to the workdir
1990 if (antiJARLocking && !(path.endsWith(".class"))) {
1991 byte[] buf = new byte[1024];
1992 File resourceFile = new File
1993 (loaderDir, jarEntry.getName());
1994 if (!resourceFile.exists()) {
1995 Enumeration entries = jarFiles[i].entries();
1996 while (entries.hasMoreElements()) {
1997 JarEntry jarEntry2 =
1998 (JarEntry) entries.nextElement();
1999 if (!(jarEntry2.isDirectory())
2000 && (!jarEntry2.getName().endsWith
2001 (".class"))) {
2002 resourceFile = new File
2003 (loaderDir, jarEntry2.getName());
2004 resourceFile.getParentFile().mkdirs();
2005 FileOutputStream os = null;
2006 InputStream is = null;
2007 try {
2008 is = jarFiles[i].getInputStream
2009 (jarEntry2);
2010 os = new FileOutputStream
2011 (resourceFile);
2012 while (true) {
2013 int n = is.read(buf);
2014 if (n <= 0) {
2015 break;
2016 }
2017 os.write(buf, 0, n);
2018 }
2019 } catch (IOException e) {
2020 // Ignore
2021 } finally {
2022 try {
2023 if (is != null) {
2024 is.close();
2025 }
2026 } catch (IOException e) {
2027 }
2028 try {
2029 if (os != null) {
2030 os.close();
2031 }
2032 } catch (IOException e) {
2033 }
2034 }
2035 }
2036 }
2037 }
2038 }
2039
2040 }
2041
2042 }
2043
2044 if (entry == null) {
2045 synchronized (notFoundResources) {
2046 notFoundResources.put(name, name);
2047 }
2048 return null;
2049 }
2050
2051 if (binaryStream != null) {
2052
2053 byte[] binaryContent = new byte[contentLength];
2054
2055 int pos = 0;
2056 try {
2057
2058 while (true) {
2059 int n = binaryStream.read(binaryContent, pos,
2060 binaryContent.length - pos);
2061 if (n <= 0)
2062 break;
2063 pos += n;
2064 }
2065 binaryStream.close();
2066 } catch (IOException e) {
2067 e.printStackTrace();
2068 return null;
2069 } catch (Exception e) {
2070 e.printStackTrace();
2071 return null;
2072 }
2073
2074 if (fileNeedConvert) {
2075 String str = new String(binaryContent,0,pos);
2076 try {
2077 binaryContent = str.getBytes("UTF-8");
2078 } catch (Exception e) {
2079 return null;
2080 }
2081 }
2082 entry.binaryContent = binaryContent;
2083
2084 // The certificates are only available after the JarEntry
2085 // associated input stream has been fully read
2086 if (jarEntry != null) {
2087 entry.certificates = jarEntry.getCertificates();
2088 }
2089
2090 }
2091
2092 }
2093
2094 // Add the entry in the local resource repository
2095 synchronized (resourceEntries) {
2096 // Ensures that all the threads which may be in a race to load
2097 // a particular class all end up with the same ResourceEntry
2098 // instance
2099 ResourceEntry entry2 = (ResourceEntry) resourceEntries.get(name);
2100 if (entry2 == null) {
2101 resourceEntries.put(name, entry);
2102 } else {
2103 entry = entry2;
2104 }
2105 }
2106
2107 return entry;
2108
2109 }
2110
2111
2112 /**
2113 * Returns true if the specified package name is sealed according to the
2114 * given manifest.
2115 */
2116 protected boolean isPackageSealed(String name, Manifest man) {
2117
2118 String path = name.replace('.', '/') + '/';
2119 Attributes attr = man.getAttributes(path);
2120 String sealed = null;
2121 if (attr != null) {
2122 sealed = attr.getValue(Name.SEALED);
2123 }
2124 if (sealed == null) {
2125 if ((attr = man.getMainAttributes()) != null) {
2126 sealed = attr.getValue(Name.SEALED);
2127 }
2128 }
2129 return "true".equalsIgnoreCase(sealed);
2130
2131 }
2132
2133
2134 /**
2135 * Finds the resource with the given name if it has previously been
2136 * loaded and cached by this class loader, and return an input stream
2137 * to the resource data. If this resource has not been cached, return
2138 * <code>null</code>.
2139 *
2140 * @param name Name of the resource to return
2141 */
2142 protected InputStream findLoadedResource(String name) {
2143
2144 ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
2145 if (entry != null) {
2146 if (entry.binaryContent != null)
2147 return new ByteArrayInputStream(entry.binaryContent);
2148 }
2149 return (null);
2150
2151 }
2152
2153
2154 /**
2155 * Finds the class with the given name if it has previously been
2156 * loaded and cached by this class loader, and return the Class object.
2157 * If this class has not been cached, return <code>null</code>.
2158 *
2159 * @param name Name of the resource to return
2160 */
2161 protected Class findLoadedClass0(String name) {
2162
2163 ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
2164 if (entry != null) {
2165 return entry.loadedClass;
2166 }
2167 return (null); // FIXME - findLoadedResource()
2168
2169 }
2170
2171
2172 /**
2173 * Refresh the system policy file, to pick up eventual changes.
2174 */
2175 protected void refreshPolicy() {
2176
2177 try {
2178 // The policy file may have been modified to adjust
2179 // permissions, so we're reloading it when loading or
2180 // reloading a Context
2181 Policy policy = Policy.getPolicy();
2182 policy.refresh();
2183 } catch (AccessControlException e) {
2184 // Some policy files may restrict this, even for the core,
2185 // so this exception is ignored
2186 }
2187
2188 }
2189
2190
2191 /**
2192 * Filter classes.
2193 *
2194 * @param name class name
2195 * @return true if the class should be filtered
2196 */
2197 protected boolean filter(String name) {
2198
2199 if (name == null)
2200 return false;
2201
2202 // Looking up the package
2203 String packageName = null;
2204 int pos = name.lastIndexOf('.');
2205 if (pos != -1)
2206 packageName = name.substring(0, pos);
2207 else
2208 return false;
2209
2210 for (int i = 0; i < packageTriggers.length; i++) {
2211 if (packageName.startsWith(packageTriggers[i]))
2212 return true;
2213 }
2214
2215 return false;
2216
2217 }
2218
2219
2220 /**
2221 * Validate a classname. As per SRV.9.7.2, we must restict loading of
2222 * classes from J2SE (java.*) and classes of the servlet API
2223 * (javax.servlet.*). That should enhance robustness and prevent a number
2224 * of user error (where an older version of servlet.jar would be present
2225 * in /WEB-INF/lib).
2226 *
2227 * @param name class name
2228 * @return true if the name is valid
2229 */
2230 protected boolean validate(String name) {
2231
2232 if (name == null)
2233 return false;
2234 if (name.startsWith("java."))
2235 return false;
2236
2237 return true;
2238
2239 }
2240
2241
2242 /**
2243 * Check the specified JAR file, and return <code>true</code> if it does
2244 * not contain any of the trigger classes.
2245 *
2246 * @param jarfile The JAR file to be checked
2247 *
2248 * @exception IOException if an input/output error occurs
2249 */
2250 protected boolean validateJarFile(File jarfile)
2251 throws IOException {
2252
2253 if (triggers == null)
2254 return (true);
2255 JarFile jarFile = new JarFile(jarfile);
2256 for (int i = 0; i < triggers.length; i++) {
2257 Class clazz = null;
2258 try {
2259 if (parent != null) {
2260 clazz = parent.loadClass(triggers[i]);
2261 } else {
2262 clazz = Class.forName(triggers[i]);
2263 }
2264 } catch (Throwable t) {
2265 clazz = null;
2266 }
2267 if (clazz == null)
2268 continue;
2269 String name = triggers[i].replace('.', '/') + ".class";
2270 if (log.isDebugEnabled())
2271 log.debug(" Checking for " + name);
2272 JarEntry jarEntry = jarFile.getJarEntry(name);
2273 if (jarEntry != null) {
2274 log.info("validateJarFile(" + jarfile +
2275 ") - jar not loaded. See Servlet Spec 2.3, "
2276 + "section 9.7.2. Offending class: " + name);
2277 jarFile.close();
2278 return (false);
2279 }
2280 }
2281 jarFile.close();
2282 return (true);
2283
2284 }
2285
2286
2287 /**
2288 * Get URL.
2289 */
2290 protected URL getURL(File file, boolean encoded)
2291 throws MalformedURLException {
2292
2293 File realFile = file;
2294 try {
2295 realFile = realFile.getCanonicalFile();
2296 } catch (IOException e) {
2297 // Ignore
2298 }
2299 if(encoded) {
2300 return getURI(realFile);
2301 } else {
2302 return realFile.toURL();
2303 }
2304
2305 }
2306
2307
2308 /**
2309 * Get URL.
2310 */
2311 protected URL getURI(File file)
2312 throws MalformedURLException {
2313
2314
2315 File realFile = file;
2316 try {
2317 realFile = realFile.getCanonicalFile();
2318 } catch (IOException e) {
2319 // Ignore
2320 }
2321 return realFile.toURI().toURL();
2322
2323 }
2324
2325
2326 /**
2327 * Delete the specified directory, including all of its contents and
2328 * subdirectories recursively.
2329 *
2330 * @param dir File object representing the directory to be deleted
2331 */
2332 protected static void deleteDir(File dir) {
2333
2334 String files[] = dir.list();
2335 if (files == null) {
2336 files = new String[0];
2337 }
2338 for (int i = 0; i < files.length; i++) {
2339 File file = new File(dir, files[i]);
2340 if (file.isDirectory()) {
2341 deleteDir(file);
2342 } else {
2343 file.delete();
2344 }
2345 }
2346 dir.delete();
2347
2348 }
2349
2350
2351 }
2352