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
9
* http://www.apache.org/licenses/LICENSE-2.0
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.
19
package org.apache.catalina.loader;
21
import java.io.ByteArrayInputStream;
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;
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;
51
import java.util.ResourceBundle;
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;
61
import javax.naming.NameClassPair;
62
import javax.naming.NamingEnumeration;
63
import javax.naming.NamingException;
64
import javax.naming.directory.DirContext;
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;
78
* Specialized web application class loader.
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.
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.
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.
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.
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>.
107
* <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
108
* security is made unless a security manager is present.
110
* @author Remy Maucherat
111
* @author Craig R. McClanahan
112
* @version $Revision: 915603 $ $Date: 2010-02-24 01:07:06 +0100 (Mi, 24. Feb 2010) $
114
public class WebappClassLoader
115
extends URLClassLoader
116
implements Reloader, Lifecycle
119
protected static org.apache.juli.logging.Log log=
120
org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
123
* List of ThreadGroup names to ignore when scanning for web application
124
* started threads that need to be shut down.
126
private static final List<String> JVM_THREAD_GROUP_NAMES =
127
new ArrayList<String>();
129
public static final boolean ENABLE_CLEAR_REFERENCES =
130
Boolean.valueOf(System.getProperty("org.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES", "true")).booleanValue();
133
* @deprecated Not used
135
protected class PrivilegedFindResource
136
implements PrivilegedAction {
139
protected String path;
141
PrivilegedFindResource(File file, String path) {
146
public Object run() {
147
return findResourceInternal(file, path);
153
JVM_THREAD_GROUP_NAMES.add("system");
154
JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
157
protected class PrivilegedFindResourceByName
158
implements PrivilegedAction<ResourceEntry> {
160
protected String name;
161
protected String path;
163
PrivilegedFindResourceByName(String name, String path) {
168
public ResourceEntry run() {
169
return findResourceInternal(name, path);
175
protected final class PrivilegedGetClassLoader
176
implements PrivilegedAction<ClassLoader> {
178
public Class<?> clazz;
180
public PrivilegedGetClassLoader(Class<?> clazz){
184
public ClassLoader run() {
185
return clazz.getClassLoader();
192
// ------------------------------------------------------- Static Variables
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
203
protected static final String[] triggers = {
204
"javax.servlet.Servlet" // Servlet API
209
* Set of package names which are not allowed to be loaded from a webapp
210
* class loader without delegating first.
212
protected static final String[] packageTriggers = {
217
* The string manager for this package.
219
protected static final StringManager sm =
220
StringManager.getManager(Constants.Package);
224
* Use anti JAR locking code, which does URL rerouting when accessing
227
boolean antiJARLocking = false;
230
// ----------------------------------------------------------- Constructors
234
* Construct a new ClassLoader with no defined repositories and no
235
* parent ClassLoader.
237
public WebappClassLoader() {
240
this.parent = getParent();
241
system = getSystemClassLoader();
242
securityManager = System.getSecurityManager();
244
if (securityManager != null) {
252
* Construct a new ClassLoader with no defined repositories and no
253
* parent ClassLoader.
255
public WebappClassLoader(ClassLoader parent) {
257
super(new URL[0], parent);
259
this.parent = getParent();
261
system = getSystemClassLoader();
262
securityManager = System.getSecurityManager();
264
if (securityManager != null) {
270
// ----------------------------------------------------- Instance Variables
274
* Associated directory context giving access to the resources in this
277
protected DirContext resources = null;
281
* The cache of ResourceEntry for classes and resources we have loaded,
282
* keyed by resource name.
284
protected HashMap resourceEntries = new HashMap();
288
* The list of not found resources.
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;
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
308
protected boolean delegate = false;
312
* Last time a JAR was accessed.
314
protected long lastJarAccessed = 0L;
318
* The list of local repositories, in the order they should be searched
319
* for locally loaded classes or resources.
321
protected String[] repositories = new String[0];
325
* Repositories URLs, used to cache the result of getURLs.
327
protected URL[] repositoryURLs = null;
331
* Repositories translated as path in the work directory (for Jasper
332
* originally), but which is used to generate fake URLs should getURLs be
335
protected File[] files = new File[0];
339
* The list of JARs, in the order they should be searched
340
* for locally loaded classes or resources.
342
protected JarFile[] jarFiles = new JarFile[0];
346
* The list of JARs, in the order they should be searched
347
* for locally loaded classes or resources.
349
protected File[] jarRealFiles = new File[0];
353
* The path which will be monitored for added Jar files.
355
protected String jarPath = null;
359
* The list of JARs, in the order they should be searched
360
* for locally loaded classes or resources.
362
protected String[] jarNames = new String[0];
366
* The list of JARs last modified dates, in the order they should be
367
* searched for locally loaded classes or resources.
369
protected long[] lastModifiedDates = new long[0];
373
* The list of resources which should be checked when checking for
376
protected String[] paths = new String[0];
380
* A list of read File and Jndi Permission's required if this loader
381
* is for a web application context.
383
protected ArrayList permissionList = new ArrayList();
387
* Path where resources loaded from JARs will be extracted.
389
protected File loaderDir = null;
390
protected String canonicalLoaderDir = null;
393
* The PermissionCollection for each CodeSource for a web
394
* application context.
396
protected HashMap loaderPC = new HashMap();
400
* Instance of the SecurityManager installed.
402
protected SecurityManager securityManager = null;
406
* The parent class loader.
408
protected ClassLoader parent = null;
412
* The system class loader.
414
protected ClassLoader system = null;
418
* Has this component been started?
420
protected boolean started = false;
424
* Has external repositories.
426
protected boolean hasExternalRepositories = false;
429
* need conversion for properties files
431
protected boolean needConvert = false;
437
protected Permission allPermission = new java.security.AllPermission();
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
451
private boolean clearReferencesStopThreads = false;
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.
459
private boolean clearReferencesLogFactoryRelease = true;
461
// ------------------------------------------------------------- Properties
465
* Get associated resources.
467
public DirContext getResources() {
469
return this.resources;
475
* Set associated resources.
477
public void setResources(DirContext resources) {
479
this.resources = resources;
485
* Return the "delegate first" flag for this class loader.
487
public boolean getDelegate() {
489
return (this.delegate);
495
* Set the "delegate first" flag for this class loader.
497
* @param delegate The new "delegate first" flag
499
public void setDelegate(boolean delegate) {
501
this.delegate = delegate;
507
* @return Returns the antiJARLocking.
509
public boolean getAntiJARLocking() {
510
return antiJARLocking;
515
* @param antiJARLocking The antiJARLocking to set.
517
public void setAntiJARLocking(boolean antiJARLocking) {
518
this.antiJARLocking = antiJARLocking;
523
* If there is a Java SecurityManager create a read FilePermission
524
* or JndiPermission for the file directory path.
526
* @param path file directory path
528
public void addPermission(String path) {
533
if (securityManager != null) {
534
Permission permission = null;
535
if( path.startsWith("jndi:") || path.startsWith("jar:jndi:") ) {
536
if (!path.endsWith("/")) {
539
permission = new JndiPermission(path + "*");
540
addPermission(permission);
542
if (!path.endsWith(File.separator)) {
543
permission = new FilePermission(path, "read");
544
addPermission(permission);
545
path = path + File.separator;
547
permission = new FilePermission(path + "-", "read");
548
addPermission(permission);
555
* If there is a Java SecurityManager create a read FilePermission
556
* or JndiPermission for URL.
558
* @param url URL for a file or directory on local system
560
public void addPermission(URL url) {
562
addPermission(url.toString());
568
* If there is a Java SecurityManager create a Permission.
570
* @param permission The permission
572
public void addPermission(Permission permission) {
573
if ((securityManager != null) && (permission != null)) {
574
permissionList.add(permission);
580
* Return the JAR path.
582
public String getJarPath() {
590
* Change the Jar path.
592
public void setJarPath(String jarPath) {
594
this.jarPath = jarPath;
600
* Change the work directory.
602
public void setWorkDir(File workDir) {
603
this.loaderDir = new File(workDir, "loader");
604
if (loaderDir == null) {
605
canonicalLoaderDir = null;
608
canonicalLoaderDir = loaderDir.getCanonicalPath();
609
if (!canonicalLoaderDir.endsWith(File.separator)) {
610
canonicalLoaderDir += File.separator;
612
} catch (IOException ioe) {
613
canonicalLoaderDir = null;
619
* Utility method for use in subclasses.
620
* Must be called before Lifecycle methods to have any effect.
622
protected void setParentClassLoader(ClassLoader pcl) {
627
* Return the clearReferencesStopThreads flag for this Context.
629
public boolean getClearReferencesStopThreads() {
630
return (this.clearReferencesStopThreads);
635
* Set the clearReferencesStopThreads feature for this Context.
637
* @param clearReferencesStopThreads The new flag value
639
public void setClearReferencesStopThreads(
640
boolean clearReferencesStopThreads) {
641
this.clearReferencesStopThreads = clearReferencesStopThreads;
646
* Return the clearReferencesLogFactoryRelease flag for this Context.
648
public boolean getClearReferencesLogFactoryRelease() {
649
return (this.clearReferencesLogFactoryRelease);
654
* Set the clearReferencesLogFactoryRelease feature for this Context.
656
* @param clearReferencesLogFactoryRelease The new flag value
658
public void setClearReferencesLogFactoryRelease(
659
boolean clearReferencesLogFactoryRelease) {
660
this.clearReferencesLogFactoryRelease =
661
clearReferencesLogFactoryRelease;
665
// ------------------------------------------------------- Reloader Methods
669
* Add a new repository to the set of places this ClassLoader can look for
670
* classes to be loaded.
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
675
* @exception IllegalArgumentException if the specified repository is
676
* invalid or does not exist
678
public void addRepository(String repository) {
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"))
686
// Add this repository to our underlying class loader
688
URL url = new URL(repository);
690
hasExternalRepositories = true;
691
repositoryURLs = null;
692
} catch (MalformedURLException e) {
693
IllegalArgumentException iae = new IllegalArgumentException
694
("Invalid repository: " + repository);
703
* Add a new repository to the set of places this ClassLoader can look for
704
* classes to be loaded.
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
709
* @exception IllegalArgumentException if the specified repository is
710
* invalid or does not exist
712
synchronized void addRepository(String repository, File file) {
714
// Note : There should be only one (of course), but I think we should
715
// keep this a bit generic
717
if (repository == null)
720
if (log.isDebugEnabled())
721
log.debug("addRepository(" + repository + ")");
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];
730
result[repositories.length] = repository;
731
repositories = result;
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];
738
result2[files.length] = file;
744
synchronized void addJar(String jar, JarFile jarFile, File file)
754
if (log.isDebugEnabled())
755
log.debug("addJar(" + jar + ")");
759
if ((jarPath != null) && (jar.startsWith(jarPath))) {
761
String jarName = jar.substring(jarPath.length());
762
while (jarName.startsWith("/"))
763
jarName = jarName.substring(1);
765
String[] result = new String[jarNames.length + 1];
766
for (i = 0; i < jarNames.length; i++) {
767
result[i] = jarNames[i];
769
result[jarNames.length] = jarName;
776
// Register the JAR for tracking
779
((ResourceAttributes) resources.getAttributes(jar))
782
String[] result = new String[paths.length + 1];
783
for (i = 0; i < paths.length; i++) {
784
result[i] = paths[i];
786
result[paths.length] = jar;
789
long[] result3 = new long[lastModifiedDates.length + 1];
790
for (i = 0; i < lastModifiedDates.length; i++) {
791
result3[i] = lastModifiedDates[i];
793
result3[lastModifiedDates.length] = lastModified;
794
lastModifiedDates = result3;
796
} catch (NamingException e) {
800
// If the JAR currently contains invalid classes, don't actually use it
802
if (!validateJarFile(file))
805
JarFile[] result2 = new JarFile[jarFiles.length + 1];
806
for (i = 0; i < jarFiles.length; i++) {
807
result2[i] = jarFiles[i];
809
result2[jarFiles.length] = jarFile;
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];
817
result4[jarRealFiles.length] = file;
818
jarRealFiles = result4;
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).
828
public String[] findRepositories() {
830
return ((String[])repositories.clone());
836
* Have one or more classes or resources been modified so that a reload
839
public boolean modified() {
841
if (log.isDebugEnabled())
842
log.debug("modified()");
844
// Checking for modified loaded resources
845
int length = paths.length;
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)
854
for (int i = 0; i < length; i++) {
857
((ResourceAttributes) resources.getAttributes(paths[i]))
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]));
867
} catch (NamingException e) {
868
log.error(" Resource '" + paths[i] + "' is missing");
873
length = jarNames.length;
875
// Check if JARs have been added or removed
876
if (getJarPath() != null) {
879
NamingEnumeration enumeration = resources.listBindings(getJarPath());
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"))
887
if (!name.equals(jarNames[i])) {
889
log.info(" Additional JARs have been added : '"
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");
907
} else if (i < jarNames.length) {
908
// There was less JARs
909
log.info(" Additional JARs have been added");
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());
923
// No classes have been modified
930
* Render a String representation of this object.
932
public String toString() {
934
StringBuffer sb = new StringBuffer("WebappClassLoader\r\n");
935
sb.append(" delegate: ");
938
sb.append(" repositories:\r\n");
939
if (repositories != null) {
940
for (int i = 0; i < repositories.length; i++) {
942
sb.append(repositories[i]);
946
if (this.parent != null) {
947
sb.append("----------> Parent Classloader:\r\n");
948
sb.append(this.parent.toString());
951
return (sb.toString());
956
// ---------------------------------------------------- ClassLoader Methods
960
* Add the specified URL to the classloader.
962
protected void addURL(URL url) {
964
hasExternalRepositories = true;
965
repositoryURLs = null;
970
* Find the specified class in our local repositories, if possible. If
971
* not found, throw <code>ClassNotFoundException</code>.
973
* @param name Name of the class to be loaded
975
* @exception ClassNotFoundException if the class was not found
977
public Class findClass(String name) throws ClassNotFoundException {
979
if (log.isDebugEnabled())
980
log.debug(" findClass(" + name + ")");
982
// Cannot load anything from local repositories if class loader is stopped
984
throw new ClassNotFoundException(name);
987
// (1) Permission to define this class when using a SecurityManager
988
if (securityManager != null) {
989
int i = name.lastIndexOf('.');
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);
1003
// Ask our superclass to locate this class, if possible
1004
// (throws ClassNotFoundException if it is not found)
1007
if (log.isTraceEnabled())
1008
log.trace(" findClassInternal(" + name + ")");
1010
clazz = findClassInternal(name);
1011
} catch(ClassNotFoundException cnfe) {
1012
if (!hasExternalRepositories) {
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);
1024
if ((clazz == null) && hasExternalRepositories) {
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);
1037
if (clazz == null) {
1038
if (log.isDebugEnabled())
1039
log.debug(" --> Returning ClassNotFoundException");
1040
throw new ClassNotFoundException(name);
1042
} catch (ClassNotFoundException e) {
1043
if (log.isTraceEnabled())
1044
log.trace(" --> Passing on ClassNotFoundException");
1048
// Return the class we have located
1049
if (log.isTraceEnabled())
1050
log.debug(" Returning class " + clazz);
1052
if ((log.isTraceEnabled()) && (clazz != null)) {
1054
if (Globals.IS_SECURITY_ENABLED){
1055
cl = AccessController.doPrivileged(
1056
new PrivilegedGetClassLoader(clazz));
1058
cl = clazz.getClassLoader();
1060
log.debug(" Loaded by " + cl.toString());
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
1072
* @param name Name of the resource to be found
1074
public URL findResource(final String name) {
1076
if (log.isDebugEnabled())
1077
log.debug(" findResource(" + name + ")");
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);
1088
entry = findResourceInternal(name, name);
1091
if (entry != null) {
1095
if ((url == null) && hasExternalRepositories)
1096
url = super.findResource(name);
1098
if (log.isDebugEnabled()) {
1100
log.debug(" --> Returning '" + url.toString() + "'");
1102
log.debug(" --> Resource not found, returning null");
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.
1114
* @param name Name of the resources to be found
1116
* @exception IOException if an input/output error occurs
1118
public Enumeration findResources(String name) throws IOException {
1120
if (log.isDebugEnabled())
1121
log.debug(" findResources(" + name + ")");
1123
Vector result = new Vector();
1125
int jarFilesLength = jarFiles.length;
1126
int repositoriesLength = repositories.length;
1130
// Looking at the repositories
1131
for (i = 0; i < repositoriesLength; i++) {
1133
String fullPath = repositories[i] + name;
1134
resources.lookup(fullPath);
1135
// Note : Not getting an exception here means the resource was
1138
result.addElement(getURI(new File(files[i], name)));
1139
} catch (MalformedURLException e) {
1142
} catch (NamingException e) {
1146
// Looking at the JAR files
1147
synchronized (jarFiles) {
1149
for (i = 0; i < jarFilesLength; i++) {
1150
JarEntry jarEntry = jarFiles[i].getJarEntry(name);
1151
if (jarEntry != null) {
1153
String jarFakeUrl = getURI(jarRealFiles[i]).toString();
1154
jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name;
1155
result.addElement(new URL(jarFakeUrl));
1156
} catch (MalformedURLException e) {
1164
// Adding the results of a call to the superclass
1165
if (hasExternalRepositories) {
1167
Enumeration otherResourcePaths = super.findResources(name);
1169
while (otherResourcePaths.hasMoreElements()) {
1170
result.addElement(otherResourcePaths.nextElement());
1175
return result.elements();
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>.
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>.
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>
1200
* @param name Name of the resource to return a URL for
1202
public URL getResource(String name) {
1204
if (log.isDebugEnabled())
1205
log.debug("getResource(" + name + ")");
1208
// (1) Delegate to parent if requested
1210
if (log.isDebugEnabled())
1211
log.debug(" Delegating to parent classloader " + parent);
1212
ClassLoader loader = parent;
1215
url = loader.getResource(name);
1217
if (log.isDebugEnabled())
1218
log.debug(" --> Returning '" + url.toString() + "'");
1223
// (2) Search local repositories
1224
url = findResource(name);
1226
// Locating the repository for special handling in the case
1228
if (antiJARLocking) {
1229
ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
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);
1238
} catch (Exception e) {
1242
if (log.isDebugEnabled())
1243
log.debug(" --> Returning '" + url.toString() + "'");
1247
// (3) Delegate to parent unconditionally if not already attempted
1249
ClassLoader loader = parent;
1252
url = loader.getResource(name);
1254
if (log.isDebugEnabled())
1255
log.debug(" --> Returning '" + url.toString() + "'");
1260
// (4) Resource was not found
1261
if (log.isDebugEnabled())
1262
log.debug(" --> Resource not found, returning null");
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>.
1275
* @param name Name of the resource to return an input stream for
1277
public InputStream getResourceAsStream(String name) {
1279
if (log.isDebugEnabled())
1280
log.debug("getResourceAsStream(" + name + ")");
1281
InputStream stream = null;
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");
1291
// (1) Delegate to parent if requested
1293
if (log.isDebugEnabled())
1294
log.debug(" Delegating to parent classloader " + parent);
1295
ClassLoader loader = parent;
1298
stream = loader.getResourceAsStream(name);
1299
if (stream != null) {
1301
if (log.isDebugEnabled())
1302
log.debug(" --> Returning stream from parent");
1307
// (2) Search local repositories
1308
if (log.isDebugEnabled())
1309
log.debug(" Searching local repositories");
1310
URL url = findResource(name);
1313
if (log.isDebugEnabled())
1314
log.debug(" --> Returning stream from local");
1315
stream = findLoadedResource(name);
1317
if (hasExternalRepositories && (stream == null))
1318
stream = url.openStream();
1319
} catch (IOException e) {
1326
// (3) Delegate to parent unconditionally
1328
if (log.isDebugEnabled())
1329
log.debug(" Delegating to parent classloader unconditionally " + parent);
1330
ClassLoader loader = parent;
1333
stream = loader.getResourceAsStream(name);
1334
if (stream != null) {
1336
if (log.isDebugEnabled())
1337
log.debug(" --> Returning stream from parent");
1342
// (4) Resource was not found
1343
if (log.isDebugEnabled())
1344
log.debug(" --> Resource not found, returning null");
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.
1355
* @param name Name of the class to be loaded
1357
* @exception ClassNotFoundException if the class was not found
1359
public Class loadClass(String name) throws ClassNotFoundException {
1361
return (loadClass(name, false));
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>.
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>
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.
1386
* @param name Name of the class to be loaded
1387
* @param resolve If <code>true</code> then resolve the class
1389
* @exception ClassNotFoundException if the class was not found
1391
public Class loadClass(String name, boolean resolve)
1392
throws ClassNotFoundException {
1394
synchronized (name.intern()) {
1395
if (log.isDebugEnabled())
1396
log.debug("loadClass(" + name + ", " + resolve + ")");
1399
// Log access to stopped classloader
1402
throw new IllegalStateException();
1403
} catch (IllegalStateException e) {
1404
log.info(sm.getString("webappClassLoader.stopped", name), e);
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");
1414
resolveClass(clazz);
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");
1424
resolveClass(clazz);
1428
// (0.2) Try loading the class with the system class loader, to prevent
1429
// the webapp from overriding J2SE classes
1431
clazz = system.loadClass(name);
1432
if (clazz != null) {
1434
resolveClass(clazz);
1437
} catch (ClassNotFoundException e) {
1441
// (0.5) Permission to access this class when using a SecurityManager
1442
if (securityManager != null) {
1443
int i = name.lastIndexOf('.');
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);
1456
boolean delegateLoad = delegate || filter(name);
1458
// (1) Delegate to our parent if requested
1460
if (log.isDebugEnabled())
1461
log.debug(" Delegating to parent classloader1 " + parent);
1462
ClassLoader loader = parent;
1466
clazz = loader.loadClass(name);
1467
if (clazz != null) {
1468
if (log.isDebugEnabled())
1469
log.debug(" Loading class from parent");
1471
resolveClass(clazz);
1474
} catch (ClassNotFoundException e) {
1479
// (2) Search local repositories
1480
if (log.isDebugEnabled())
1481
log.debug(" Searching local repositories");
1483
clazz = findClass(name);
1484
if (clazz != null) {
1485
if (log.isDebugEnabled())
1486
log.debug(" Loading class from local repository");
1488
resolveClass(clazz);
1491
} catch (ClassNotFoundException e) {
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;
1503
clazz = loader.loadClass(name);
1504
if (clazz != null) {
1505
if (log.isDebugEnabled())
1506
log.debug(" Loading class from parent");
1508
resolveClass(clazz);
1511
} catch (ClassNotFoundException e) {
1516
throw new ClassNotFoundException(name);
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.
1528
* @param codeSource where the code was loaded from
1529
* @return PermissionCollection for CodeSource
1531
protected PermissionCollection getPermissions(CodeSource codeSource) {
1533
String codeUrl = codeSource.getLocation().toString();
1534
PermissionCollection pc;
1535
if ((pc = (PermissionCollection)loaderPC.get(codeUrl)) == null) {
1536
pc = super.getPermissions(codeSource);
1538
Iterator perms = permissionList.iterator();
1539
while (perms.hasNext()) {
1540
Permission p = (Permission)perms.next();
1543
loaderPC.put(codeUrl,pc);
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.
1557
public URL[] getURLs() {
1559
if (repositoryURLs != null) {
1560
return repositoryURLs;
1563
URL[] external = super.getURLs();
1565
int filesLength = files.length;
1566
int jarFilesLength = jarRealFiles.length;
1567
int length = filesLength + jarFilesLength + external.length;
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);
1579
urls[i] = external[i - filesLength - jarFilesLength];
1583
repositoryURLs = urls;
1585
} catch (MalformedURLException e) {
1586
repositoryURLs = new URL[0];
1589
return repositoryURLs;
1594
// ------------------------------------------------------ Lifecycle Methods
1598
* Add a lifecycle event listener to this component.
1600
* @param listener The listener to add
1602
public void addLifecycleListener(LifecycleListener listener) {
1607
* Get the lifecycle listeners associated with this lifecycle. If this
1608
* Lifecycle has no listeners registered, a zero-length array is returned.
1610
public LifecycleListener[] findLifecycleListeners() {
1611
return new LifecycleListener[0];
1616
* Remove a lifecycle event listener from this component.
1618
* @param listener The listener to remove
1620
public void removeLifecycleListener(LifecycleListener listener) {
1625
* Start the class loader.
1627
* @exception LifecycleException if a lifecycle error occurs
1629
public void start() throws LifecycleException {
1632
String encoding = null;
1634
encoding = System.getProperty("file.encoding");
1635
} catch (Exception e) {
1638
if (encoding.indexOf("EBCDIC")!=-1) {
1645
public boolean isStarted() {
1650
* Stop the class loader.
1652
* @exception LifecycleException if a lifecycle error occurs
1654
public void stop() throws LifecycleException {
1656
// Clearing references should be done before setting started to
1657
// false, due to possible side effects
1662
int length = files.length;
1663
for (int i = 0; i < length; i++) {
1667
length = jarFiles.length;
1668
for (int i = 0; i < length; i++) {
1670
if (jarFiles[i] != null) {
1671
jarFiles[i].close();
1673
} catch (IOException e) {
1679
notFoundResources.clear();
1680
resourceEntries.clear();
1682
repositories = null;
1683
repositoryURLs = null;
1686
jarRealFiles = null;
1689
lastModifiedDates = null;
1691
hasExternalRepositories = false;
1694
permissionList.clear();
1697
if (loaderDir != null) {
1698
deleteDir(loaderDir);
1705
* Used to periodically signal to the classloader to release
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++) {
1715
if (jarFiles[i] != null) {
1716
jarFiles[i].close();
1719
} catch (IOException e) {
1720
if (log.isDebugEnabled()) {
1721
log.debug("Failed to close JAR", e);
1731
// ------------------------------------------------------ Protected Methods
1737
protected void clearReferences() {
1739
// De-register any remaining JDBC drivers
1740
clearReferencesJdbc();
1742
// Stop any threads the web application started
1743
clearReferencesThreads();
1745
// Clear any ThreadLocals loaded by this class loader
1746
clearReferencesThreadLocals();
1748
// Clear RMI Targets loaded by this class loader
1749
clearReferencesRmiTargets();
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();
1757
// Clear the IntrospectionUtils cache.
1758
IntrospectionUtils.clear();
1760
// Clear the classloader reference in common-logging
1761
if (clearReferencesLogFactoryRelease) {
1762
org.apache.juli.logging.LogFactory.release(this);
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();
1772
// Clear the classloader reference in the VM's bean introspector
1773
java.beans.Introspector.flushCaches();
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,
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
1794
* If only apps cleaned up after themselves...
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
1801
byte[] classBytes = new byte[2048];
1804
int read = is.read(classBytes, offset, classBytes.length-offset);
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);
1813
read = is.read(classBytes, offset, classBytes.length-offset);
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));
1825
} catch (Exception e) {
1826
// So many things to go wrong above...
1827
log.warn(sm.getString("webappClassLoader.jdbcRemoveFailed"), e);
1832
} catch (IOException ioe) {
1833
log.warn(sm.getString(
1834
"webappClassLoader.jdbcRemoveStreamError"), ioe);
1841
private final void clearReferencesStaticFinal() {
1843
@SuppressWarnings("unchecked")
1844
Collection<ResourceEntry> values =
1845
((HashMap<String,ResourceEntry>) resourceEntries.clone()).values();
1846
Iterator<ResourceEntry> loadedClasses = values.iterator();
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;
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);
1863
} catch(Throwable t) {
1868
loadedClasses = values.iterator();
1869
while (loadedClasses.hasNext()) {
1870
ResourceEntry entry = loadedClasses.next();
1871
if (entry.loadedClass != null) {
1872
Class<?> clazz = entry.loadedClass;
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)) {
1882
if (Modifier.isStatic(mods)) {
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));
1891
field.set(null, null);
1892
if (log.isDebugEnabled()) {
1893
log.debug("Set field " + field.getName()
1894
+ " to null in class " + clazz.getName());
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);
1905
} catch (Throwable t) {
1906
if (log.isDebugEnabled()) {
1907
log.debug("Could not clean fields for class " + clazz.getName(), t);
1916
private void nullInstance(Object instance) {
1917
if (instance == null) {
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)) {
1929
field.setAccessible(true);
1930
if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) {
1931
// Doing something recursively is too risky
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.");
1947
field.set(instance, null);
1948
if (log.isDebugEnabled()) {
1949
log.debug("Set field " + field.getName()
1950
+ " to null in class " + instance.getClass().getName());
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);
1965
@SuppressWarnings("deprecation")
1966
private void clearReferencesThreads() {
1967
Thread[] threads = getThreads();
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()) {
1979
// Skip threads that have already died
1980
if (!thread.isAlive()) {
1984
// Don't warn about JVM controlled threads
1985
ThreadGroup tg = thread.getThreadGroup();
1987
JVM_THREAD_GROUP_NAMES.contains(tg.getName())) {
1991
// TimerThread is not normally visible
1992
if (thread.getClass().getName().equals(
1993
"java.util.TimerThread")) {
1994
clearReferencesStopTimerThread(thread);
1998
log.error(sm.getString("webappClassLoader.warnThread",
2001
// Don't try an stop the threads unless explicitly
2002
// configured to do so
2003
if (!clearReferencesStopThreads) {
2007
// If the thread has been started via an executor, try
2008
// shutting down the executor
2011
thread.getClass().getDeclaredField("target");
2012
targetField.setAccessible(true);
2013
Object target = targetField.get(thread);
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();
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);
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
2055
private void clearReferencesStopTimerThread(Thread thread) {
2057
// Need to get references to:
2058
// - newTasksMayBeScheduled field
2063
Field newTasksMayBeScheduledField =
2064
thread.getClass().getDeclaredField("newTasksMayBeScheduled");
2065
newTasksMayBeScheduledField.setAccessible(true);
2066
Field queueField = thread.getClass().getDeclaredField("queue");
2067
queueField.setAccessible(true);
2069
Object queue = queueField.get(thread);
2071
Method clearMethod = queue.getClass().getDeclaredMethod("clear");
2072
clearMethod.setAccessible(true);
2074
synchronized(queue) {
2075
newTasksMayBeScheduledField.setBoolean(thread, false);
2076
clearMethod.invoke(queue);
2077
queue.notify(); // In case queue was already empty.
2080
log.error(sm.getString("webappClassLoader.warnTimerThread",
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);
2102
private void clearReferencesThreadLocals() {
2103
Thread[] threads = getThreads();
2106
// Make the fields in the Thread class that store ThreadLocals
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
2117
Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
2118
Field tableField = tlmClass.getDeclaredField("table");
2119
tableField.setAccessible(true);
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
2129
inheritableThreadLocalsField.get(threads[i]);
2130
clearThreadLocalMap(threadLocalMap, tableField);
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);
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.
2156
private void clearThreadLocalMap(Object map, Field internalTableField)
2157
throws NoSuchMethodException, IllegalAccessException,
2158
NoSuchFieldException, InvocationTargetException {
2161
map.getClass().getDeclaredMethod("remove",
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;
2171
Object key = ((Reference<?>) table[j]).get();
2172
if (this.equals(key) || (key != null &&
2173
this == key.getClass().getClassLoader())) {
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())) {
2186
Object[] args = new Object[4];
2188
args[0] = key.getClass().getCanonicalName();
2189
args[1] = key.toString();
2191
if (value != null) {
2192
args[2] = value.getClass().getCanonicalName();
2193
args[3] = value.toString();
2195
if (value == null) {
2196
if (log.isDebugEnabled()) {
2197
log.debug(sm.getString(
2198
"webappClassLoader.clearThreadLocalDebug",
2202
log.error(sm.getString(
2203
"webappClassLoader.clearThreadLocal",
2207
staleEntriesCount++;
2209
mapRemove.invoke(map, key);
2215
if (staleEntriesCount > 0) {
2216
Method mapRemoveStale =
2217
map.getClass().getDeclaredMethod("expungeStaleEntries");
2218
mapRemoveStale.setAccessible(true);
2219
mapRemoveStale.invoke(map);
2225
* Get the set of current threads as an array.
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();
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);
2252
* This depends on the internals of the Sun JVM so it does everything by
2255
private void clearReferencesRmiTargets() {
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);
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) {
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) {
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) {
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) {
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);
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.
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
2329
private void clearReferencesResourceBundles() {
2330
// Get a reference to the cache
2332
Field cacheListField =
2333
ResourceBundle.class.getDeclaredField("cacheList");
2334
cacheListField.setAccessible(true);
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);
2341
// Get the keys (loader references are in the key)
2342
Set<?> keys = cacheList.keySet();
2344
Field loaderRefField = null;
2346
// Iterate over the keys looking at the loader instances
2347
Iterator<?> keysIter = keys.iterator();
2349
int countRemoved = 0;
2351
while (keysIter.hasNext()) {
2352
Object key = keysIter.next();
2354
if (loaderRefField == null) {
2356
key.getClass().getDeclaredField("loaderRef");
2357
loaderRefField.setAccessible(true);
2359
WeakReference<?> loaderRef =
2360
(WeakReference<?>) loaderRefField.get(key);
2362
ClassLoader loader = (ClassLoader) loaderRef.get();
2364
while (loader != null && loader != this) {
2365
loader = loader.getParent();
2368
if (loader != null) {
2374
if (countRemoved > 0 && log.isDebugEnabled()) {
2375
log.debug(sm.getString(
2376
"webappClassLoader.clearReferencesResourceBundlesCount",
2377
Integer.valueOf(countRemoved)));
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);
2387
log.debug(sm.getString(
2388
"webappClassLoader.clearReferencesResourceBundlesFail"), e);
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);
2401
* Determine whether a class was loaded by this class loader or one of
2402
* its child class loaders.
2404
protected boolean loadedByThisOrChild(Class clazz)
2406
boolean result = false;
2407
for (ClassLoader classLoader = clazz.getClassLoader();
2408
null != classLoader; classLoader = classLoader.getParent()) {
2409
if (classLoader.equals(this)) {
2419
* Used to periodically signal to the classloader to release JAR resources.
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++) {
2427
jarFiles[i] = new JarFile(jarRealFiles[i]);
2428
} catch (IOException e) {
2429
if (log.isDebugEnabled()) {
2430
log.debug("Failed to open JAR", e);
2442
* Find specified class in local repositories.
2444
* @return the loaded class, or null if the class isn't found
2446
protected Class findClassInternal(String name)
2447
throws ClassNotFoundException {
2449
if (!validate(name))
2450
throw new ClassNotFoundException(name);
2452
String tempPath = name.replace('.', '/');
2453
String classPath = tempPath + ".class";
2455
ResourceEntry entry = null;
2457
if (securityManager != null) {
2458
PrivilegedAction<ResourceEntry> dp =
2459
new PrivilegedFindResourceByName(name, classPath);
2460
entry = AccessController.doPrivileged(dp);
2462
entry = findResourceInternal(name, classPath);
2466
throw new ClassNotFoundException(name);
2468
Class clazz = entry.loadedClass;
2472
synchronized (name.intern()) {
2473
clazz = entry.loadedClass;
2477
if (entry.binaryContent == null)
2478
throw new ClassNotFoundException(name);
2480
// Looking up the package
2481
String packageName = null;
2482
int pos = name.lastIndexOf('.');
2484
packageName = name.substring(0, pos);
2488
if (packageName != null) {
2489
pkg = getPackage(packageName);
2490
// Define the package (if null)
2493
if (entry.manifest == null) {
2494
definePackage(packageName, null, null, null, null,
2497
definePackage(packageName, entry.manifest,
2500
} catch (IllegalArgumentException e) {
2501
// Ignore: normal error due to dual definition of package
2503
pkg = getPackage(packageName);
2507
if (securityManager != null) {
2511
boolean sealCheck = true;
2512
if (pkg.isSealed()) {
2513
sealCheck = pkg.isSealed(entry.codeBase);
2515
sealCheck = (entry.manifest == null)
2516
|| !isPackageSealed(packageName, entry.manifest);
2519
throw new SecurityException
2520
("Sealing violation loading " + name + " : Package "
2521
+ packageName + " is sealed.");
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",
2536
entry.loadedClass = clazz;
2537
entry.binaryContent = null;
2538
entry.source = null;
2539
entry.codeBase = null;
2540
entry.manifest = null;
2541
entry.certificates = null;
2549
* Find specified resource in local repositories.
2551
* @return the loaded resource, or null if the resource isn't found
2553
protected ResourceEntry findResourceInternal(File file, String path){
2554
ResourceEntry entry = new ResourceEntry();
2556
entry.source = getURI(new File(file, path));
2557
entry.codeBase = getURL(new File(file, path), false);
2558
} catch (MalformedURLException e) {
2566
* Find specified resource in local repositories.
2568
* @return the loaded resource, or null if the resource isn't found
2570
protected ResourceEntry findResourceInternal(String name, String path) {
2573
log.info(sm.getString("webappClassLoader.stopped", name));
2577
if ((name == null) || (path == null))
2580
ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
2584
int contentLength = -1;
2585
InputStream binaryStream = null;
2587
int jarFilesLength = jarFiles.length;
2588
int repositoriesLength = repositories.length;
2592
Resource resource = null;
2594
boolean fileNeedConvert = false;
2596
for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
2599
String fullPath = repositories[i] + path;
2601
Object lookupResult = resources.lookup(fullPath);
2602
if (lookupResult instanceof Resource) {
2603
resource = (Resource) lookupResult;
2606
// Note : Not getting an exception here means the resource was
2608
entry = findResourceInternal(files[i], path);
2610
ResourceAttributes attributes =
2611
(ResourceAttributes) resources.getAttributes(fullPath);
2612
contentLength = (int) attributes.getContentLength();
2613
entry.lastModified = attributes.getLastModified();
2615
if (resource != null) {
2619
binaryStream = resource.streamContent();
2620
} catch (IOException e) {
2625
if (path.endsWith(".properties")) {
2626
fileNeedConvert = true;
2630
// Register the full path for modification checking
2631
// Note: Only syncing on a 'constant' object is needed
2632
synchronized (allPermission) {
2637
new long[lastModifiedDates.length + 1];
2638
for (j = 0; j < lastModifiedDates.length; j++) {
2639
result2[j] = lastModifiedDates[j];
2641
result2[lastModifiedDates.length] = entry.lastModified;
2642
lastModifiedDates = result2;
2644
String[] result = new String[paths.length + 1];
2645
for (j = 0; j < paths.length; j++) {
2646
result[j] = paths[j];
2648
result[paths.length] = fullPath;
2655
} catch (NamingException e) {
2659
if ((entry == null) && (notFoundResources.containsKey(name)))
2662
JarEntry jarEntry = null;
2664
synchronized (jarFiles) {
2670
for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
2672
jarEntry = jarFiles[i].getJarEntry(path);
2674
if (jarEntry != null) {
2676
entry = new ResourceEntry();
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) {
2686
contentLength = (int) jarEntry.getSize();
2688
entry.manifest = jarFiles[i].getManifest();
2689
binaryStream = jarFiles[i].getInputStream(jarEntry);
2690
} catch (IOException e) {
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
2707
resourceFile = new File
2708
(loaderDir, jarEntry2.getName());
2710
if (!resourceFile.getCanonicalPath().startsWith(
2711
canonicalLoaderDir)) {
2712
throw new IllegalArgumentException(
2713
sm.getString("webappClassLoader.illegalJarPath",
2714
jarEntry2.getName()));
2716
} catch (IOException ioe) {
2717
throw new IllegalArgumentException(
2718
sm.getString("webappClassLoader.validationErrorJarPath",
2719
jarEntry2.getName()), ioe);
2721
resourceFile.getParentFile().mkdirs();
2722
FileOutputStream os = null;
2723
InputStream is = null;
2725
is = jarFiles[i].getInputStream
2727
os = new FileOutputStream
2730
int n = is.read(buf);
2734
os.write(buf, 0, n);
2736
} catch (IOException e) {
2743
} catch (IOException e) {
2749
} catch (IOException e) {
2761
if (entry == null) {
2762
synchronized (notFoundResources) {
2763
notFoundResources.put(name, name);
2768
if (binaryStream != null) {
2770
byte[] binaryContent = new byte[contentLength];
2776
int n = binaryStream.read(binaryContent, pos,
2777
binaryContent.length - pos);
2782
} catch (IOException e) {
2783
log.error(sm.getString("webappClassLoader.readError", name), e);
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);
2793
binaryContent = str.getBytes("UTF-8");
2794
} catch (Exception e) {
2798
entry.binaryContent = binaryContent;
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();
2808
if (binaryStream != null) {
2810
binaryStream.close();
2811
} catch (IOException e) { /* Ignore */}
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
2821
ResourceEntry entry2 = (ResourceEntry) resourceEntries.get(name);
2822
if (entry2 == null) {
2823
resourceEntries.put(name, entry);
2835
* Returns true if the specified package name is sealed according to the
2838
protected boolean isPackageSealed(String name, Manifest man) {
2840
String path = name.replace('.', '/') + '/';
2841
Attributes attr = man.getAttributes(path);
2842
String sealed = null;
2844
sealed = attr.getValue(Name.SEALED);
2846
if (sealed == null) {
2847
if ((attr = man.getMainAttributes()) != null) {
2848
sealed = attr.getValue(Name.SEALED);
2851
return "true".equalsIgnoreCase(sealed);
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>.
2862
* @param name Name of the resource to return
2864
protected InputStream findLoadedResource(String name) {
2866
ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
2867
if (entry != null) {
2868
if (entry.binaryContent != null)
2869
return new ByteArrayInputStream(entry.binaryContent);
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>.
2881
* @param name Name of the resource to return
2883
protected Class findLoadedClass0(String name) {
2885
ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
2886
if (entry != null) {
2887
return entry.loadedClass;
2889
return (null); // FIXME - findLoadedResource()
2895
* Refresh the system policy file, to pick up eventual changes.
2897
protected void refreshPolicy() {
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();
2905
} catch (AccessControlException e) {
2906
// Some policy files may restrict this, even for the core,
2907
// so this exception is ignored
2916
* @param name class name
2917
* @return true if the class should be filtered
2919
protected boolean filter(String name) {
2924
// Looking up the package
2925
String packageName = null;
2926
int pos = name.lastIndexOf('.');
2928
packageName = name.substring(0, pos);
2932
for (int i = 0; i < packageTriggers.length; i++) {
2933
if (packageName.startsWith(packageTriggers[i]))
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
2949
* @param name class name
2950
* @return true if the name is valid
2952
protected boolean validate(String name) {
2956
if (name.startsWith("java."))
2965
* Check the specified JAR file, and return <code>true</code> if it does
2966
* not contain any of the trigger classes.
2968
* @param jarfile The JAR file to be checked
2970
* @exception IOException if an input/output error occurs
2972
protected boolean validateJarFile(File jarfile)
2973
throws IOException {
2975
if (triggers == null)
2977
JarFile jarFile = new JarFile(jarfile);
2978
for (int i = 0; i < triggers.length; i++) {
2981
if (parent != null) {
2982
clazz = parent.loadClass(triggers[i]);
2984
clazz = Class.forName(triggers[i]);
2986
} catch (Throwable t) {
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);
3012
protected URL getURL(File file, boolean encoded)
3013
throws MalformedURLException {
3015
File realFile = file;
3017
realFile = realFile.getCanonicalFile();
3018
} catch (IOException e) {
3022
return getURI(realFile);
3024
return realFile.toURL();
3033
protected URL getURI(File file)
3034
throws MalformedURLException {
3037
File realFile = file;
3039
realFile = realFile.getCanonicalFile();
3040
} catch (IOException e) {
3043
return realFile.toURI().toURL();
3049
* Delete the specified directory, including all of its contents and
3050
* subdirectories recursively.
3052
* @param dir File object representing the directory to be deleted
3054
protected static void deleteDir(File dir) {
3056
String files[] = dir.list();
3057
if (files == null) {
3058
files = new String[0];
3060
for (int i = 0; i < files.length; i++) {
3061
File file = new File(dir, files[i]);
3062
if (file.isDirectory()) {