View | Details | Raw Unified | Return to bug 57681
Collapse All | Expand All

(-)java/org/apache/catalina/loader/LocalStrings.properties (+1 lines)
Lines 84-86 webappLoader.failModifiedCheck=Error tracking modifications Link Here
84
webappLoader.copyFailure=Failed to copy resources
84
webappLoader.copyFailure=Failed to copy resources
85
webappLoader.mkdirFailure=Failed to create destination directory to copy resources
85
webappLoader.mkdirFailure=Failed to create destination directory to copy resources
86
webappLoader.namingFailure=Failed to access resource {0}
86
webappLoader.namingFailure=Failed to access resource {0}
87
webappClassLoaderParallel.registrationFailed=Registration of org.apache.catalina.loader.ParallelWebappClassLoader as capable of loading classes in parallel failed
(-)java/org/apache/catalina/loader/ParallelWebappClassLoader.java (+100 lines)
Line 0 Link Here
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 org.apache.catalina.LifecycleException;
22
import org.apache.tomcat.util.compat.JreCompat;
23
24
import java.lang.reflect.Method;
25
import java.security.AccessController;
26
import java.security.PrivilegedAction;
27
28
/**
29
 * Parallel class loading implementation of WebappClassLoaderBase.
30
 * Parallel class loading feature will only be enabled on JRE7 or above,
31
 * even ParallelWebappClassLoader is specified,
32
 * it will remain serializable on JRE6 or lower.
33
 * @author Huxing Zhang (huxing.zhx@alibaba-inc.com)
34
 */
35
public class ParallelWebappClassLoader extends WebappClassLoaderBase {
36
37
    private static final org.apache.juli.logging.Log log =
38
            org.apache.juli.logging.LogFactory.getLog(ParallelWebappClassLoader.class);
39
40
    static {
41
        try {
42
            if (JreCompat.getInstance().isJre7Available()) {
43
                // parallel class loading capable
44
                final Method registerParallel =
45
                        ClassLoader.class.getDeclaredMethod("registerAsParallelCapable");
46
                AccessController.doPrivileged(new PrivilegedAction<Object>() {
47
                    public Object run() {
48
                        registerParallel.setAccessible(true);
49
                        return null;
50
                    }
51
                });
52
                Boolean result = (Boolean)registerParallel.invoke(null);
53
                if (!result) {
54
                    log.warn(sm.getString("webappClassLoaderParallel.registrationFailed"));
55
                }
56
            }
57
        } catch (Exception e) {
58
            // ignore
59
        }
60
    }
61
62
    public ParallelWebappClassLoader() {
63
        super();
64
    }
65
66
    public ParallelWebappClassLoader(ClassLoader parent) {
67
        super(parent);
68
    }
69
70
    /**
71
     * Returns a copy of this class loader without any class file
72
     * transformers. This is a tool often used by Java Persistence API
73
     * providers to inspect entity classes in the absence of any
74
     * instrumentation, something that can't be guaranteed within the
75
     * context of a {@link java.lang.instrument.ClassFileTransformer}'s
76
     * {@link java.lang.instrument.ClassFileTransformer#transform(ClassLoader,
77
     * String, Class, java.security.ProtectionDomain, byte[]) transform} method.
78
     * <p>
79
     * The returned class loader's resource cache will have been cleared
80
     * so that classes already instrumented will not be retained or
81
     * returned.
82
     *
83
     * @return the transformer-free copy of this class loader.
84
     */
85
    @Override
86
    public ParallelWebappClassLoader copyWithoutTransformers() {
87
88
        ParallelWebappClassLoader result = new ParallelWebappClassLoader(getParent());
89
90
        super.copyStateWithoutTransformers(result);
91
92
        try {
93
            result.start();
94
        } catch (LifecycleException e) {
95
            throw new IllegalStateException(e);
96
        }
97
98
        return result;
99
    }
100
}
(-)java/org/apache/catalina/loader/StandardClassLoader.java (-46 lines)
Lines 1-46 Link Here
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.net.URL;
22
import java.net.URLClassLoader;
23
24
/**
25
 * Subclass implementation of <b>java.net.URLClassLoader</b>. There are no
26
 * functional differences between this class and <b>java.net.URLClassLoader</b>.
27
 *
28
 * @author Craig R. McClanahan
29
 * @author Remy Maucherat
30
 * @deprecated  Unnecessary. Will be removed in Tomcat 8.0.x.
31
 */
32
@Deprecated
33
public class StandardClassLoader
34
    extends URLClassLoader
35
    implements StandardClassLoaderMBean {
36
37
    public StandardClassLoader(URL repositories[]) {
38
        super(repositories);
39
    }
40
41
    public StandardClassLoader(URL repositories[], ClassLoader parent) {
42
        super(repositories, parent);
43
    }
44
45
}
46
(-)java/org/apache/catalina/loader/StandardClassLoaderMBean.java (-32 lines)
Lines 1-32 Link Here
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
/**
22
 * MBean interface for StandardClassLoader, to allow JMX remote management.
23
 *
24
 * @author Remy Maucherat
25
 *
26
 * @deprecated  Unused. Will be removed in Tomcat 8.0.x.
27
 */
28
@Deprecated
29
public interface StandardClassLoaderMBean {
30
    // Marker interface
31
}
32
(-)java/org/apache/catalina/loader/WebappClassLoader.java (-3718 / +28 lines)
Lines 15-3758 Link Here
15
 * limitations under the License.
15
 * limitations under the License.
16
 */
16
 */
17
17
18
19
package org.apache.catalina.loader;
18
package org.apache.catalina.loader;
20
19
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.instrument.ClassFileTransformer;
28
import java.lang.instrument.IllegalClassFormatException;
29
import java.lang.ref.Reference;
30
import java.lang.ref.WeakReference;
31
import java.lang.reflect.Field;
32
import java.lang.reflect.Method;
33
import java.lang.reflect.Modifier;
34
import java.net.MalformedURLException;
35
import java.net.URL;
36
import java.net.URLClassLoader;
37
import java.nio.charset.Charset;
38
import java.security.AccessControlException;
39
import java.security.AccessController;
40
import java.security.CodeSource;
41
import java.security.Permission;
42
import java.security.PermissionCollection;
43
import java.security.Policy;
44
import java.security.PrivilegedAction;
45
import java.security.ProtectionDomain;
46
import java.util.ArrayList;
47
import java.util.Collection;
48
import java.util.Collections;
49
import java.util.ConcurrentModificationException;
50
import java.util.Enumeration;
51
import java.util.HashMap;
52
import java.util.Iterator;
53
import java.util.LinkedHashMap;
54
import java.util.LinkedHashSet;
55
import java.util.List;
56
import java.util.Map;
57
import java.util.ResourceBundle;
58
import java.util.Set;
59
import java.util.concurrent.CopyOnWriteArrayList;
60
import java.util.concurrent.ThreadPoolExecutor;
61
import java.util.jar.Attributes;
62
import java.util.jar.Attributes.Name;
63
import java.util.jar.JarEntry;
64
import java.util.jar.JarFile;
65
import java.util.jar.Manifest;
66
67
import javax.naming.Binding;
68
import javax.naming.NameClassPair;
69
import javax.naming.NamingEnumeration;
70
import javax.naming.NamingException;
71
import javax.naming.directory.DirContext;
72
73
import org.apache.catalina.Globals;
74
import org.apache.catalina.Lifecycle;
75
import org.apache.catalina.LifecycleException;
20
import org.apache.catalina.LifecycleException;
76
import org.apache.catalina.LifecycleListener;
77
import org.apache.catalina.LifecycleState;
78
import org.apache.naming.JndiPermission;
79
import org.apache.naming.resources.ProxyDirContext;
80
import org.apache.naming.resources.Resource;
81
import org.apache.naming.resources.ResourceAttributes;
82
import org.apache.tomcat.InstrumentableClassLoader;
83
import org.apache.tomcat.util.ExceptionUtils;
84
import org.apache.tomcat.util.IntrospectionUtils;
85
import org.apache.tomcat.util.res.StringManager;
86
21
87
/**
22
/**
88
 * Specialized web application class loader.
23
 * Serial class loading implementation of WebappClassLoaderBase.
89
 * <p>
24
 * This class loader will be used by default.
90
 * This class loader is a full reimplementation of the
25
 * @author Huxing Zhang (huxing.zhx@alibaba-inc.com)
91
 * <code>URLClassLoader</code> from the JDK. It is designed to be fully
92
 * compatible with a normal <code>URLClassLoader</code>, although its internal
93
 * behavior may be completely different.
94
 * <p>
95
 * <strong>IMPLEMENTATION NOTE</strong> - By default, this class loader follows
96
 * the delegation model required by the specification. The system class
97
 * loader will be queried first, then the local repositories, and only then
98
 * delegation to the parent class loader will occur. This allows the web
99
 * application to override any shared class except the classes from J2SE.
100
 * Special handling is provided from the JAXP XML parser interfaces, the JNDI
101
 * interfaces, and the classes from the servlet API, which are never loaded
102
 * from the webapp repositories. The <code>delegate</code> property
103
 * allows an application to modify this behavior to move the parent class loader
104
 * ahead of the local repositories.
105
 * <p>
106
 * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper
107
 * compilation technology, any repository which contains classes from
108
 * the servlet API will be ignored by the class loader.
109
 * <p>
110
 * <strong>IMPLEMENTATION NOTE</strong> - The class loader generates source
111
 * URLs which include the full JAR URL when a class is loaded from a JAR file,
112
 * which allows setting security permission at the class level, even when a
113
 * class is contained inside a JAR.
114
 * <p>
115
 * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in
116
 * the order they are added via the initial constructor and/or any subsequent
117
 * calls to <code>addRepository()</code> or <code>addJar()</code>.
118
 * <p>
119
 * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
120
 * security is made unless a security manager is present.
121
 * <p>
122
 * TODO: Is there any requirement to provide a proper Lifecycle implementation
123
 *       rather than the current stubbed implementation?
124
 * <strong>IMPLEMENTATION NOTE</strong> - As of 7.0.64/8.0, this class
125
 * loader implements {@link InstrumentableClassLoader}, permitting web
126
 * application classes to instrument other classes in the same web
127
 * application. It does not permit instrumentation of system or container
128
 * classes or classes in other web apps.
129
 *
130
 * @author Remy Maucherat
131
 * @author Craig R. McClanahan
132
 */
26
 */
133
public class WebappClassLoader extends URLClassLoader
27
public class WebappClassLoader extends WebappClassLoaderBase {
134
        implements Lifecycle, InstrumentableClassLoader {
135
136
    private static final org.apache.juli.logging.Log log=
137
        org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
138
139
    private static final Charset CHARSET_UTF8 = Charset.forName("UTF-8");
140
    /**
141
     * List of ThreadGroup names to ignore when scanning for web application
142
     * started threads that need to be shut down.
143
     */
144
    private static final List<String> JVM_THREAD_GROUP_NAMES =
145
        new ArrayList<String>();
146
147
    private static final String JVM_THREAD_GROUP_SYSTEM = "system";
148
149
    private static final String SERVICES_PREFIX = "META-INF/services/";
150
151
    private static final String CLASS_FILE_SUFFIX = ".class";
152
153
    static {
154
        JVM_THREAD_GROUP_NAMES.add(JVM_THREAD_GROUP_SYSTEM);
155
        JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
156
    }
157
158
    protected class PrivilegedFindResourceByName
159
        implements PrivilegedAction<ResourceEntry> {
160
161
        protected String name;
162
        protected String path;
163
        protected boolean manifestRequired;
164
165
        PrivilegedFindResourceByName(String name, String path, boolean manifestRequired) {
166
            this.name = name;
167
            this.path = path;
168
            this.manifestRequired = manifestRequired;
169
        }
170
171
        @Override
172
        public ResourceEntry run() {
173
            return findResourceInternal(name, path, manifestRequired);
174
        }
175
176
    }
177
178
179
    protected static final class PrivilegedGetClassLoader
180
        implements PrivilegedAction<ClassLoader> {
181
182
        public Class<?> clazz;
183
184
        public PrivilegedGetClassLoader(Class<?> clazz){
185
            this.clazz = clazz;
186
        }
187
188
        @Override
189
        public ClassLoader run() {
190
            return clazz.getClassLoader();
191
        }
192
    }
193
194
195
    // ------------------------------------------------------- Static Variables
196
197
28
198
    /**
199
     * The set of trigger classes that will cause a proposed repository not
200
     * to be added if this class is visible to the class loader that loaded
201
     * this factory class.  Typically, trigger classes will be listed for
202
     * components that have been integrated into the JDK for later versions,
203
     * but where the corresponding JAR files are required to run on
204
     * earlier versions.
205
     */
206
    protected static final String[] triggers = {
207
        "javax.servlet.Servlet", "javax.el.Expression"       // Servlet API
208
    };
209
210
211
    /**
212
     * Set of package names which are not allowed to be loaded from a webapp
213
     * class loader without delegating first.
214
     */
215
    protected static final String[] packageTriggers = {
216
    };
217
218
219
    /**
220
     * The string manager for this package.
221
     */
222
    protected static final StringManager sm =
223
        StringManager.getManager(Constants.Package);
224
225
226
    /**
227
     * Use anti JAR locking code, which does URL rerouting when accessing
228
     * resources.
229
     */
230
    boolean antiJARLocking = false;
231
232
    // ----------------------------------------------------------- Constructors
233
234
235
    /**
236
     * Construct a new ClassLoader with no defined repositories and no
237
     * parent ClassLoader.
238
     */
239
    public WebappClassLoader() {
29
    public WebappClassLoader() {
240
30
        super();
241
        super(new URL[0]);
242
243
        ClassLoader p = getParent();
244
        if (p == null) {
245
            p = getSystemClassLoader();
246
        }
247
        this.parent = p;
248
249
        ClassLoader j = String.class.getClassLoader();
250
        if (j == null) {
251
            j = getSystemClassLoader();
252
            while (j.getParent() != null) {
253
                j = j.getParent();
254
            }
255
        }
256
        this.j2seClassLoader = j;
257
258
        securityManager = System.getSecurityManager();
259
        if (securityManager != null) {
260
            refreshPolicy();
261
        }
262
    }
31
    }
263
32
264
265
    /**
266
     * Construct a new ClassLoader with no defined repositories and the given
267
     * parent ClassLoader.
268
     * <p>
269
     * Method is used via reflection -
270
     * see {@link WebappLoader#createClassLoader()}
271
     *
272
     * @param parent Our parent class loader
273
     */
274
    public WebappClassLoader(ClassLoader parent) {
33
    public WebappClassLoader(ClassLoader parent) {
275
34
        super(parent);
276
        super(new URL[0], parent);
277
278
        ClassLoader p = getParent();
279
        if (p == null) {
280
            p = getSystemClassLoader();
281
        }
282
        this.parent = p;
283
284
        ClassLoader j = String.class.getClassLoader();
285
        if (j == null) {
286
            j = getSystemClassLoader();
287
            while (j.getParent() != null) {
288
                j = j.getParent();
289
            }
290
        }
291
        this.j2seClassLoader = j;
292
293
        securityManager = System.getSecurityManager();
294
        if (securityManager != null) {
295
            refreshPolicy();
296
        }
297
    }
298
299
300
    // ----------------------------------------------------- Instance Variables
301
302
303
    /**
304
     * Associated directory context giving access to the resources in this
305
     * webapp.
306
     */
307
    protected DirContext resources = null;
308
309
310
    /**
311
     * The cache of ResourceEntry for classes and resources we have loaded,
312
     * keyed by resource name.
313
     */
314
    protected HashMap<String, ResourceEntry> resourceEntries = new HashMap<String, ResourceEntry>();
315
316
317
    /**
318
     * The list of not found resources.
319
     */
320
    protected HashMap<String, String> notFoundResources =
321
        new LinkedHashMap<String, String>() {
322
        private static final long serialVersionUID = 1L;
323
        @Override
324
        protected boolean removeEldestEntry(
325
                Map.Entry<String, String> eldest) {
326
            return size() > 1000;
327
        }
328
    };
329
330
331
    /**
332
     * Should this class loader delegate to the parent class loader
333
     * <strong>before</strong> searching its own repositories (i.e. the
334
     * usual Java2 delegation model)?  If set to <code>false</code>,
335
     * this class loader will search its own repositories first, and
336
     * delegate to the parent only if the class or resource is not
337
     * found locally. Note that the default, <code>false</code>, is
338
     * the behavior called for by the servlet specification.
339
     */
340
    protected boolean delegate = false;
341
342
343
    /**
344
     * Last time a JAR was accessed.
345
     */
346
    protected long lastJarAccessed = 0L;
347
348
349
    /**
350
     * The list of local repositories, in the order they should be searched
351
     * for locally loaded classes or resources.
352
     */
353
    protected String[] repositories = new String[0];
354
355
356
     /**
357
      * Repositories URLs, used to cache the result of getURLs.
358
      */
359
     protected URL[] repositoryURLs = null;
360
361
362
    /**
363
     * Repositories translated as path in the work directory (for Jasper
364
     * originally), but which is used to generate fake URLs should getURLs be
365
     * called.
366
     */
367
    protected File[] files = new File[0];
368
369
370
    /**
371
     * The list of JARs, in the order they should be searched
372
     * for locally loaded classes or resources.
373
     */
374
    protected JarFile[] jarFiles = new JarFile[0];
375
376
377
    /**
378
     * The list of JARs, in the order they should be searched
379
     * for locally loaded classes or resources.
380
     */
381
    protected File[] jarRealFiles = new File[0];
382
383
384
    /**
385
     * The path which will be monitored for added Jar files.
386
     */
387
    protected String jarPath = null;
388
389
390
    /**
391
     * The list of JARs, in the order they should be searched
392
     * for locally loaded classes or resources.
393
     */
394
    protected String[] jarNames = new String[0];
395
396
397
    /**
398
     * The list of JARs last modified dates, in the order they should be
399
     * searched for locally loaded classes or resources.
400
     */
401
    protected long[] lastModifiedDates = new long[0];
402
403
404
    /**
405
     * The list of resources which should be checked when checking for
406
     * modifications.
407
     */
408
    protected String[] paths = new String[0];
409
410
411
    /**
412
     * A list of read File and Jndi Permission's required if this loader
413
     * is for a web application context.
414
     */
415
    protected ArrayList<Permission> permissionList =
416
        new ArrayList<Permission>();
417
418
419
    /**
420
     * Path where resources loaded from JARs will be extracted.
421
     */
422
    protected File loaderDir = null;
423
    protected String canonicalLoaderDir = null;
424
425
    /**
426
     * The PermissionCollection for each CodeSource for a web
427
     * application context.
428
     */
429
    protected HashMap<String, PermissionCollection> loaderPC = new HashMap<String, PermissionCollection>();
430
431
432
    /**
433
     * Instance of the SecurityManager installed.
434
     */
435
    protected SecurityManager securityManager = null;
436
437
438
    /**
439
     * The parent class loader.
440
     */
441
    protected ClassLoader parent = null;
442
443
444
    /**
445
     * The system class loader.
446
     */
447
    protected ClassLoader system = null;
448
449
450
    /**
451
     * The bootstrap class loader used to load the JavaSE classes. In some
452
     * implementations this class loader is always <code>null</null> and in
453
     * those cases {@link ClassLoader#getParent()} will be called recursively on
454
     * the system class loader and the last non-null result used.
455
     */
456
    protected ClassLoader j2seClassLoader;
457
458
459
    /**
460
     * Has this component been started?
461
     */
462
    protected boolean started = false;
463
464
465
    /**
466
     * Has external repositories.
467
     */
468
    protected boolean hasExternalRepositories = false;
469
470
    /**
471
     * Search external repositories first
472
     */
473
    protected boolean searchExternalFirst = false;
474
475
    /**
476
     * need conversion for properties files
477
     */
478
    protected boolean needConvert = false;
479
480
481
    /**
482
     * All permission.
483
     */
484
    protected Permission allPermission = new java.security.AllPermission();
485
486
487
    /**
488
     * Should Tomcat attempt to null out any static or final fields from loaded
489
     * classes when a web application is stopped as a work around for apparent
490
     * garbage collection bugs and application coding errors? There have been
491
     * some issues reported with log4j when this option is true. Applications
492
     * without memory leaks using recent JVMs should operate correctly with this
493
     * option set to <code>false</code>. If not specified, the default value of
494
     * <code>false</code> will be used.
495
     */
496
    private boolean clearReferencesStatic = false;
497
498
    /**
499
     * Should Tomcat attempt to terminate threads that have been started by the
500
     * web application? Stopping threads is performed via the deprecated (for
501
     * good reason) <code>Thread.stop()</code> method and is likely to result in
502
     * instability. As such, enabling this should be viewed as an option of last
503
     * resort in a development environment and is not recommended in a
504
     * production environment. If not specified, the default value of
505
     * <code>false</code> will be used.
506
     */
507
    private boolean clearReferencesStopThreads = false;
508
509
    /**
510
     * Should Tomcat attempt to terminate any {@link java.util.TimerThread}s
511
     * that have been started by the web application? If not specified, the
512
     * default value of <code>false</code> will be used.
513
     */
514
    private boolean clearReferencesStopTimerThreads = false;
515
516
    /**
517
     * Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()}
518
     * when the class loader is stopped? If not specified, the default value
519
     * of <code>true</code> is used. Changing the default setting is likely to
520
     * lead to memory leaks and other issues.
521
     */
522
    private boolean clearReferencesLogFactoryRelease = true;
523
524
    /**
525
     * If an HttpClient keep-alive timer thread has been started by this web
526
     * application and is still running, should Tomcat change the context class
527
     * loader from the current {@link WebappClassLoader} to
528
     * {@link WebappClassLoader#parent} to prevent a memory leak? Note that the
529
     * keep-alive timer thread will stop on its own once the keep-alives all
530
     * expire however, on a busy system that might not happen for some time.
531
     */
532
    private boolean clearReferencesHttpClientKeepAliveThread = true;
533
534
    /**
535
     * Name of associated context used with logging and JMX to associate with
536
     * the right web application. Particularly useful for the clear references
537
     * messages. Defaults to unknown but if standard Tomcat components are used
538
     * it will be updated during initialisation from the resources.
539
     */
540
    private String contextName = "unknown";
541
542
    /**
543
     * Holds the class file transformers decorating this class loader. The
544
     * CopyOnWriteArrayList is thread safe. It is expensive on writes, but
545
     * those should be rare. It is very fast on reads, since synchronization
546
     * is not actually used. Importantly, the ClassLoader will never block
547
     * iterating over the transformers while loading a class.
548
     */
549
    private final List<ClassFileTransformer> transformers = new CopyOnWriteArrayList<ClassFileTransformer>();
550
551
    /**
552
     * Code base to use for classes loaded from WEB-INF/classes.
553
     */
554
    private URL webInfClassesCodeBase = null;
555
556
    // ------------------------------------------------------------- Properties
557
558
559
    /**
560
     * Get associated resources.
561
     */
562
    public DirContext getResources() {
563
564
        return this.resources;
565
566
    }
567
568
569
    /**
570
     * Set associated resources.
571
     */
572
    public void setResources(DirContext resources) {
573
574
        this.resources = resources;
575
576
        if (resources instanceof ProxyDirContext) {
577
            contextName = ((ProxyDirContext) resources).getContextName();
578
        }
579
    }
580
581
582
    /**
583
     * Return the context name for this class loader.
584
     */
585
    public String getContextName() {
586
587
        return (this.contextName);
588
589
    }
590
591
592
    /**
593
     * Return the "delegate first" flag for this class loader.
594
     */
595
    public boolean getDelegate() {
596
597
        return (this.delegate);
598
599
    }
35
    }
600
36
601
602
    /**
37
    /**
603
     * Set the "delegate first" flag for this class loader.
38
     * Returns a copy of this class loader without any class file
604
     * If this flag is true, this class loader delegates
39
     * transformers. This is a tool often used by Java Persistence API
605
     * to the parent class loader
40
     * providers to inspect entity classes in the absence of any
606
     * <strong>before</strong> searching its own repositories, as
41
     * instrumentation, something that can't be guaranteed within the
607
     * in an ordinary (non-servlet) chain of Java class loaders.
42
     * context of a {@link java.lang.instrument.ClassFileTransformer}'s
608
     * If set to <code>false</code> (the default),
43
     * {@link java.lang.instrument.ClassFileTransformer#transform(ClassLoader,
609
     * this class loader will search its own repositories first, and
44
     * String, Class, java.security.ProtectionDomain, byte[]) transform} method.
610
     * delegate to the parent only if the class or resource is not
45
     * <p>
611
     * found locally, as per the servlet specification.
46
     * The returned class loader's resource cache will have been cleared
47
     * so that classes already instrumented will not be retained or
48
     * returned.
612
     *
49
     *
613
     * @param delegate The new "delegate first" flag
50
     * @return the transformer-free copy of this class loader.
614
     */
51
     */
615
    public void setDelegate(boolean delegate) {
52
    @Override
616
53
    public WebappClassLoader copyWithoutTransformers() {
617
        this.delegate = delegate;
618
619
    }
620
54
55
        WebappClassLoader result = new WebappClassLoader(getParent());
621
56
622
    /**
57
        super.copyStateWithoutTransformers(result);
623
     * @return Returns the antiJARLocking.
624
     */
625
    public boolean getAntiJARLocking() {
626
        return antiJARLocking;
627
    }
628
58
59
        try {
60
            result.start();
61
        } catch (LifecycleException e) {
62
            throw new IllegalStateException(e);
63
        }
629
64
630
    /**
65
        return result;
631
     * @param antiJARLocking The antiJARLocking to set.
632
     */
633
    public void setAntiJARLocking(boolean antiJARLocking) {
634
        this.antiJARLocking = antiJARLocking;
635
    }
66
    }
636
67
637
    /**
638
     * @return Returns the searchExternalFirst.
639
     */
640
    public boolean getSearchExternalFirst() {
641
        return searchExternalFirst;
642
    }
643
644
    /**
645
     * @param searchExternalFirst Whether external repositories should be searched first
646
     */
647
    public void setSearchExternalFirst(boolean searchExternalFirst) {
648
        this.searchExternalFirst = searchExternalFirst;
649
    }
650
651
652
    /**
653
     * If there is a Java SecurityManager create a read FilePermission
654
     * or JndiPermission for the file directory path.
655
     *
656
     * @param filepath file directory path
657
     */
658
    public void addPermission(String filepath) {
659
        if (filepath == null) {
660
            return;
661
        }
662
663
        String path = filepath;
664
665
        if (securityManager != null) {
666
            Permission permission = null;
667
            if (path.startsWith("jndi:") || path.startsWith("jar:jndi:")) {
668
                if (!path.endsWith("/")) {
669
                    path = path + "/";
670
                }
671
                permission = new JndiPermission(path + "*");
672
                addPermission(permission);
673
            } else {
674
                if (!path.endsWith(File.separator)) {
675
                    permission = new FilePermission(path, "read");
676
                    addPermission(permission);
677
                    path = path + File.separator;
678
                }
679
                permission = new FilePermission(path + "-", "read");
680
                addPermission(permission);
681
            }
682
        }
683
    }
684
685
686
    /**
687
     * If there is a Java SecurityManager create a read FilePermission
688
     * or JndiPermission for URL.
689
     *
690
     * @param url URL for a file or directory on local system
691
     */
692
    public void addPermission(URL url) {
693
        if (url != null) {
694
            addPermission(url.toString());
695
        }
696
    }
697
698
699
    /**
700
     * If there is a Java SecurityManager create a Permission.
701
     *
702
     * @param permission The permission
703
     */
704
    public void addPermission(Permission permission) {
705
        if ((securityManager != null) && (permission != null)) {
706
            permissionList.add(permission);
707
        }
708
    }
709
710
711
    /**
712
     * Return the JAR path.
713
     */
714
    public String getJarPath() {
715
716
        return this.jarPath;
717
718
    }
719
720
721
    /**
722
     * Change the Jar path.
723
     */
724
    public void setJarPath(String jarPath) {
725
726
        this.jarPath = jarPath;
727
728
    }
729
730
731
    /**
732
     * Change the work directory.
733
     */
734
    public void setWorkDir(File workDir) {
735
        this.loaderDir = new File(workDir, "loader");
736
        if (loaderDir == null) {
737
            canonicalLoaderDir = null;
738
        } else {
739
            try {
740
                canonicalLoaderDir = loaderDir.getCanonicalPath();
741
                if (!canonicalLoaderDir.endsWith(File.separator)) {
742
                    canonicalLoaderDir += File.separator;
743
                }
744
            } catch (IOException ioe) {
745
                canonicalLoaderDir = null;
746
            }
747
        }
748
    }
749
750
     /**
751
      * Utility method for use in subclasses.
752
      * Must be called before Lifecycle methods to have any effect.
753
      *
754
      * @deprecated Will be removed in 8.0.x onwards.
755
      */
756
    @Deprecated
757
     protected void setParentClassLoader(ClassLoader pcl) {
758
         parent = pcl;
759
     }
760
761
     /**
762
      * Return the clearReferencesStatic flag for this Context.
763
      */
764
     public boolean getClearReferencesStatic() {
765
         return (this.clearReferencesStatic);
766
     }
767
768
769
     /**
770
      * Set the clearReferencesStatic feature for this Context.
771
      *
772
      * @param clearReferencesStatic The new flag value
773
      */
774
     public void setClearReferencesStatic(boolean clearReferencesStatic) {
775
         this.clearReferencesStatic = clearReferencesStatic;
776
     }
777
778
779
     /**
780
      * Return the clearReferencesStopThreads flag for this Context.
781
      */
782
     public boolean getClearReferencesStopThreads() {
783
         return (this.clearReferencesStopThreads);
784
     }
785
786
787
     /**
788
      * Set the clearReferencesStopThreads feature for this Context.
789
      *
790
      * @param clearReferencesStopThreads The new flag value
791
      */
792
     public void setClearReferencesStopThreads(
793
             boolean clearReferencesStopThreads) {
794
         this.clearReferencesStopThreads = clearReferencesStopThreads;
795
     }
796
797
798
     /**
799
      * Return the clearReferencesStopTimerThreads flag for this Context.
800
      */
801
     public boolean getClearReferencesStopTimerThreads() {
802
         return (this.clearReferencesStopTimerThreads);
803
     }
804
805
806
     /**
807
      * Set the clearReferencesStopTimerThreads feature for this Context.
808
      *
809
      * @param clearReferencesStopTimerThreads The new flag value
810
      */
811
     public void setClearReferencesStopTimerThreads(
812
             boolean clearReferencesStopTimerThreads) {
813
         this.clearReferencesStopTimerThreads = clearReferencesStopTimerThreads;
814
     }
815
816
817
     /**
818
      * Return the clearReferencesLogFactoryRelease flag for this Context.
819
      */
820
     public boolean getClearReferencesLogFactoryRelease() {
821
         return (this.clearReferencesLogFactoryRelease);
822
     }
823
824
825
     /**
826
      * Set the clearReferencesLogFactoryRelease feature for this Context.
827
      *
828
      * @param clearReferencesLogFactoryRelease The new flag value
829
      */
830
     public void setClearReferencesLogFactoryRelease(
831
             boolean clearReferencesLogFactoryRelease) {
832
         this.clearReferencesLogFactoryRelease =
833
             clearReferencesLogFactoryRelease;
834
     }
835
836
837
     /**
838
      * Return the clearReferencesHttpClientKeepAliveThread flag for this
839
      * Context.
840
      */
841
     public boolean getClearReferencesHttpClientKeepAliveThread() {
842
         return (this.clearReferencesHttpClientKeepAliveThread);
843
     }
844
845
846
     /**
847
      * Set the clearReferencesHttpClientKeepAliveThread feature for this
848
      * Context.
849
      *
850
      * @param clearReferencesHttpClientKeepAliveThread The new flag value
851
      */
852
     public void setClearReferencesHttpClientKeepAliveThread(
853
             boolean clearReferencesHttpClientKeepAliveThread) {
854
         this.clearReferencesHttpClientKeepAliveThread =
855
             clearReferencesHttpClientKeepAliveThread;
856
     }
857
858
859
    // ------------------------------------------------------- Reloader Methods
860
861
    /**
862
     * Adds the specified class file transformer to this class loader. The
863
     * transformer will then be able to modify the bytecode of any classes
864
     * loaded by this class loader after the invocation of this method.
865
     *
866
     * @param transformer The transformer to add to the class loader
867
     */
868
    @Override
869
    public void addTransformer(ClassFileTransformer transformer) {
870
871
        if (transformer == null) {
872
            throw new IllegalArgumentException(sm.getString(
873
                    "webappClassLoader.addTransformer.illegalArgument", getContextName()));
874
        }
875
876
        if (this.transformers.contains(transformer)) {
877
            // if the same instance of this transformer was already added, bail out
878
            log.warn(sm.getString("webappClassLoader.addTransformer.duplicate",
879
                    transformer, getContextName()));
880
            return;
881
        }
882
        this.transformers.add(transformer);
883
884
        log.info(sm.getString("webappClassLoader.addTransformer", transformer, getContextName()));
885
886
    }
887
888
    /**
889
     * Removes the specified class file transformer from this class loader.
890
     * It will no longer be able to modify the byte code of any classes
891
     * loaded by the class loader after the invocation of this method.
892
     * However, any classes already modified by this transformer will
893
     * remain transformed.
894
     *
895
     * @param transformer The transformer to remove
896
     */
897
    @Override
898
    public void removeTransformer(ClassFileTransformer transformer) {
899
900
        if (transformer == null) {
901
            return;
902
        }
903
904
        if (this.transformers.remove(transformer)) {
905
            log.info(sm.getString("webappClassLoader.removeTransformer",
906
                    transformer, getContextName()));
907
            return;
908
        }
909
910
    }
911
912
    /**
913
     * Returns a copy of this class loader without any class file
914
     * transformers. This is a tool often used by Java Persistence API
915
     * providers to inspect entity classes in the absence of any
916
     * instrumentation, something that can't be guaranteed within the
917
     * context of a {@link ClassFileTransformer}'s
918
     * {@link ClassFileTransformer#transform(ClassLoader, String, Class,
919
     * ProtectionDomain, byte[]) transform} method.
920
     * <p>
921
     * The returned class loader's resource cache will have been cleared
922
     * so that classes already instrumented will not be retained or
923
     * returned.
924
     *
925
     * @return the transformer-free copy of this class loader.
926
     */
927
    @Override
928
    public WebappClassLoader copyWithoutTransformers() {
929
930
        WebappClassLoader result = new WebappClassLoader(this.parent);
931
932
        result.antiJARLocking = this.antiJARLocking;
933
        result.resources = this.resources;
934
        result.files = this.files;
935
        result.delegate = this.delegate;
936
        result.lastJarAccessed = this.lastJarAccessed;
937
        result.repositories = this.repositories;
938
        result.jarPath = this.jarPath;
939
        result.loaderDir = this.loaderDir;
940
        result.canonicalLoaderDir = this.canonicalLoaderDir;
941
        result.clearReferencesStatic = this.clearReferencesStatic;
942
        result.clearReferencesStopThreads = this.clearReferencesStopThreads;
943
        result.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads;
944
        result.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease;
945
        result.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread;
946
        result.repositoryURLs = this.repositoryURLs.clone();
947
        result.jarFiles = this.jarFiles.clone();
948
        result.jarRealFiles = this.jarRealFiles.clone();
949
        result.jarNames = this.jarNames.clone();
950
        result.lastModifiedDates = this.lastModifiedDates.clone();
951
        result.paths = this.paths.clone();
952
        result.notFoundResources.putAll(this.notFoundResources);
953
        result.permissionList.addAll(this.permissionList);
954
        result.loaderPC.putAll(this.loaderPC);
955
        result.contextName = this.contextName;
956
        result.hasExternalRepositories = this.hasExternalRepositories;
957
        result.searchExternalFirst = this.searchExternalFirst;
958
959
        try {
960
            result.start();
961
        } catch (LifecycleException e) {
962
            throw new IllegalStateException(e);
963
        }
964
965
        return result;
966
    }
967
968
   /**
969
     * Add a new repository to the set of places this ClassLoader can look for
970
     * classes to be loaded.
971
     *
972
     * @param repository Name of a source of classes to be loaded, such as a
973
     *  directory pathname, a JAR file pathname, or a ZIP file pathname
974
     *
975
     * @exception IllegalArgumentException if the specified repository is
976
     *  invalid or does not exist
977
     */
978
    public void addRepository(String repository) {
979
980
        // Ignore any of the standard repositories, as they are set up using
981
        // either addJar or addRepository
982
        if (repository.startsWith("/WEB-INF/lib")
983
            || repository.startsWith("/WEB-INF/classes"))
984
            return;
985
986
        // Add this repository to our underlying class loader
987
        try {
988
            URL url = new URL(repository);
989
            super.addURL(url);
990
            hasExternalRepositories = true;
991
            repositoryURLs = null;
992
        } catch (MalformedURLException e) {
993
            IllegalArgumentException iae = new IllegalArgumentException
994
                ("Invalid repository: " + repository);
995
            iae.initCause(e);
996
            throw iae;
997
        }
998
999
    }
1000
1001
1002
    /**
1003
     * Add a new repository to the set of places this ClassLoader can look for
1004
     * classes to be loaded.
1005
     *
1006
     * @param repository Name of a source of classes to be loaded, such as a
1007
     *  directory pathname, a JAR file pathname, or a ZIP file pathname
1008
     *
1009
     * @exception IllegalArgumentException if the specified repository is
1010
     *  invalid or does not exist
1011
     */
1012
    synchronized void addRepository(String repository, File file) {
1013
1014
        // Note : There should be only one (of course), but I think we should
1015
        // keep this a bit generic
1016
1017
        if (repository == null)
1018
            return;
1019
1020
        if (log.isDebugEnabled())
1021
            log.debug("addRepository(" + repository + ")");
1022
1023
        int i;
1024
1025
        // Add this repository to our internal list
1026
        String[] result = new String[repositories.length + 1];
1027
        for (i = 0; i < repositories.length; i++) {
1028
            result[i] = repositories[i];
1029
        }
1030
        result[repositories.length] = repository;
1031
        repositories = result;
1032
1033
        // Add the file to the list
1034
        File[] result2 = new File[files.length + 1];
1035
        for (i = 0; i < files.length; i++) {
1036
            result2[i] = files[i];
1037
        }
1038
        result2[files.length] = file;
1039
        files = result2;
1040
1041
    }
1042
1043
1044
    synchronized void addJar(String jar, JarFile jarFile, File file)
1045
        throws IOException {
1046
1047
        if (jar == null)
1048
            return;
1049
        if (jarFile == null)
1050
            return;
1051
        if (file == null)
1052
            return;
1053
1054
        if (log.isDebugEnabled())
1055
            log.debug("addJar(" + jar + ")");
1056
1057
        int i;
1058
1059
        if ((jarPath != null) && (jar.startsWith(jarPath))) {
1060
1061
            String jarName = jar.substring(jarPath.length());
1062
            while (jarName.startsWith("/"))
1063
                jarName = jarName.substring(1);
1064
1065
            String[] result = new String[jarNames.length + 1];
1066
            for (i = 0; i < jarNames.length; i++) {
1067
                result[i] = jarNames[i];
1068
            }
1069
            result[jarNames.length] = jarName;
1070
            jarNames = result;
1071
1072
        }
1073
1074
        try {
1075
1076
            // Register the JAR for tracking
1077
1078
            long lastModified =
1079
                ((ResourceAttributes) resources.getAttributes(jar))
1080
                .getLastModified();
1081
1082
            String[] result = new String[paths.length + 1];
1083
            for (i = 0; i < paths.length; i++) {
1084
                result[i] = paths[i];
1085
            }
1086
            result[paths.length] = jar;
1087
            paths = result;
1088
1089
            long[] result3 = new long[lastModifiedDates.length + 1];
1090
            for (i = 0; i < lastModifiedDates.length; i++) {
1091
                result3[i] = lastModifiedDates[i];
1092
            }
1093
            result3[lastModifiedDates.length] = lastModified;
1094
            lastModifiedDates = result3;
1095
1096
        } catch (NamingException e) {
1097
            // Ignore
1098
        }
1099
1100
        // If the JAR currently contains invalid classes, don't actually use it
1101
        // for classloading
1102
        if (!validateJarFile(file))
1103
            return;
1104
1105
        JarFile[] result2 = new JarFile[jarFiles.length + 1];
1106
        for (i = 0; i < jarFiles.length; i++) {
1107
            result2[i] = jarFiles[i];
1108
        }
1109
        result2[jarFiles.length] = jarFile;
1110
        jarFiles = result2;
1111
1112
        // Add the file to the list
1113
        File[] result4 = new File[jarRealFiles.length + 1];
1114
        for (i = 0; i < jarRealFiles.length; i++) {
1115
            result4[i] = jarRealFiles[i];
1116
        }
1117
        result4[jarRealFiles.length] = file;
1118
        jarRealFiles = result4;
1119
    }
1120
1121
1122
    /**
1123
     * Return a String array of the current repositories for this class
1124
     * loader.  If there are no repositories, a zero-length array is
1125
     * returned.For security reason, returns a clone of the Array (since
1126
     * String are immutable).
1127
     */
1128
    public String[] findRepositories() {
1129
1130
        return (repositories.clone());
1131
1132
    }
1133
1134
1135
    /**
1136
     * Have one or more classes or resources been modified so that a reload
1137
     * is appropriate?
1138
     */
1139
    public boolean modified() {
1140
1141
        if (log.isDebugEnabled())
1142
            log.debug("modified()");
1143
1144
        // Checking for modified loaded resources
1145
        int length = paths.length;
1146
1147
        // A rare race condition can occur in the updates of the two arrays
1148
        // It's totally ok if the latest class added is not checked (it will
1149
        // be checked the next time
1150
        int length2 = lastModifiedDates.length;
1151
        if (length > length2)
1152
            length = length2;
1153
1154
        for (int i = 0; i < length; i++) {
1155
            try {
1156
                long lastModified =
1157
                    ((ResourceAttributes) resources.getAttributes(paths[i]))
1158
                    .getLastModified();
1159
                if (lastModified != lastModifiedDates[i]) {
1160
                    if( log.isDebugEnabled() )
1161
                        log.debug("  Resource '" + paths[i]
1162
                                  + "' was modified; Date is now: "
1163
                                  + new java.util.Date(lastModified) + " Was: "
1164
                                  + new java.util.Date(lastModifiedDates[i]));
1165
                    return (true);
1166
                }
1167
            } catch (NamingException e) {
1168
                log.error("    Resource '" + paths[i] + "' is missing");
1169
                return (true);
1170
            }
1171
        }
1172
1173
        length = jarNames.length;
1174
1175
        // Check if JARs have been added or removed
1176
        if (getJarPath() != null) {
1177
1178
            try {
1179
                NamingEnumeration<Binding> enumeration =
1180
                    resources.listBindings(getJarPath());
1181
                int i = 0;
1182
                while (enumeration.hasMoreElements() && (i < length)) {
1183
                    NameClassPair ncPair = enumeration.nextElement();
1184
                    String name = ncPair.getName();
1185
                    // Ignore non JARs present in the lib folder
1186
                    if (!name.endsWith(".jar"))
1187
                        continue;
1188
                    if (!name.equals(jarNames[i])) {
1189
                        // Missing JAR
1190
                        log.info("    Additional JARs have been added : '"
1191
                                 + name + "'");
1192
                        return (true);
1193
                    }
1194
                    i++;
1195
                }
1196
                if (enumeration.hasMoreElements()) {
1197
                    while (enumeration.hasMoreElements()) {
1198
                        NameClassPair ncPair = enumeration.nextElement();
1199
                        String name = ncPair.getName();
1200
                        // Additional non-JAR files are allowed
1201
                        if (name.endsWith(".jar")) {
1202
                            // There was more JARs
1203
                            log.info("    Additional JARs have been added");
1204
                            return (true);
1205
                        }
1206
                    }
1207
                } else if (i < jarNames.length) {
1208
                    // There was less JARs
1209
                    log.info("    Additional JARs have been added");
1210
                    return (true);
1211
                }
1212
            } catch (NamingException e) {
1213
                if (log.isDebugEnabled())
1214
                    log.debug("    Failed tracking modifications of '"
1215
                        + getJarPath() + "'");
1216
            } catch (ClassCastException e) {
1217
                log.error("    Failed tracking modifications of '"
1218
                          + getJarPath() + "' : " + e.getMessage());
1219
            }
1220
1221
        }
1222
1223
        // No classes have been modified
1224
        return (false);
1225
1226
    }
1227
1228
1229
    /**
1230
     * Render a String representation of this object.
1231
     */
1232
    @Override
1233
    public String toString() {
1234
1235
        StringBuilder sb = new StringBuilder("WebappClassLoader\r\n");
1236
        sb.append("  context: ");
1237
        sb.append(contextName);
1238
        sb.append("\r\n");
1239
        sb.append("  delegate: ");
1240
        sb.append(delegate);
1241
        sb.append("\r\n");
1242
        sb.append("  repositories:\r\n");
1243
        if (repositories != null) {
1244
            for (int i = 0; i < repositories.length; i++) {
1245
                sb.append("    ");
1246
                sb.append(repositories[i]);
1247
                sb.append("\r\n");
1248
            }
1249
        }
1250
        if (this.parent != null) {
1251
            sb.append("----------> Parent Classloader:\r\n");
1252
            sb.append(this.parent.toString());
1253
            sb.append("\r\n");
1254
        }
1255
        if (this.transformers.size() > 0) {
1256
            sb.append("----------> Class file transformers:\r\n");
1257
            for (ClassFileTransformer transformer : this.transformers) {
1258
                sb.append(transformer).append("\r\n");
1259
            }
1260
        }
1261
        return (sb.toString());
1262
1263
    }
1264
1265
1266
    // ---------------------------------------------------- ClassLoader Methods
1267
1268
1269
    /**
1270
     * Add the specified URL to the classloader.
1271
     */
1272
    @Override
1273
    protected void addURL(URL url) {
1274
        super.addURL(url);
1275
        hasExternalRepositories = true;
1276
        repositoryURLs = null;
1277
    }
1278
1279
1280
    /**
1281
     * Expose this method for use by the unit tests.
1282
     */
1283
    protected final Class<?> doDefineClass(String name, byte[] b, int off, int len,
1284
            ProtectionDomain protectionDomain) {
1285
        return super.defineClass(name, b, off, len, protectionDomain);
1286
    }
1287
1288
    /**
1289
     * Find the specified class in our local repositories, if possible.  If
1290
     * not found, throw <code>ClassNotFoundException</code>.
1291
     *
1292
     * @param name Name of the class to be loaded
1293
     *
1294
     * @exception ClassNotFoundException if the class was not found
1295
     */
1296
    @Override
1297
    public Class<?> findClass(String name) throws ClassNotFoundException {
1298
1299
        if (log.isDebugEnabled())
1300
            log.debug("    findClass(" + name + ")");
1301
1302
        // Cannot load anything from local repositories if class loader is stopped
1303
        if (!started) {
1304
            throw new ClassNotFoundException(name);
1305
        }
1306
1307
        // (1) Permission to define this class when using a SecurityManager
1308
        if (securityManager != null) {
1309
            int i = name.lastIndexOf('.');
1310
            if (i >= 0) {
1311
                try {
1312
                    if (log.isTraceEnabled())
1313
                        log.trace("      securityManager.checkPackageDefinition");
1314
                    securityManager.checkPackageDefinition(name.substring(0,i));
1315
                } catch (Exception se) {
1316
                    if (log.isTraceEnabled())
1317
                        log.trace("      -->Exception-->ClassNotFoundException", se);
1318
                    throw new ClassNotFoundException(name, se);
1319
                }
1320
            }
1321
        }
1322
1323
        // Ask our superclass to locate this class, if possible
1324
        // (throws ClassNotFoundException if it is not found)
1325
        Class<?> clazz = null;
1326
        try {
1327
            if (log.isTraceEnabled())
1328
                log.trace("      findClassInternal(" + name + ")");
1329
            if (hasExternalRepositories && searchExternalFirst) {
1330
                try {
1331
                    clazz = super.findClass(name);
1332
                } catch(ClassNotFoundException cnfe) {
1333
                    // Ignore - will search internal repositories next
1334
                } catch(AccessControlException ace) {
1335
                    log.warn("WebappClassLoader.findClassInternal(" + name
1336
                            + ") security exception: " + ace.getMessage(), ace);
1337
                    throw new ClassNotFoundException(name, ace);
1338
                } catch (RuntimeException e) {
1339
                    if (log.isTraceEnabled())
1340
                        log.trace("      -->RuntimeException Rethrown", e);
1341
                    throw e;
1342
                }
1343
            }
1344
            if ((clazz == null)) {
1345
                try {
1346
                    clazz = findClassInternal(name);
1347
                } catch(ClassNotFoundException cnfe) {
1348
                    if (!hasExternalRepositories || searchExternalFirst) {
1349
                        throw cnfe;
1350
                    }
1351
                } catch(AccessControlException ace) {
1352
                    log.warn("WebappClassLoader.findClassInternal(" + name
1353
                            + ") security exception: " + ace.getMessage(), ace);
1354
                    throw new ClassNotFoundException(name, ace);
1355
                } catch (RuntimeException e) {
1356
                    if (log.isTraceEnabled())
1357
                        log.trace("      -->RuntimeException Rethrown", e);
1358
                    throw e;
1359
                }
1360
            }
1361
            if ((clazz == null) && hasExternalRepositories && !searchExternalFirst) {
1362
                try {
1363
                    clazz = super.findClass(name);
1364
                } catch(AccessControlException ace) {
1365
                    log.warn("WebappClassLoader.findClassInternal(" + name
1366
                            + ") security exception: " + ace.getMessage(), ace);
1367
                    throw new ClassNotFoundException(name, ace);
1368
                } catch (RuntimeException e) {
1369
                    if (log.isTraceEnabled())
1370
                        log.trace("      -->RuntimeException Rethrown", e);
1371
                    throw e;
1372
                }
1373
            }
1374
            if (clazz == null) {
1375
                if (log.isDebugEnabled())
1376
                    log.debug("    --> Returning ClassNotFoundException");
1377
                throw new ClassNotFoundException(name);
1378
            }
1379
        } catch (ClassNotFoundException e) {
1380
            if (log.isTraceEnabled())
1381
                log.trace("    --> Passing on ClassNotFoundException");
1382
            throw e;
1383
        }
1384
1385
        // Return the class we have located
1386
        if (log.isTraceEnabled())
1387
            log.debug("      Returning class " + clazz);
1388
1389
        if (log.isTraceEnabled()) {
1390
            ClassLoader cl;
1391
            if (Globals.IS_SECURITY_ENABLED){
1392
                cl = AccessController.doPrivileged(
1393
                    new PrivilegedGetClassLoader(clazz));
1394
            } else {
1395
                cl = clazz.getClassLoader();
1396
            }
1397
            log.debug("      Loaded by " + cl.toString());
1398
        }
1399
        return (clazz);
1400
1401
    }
1402
1403
1404
    /**
1405
     * Find the specified resource in our local repository, and return a
1406
     * <code>URL</code> referring to it, or <code>null</code> if this resource
1407
     * cannot be found.
1408
     *
1409
     * @param name Name of the resource to be found
1410
     */
1411
    @Override
1412
    public URL findResource(final String name) {
1413
1414
        if (log.isDebugEnabled())
1415
            log.debug("    findResource(" + name + ")");
1416
1417
        URL url = null;
1418
1419
        if (hasExternalRepositories && searchExternalFirst)
1420
            url = super.findResource(name);
1421
1422
        if (url == null) {
1423
            ResourceEntry entry = resourceEntries.get(name);
1424
            if (entry == null) {
1425
                if (securityManager != null) {
1426
                    PrivilegedAction<ResourceEntry> dp =
1427
                        new PrivilegedFindResourceByName(name, name, false);
1428
                    entry = AccessController.doPrivileged(dp);
1429
                } else {
1430
                    entry = findResourceInternal(name, name, false);
1431
                }
1432
            }
1433
            if (entry != null) {
1434
                url = entry.source;
1435
            }
1436
        }
1437
1438
        if ((url == null) && hasExternalRepositories && !searchExternalFirst)
1439
            url = super.findResource(name);
1440
1441
        if (log.isDebugEnabled()) {
1442
            if (url != null)
1443
                log.debug("    --> Returning '" + url.toString() + "'");
1444
            else
1445
                log.debug("    --> Resource not found, returning null");
1446
        }
1447
        return (url);
1448
1449
    }
1450
1451
1452
    /**
1453
     * Return an enumeration of <code>URLs</code> representing all of the
1454
     * resources with the given name.  If no resources with this name are
1455
     * found, return an empty enumeration.
1456
     *
1457
     * @param name Name of the resources to be found
1458
     *
1459
     * @exception IOException if an input/output error occurs
1460
     */
1461
    @Override
1462
    public Enumeration<URL> findResources(String name) throws IOException {
1463
1464
        if (log.isDebugEnabled())
1465
            log.debug("    findResources(" + name + ")");
1466
1467
        //we use a LinkedHashSet instead of a Vector to avoid duplicates with virtualmappings
1468
        LinkedHashSet<URL> result = new LinkedHashSet<URL>();
1469
1470
        int jarFilesLength = jarFiles.length;
1471
        int repositoriesLength = repositories.length;
1472
1473
        int i;
1474
1475
        // Adding the results of a call to the superclass
1476
        if (hasExternalRepositories && searchExternalFirst) {
1477
1478
            Enumeration<URL> otherResourcePaths = super.findResources(name);
1479
1480
            while (otherResourcePaths.hasMoreElements()) {
1481
                result.add(otherResourcePaths.nextElement());
1482
            }
1483
1484
        }
1485
        // Looking at the repositories
1486
        for (i = 0; i < repositoriesLength; i++) {
1487
            try {
1488
                String fullPath = repositories[i] + name;
1489
                resources.lookup(fullPath);
1490
                // Note : Not getting an exception here means the resource was
1491
                // found
1492
                try {
1493
                    result.add(getURI(new File(files[i], name)));
1494
                } catch (MalformedURLException e) {
1495
                    // Ignore
1496
                }
1497
            } catch (NamingException e) {
1498
                // Ignore
1499
            }
1500
        }
1501
1502
        // Looking at the JAR files
1503
        synchronized (jarFiles) {
1504
            if (openJARs()) {
1505
                for (i = 0; i < jarFilesLength; i++) {
1506
                    JarEntry jarEntry = jarFiles[i].getJarEntry(name);
1507
                    if (jarEntry != null) {
1508
                        try {
1509
                            String jarFakeUrl = getURI(jarRealFiles[i]).toString();
1510
                            jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name;
1511
                            result.add(new URL(jarFakeUrl));
1512
                        } catch (MalformedURLException e) {
1513
                            // Ignore
1514
                        }
1515
                    }
1516
                }
1517
            }
1518
        }
1519
1520
        // Adding the results of a call to the superclass
1521
        if (hasExternalRepositories && !searchExternalFirst) {
1522
1523
            Enumeration<URL> otherResourcePaths = super.findResources(name);
1524
1525
            while (otherResourcePaths.hasMoreElements()) {
1526
                result.add(otherResourcePaths.nextElement());
1527
            }
1528
1529
        }
1530
1531
        return Collections.enumeration(result);
1532
    }
1533
1534
1535
    /**
1536
     * Find the resource with the given name.  A resource is some data
1537
     * (images, audio, text, etc.) that can be accessed by class code in a
1538
     * way that is independent of the location of the code.  The name of a
1539
     * resource is a "/"-separated path name that identifies the resource.
1540
     * If the resource cannot be found, return <code>null</code>.
1541
     * <p>
1542
     * This method searches according to the following algorithm, returning
1543
     * as soon as it finds the appropriate URL.  If the resource cannot be
1544
     * found, returns <code>null</code>.
1545
     * <ul>
1546
     * <li>If the <code>delegate</code> property is set to <code>true</code>,
1547
     *     call the <code>getResource()</code> method of the parent class
1548
     *     loader, if any.</li>
1549
     * <li>Call <code>findResource()</code> to find this resource in our
1550
     *     locally defined repositories.</li>
1551
     * <li>Call the <code>getResource()</code> method of the parent class
1552
     *     loader, if any.</li>
1553
     * </ul>
1554
     *
1555
     * @param name Name of the resource to return a URL for
1556
     */
1557
    @Override
1558
    public URL getResource(String name) {
1559
1560
        if (log.isDebugEnabled())
1561
            log.debug("getResource(" + name + ")");
1562
        URL url = null;
1563
1564
        // (1) Delegate to parent if requested
1565
        if (delegate) {
1566
            if (log.isDebugEnabled())
1567
                log.debug("  Delegating to parent classloader " + parent);
1568
            url = parent.getResource(name);
1569
            if (url != null) {
1570
                if (log.isDebugEnabled())
1571
                    log.debug("  --> Returning '" + url.toString() + "'");
1572
                return (url);
1573
            }
1574
        }
1575
1576
        // (2) Search local repositories
1577
        url = findResource(name);
1578
        if (url != null) {
1579
            // Locating the repository for special handling in the case
1580
            // of a JAR
1581
            if (antiJARLocking) {
1582
                ResourceEntry entry = resourceEntries.get(name);
1583
                try {
1584
                    String repository = entry.codeBase.toString();
1585
                    if ((repository.endsWith(".jar"))
1586
                            && (!(name.endsWith(CLASS_FILE_SUFFIX)))) {
1587
                        // Copy binary content to the work directory if not present
1588
                        File resourceFile = new File(loaderDir, name);
1589
                        url = getURI(resourceFile);
1590
                    }
1591
                } catch (Exception e) {
1592
                    // Ignore
1593
                }
1594
            }
1595
            if (log.isDebugEnabled())
1596
                log.debug("  --> Returning '" + url.toString() + "'");
1597
            return (url);
1598
        }
1599
1600
        // (3) Delegate to parent unconditionally if not already attempted
1601
        if( !delegate ) {
1602
            url = parent.getResource(name);
1603
            if (url != null) {
1604
                if (log.isDebugEnabled())
1605
                    log.debug("  --> Returning '" + url.toString() + "'");
1606
                return (url);
1607
            }
1608
        }
1609
1610
        // (4) Resource was not found
1611
        if (log.isDebugEnabled())
1612
            log.debug("  --> Resource not found, returning null");
1613
        return (null);
1614
1615
    }
1616
1617
1618
    /**
1619
     * Find the resource with the given name, and return an input stream
1620
     * that can be used for reading it.  The search order is as described
1621
     * for <code>getResource()</code>, after checking to see if the resource
1622
     * data has been previously cached.  If the resource cannot be found,
1623
     * return <code>null</code>.
1624
     *
1625
     * @param name Name of the resource to return an input stream for
1626
     */
1627
    @Override
1628
    public InputStream getResourceAsStream(String name) {
1629
1630
        if (log.isDebugEnabled())
1631
            log.debug("getResourceAsStream(" + name + ")");
1632
        InputStream stream = null;
1633
1634
        // (0) Check for a cached copy of this resource
1635
        stream = findLoadedResource(name);
1636
        if (stream != null) {
1637
            if (log.isDebugEnabled())
1638
                log.debug("  --> Returning stream from cache");
1639
            return (stream);
1640
        }
1641
1642
        // (1) Delegate to parent if requested
1643
        if (delegate) {
1644
            if (log.isDebugEnabled())
1645
                log.debug("  Delegating to parent classloader " + parent);
1646
            stream = parent.getResourceAsStream(name);
1647
            if (stream != null) {
1648
                // FIXME - cache???
1649
                if (log.isDebugEnabled())
1650
                    log.debug("  --> Returning stream from parent");
1651
                return (stream);
1652
            }
1653
        }
1654
1655
        // (2) Search local repositories
1656
        if (log.isDebugEnabled())
1657
            log.debug("  Searching local repositories");
1658
        URL url = findResource(name);
1659
        if (url != null) {
1660
            // FIXME - cache???
1661
            if (log.isDebugEnabled())
1662
                log.debug("  --> Returning stream from local");
1663
            stream = findLoadedResource(name);
1664
            try {
1665
                if (hasExternalRepositories && (stream == null))
1666
                    stream = url.openStream();
1667
            } catch (IOException e) {
1668
                // Ignore
1669
            }
1670
            if (stream != null)
1671
                return (stream);
1672
        }
1673
1674
        // (3) Delegate to parent unconditionally
1675
        if (!delegate) {
1676
            if (log.isDebugEnabled())
1677
                log.debug("  Delegating to parent classloader unconditionally " + parent);
1678
            stream = parent.getResourceAsStream(name);
1679
            if (stream != null) {
1680
                // FIXME - cache???
1681
                if (log.isDebugEnabled())
1682
                    log.debug("  --> Returning stream from parent");
1683
                return (stream);
1684
            }
1685
        }
1686
1687
        // (4) Resource was not found
1688
        if (log.isDebugEnabled())
1689
            log.debug("  --> Resource not found, returning null");
1690
        return (null);
1691
1692
    }
1693
1694
1695
    /**
1696
     * Load the class with the specified name.  This method searches for
1697
     * classes in the same manner as <code>loadClass(String, boolean)</code>
1698
     * with <code>false</code> as the second argument.
1699
     *
1700
     * @param name Name of the class to be loaded
1701
     *
1702
     * @exception ClassNotFoundException if the class was not found
1703
     */
1704
    @Override
1705
    public Class<?> loadClass(String name) throws ClassNotFoundException {
1706
1707
        return (loadClass(name, false));
1708
1709
    }
1710
1711
1712
    /**
1713
     * Load the class with the specified name, searching using the following
1714
     * algorithm until it finds and returns the class.  If the class cannot
1715
     * be found, returns <code>ClassNotFoundException</code>.
1716
     * <ul>
1717
     * <li>Call <code>findLoadedClass(String)</code> to check if the
1718
     *     class has already been loaded.  If it has, the same
1719
     *     <code>Class</code> object is returned.</li>
1720
     * <li>If the <code>delegate</code> property is set to <code>true</code>,
1721
     *     call the <code>loadClass()</code> method of the parent class
1722
     *     loader, if any.</li>
1723
     * <li>Call <code>findClass()</code> to find this class in our locally
1724
     *     defined repositories.</li>
1725
     * <li>Call the <code>loadClass()</code> method of our parent
1726
     *     class loader, if any.</li>
1727
     * </ul>
1728
     * If the class was found using the above steps, and the
1729
     * <code>resolve</code> flag is <code>true</code>, this method will then
1730
     * call <code>resolveClass(Class)</code> on the resulting Class object.
1731
     *
1732
     * @param name Name of the class to be loaded
1733
     * @param resolve If <code>true</code> then resolve the class
1734
     *
1735
     * @exception ClassNotFoundException if the class was not found
1736
     */
1737
    @Override
1738
    public synchronized Class<?> loadClass(String name, boolean resolve)
1739
        throws ClassNotFoundException {
1740
1741
        if (log.isDebugEnabled())
1742
            log.debug("loadClass(" + name + ", " + resolve + ")");
1743
        Class<?> clazz = null;
1744
1745
        // Log access to stopped classloader
1746
        if (!started) {
1747
            try {
1748
                throw new IllegalStateException();
1749
            } catch (IllegalStateException e) {
1750
                log.info(sm.getString("webappClassLoader.stopped", name), e);
1751
            }
1752
        }
1753
1754
        // (0) Check our previously loaded local class cache
1755
        clazz = findLoadedClass0(name);
1756
        if (clazz != null) {
1757
            if (log.isDebugEnabled())
1758
                log.debug("  Returning class from cache");
1759
            if (resolve)
1760
                resolveClass(clazz);
1761
            return (clazz);
1762
        }
1763
1764
        // (0.1) Check our previously loaded class cache
1765
        clazz = findLoadedClass(name);
1766
        if (clazz != null) {
1767
            if (log.isDebugEnabled())
1768
                log.debug("  Returning class from cache");
1769
            if (resolve)
1770
                resolveClass(clazz);
1771
            return (clazz);
1772
        }
1773
1774
        // (0.2) Try loading the class with the system class loader, to prevent
1775
        //       the webapp from overriding J2SE classes
1776
        try {
1777
            clazz = j2seClassLoader.loadClass(name);
1778
            if (clazz != null) {
1779
                if (resolve)
1780
                    resolveClass(clazz);
1781
                return (clazz);
1782
            }
1783
        } catch (ClassNotFoundException e) {
1784
            // Ignore
1785
        }
1786
1787
        // (0.5) Permission to access this class when using a SecurityManager
1788
        if (securityManager != null) {
1789
            int i = name.lastIndexOf('.');
1790
            if (i >= 0) {
1791
                try {
1792
                    securityManager.checkPackageAccess(name.substring(0,i));
1793
                } catch (SecurityException se) {
1794
                    String error = "Security Violation, attempt to use " +
1795
                        "Restricted Class: " + name;
1796
                    log.info(error, se);
1797
                    throw new ClassNotFoundException(error, se);
1798
                }
1799
            }
1800
        }
1801
1802
        boolean delegateLoad = delegate || filter(name);
1803
1804
        // (1) Delegate to our parent if requested
1805
        if (delegateLoad) {
1806
            if (log.isDebugEnabled())
1807
                log.debug("  Delegating to parent classloader1 " + parent);
1808
            try {
1809
                clazz = Class.forName(name, false, parent);
1810
                if (clazz != null) {
1811
                    if (log.isDebugEnabled())
1812
                        log.debug("  Loading class from parent");
1813
                    if (resolve)
1814
                        resolveClass(clazz);
1815
                    return (clazz);
1816
                }
1817
            } catch (ClassNotFoundException e) {
1818
                // Ignore
1819
            }
1820
        }
1821
1822
        // (2) Search local repositories
1823
        if (log.isDebugEnabled())
1824
            log.debug("  Searching local repositories");
1825
        try {
1826
            clazz = findClass(name);
1827
            if (clazz != null) {
1828
                if (log.isDebugEnabled())
1829
                    log.debug("  Loading class from local repository");
1830
                if (resolve)
1831
                    resolveClass(clazz);
1832
                return (clazz);
1833
            }
1834
        } catch (ClassNotFoundException e) {
1835
            // Ignore
1836
        }
1837
1838
        // (3) Delegate to parent unconditionally
1839
        if (!delegateLoad) {
1840
            if (log.isDebugEnabled())
1841
                log.debug("  Delegating to parent classloader at end: " + parent);
1842
            try {
1843
                clazz = Class.forName(name, false, parent);
1844
                if (clazz != null) {
1845
                    if (log.isDebugEnabled())
1846
                        log.debug("  Loading class from parent");
1847
                    if (resolve)
1848
                        resolveClass(clazz);
1849
                    return (clazz);
1850
                }
1851
            } catch (ClassNotFoundException e) {
1852
                // Ignore
1853
            }
1854
        }
1855
1856
        throw new ClassNotFoundException(name);
1857
1858
    }
1859
1860
1861
    /**
1862
     * Get the Permissions for a CodeSource.  If this instance
1863
     * of WebappClassLoader is for a web application context,
1864
     * add read FilePermission or JndiPermissions for the base
1865
     * directory (if unpacked),
1866
     * the context URL, and jar file resources.
1867
     *
1868
     * @param codeSource where the code was loaded from
1869
     * @return PermissionCollection for CodeSource
1870
     */
1871
    @Override
1872
    protected PermissionCollection getPermissions(CodeSource codeSource) {
1873
1874
        String codeUrl = codeSource.getLocation().toString();
1875
        PermissionCollection pc;
1876
        if ((pc = loaderPC.get(codeUrl)) == null) {
1877
            pc = super.getPermissions(codeSource);
1878
            if (pc != null) {
1879
                Iterator<Permission> perms = permissionList.iterator();
1880
                while (perms.hasNext()) {
1881
                    Permission p = perms.next();
1882
                    pc.add(p);
1883
                }
1884
                loaderPC.put(codeUrl,pc);
1885
            }
1886
        }
1887
        return (pc);
1888
1889
    }
1890
1891
1892
    /**
1893
     * Returns the search path of URLs for loading classes and resources.
1894
     * This includes the original list of URLs specified to the constructor,
1895
     * along with any URLs subsequently appended by the addURL() method.
1896
     * @return the search path of URLs for loading classes and resources.
1897
     */
1898
    @Override
1899
    public URL[] getURLs() {
1900
1901
        if (repositoryURLs != null) {
1902
            return repositoryURLs.clone();
1903
        }
1904
1905
        URL[] external = super.getURLs();
1906
1907
        int filesLength = files.length;
1908
        int jarFilesLength = jarRealFiles.length;
1909
        int externalsLength = external.length;
1910
        int off = 0;
1911
        int i;
1912
1913
        try {
1914
1915
            URL[] urls = new URL[filesLength + jarFilesLength + externalsLength];
1916
            if (searchExternalFirst) {
1917
                for (i = 0; i < externalsLength; i++) {
1918
                    urls[i] = external[i];
1919
                }
1920
                off = externalsLength;
1921
            }
1922
            for (i = 0; i < filesLength; i++) {
1923
                urls[off + i] = getURI(files[i]);
1924
            }
1925
            off += filesLength;
1926
            for (i = 0; i < jarFilesLength; i++) {
1927
                urls[off + i] = getURI(jarRealFiles[i]);
1928
            }
1929
            off += jarFilesLength;
1930
            if (!searchExternalFirst) {
1931
                for (i = 0; i < externalsLength; i++) {
1932
                    urls[off + i] = external[i];
1933
                }
1934
            }
1935
1936
            repositoryURLs = urls;
1937
1938
        } catch (MalformedURLException e) {
1939
            repositoryURLs = new URL[0];
1940
        }
1941
1942
        return repositoryURLs.clone();
1943
1944
    }
1945
1946
1947
    // ------------------------------------------------------ Lifecycle Methods
1948
1949
1950
    /**
1951
     * Add a lifecycle event listener to this component.
1952
     *
1953
     * @param listener The listener to add
1954
     */
1955
    @Override
1956
    public void addLifecycleListener(LifecycleListener listener) {
1957
        // NOOP
1958
    }
1959
1960
1961
    /**
1962
     * Get the lifecycle listeners associated with this lifecycle. If this
1963
     * Lifecycle has no listeners registered, a zero-length array is returned.
1964
     */
1965
    @Override
1966
    public LifecycleListener[] findLifecycleListeners() {
1967
        return new LifecycleListener[0];
1968
    }
1969
1970
1971
    /**
1972
     * Remove a lifecycle event listener from this component.
1973
     *
1974
     * @param listener The listener to remove
1975
     */
1976
    @Override
1977
    public void removeLifecycleListener(LifecycleListener listener) {
1978
        // NOOP
1979
    }
1980
1981
1982
    /**
1983
     * Obtain the current state of the source component.
1984
     *
1985
     * @return The current state of the source component.
1986
     */
1987
    @Override
1988
    public LifecycleState getState() {
1989
        return LifecycleState.NEW;
1990
    }
1991
1992
1993
    /**
1994
     * {@inheritDoc}
1995
     */
1996
    @Override
1997
    public String getStateName() {
1998
        return getState().toString();
1999
    }
2000
2001
2002
    @Override
2003
    public void init() {
2004
        // NOOP
2005
    }
2006
2007
2008
    /**
2009
     * Start the class loader.
2010
     *
2011
     * @exception LifecycleException if a lifecycle error occurs
2012
     */
2013
    @Override
2014
    public void start() throws LifecycleException {
2015
2016
        started = true;
2017
        String encoding = null;
2018
        try {
2019
            encoding = System.getProperty("file.encoding");
2020
        } catch (SecurityException e) {
2021
            return;
2022
        }
2023
        if (encoding.indexOf("EBCDIC")!=-1) {
2024
            needConvert = true;
2025
        }
2026
2027
        for (int i = 0; i < repositories.length; i++) {
2028
            if (repositories[i].equals("/WEB-INF/classes/")) {
2029
                try {
2030
                    webInfClassesCodeBase = files[i].toURI().toURL();
2031
                } catch (MalformedURLException e) {
2032
                    // Ignore - leave it as null
2033
                }
2034
                break;
2035
            }
2036
        }
2037
        
2038
    }
2039
2040
2041
    public boolean isStarted() {
2042
        return started;
2043
    }
2044
2045
    /**
2046
     * Stop the class loader.
2047
     *
2048
     * @exception LifecycleException if a lifecycle error occurs
2049
     */
2050
    @Override
2051
    public void stop() throws LifecycleException {
2052
2053
        // Clearing references should be done before setting started to
2054
        // false, due to possible side effects
2055
        clearReferences();
2056
2057
        started = false;
2058
2059
        int length = files.length;
2060
        for (int i = 0; i < length; i++) {
2061
            files[i] = null;
2062
        }
2063
2064
        length = jarFiles.length;
2065
        for (int i = 0; i < length; i++) {
2066
            try {
2067
                if (jarFiles[i] != null) {
2068
                    jarFiles[i].close();
2069
                }
2070
            } catch (IOException e) {
2071
                // Ignore
2072
            }
2073
            jarFiles[i] = null;
2074
        }
2075
2076
        notFoundResources.clear();
2077
        resourceEntries.clear();
2078
        resources = null;
2079
        repositories = null;
2080
        repositoryURLs = null;
2081
        files = null;
2082
        jarFiles = null;
2083
        jarRealFiles = null;
2084
        jarPath = null;
2085
        jarNames = null;
2086
        lastModifiedDates = null;
2087
        paths = null;
2088
        hasExternalRepositories = false;
2089
        parent = null;
2090
        webInfClassesCodeBase = null;
2091
2092
        permissionList.clear();
2093
        loaderPC.clear();
2094
2095
        if (loaderDir != null) {
2096
            deleteDir(loaderDir);
2097
        }
2098
2099
    }
2100
2101
2102
    @Override
2103
    public void destroy() {
2104
        // NOOP
2105
    }
2106
2107
2108
    /**
2109
     * Used to periodically signal to the classloader to release
2110
     * JAR resources.
2111
     */
2112
    public void closeJARs(boolean force) {
2113
        if (jarFiles.length > 0) {
2114
                synchronized (jarFiles) {
2115
                    if (force || (System.currentTimeMillis()
2116
                                  > (lastJarAccessed + 90000))) {
2117
                        for (int i = 0; i < jarFiles.length; i++) {
2118
                            try {
2119
                                if (jarFiles[i] != null) {
2120
                                    jarFiles[i].close();
2121
                                    jarFiles[i] = null;
2122
                                }
2123
                            } catch (IOException e) {
2124
                                if (log.isDebugEnabled()) {
2125
                                    log.debug("Failed to close JAR", e);
2126
                                }
2127
                            }
2128
                        }
2129
                    }
2130
                }
2131
        }
2132
    }
2133
2134
2135
    // ------------------------------------------------------ Protected Methods
2136
2137
    protected ClassLoader getJavaseClassLoader() {
2138
        return j2seClassLoader;
2139
    }
2140
2141
    protected void setJavaseClassLoader(ClassLoader classLoader) {
2142
        if (classLoader == null) {
2143
            throw new IllegalArgumentException(
2144
                    sm.getString("webappClassLoader.javaseClassLoaderNull"));
2145
        }
2146
        j2seClassLoader = classLoader;
2147
    }
2148
2149
    /**
2150
     * Clear references.
2151
     */
2152
    protected void clearReferences() {
2153
2154
        // De-register any remaining JDBC drivers
2155
        clearReferencesJdbc();
2156
2157
        // Stop any threads the web application started
2158
        clearReferencesThreads();
2159
2160
        // Check for leaks triggered by ThreadLocals loaded by this class loader
2161
        checkThreadLocalsForLeaks();
2162
2163
        // Clear RMI Targets loaded by this class loader
2164
        clearReferencesRmiTargets();
2165
2166
        // Null out any static or final fields from loaded classes,
2167
        // as a workaround for apparent garbage collection bugs
2168
        if (clearReferencesStatic) {
2169
            clearReferencesStaticFinal();
2170
        }
2171
2172
         // Clear the IntrospectionUtils cache.
2173
        IntrospectionUtils.clear();
2174
2175
        // Clear the classloader reference in common-logging
2176
        if (clearReferencesLogFactoryRelease) {
2177
            org.apache.juli.logging.LogFactory.release(this);
2178
        }
2179
2180
        // Clear the resource bundle cache
2181
        // This shouldn't be necessary, the cache uses weak references but
2182
        // it has caused leaks. Oddly, using the leak detection code in
2183
        // standard host allows the class loader to be GC'd. This has been seen
2184
        // on Sun but not IBM JREs. Maybe a bug in Sun's GC impl?
2185
        clearReferencesResourceBundles();
2186
2187
        // Clear the classloader reference in the VM's bean introspector
2188
        java.beans.Introspector.flushCaches();
2189
2190
    }
2191
2192
2193
    /**
2194
     * Deregister any JDBC drivers registered by the webapp that the webapp
2195
     * forgot. This is made unnecessary complex because a) DriverManager
2196
     * checks the class loader of the calling class (it would be much easier
2197
     * if it checked the context class loader) b) using reflection would
2198
     * create a dependency on the DriverManager implementation which can,
2199
     * and has, changed.
2200
     *
2201
     * We can't just create an instance of JdbcLeakPrevention as it will be
2202
     * loaded by the common class loader (since it's .class file is in the
2203
     * $CATALINA_HOME/lib directory). This would fail DriverManager's check
2204
     * on the class loader of the calling class. So, we load the bytes via
2205
     * our parent class loader but define the class with this class loader
2206
     * so the JdbcLeakPrevention looks like a webapp class to the
2207
     * DriverManager.
2208
     *
2209
     * If only apps cleaned up after themselves...
2210
     */
2211
    private final void clearReferencesJdbc() {
2212
        InputStream is = getResourceAsStream(
2213
                "org/apache/catalina/loader/JdbcLeakPrevention.class");
2214
        // We know roughly how big the class will be (~ 1K) so allow 2k as a
2215
        // starting point
2216
        byte[] classBytes = new byte[2048];
2217
        int offset = 0;
2218
        try {
2219
            int read = is.read(classBytes, offset, classBytes.length-offset);
2220
            while (read > -1) {
2221
                offset += read;
2222
                if (offset == classBytes.length) {
2223
                    // Buffer full - double size
2224
                    byte[] tmp = new byte[classBytes.length * 2];
2225
                    System.arraycopy(classBytes, 0, tmp, 0, classBytes.length);
2226
                    classBytes = tmp;
2227
                }
2228
                read = is.read(classBytes, offset, classBytes.length-offset);
2229
            }
2230
            Class<?> lpClass =
2231
                defineClass("org.apache.catalina.loader.JdbcLeakPrevention",
2232
                    classBytes, 0, offset, this.getClass().getProtectionDomain());
2233
            Object obj = lpClass.newInstance();
2234
            @SuppressWarnings("unchecked") // clearJdbcDriverRegistrations() returns List<String>
2235
            List<String> driverNames = (List<String>) obj.getClass().getMethod(
2236
                    "clearJdbcDriverRegistrations").invoke(obj);
2237
            for (String name : driverNames) {
2238
                log.error(sm.getString("webappClassLoader.clearJdbc",
2239
                        contextName, name));
2240
            }
2241
        } catch (Exception e) {
2242
            // So many things to go wrong above...
2243
            Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
2244
            ExceptionUtils.handleThrowable(t);
2245
            log.warn(sm.getString(
2246
                    "webappClassLoader.jdbcRemoveFailed", contextName), t);
2247
        } finally {
2248
            if (is != null) {
2249
                try {
2250
                    is.close();
2251
                } catch (IOException ioe) {
2252
                    log.warn(sm.getString(
2253
                            "webappClassLoader.jdbcRemoveStreamError",
2254
                            contextName), ioe);
2255
                }
2256
            }
2257
        }
2258
    }
2259
2260
2261
    private final void clearReferencesStaticFinal() {
2262
2263
        @SuppressWarnings("unchecked") // resourceEntries is HashMap<String, ResourceEntry>
2264
        Collection<ResourceEntry> values =
2265
            ((HashMap<String,ResourceEntry>) resourceEntries.clone()).values();
2266
        Iterator<ResourceEntry> loadedClasses = values.iterator();
2267
        //
2268
        // walk through all loaded class to trigger initialization for
2269
        //    any uninitialized classes, otherwise initialization of
2270
        //    one class may call a previously cleared class.
2271
        while(loadedClasses.hasNext()) {
2272
            ResourceEntry entry = loadedClasses.next();
2273
            if (entry.loadedClass != null) {
2274
                Class<?> clazz = entry.loadedClass;
2275
                try {
2276
                    Field[] fields = clazz.getDeclaredFields();
2277
                    for (int i = 0; i < fields.length; i++) {
2278
                        if(Modifier.isStatic(fields[i].getModifiers())) {
2279
                            fields[i].get(null);
2280
                            break;
2281
                        }
2282
                    }
2283
                } catch(Throwable t) {
2284
                    // Ignore
2285
                }
2286
            }
2287
        }
2288
        loadedClasses = values.iterator();
2289
        while (loadedClasses.hasNext()) {
2290
            ResourceEntry entry = loadedClasses.next();
2291
            if (entry.loadedClass != null) {
2292
                Class<?> clazz = entry.loadedClass;
2293
                try {
2294
                    Field[] fields = clazz.getDeclaredFields();
2295
                    for (int i = 0; i < fields.length; i++) {
2296
                        Field field = fields[i];
2297
                        int mods = field.getModifiers();
2298
                        if (field.getType().isPrimitive()
2299
                                || (field.getName().indexOf("$") != -1)) {
2300
                            continue;
2301
                        }
2302
                        if (Modifier.isStatic(mods)) {
2303
                            try {
2304
                                field.setAccessible(true);
2305
                                if (Modifier.isFinal(mods)) {
2306
                                    if (!((field.getType().getName().startsWith("java."))
2307
                                            || (field.getType().getName().startsWith("javax.")))) {
2308
                                        nullInstance(field.get(null));
2309
                                    }
2310
                                } else {
2311
                                    field.set(null, null);
2312
                                    if (log.isDebugEnabled()) {
2313
                                        log.debug("Set field " + field.getName()
2314
                                                + " to null in class " + clazz.getName());
2315
                                    }
2316
                                }
2317
                            } catch (Throwable t) {
2318
                                ExceptionUtils.handleThrowable(t);
2319
                                if (log.isDebugEnabled()) {
2320
                                    log.debug("Could not set field " + field.getName()
2321
                                            + " to null in class " + clazz.getName(), t);
2322
                                }
2323
                            }
2324
                        }
2325
                    }
2326
                } catch (Throwable t) {
2327
                    ExceptionUtils.handleThrowable(t);
2328
                    if (log.isDebugEnabled()) {
2329
                        log.debug("Could not clean fields for class " + clazz.getName(), t);
2330
                    }
2331
                }
2332
            }
2333
        }
2334
2335
    }
2336
2337
2338
    private void nullInstance(Object instance) {
2339
        if (instance == null) {
2340
            return;
2341
        }
2342
        Field[] fields = instance.getClass().getDeclaredFields();
2343
        for (int i = 0; i < fields.length; i++) {
2344
            Field field = fields[i];
2345
            int mods = field.getModifiers();
2346
            if (field.getType().isPrimitive()
2347
                    || (field.getName().indexOf("$") != -1)) {
2348
                continue;
2349
            }
2350
            try {
2351
                field.setAccessible(true);
2352
                if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) {
2353
                    // Doing something recursively is too risky
2354
                    continue;
2355
                }
2356
                Object value = field.get(instance);
2357
                if (null != value) {
2358
                    Class<? extends Object> valueClass = value.getClass();
2359
                    if (!loadedByThisOrChild(valueClass)) {
2360
                        if (log.isDebugEnabled()) {
2361
                            log.debug("Not setting field " + field.getName() +
2362
                                    " to null in object of class " +
2363
                                    instance.getClass().getName() +
2364
                                    " because the referenced object was of type " +
2365
                                    valueClass.getName() +
2366
                                    " which was not loaded by this WebappClassLoader.");
2367
                        }
2368
                    } else {
2369
                        field.set(instance, null);
2370
                        if (log.isDebugEnabled()) {
2371
                            log.debug("Set field " + field.getName()
2372
                                    + " to null in class " + instance.getClass().getName());
2373
                        }
2374
                    }
2375
                }
2376
            } catch (Throwable t) {
2377
                ExceptionUtils.handleThrowable(t);
2378
                if (log.isDebugEnabled()) {
2379
                    log.debug("Could not set field " + field.getName()
2380
                            + " to null in object instance of class "
2381
                            + instance.getClass().getName(), t);
2382
                }
2383
            }
2384
        }
2385
    }
2386
2387
2388
    @SuppressWarnings("deprecation") // thread.stop()
2389
    private void clearReferencesThreads() {
2390
        Thread[] threads = getThreads();
2391
        List<Thread> executorThreadsToStop = new ArrayList<Thread>();
2392
2393
        // Iterate over the set of threads
2394
        for (Thread thread : threads) {
2395
            if (thread != null) {
2396
                ClassLoader ccl = thread.getContextClassLoader();
2397
                if (ccl == this) {
2398
                    // Don't warn about this thread
2399
                    if (thread == Thread.currentThread()) {
2400
                        continue;
2401
                    }
2402
2403
                    // JVM controlled threads
2404
                    ThreadGroup tg = thread.getThreadGroup();
2405
                    if (tg != null &&
2406
                            JVM_THREAD_GROUP_NAMES.contains(tg.getName())) {
2407
2408
                        // HttpClient keep-alive threads
2409
                        if (clearReferencesHttpClientKeepAliveThread &&
2410
                                thread.getName().equals("Keep-Alive-Timer")) {
2411
                            thread.setContextClassLoader(parent);
2412
                            log.debug(sm.getString(
2413
                                    "webappClassLoader.checkThreadsHttpClient"));
2414
                        }
2415
2416
                        // Don't warn about remaining JVM controlled threads
2417
                        continue;
2418
                    }
2419
2420
                    // Skip threads that have already died
2421
                    if (!thread.isAlive()) {
2422
                        continue;
2423
                    }
2424
2425
                    // TimerThread can be stopped safely so treat separately
2426
                    // "java.util.TimerThread" in Sun/Oracle JDK
2427
                    // "java.util.Timer$TimerImpl" in Apache Harmony and in IBM JDK
2428
                    if (thread.getClass().getName().startsWith("java.util.Timer") &&
2429
                            clearReferencesStopTimerThreads) {
2430
                        clearReferencesStopTimerThread(thread);
2431
                        continue;
2432
                    }
2433
2434
                    if (isRequestThread(thread)) {
2435
                        log.error(sm.getString("webappClassLoader.warnRequestThread",
2436
                                contextName, thread.getName()));
2437
                    } else {
2438
                        log.error(sm.getString("webappClassLoader.warnThread",
2439
                                contextName, thread.getName()));
2440
                    }
2441
2442
                    // Don't try an stop the threads unless explicitly
2443
                    // configured to do so
2444
                    if (!clearReferencesStopThreads) {
2445
                        continue;
2446
                    }
2447
2448
                    // If the thread has been started via an executor, try
2449
                    // shutting down the executor
2450
                    boolean usingExecutor = false;
2451
                    try {
2452
2453
                        // Runnable wrapped by Thread
2454
                        // "target" in Sun/Oracle JDK
2455
                        // "runnable" in IBM JDK
2456
                        // "action" in Apache Harmony
2457
                        Object target = null;
2458
                        for (String fieldName : new String[] { "target",
2459
                                "runnable", "action" }) {
2460
                            try {
2461
                                Field targetField = thread.getClass()
2462
                                        .getDeclaredField(fieldName);
2463
                                targetField.setAccessible(true);
2464
                                target = targetField.get(thread);
2465
                                break;
2466
                            } catch (NoSuchFieldException nfe) {
2467
                                continue;
2468
                            }
2469
                        }
2470
2471
                        // "java.util.concurrent" code is in public domain,
2472
                        // so all implementations are similar
2473
                        if (target != null &&
2474
                                target.getClass().getCanonicalName() != null
2475
                                && target.getClass().getCanonicalName().equals(
2476
                                "java.util.concurrent.ThreadPoolExecutor.Worker")) {
2477
                            Field executorField =
2478
                                target.getClass().getDeclaredField("this$0");
2479
                            executorField.setAccessible(true);
2480
                            Object executor = executorField.get(target);
2481
                            if (executor instanceof ThreadPoolExecutor) {
2482
                                ((ThreadPoolExecutor) executor).shutdownNow();
2483
                                usingExecutor = true;
2484
                            }
2485
                        }
2486
                    } catch (SecurityException e) {
2487
                        log.warn(sm.getString(
2488
                                "webappClassLoader.stopThreadFail",
2489
                                thread.getName(), contextName), e);
2490
                    } catch (NoSuchFieldException e) {
2491
                        log.warn(sm.getString(
2492
                                "webappClassLoader.stopThreadFail",
2493
                                thread.getName(), contextName), e);
2494
                    } catch (IllegalArgumentException e) {
2495
                        log.warn(sm.getString(
2496
                                "webappClassLoader.stopThreadFail",
2497
                                thread.getName(), contextName), e);
2498
                    } catch (IllegalAccessException e) {
2499
                        log.warn(sm.getString(
2500
                                "webappClassLoader.stopThreadFail",
2501
                                thread.getName(), contextName), e);
2502
                    }
2503
2504
                    if (usingExecutor) {
2505
                        // Executor may take a short time to stop all the
2506
                        // threads. Make a note of threads that should be
2507
                        // stopped and check them at the end of the method.
2508
                        executorThreadsToStop.add(thread);
2509
                    } else {
2510
                        // This method is deprecated and for good reason. This
2511
                        // is very risky code but is the only option at this
2512
                        // point. A *very* good reason for apps to do this
2513
                        // clean-up themselves.
2514
                        thread.stop();
2515
                    }
2516
                }
2517
            }
2518
        }
2519
2520
        // If thread stopping is enabled, executor threads should have been
2521
        // stopped above when the executor was shut down but that depends on the
2522
        // thread correctly handling the interrupt. Give all the executor
2523
        // threads a few seconds shutdown and if they are still running
2524
        // Give threads up to 2 seconds to shutdown
2525
        int count = 0;
2526
        for (Thread t : executorThreadsToStop) {
2527
            while (t.isAlive() && count < 100) {
2528
                try {
2529
                    Thread.sleep(20);
2530
                } catch (InterruptedException e) {
2531
                    // Quit the while loop
2532
                    break;
2533
                }
2534
                count++;
2535
            }
2536
            if (t.isAlive()) {
2537
                // This method is deprecated and for good reason. This is
2538
                // very risky code but is the only option at this point.
2539
                // A *very* good reason for apps to do this clean-up
2540
                // themselves.
2541
                t.stop();
2542
            }
2543
        }
2544
    }
2545
2546
2547
    /*
2548
     * Look at a threads stack trace to see if it is a request thread or not. It
2549
     * isn't perfect, but it should be good-enough for most cases.
2550
     */
2551
    private boolean isRequestThread(Thread thread) {
2552
2553
        StackTraceElement[] elements = thread.getStackTrace();
2554
2555
        if (elements == null || elements.length == 0) {
2556
            // Must have stopped already. Too late to ignore it. Assume not a
2557
            // request processing thread.
2558
            return false;
2559
        }
2560
2561
        // Step through the methods in reverse order looking for calls to any
2562
        // CoyoteAdapter method. All request threads will have this unless
2563
        // Tomcat has been heavily modified - in which case there isn't much we
2564
        // can do.
2565
        for (int i = 0; i < elements.length; i++) {
2566
            StackTraceElement element = elements[elements.length - (i+1)];
2567
            if ("org.apache.catalina.connector.CoyoteAdapter".equals(
2568
                    element.getClassName())) {
2569
                return true;
2570
            }
2571
        }
2572
        return false;
2573
    }
2574
2575
2576
    private void clearReferencesStopTimerThread(Thread thread) {
2577
2578
        // Need to get references to:
2579
        // in Sun/Oracle JDK:
2580
        // - newTasksMayBeScheduled field (in java.util.TimerThread)
2581
        // - queue field
2582
        // - queue.clear()
2583
        // in IBM JDK, Apache Harmony:
2584
        // - cancel() method (in java.util.Timer$TimerImpl)
2585
2586
        try {
2587
2588
            try {
2589
                Field newTasksMayBeScheduledField =
2590
                    thread.getClass().getDeclaredField("newTasksMayBeScheduled");
2591
                newTasksMayBeScheduledField.setAccessible(true);
2592
                Field queueField = thread.getClass().getDeclaredField("queue");
2593
                queueField.setAccessible(true);
2594
2595
                Object queue = queueField.get(thread);
2596
2597
                Method clearMethod = queue.getClass().getDeclaredMethod("clear");
2598
                clearMethod.setAccessible(true);
2599
2600
                synchronized(queue) {
2601
                    newTasksMayBeScheduledField.setBoolean(thread, false);
2602
                    clearMethod.invoke(queue);
2603
                    queue.notify();  // In case queue was already empty.
2604
                }
2605
2606
            }catch (NoSuchFieldException nfe){
2607
                Method cancelMethod = thread.getClass().getDeclaredMethod("cancel");
2608
                synchronized(thread) {
2609
                    cancelMethod.setAccessible(true);
2610
                    cancelMethod.invoke(thread);
2611
                }
2612
            }
2613
2614
            log.error(sm.getString("webappClassLoader.warnTimerThread",
2615
                    contextName, thread.getName()));
2616
2617
        } catch (Exception e) {
2618
            // So many things to go wrong above...
2619
            Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
2620
            ExceptionUtils.handleThrowable(t);
2621
            log.warn(sm.getString(
2622
                    "webappClassLoader.stopTimerThreadFail",
2623
                    thread.getName(), contextName), t);
2624
        }
2625
    }
2626
2627
    private void checkThreadLocalsForLeaks() {
2628
        Thread[] threads = getThreads();
2629
2630
        try {
2631
            // Make the fields in the Thread class that store ThreadLocals
2632
            // accessible
2633
            Field threadLocalsField =
2634
                Thread.class.getDeclaredField("threadLocals");
2635
            threadLocalsField.setAccessible(true);
2636
            Field inheritableThreadLocalsField =
2637
                Thread.class.getDeclaredField("inheritableThreadLocals");
2638
            inheritableThreadLocalsField.setAccessible(true);
2639
            // Make the underlying array of ThreadLoad.ThreadLocalMap.Entry objects
2640
            // accessible
2641
            Class<?> tlmClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
2642
            Field tableField = tlmClass.getDeclaredField("table");
2643
            tableField.setAccessible(true);
2644
            Method expungeStaleEntriesMethod = tlmClass.getDeclaredMethod("expungeStaleEntries");
2645
            expungeStaleEntriesMethod.setAccessible(true);
2646
2647
            for (int i = 0; i < threads.length; i++) {
2648
                Object threadLocalMap;
2649
                if (threads[i] != null) {
2650
2651
                    // Clear the first map
2652
                    threadLocalMap = threadLocalsField.get(threads[i]);
2653
                    if (null != threadLocalMap){
2654
                        expungeStaleEntriesMethod.invoke(threadLocalMap);
2655
                        checkThreadLocalMapForLeaks(threadLocalMap, tableField);
2656
                    }
2657
2658
                    // Clear the second map
2659
                    threadLocalMap =inheritableThreadLocalsField.get(threads[i]);
2660
                    if (null != threadLocalMap){
2661
                        expungeStaleEntriesMethod.invoke(threadLocalMap);
2662
                        checkThreadLocalMapForLeaks(threadLocalMap, tableField);
2663
                    }
2664
                }
2665
            }
2666
        } catch (Throwable t) {
2667
            ExceptionUtils.handleThrowable(t);
2668
            log.warn(sm.getString(
2669
                    "webappClassLoader.checkThreadLocalsForLeaksFail",
2670
                    getContextName()), t);
2671
        }
2672
    }
2673
2674
2675
    /**
2676
     * Analyzes the given thread local map object. Also pass in the field that
2677
     * points to the internal table to save re-calculating it on every
2678
     * call to this method.
2679
     */
2680
    private void checkThreadLocalMapForLeaks(Object map,
2681
            Field internalTableField) throws IllegalAccessException,
2682
            NoSuchFieldException {
2683
        if (map != null) {
2684
            Object[] table = (Object[]) internalTableField.get(map);
2685
            if (table != null) {
2686
                for (int j =0; j < table.length; j++) {
2687
                    Object obj = table[j];
2688
                    if (obj != null) {
2689
                        boolean potentialLeak = false;
2690
                        // Check the key
2691
                        Object key = ((Reference<?>) obj).get();
2692
                        if (this.equals(key) || loadedByThisOrChild(key)) {
2693
                            potentialLeak = true;
2694
                        }
2695
                        // Check the value
2696
                        Field valueField =
2697
                                obj.getClass().getDeclaredField("value");
2698
                        valueField.setAccessible(true);
2699
                        Object value = valueField.get(obj);
2700
                        if (this.equals(value) || loadedByThisOrChild(value)) {
2701
                            potentialLeak = true;
2702
                        }
2703
                        if (potentialLeak) {
2704
                            Object[] args = new Object[5];
2705
                            args[0] = contextName;
2706
                            if (key != null) {
2707
                                args[1] = getPrettyClassName(key.getClass());
2708
                                try {
2709
                                    args[2] = key.toString();
2710
                                } catch (Exception e) {
2711
                                    log.error(sm.getString(
2712
                                            "webappClassLoader.checkThreadLocalsForLeaks.badKey",
2713
                                            args[1]), e);
2714
                                    args[2] = sm.getString(
2715
                                            "webappClassLoader.checkThreadLocalsForLeaks.unknown");
2716
                                }
2717
                            }
2718
                            if (value != null) {
2719
                                args[3] = getPrettyClassName(value.getClass());
2720
                                try {
2721
                                    args[4] = value.toString();
2722
                                } catch (Exception e) {
2723
                                    log.error(sm.getString(
2724
                                            "webappClassLoader.checkThreadLocalsForLeaks.badValue",
2725
                                            args[3]), e);
2726
                                    args[4] = sm.getString(
2727
                                    "webappClassLoader.checkThreadLocalsForLeaks.unknown");
2728
                                }
2729
                            }
2730
                            if (value == null) {
2731
                                if (log.isDebugEnabled()) {
2732
                                    log.debug(sm.getString(
2733
                                            "webappClassLoader.checkThreadLocalsForLeaksDebug",
2734
                                            args));
2735
                                }
2736
                            } else {
2737
                                log.error(sm.getString(
2738
                                        "webappClassLoader.checkThreadLocalsForLeaks",
2739
                                        args));
2740
                            }
2741
                        }
2742
                    }
2743
                }
2744
            }
2745
        }
2746
    }
2747
2748
    private String getPrettyClassName(Class<?> clazz) {
2749
        String name = clazz.getCanonicalName();
2750
        if (name==null){
2751
            name = clazz.getName();
2752
        }
2753
        return name;
2754
    }
2755
2756
    /**
2757
     * @param o object to test, may be null
2758
     * @return <code>true</code> if o has been loaded by the current classloader
2759
     * or one of its descendants.
2760
     */
2761
    private boolean loadedByThisOrChild(Object o) {
2762
        if (o == null) {
2763
            return false;
2764
        }
2765
2766
        Class<?> clazz;
2767
        if (o instanceof Class) {
2768
            clazz = (Class<?>) o;
2769
        } else {
2770
            clazz = o.getClass();
2771
        }
2772
2773
        ClassLoader cl = clazz.getClassLoader();
2774
        while (cl != null) {
2775
            if (cl == this) {
2776
                return true;
2777
            }
2778
            cl = cl.getParent();
2779
        }
2780
2781
        if (o instanceof Collection<?>) {
2782
            Iterator<?> iter = ((Collection<?>) o).iterator();
2783
            try {
2784
                while (iter.hasNext()) {
2785
                    Object entry = iter.next();
2786
                    if (loadedByThisOrChild(entry)) {
2787
                        return true;
2788
                    }
2789
                }
2790
            } catch (ConcurrentModificationException e) {
2791
                log.warn(sm.getString(
2792
                        "webappClassLoader", clazz.getName(), getContextName()),
2793
                        e);
2794
            }
2795
        }
2796
        return false;
2797
    }
2798
2799
    /*
2800
     * Get the set of current threads as an array.
2801
     */
2802
    private Thread[] getThreads() {
2803
        // Get the current thread group
2804
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
2805
        // Find the root thread group
2806
        try {
2807
            while (tg.getParent() != null) {
2808
                tg = tg.getParent();
2809
            }
2810
        } catch (SecurityException se) {
2811
            String msg = sm.getString(
2812
                    "webappClassLoader.getThreadGroupError", tg.getName());
2813
            if (log.isDebugEnabled()) {
2814
                log.debug(msg, se);
2815
            } else {
2816
                log.warn(msg);
2817
            }
2818
        }
2819
2820
        int threadCountGuess = tg.activeCount() + 50;
2821
        Thread[] threads = new Thread[threadCountGuess];
2822
        int threadCountActual = tg.enumerate(threads);
2823
        // Make sure we don't miss any threads
2824
        while (threadCountActual == threadCountGuess) {
2825
            threadCountGuess *=2;
2826
            threads = new Thread[threadCountGuess];
2827
            // Note tg.enumerate(Thread[]) silently ignores any threads that
2828
            // can't fit into the array
2829
            threadCountActual = tg.enumerate(threads);
2830
        }
2831
2832
        return threads;
2833
    }
2834
2835
2836
    /**
2837
     * This depends on the internals of the Sun JVM so it does everything by
2838
     * reflection.
2839
     */
2840
    private void clearReferencesRmiTargets() {
2841
        try {
2842
            // Need access to the ccl field of sun.rmi.transport.Target
2843
            Class<?> objectTargetClass =
2844
                Class.forName("sun.rmi.transport.Target");
2845
            Field cclField = objectTargetClass.getDeclaredField("ccl");
2846
            cclField.setAccessible(true);
2847
2848
            // Clear the objTable map
2849
            Class<?> objectTableClass =
2850
                Class.forName("sun.rmi.transport.ObjectTable");
2851
            Field objTableField = objectTableClass.getDeclaredField("objTable");
2852
            objTableField.setAccessible(true);
2853
            Object objTable = objTableField.get(null);
2854
            if (objTable == null) {
2855
                return;
2856
            }
2857
2858
            // Iterate over the values in the table
2859
            if (objTable instanceof Map<?,?>) {
2860
                Iterator<?> iter = ((Map<?,?>) objTable).values().iterator();
2861
                while (iter.hasNext()) {
2862
                    Object obj = iter.next();
2863
                    Object cclObject = cclField.get(obj);
2864
                    if (this == cclObject) {
2865
                        iter.remove();
2866
                    }
2867
                }
2868
            }
2869
2870
            // Clear the implTable map
2871
            Field implTableField = objectTableClass.getDeclaredField("implTable");
2872
            implTableField.setAccessible(true);
2873
            Object implTable = implTableField.get(null);
2874
            if (implTable == null) {
2875
                return;
2876
            }
2877
2878
            // Iterate over the values in the table
2879
            if (implTable instanceof Map<?,?>) {
2880
                Iterator<?> iter = ((Map<?,?>) implTable).values().iterator();
2881
                while (iter.hasNext()) {
2882
                    Object obj = iter.next();
2883
                    Object cclObject = cclField.get(obj);
2884
                    if (this == cclObject) {
2885
                        iter.remove();
2886
                    }
2887
                }
2888
            }
2889
        } catch (ClassNotFoundException e) {
2890
            log.info(sm.getString("webappClassLoader.clearRmiInfo",
2891
                    contextName), e);
2892
        } catch (SecurityException e) {
2893
            log.warn(sm.getString("webappClassLoader.clearRmiFail",
2894
                    contextName), e);
2895
        } catch (NoSuchFieldException e) {
2896
            log.warn(sm.getString("webappClassLoader.clearRmiFail",
2897
                    contextName), e);
2898
        } catch (IllegalArgumentException e) {
2899
            log.warn(sm.getString("webappClassLoader.clearRmiFail",
2900
                    contextName), e);
2901
        } catch (IllegalAccessException e) {
2902
            log.warn(sm.getString("webappClassLoader.clearRmiFail",
2903
                    contextName), e);
2904
        }
2905
    }
2906
2907
2908
    /**
2909
     * Clear the {@link ResourceBundle} cache of any bundles loaded by this
2910
     * class loader or any class loader where this loader is a parent class
2911
     * loader. Whilst {@link ResourceBundle#clearCache()} could be used there
2912
     * are complications around the
2913
     * {@link org.apache.jasper.servlet.JasperLoader} that mean a reflection
2914
     * based approach is more likely to be complete.
2915
     *
2916
     * The ResourceBundle is using WeakReferences so it shouldn't be pinning the
2917
     * class loader in memory. However, it is. Therefore clear ou the
2918
     * references.
2919
     */
2920
    private void clearReferencesResourceBundles() {
2921
        // Get a reference to the cache
2922
        try {
2923
            Field cacheListField =
2924
                ResourceBundle.class.getDeclaredField("cacheList");
2925
            cacheListField.setAccessible(true);
2926
2927
            // Java 6 uses ConcurrentMap
2928
            // Java 5 uses SoftCache extends Abstract Map
2929
            // So use Map and it *should* work with both
2930
            Map<?,?> cacheList = (Map<?,?>) cacheListField.get(null);
2931
2932
            // Get the keys (loader references are in the key)
2933
            Set<?> keys = cacheList.keySet();
2934
2935
            Field loaderRefField = null;
2936
2937
            // Iterate over the keys looking at the loader instances
2938
            Iterator<?> keysIter = keys.iterator();
2939
2940
            int countRemoved = 0;
2941
2942
            while (keysIter.hasNext()) {
2943
                Object key = keysIter.next();
2944
2945
                if (loaderRefField == null) {
2946
                    loaderRefField =
2947
                        key.getClass().getDeclaredField("loaderRef");
2948
                    loaderRefField.setAccessible(true);
2949
                }
2950
                WeakReference<?> loaderRef =
2951
                    (WeakReference<?>) loaderRefField.get(key);
2952
2953
                ClassLoader loader = (ClassLoader) loaderRef.get();
2954
2955
                while (loader != null && loader != this) {
2956
                    loader = loader.getParent();
2957
                }
2958
2959
                if (loader != null) {
2960
                    keysIter.remove();
2961
                    countRemoved++;
2962
                }
2963
            }
2964
2965
            if (countRemoved > 0 && log.isDebugEnabled()) {
2966
                log.debug(sm.getString(
2967
                        "webappClassLoader.clearReferencesResourceBundlesCount",
2968
                        Integer.valueOf(countRemoved), contextName));
2969
            }
2970
        } catch (SecurityException e) {
2971
            log.error(sm.getString(
2972
                    "webappClassLoader.clearReferencesResourceBundlesFail",
2973
                    contextName), e);
2974
        } catch (NoSuchFieldException e) {
2975
            if (Globals.IS_ORACLE_JVM) {
2976
                log.error(sm.getString(
2977
                        "webappClassLoader.clearReferencesResourceBundlesFail",
2978
                        getContextName()), e);
2979
            } else {
2980
                log.debug(sm.getString(
2981
                        "webappClassLoader.clearReferencesResourceBundlesFail",
2982
                        getContextName()), e);
2983
            }
2984
        } catch (IllegalArgumentException e) {
2985
            log.error(sm.getString(
2986
                    "webappClassLoader.clearReferencesResourceBundlesFail",
2987
                    contextName), e);
2988
        } catch (IllegalAccessException e) {
2989
            log.error(sm.getString(
2990
                    "webappClassLoader.clearReferencesResourceBundlesFail",
2991
                    contextName), e);
2992
        }
2993
    }
2994
2995
2996
    /**
2997
     * Used to periodically signal to the classloader to release JAR resources.
2998
     */
2999
    protected boolean openJARs() {
3000
        if (started && (jarFiles.length > 0)) {
3001
            lastJarAccessed = System.currentTimeMillis();
3002
            if (jarFiles[0] == null) {
3003
                for (int i = 0; i < jarFiles.length; i++) {
3004
                    try {
3005
                        jarFiles[i] = new JarFile(jarRealFiles[i]);
3006
                    } catch (IOException e) {
3007
                        if (log.isDebugEnabled()) {
3008
                            log.debug("Failed to open JAR", e);
3009
                        }
3010
                        return false;
3011
                    }
3012
                }
3013
            }
3014
        }
3015
        return true;
3016
    }
3017
3018
3019
    /**
3020
     * Find specified class in local repositories.
3021
     *
3022
     * @return the loaded class, or null if the class isn't found
3023
     */
3024
    protected Class<?> findClassInternal(String name)
3025
        throws ClassNotFoundException {
3026
3027
        if (!validate(name))
3028
            throw new ClassNotFoundException(name);
3029
3030
        String tempPath = name.replace('.', '/');
3031
        String classPath = tempPath + CLASS_FILE_SUFFIX;
3032
3033
        ResourceEntry entry = null;
3034
3035
        if (securityManager != null) {
3036
            PrivilegedAction<ResourceEntry> dp =
3037
                new PrivilegedFindResourceByName(name, classPath, true);
3038
            entry = AccessController.doPrivileged(dp);
3039
        } else {
3040
            entry = findResourceInternal(name, classPath, true);
3041
        }
3042
3043
        if (entry == null)
3044
            throw new ClassNotFoundException(name);
3045
3046
        Class<?> clazz = entry.loadedClass;
3047
        if (clazz != null)
3048
            return clazz;
3049
3050
        synchronized (this) {
3051
            clazz = entry.loadedClass;
3052
            if (clazz != null)
3053
                return clazz;
3054
3055
            if (entry.binaryContent == null)
3056
                throw new ClassNotFoundException(name);
3057
3058
            // Looking up the package
3059
            String packageName = null;
3060
            int pos = name.lastIndexOf('.');
3061
            if (pos != -1)
3062
                packageName = name.substring(0, pos);
3063
3064
            Package pkg = null;
3065
3066
            if (packageName != null) {
3067
                pkg = getPackage(packageName);
3068
                // Define the package (if null)
3069
                if (pkg == null) {
3070
                    try {
3071
                        if (entry.manifest == null) {
3072
                            definePackage(packageName, null, null, null, null,
3073
                                    null, null, null);
3074
                        } else {
3075
                            definePackage(packageName, entry.manifest,
3076
                                    entry.codeBase);
3077
                        }
3078
                    } catch (IllegalArgumentException e) {
3079
                        // Ignore: normal error due to dual definition of package
3080
                    }
3081
                    pkg = getPackage(packageName);
3082
                }
3083
            }
3084
3085
            if (securityManager != null) {
3086
3087
                // Checking sealing
3088
                if (pkg != null) {
3089
                    boolean sealCheck = true;
3090
                    if (pkg.isSealed()) {
3091
                        sealCheck = pkg.isSealed(entry.codeBase);
3092
                    } else {
3093
                        sealCheck = (entry.manifest == null)
3094
                            || !isPackageSealed(packageName, entry.manifest);
3095
                    }
3096
                    if (!sealCheck)
3097
                        throw new SecurityException
3098
                            ("Sealing violation loading " + name + " : Package "
3099
                             + packageName + " is sealed.");
3100
                }
3101
3102
            }
3103
3104
            try {
3105
                clazz = defineClass(name, entry.binaryContent, 0,
3106
                        entry.binaryContent.length,
3107
                        new CodeSource(entry.codeBase, entry.certificates));
3108
            } catch (UnsupportedClassVersionError ucve) {
3109
                throw new UnsupportedClassVersionError(
3110
                        ucve.getLocalizedMessage() + " " +
3111
                        sm.getString("webappClassLoader.wrongVersion",
3112
                                name));
3113
            }
3114
            entry.loadedClass = clazz;
3115
            entry.binaryContent = null;
3116
            entry.source = null;
3117
            entry.codeBase = null;
3118
            entry.manifest = null;
3119
            entry.certificates = null;
3120
        }
3121
3122
        return clazz;
3123
3124
    }
3125
3126
    /**
3127
     * Find specified resource in local repositories.
3128
     *
3129
     * @return the loaded resource, or null if the resource isn't found
3130
     */
3131
    protected ResourceEntry findResourceInternal(File file, String path){
3132
        ResourceEntry entry = new ResourceEntry();
3133
        try {
3134
            entry.source = getURI(new File(file, path));
3135
            String sourceString = entry.source.toString();
3136
            if (sourceString.startsWith(webInfClassesCodeBase.toString()) &&
3137
                    sourceString.endsWith(CLASS_FILE_SUFFIX)) {
3138
                entry.codeBase = webInfClassesCodeBase;
3139
            } else {
3140
                entry.codeBase = entry.source;
3141
            }
3142
        } catch (MalformedURLException e) {
3143
            return null;
3144
        }
3145
        return entry;
3146
    }
3147
3148
3149
    /**
3150
     * Find specified resource in local repositories.
3151
     *
3152
     * @return the loaded resource, or null if the resource isn't found
3153
     */
3154
    protected ResourceEntry findResourceInternal(final String name, final String path,
3155
            final boolean manifestRequired) {
3156
3157
        if (!started) {
3158
            log.info(sm.getString("webappClassLoader.stopped", name));
3159
            return null;
3160
        }
3161
3162
        if ((name == null) || (path == null))
3163
            return null;
3164
3165
        ResourceEntry entry = resourceEntries.get(name);
3166
        if (entry != null)
3167
            return entry;
3168
3169
        int contentLength = -1;
3170
        InputStream binaryStream = null;
3171
        boolean isClassResource = path.endsWith(CLASS_FILE_SUFFIX);
3172
        boolean isCacheable = isClassResource;
3173
        if (!isCacheable) {
3174
             isCacheable = path.startsWith(SERVICES_PREFIX);
3175
        }
3176
3177
        int jarFilesLength = jarFiles.length;
3178
        int repositoriesLength = repositories.length;
3179
3180
        int i;
3181
3182
        Resource resource = null;
3183
3184
        boolean fileNeedConvert = false;
3185
3186
        for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
3187
            try {
3188
3189
                String fullPath = repositories[i] + path;
3190
3191
                Object lookupResult = resources.lookup(fullPath);
3192
                if (lookupResult instanceof Resource) {
3193
                    resource = (Resource) lookupResult;
3194
                }
3195
3196
                // Note : Not getting an exception here means the resource was
3197
                // found
3198
3199
                ResourceAttributes attributes =
3200
                    (ResourceAttributes) resources.getAttributes(fullPath);
3201
                contentLength = (int) attributes.getContentLength();
3202
                String canonicalPath = attributes.getCanonicalPath();
3203
                if (canonicalPath != null) {
3204
                    // we create the ResourceEntry based on the information returned
3205
                    // by the DirContext rather than just using the path to the
3206
                    // repository. This allows to have smart DirContext implementations
3207
                    // that "virtualize" the docbase (e.g. Eclipse WTP)
3208
                    entry = findResourceInternal(new File(canonicalPath), "");
3209
                } else {
3210
                    // probably a resource not in the filesystem (e.g. in a
3211
                    // packaged war)
3212
                    entry = findResourceInternal(files[i], path);
3213
                }
3214
                entry.lastModified = attributes.getLastModified();
3215
3216
                if (resource != null) {
3217
3218
3219
                    try {
3220
                        binaryStream = resource.streamContent();
3221
                    } catch (IOException e) {
3222
                        return null;
3223
                    }
3224
3225
                    if (needConvert) {
3226
                        if (path.endsWith(".properties")) {
3227
                            fileNeedConvert = true;
3228
                        }
3229
                    }
3230
3231
                    // Register the full path for modification checking
3232
                    // Note: Only syncing on a 'constant' object is needed
3233
                    synchronized (allPermission) {
3234
3235
                        int j;
3236
3237
                        long[] result2 =
3238
                            new long[lastModifiedDates.length + 1];
3239
                        for (j = 0; j < lastModifiedDates.length; j++) {
3240
                            result2[j] = lastModifiedDates[j];
3241
                        }
3242
                        result2[lastModifiedDates.length] = entry.lastModified;
3243
                        lastModifiedDates = result2;
3244
3245
                        String[] result = new String[paths.length + 1];
3246
                        for (j = 0; j < paths.length; j++) {
3247
                            result[j] = paths[j];
3248
                        }
3249
                        result[paths.length] = fullPath;
3250
                        paths = result;
3251
3252
                    }
3253
3254
                }
3255
3256
            } catch (NamingException e) {
3257
                // Ignore
3258
            }
3259
        }
3260
3261
        if ((entry == null) && (notFoundResources.containsKey(name)))
3262
            return null;
3263
3264
        JarEntry jarEntry = null;
3265
3266
        synchronized (jarFiles) {
3267
3268
            try {
3269
                if (!openJARs()) {
3270
                    return null;
3271
                }
3272
                for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
3273
3274
                    jarEntry = jarFiles[i].getJarEntry(path);
3275
3276
                    if (jarEntry != null) {
3277
3278
                        entry = new ResourceEntry();
3279
                        try {
3280
                            entry.codeBase = getURI(jarRealFiles[i]);
3281
                            String jarFakeUrl = entry.codeBase.toString();
3282
                            jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
3283
                            entry.source = new URL(jarFakeUrl);
3284
                            entry.lastModified = jarRealFiles[i].lastModified();
3285
                        } catch (MalformedURLException e) {
3286
                            return null;
3287
                        }
3288
                        contentLength = (int) jarEntry.getSize();
3289
                        try {
3290
                            if (manifestRequired) {
3291
                                entry.manifest = jarFiles[i].getManifest();
3292
                            }
3293
                            binaryStream = jarFiles[i].getInputStream(jarEntry);
3294
                        } catch (IOException e) {
3295
                            return null;
3296
                        }
3297
3298
                        // Extract resources contained in JAR to the workdir
3299
                        if (antiJARLocking && !(path.endsWith(CLASS_FILE_SUFFIX))) {
3300
                            byte[] buf = new byte[1024];
3301
                            File resourceFile = new File
3302
                                (loaderDir, jarEntry.getName());
3303
                            if (!resourceFile.exists()) {
3304
                                Enumeration<JarEntry> entries =
3305
                                    jarFiles[i].entries();
3306
                                while (entries.hasMoreElements()) {
3307
                                    JarEntry jarEntry2 =  entries.nextElement();
3308
                                    if (!(jarEntry2.isDirectory())
3309
                                        && (!jarEntry2.getName().endsWith
3310
                                            (CLASS_FILE_SUFFIX))) {
3311
                                        resourceFile = new File
3312
                                            (loaderDir, jarEntry2.getName());
3313
                                        try {
3314
                                            if (!resourceFile.getCanonicalPath().startsWith(
3315
                                                    canonicalLoaderDir)) {
3316
                                                throw new IllegalArgumentException(
3317
                                                        sm.getString("webappClassLoader.illegalJarPath",
3318
                                                    jarEntry2.getName()));
3319
                                            }
3320
                                        } catch (IOException ioe) {
3321
                                            throw new IllegalArgumentException(
3322
                                                    sm.getString("webappClassLoader.validationErrorJarPath",
3323
                                                            jarEntry2.getName()), ioe);
3324
                                        }
3325
                                        File parentFile = resourceFile.getParentFile();
3326
                                        if (!parentFile.mkdirs() && !parentFile.exists()) {
3327
                                            // Ignore the error (like the IOExceptions below)
3328
                                        }
3329
                                        FileOutputStream os = null;
3330
                                        InputStream is = null;
3331
                                        try {
3332
                                            is = jarFiles[i].getInputStream
3333
                                                (jarEntry2);
3334
                                            os = new FileOutputStream
3335
                                                (resourceFile);
3336
                                            while (true) {
3337
                                                int n = is.read(buf);
3338
                                                if (n <= 0) {
3339
                                                    break;
3340
                                                }
3341
                                                os.write(buf, 0, n);
3342
                                            }
3343
                                            resourceFile.setLastModified(
3344
                                                    jarEntry2.getTime());
3345
                                        } catch (IOException e) {
3346
                                            // Ignore
3347
                                        } finally {
3348
                                            try {
3349
                                                if (is != null) {
3350
                                                    is.close();
3351
                                                }
3352
                                            } catch (IOException e) {
3353
                                                // Ignore
3354
                                            }
3355
                                            try {
3356
                                                if (os != null) {
3357
                                                    os.close();
3358
                                                }
3359
                                            } catch (IOException e) {
3360
                                                // Ignore
3361
                                            }
3362
                                        }
3363
                                    }
3364
                                }
3365
                            }
3366
                        }
3367
3368
                    }
3369
3370
                }
3371
3372
                if (entry == null) {
3373
                    synchronized (notFoundResources) {
3374
                        notFoundResources.put(name, name);
3375
                    }
3376
                    return null;
3377
                }
3378
3379
                /* Only cache the binary content if there is some content
3380
                 * available one of the following is true:
3381
                 * a) It is a class file since the binary content is only cached
3382
                 *    until the class has been loaded
3383
                 *    or
3384
                 * b) The file needs conversion to address encoding issues (see
3385
                 *    below)
3386
                 *    or
3387
                 * c) The resource is a service provider configuration file located
3388
                 *    under META=INF/services
3389
                 *
3390
                 * In all other cases do not cache the content to prevent
3391
                 * excessive memory usage if large resources are present (see
3392
                 * https://bz.apache.org/bugzilla/show_bug.cgi?id=53081).
3393
                 */
3394
                if (binaryStream != null &&
3395
                        (isCacheable || fileNeedConvert)) {
3396
3397
                    byte[] binaryContent = new byte[contentLength];
3398
3399
                    int pos = 0;
3400
                    try {
3401
3402
                        while (true) {
3403
                            int n = binaryStream.read(binaryContent, pos,
3404
                                                      binaryContent.length - pos);
3405
                            if (n <= 0)
3406
                                break;
3407
                            pos += n;
3408
                        }
3409
                    } catch (IOException e) {
3410
                        log.error(sm.getString("webappClassLoader.readError", name), e);
3411
                        return null;
3412
                    }
3413
                    if (fileNeedConvert) {
3414
                        // Workaround for certain files on platforms that use
3415
                        // EBCDIC encoding, when they are read through FileInputStream.
3416
                        // See commit message of rev.303915 for details
3417
                        // http://svn.apache.org/viewvc?view=revision&revision=303915
3418
                        String str = new String(binaryContent,0,pos);
3419
                        try {
3420
                            binaryContent = str.getBytes(CHARSET_UTF8);
3421
                        } catch (Exception e) {
3422
                            return null;
3423
                        }
3424
                    }
3425
                    entry.binaryContent = binaryContent;
3426
3427
                    // The certificates are only available after the JarEntry
3428
                    // associated input stream has been fully read
3429
                    if (jarEntry != null) {
3430
                        entry.certificates = jarEntry.getCertificates();
3431
                    }
3432
3433
                }
3434
            } finally {
3435
                if (binaryStream != null) {
3436
                    try {
3437
                        binaryStream.close();
3438
                    } catch (IOException e) { /* Ignore */}
3439
                }
3440
            }
3441
        }
3442
3443
        if (isClassResource && entry.binaryContent != null &&
3444
                this.transformers.size() > 0) {
3445
            // If the resource is a class just being loaded, decorate it
3446
            // with any attached transformers
3447
            String className = name.endsWith(CLASS_FILE_SUFFIX) ?
3448
                    name.substring(0, name.length() - CLASS_FILE_SUFFIX.length()) : name;
3449
            String internalName = className.replace(".", "/");
3450
3451
            for (ClassFileTransformer transformer : this.transformers) {
3452
                try {
3453
                    byte[] transformed = transformer.transform(
3454
                            this, internalName, null, null, entry.binaryContent
3455
                    );
3456
                    if (transformed != null) {
3457
                        entry.binaryContent = transformed;
3458
                    }
3459
                } catch (IllegalClassFormatException e) {
3460
                    log.error(sm.getString("webappClassLoader.transformError", name), e);
3461
                    return null;
3462
                }
3463
            }
3464
        }
3465
3466
        // Add the entry in the local resource repository
3467
        synchronized (resourceEntries) {
3468
            // Ensures that all the threads which may be in a race to load
3469
            // a particular class all end up with the same ResourceEntry
3470
            // instance
3471
            ResourceEntry entry2 = resourceEntries.get(name);
3472
            if (entry2 == null) {
3473
                resourceEntries.put(name, entry);
3474
            } else {
3475
                entry = entry2;
3476
            }
3477
        }
3478
3479
        return entry;
3480
3481
    }
3482
3483
3484
    /**
3485
     * Returns true if the specified package name is sealed according to the
3486
     * given manifest.
3487
     */
3488
    protected boolean isPackageSealed(String name, Manifest man) {
3489
3490
        String path = name.replace('.', '/') + '/';
3491
        Attributes attr = man.getAttributes(path);
3492
        String sealed = null;
3493
        if (attr != null) {
3494
            sealed = attr.getValue(Name.SEALED);
3495
        }
3496
        if (sealed == null) {
3497
            if ((attr = man.getMainAttributes()) != null) {
3498
                sealed = attr.getValue(Name.SEALED);
3499
            }
3500
        }
3501
        return "true".equalsIgnoreCase(sealed);
3502
3503
    }
3504
3505
3506
    /**
3507
     * Finds the resource with the given name if it has previously been
3508
     * loaded and cached by this class loader, and return an input stream
3509
     * to the resource data.  If this resource has not been cached, return
3510
     * <code>null</code>.
3511
     *
3512
     * @param name Name of the resource to return
3513
     */
3514
    protected InputStream findLoadedResource(String name) {
3515
3516
        ResourceEntry entry = resourceEntries.get(name);
3517
        if (entry != null) {
3518
            if (entry.binaryContent != null)
3519
                return new ByteArrayInputStream(entry.binaryContent);
3520
            else {
3521
                try {
3522
                    return entry.source.openStream();
3523
                } catch (IOException ioe) {
3524
                    // Ignore
3525
                }
3526
            }
3527
        }
3528
        return null;
3529
3530
    }
3531
3532
3533
    /**
3534
     * Finds the class with the given name if it has previously been
3535
     * loaded and cached by this class loader, and return the Class object.
3536
     * If this class has not been cached, return <code>null</code>.
3537
     *
3538
     * @param name Name of the resource to return
3539
     */
3540
    protected Class<?> findLoadedClass0(String name) {
3541
3542
        ResourceEntry entry = resourceEntries.get(name);
3543
        if (entry != null) {
3544
            return entry.loadedClass;
3545
        }
3546
        return (null);  // FIXME - findLoadedResource()
3547
3548
    }
3549
3550
3551
    /**
3552
     * Refresh the system policy file, to pick up eventual changes.
3553
     */
3554
    protected void refreshPolicy() {
3555
3556
        try {
3557
            // The policy file may have been modified to adjust
3558
            // permissions, so we're reloading it when loading or
3559
            // reloading a Context
3560
            Policy policy = Policy.getPolicy();
3561
            policy.refresh();
3562
        } catch (AccessControlException e) {
3563
            // Some policy files may restrict this, even for the core,
3564
            // so this exception is ignored
3565
        }
3566
3567
    }
3568
3569
3570
    /**
3571
     * Filter classes.
3572
     *
3573
     * @param name class name
3574
     * @return true if the class should be filtered
3575
     */
3576
    protected boolean filter(String name) {
3577
3578
        if (name == null)
3579
            return false;
3580
3581
        // Looking up the package
3582
        String packageName = null;
3583
        int pos = name.lastIndexOf('.');
3584
        if (pos != -1)
3585
            packageName = name.substring(0, pos);
3586
        else
3587
            return false;
3588
3589
        for (int i = 0; i < packageTriggers.length; i++) {
3590
            if (packageName.startsWith(packageTriggers[i]))
3591
                return true;
3592
        }
3593
3594
        return false;
3595
3596
    }
3597
3598
3599
    /**
3600
     * Validate a classname. As per SRV.9.7.2, we must restrict loading of
3601
     * classes from J2SE (java.*) and most classes of the servlet API
3602
     * (javax.servlet.*). That should enhance robustness and prevent a number
3603
     * of user error (where an older version of servlet.jar would be present
3604
     * in /WEB-INF/lib).
3605
     *
3606
     * @param name class name
3607
     * @return true if the name is valid
3608
     */
3609
    protected boolean validate(String name) {
3610
3611
        // Need to be careful with order here
3612
        if (name == null) {
3613
            // Can't load a class without a name
3614
            return false;
3615
        }
3616
        if (name.startsWith("java.")) {
3617
            // Must never load java.* classes
3618
            return false;
3619
        }
3620
        if (name.startsWith("javax.servlet.jsp.jstl")) {
3621
            // OK for web apps to package JSTL
3622
            return true;
3623
        }
3624
        if (name.startsWith("javax.servlet.")) {
3625
            // Web apps should never package any other Servlet or JSP classes
3626
            return false;
3627
        }
3628
        if (name.startsWith("javax.el")) {
3629
            // Must never load javax.el.* classes
3630
            return false;
3631
        }
3632
3633
        // Assume everything else is OK
3634
        return true;
3635
3636
    }
3637
3638
3639
    /**
3640
     * Check the specified JAR file, and return <code>true</code> if it does
3641
     * not contain any of the trigger classes.
3642
     *
3643
     * @param file  The JAR file to be checked
3644
     *
3645
     * @exception IOException if an input/output error occurs
3646
     */
3647
    protected boolean validateJarFile(File file)
3648
        throws IOException {
3649
3650
        if (triggers == null)
3651
            return (true);
3652
3653
        JarFile jarFile = null;
3654
        try {
3655
            jarFile = new JarFile(file);
3656
            for (int i = 0; i < triggers.length; i++) {
3657
                Class<?> clazz = null;
3658
                try {
3659
                    if (parent != null) {
3660
                        clazz = parent.loadClass(triggers[i]);
3661
                    } else {
3662
                        clazz = Class.forName(triggers[i]);
3663
                    }
3664
                } catch (Exception e) {
3665
                    clazz = null;
3666
                }
3667
                if (clazz == null)
3668
                    continue;
3669
                String name = triggers[i].replace('.', '/') + CLASS_FILE_SUFFIX;
3670
                if (log.isDebugEnabled())
3671
                    log.debug(" Checking for " + name);
3672
                JarEntry jarEntry = jarFile.getJarEntry(name);
3673
                if (jarEntry != null) {
3674
                    log.info("validateJarFile(" + file +
3675
                        ") - jar not loaded. See Servlet Spec 3.0, "
3676
                        + "section 10.7.2. Offending class: " + name);
3677
                    return false;
3678
                }
3679
            }
3680
            return true;
3681
        } finally {
3682
            if (jarFile != null) {
3683
                try {
3684
                    jarFile.close();
3685
                } catch (IOException ioe) {
3686
                    // Ignore
3687
                }
3688
            }
3689
        }
3690
    }
3691
3692
3693
    /**
3694
     * Get URL.
3695
     * @deprecated Use {@link #getURI(File)} instead
3696
     */
3697
    @Deprecated
3698
    protected URL getURL(File file, boolean encoded)
3699
        throws MalformedURLException {
3700
3701
        File realFile = file;
3702
        try {
3703
            realFile = realFile.getCanonicalFile();
3704
        } catch (IOException e) {
3705
            // Ignore
3706
        }
3707
        if(encoded) {
3708
            return getURI(realFile);
3709
        }
3710
3711
        return realFile.toURI().toURL();
3712
    }
3713
3714
3715
    /**
3716
     * Get the URI for the given file.
3717
     */
3718
    protected URL getURI(File file)
3719
        throws MalformedURLException {
3720
3721
3722
        File realFile = file;
3723
        try {
3724
            realFile = realFile.getCanonicalFile();
3725
        } catch (IOException e) {
3726
            // Ignore
3727
        }
3728
        return realFile.toURI().toURL();
3729
3730
    }
3731
3732
3733
    /**
3734
     * Delete the specified directory, including all of its contents and
3735
     * subdirectories recursively.
3736
     *
3737
     * @param dir File object representing the directory to be deleted
3738
     */
3739
    protected static void deleteDir(File dir) {
3740
3741
        String files[] = dir.list();
3742
        if (files == null) {
3743
            files = new String[0];
3744
        }
3745
        for (int i = 0; i < files.length; i++) {
3746
            File file = new File(dir, files[i]);
3747
            if (file.isDirectory()) {
3748
                deleteDir(file);
3749
            } else {
3750
                file.delete();
3751
            }
3752
        }
3753
        dir.delete();
3754
3755
    }
3756
3757
3758
}
68
}
(-)java/org/apache/catalina/loader/WebappClassLoaderBase.java (+3768 lines)
Line 0 Link Here
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.instrument.ClassFileTransformer;
28
import java.lang.instrument.IllegalClassFormatException;
29
import java.lang.ref.Reference;
30
import java.lang.ref.WeakReference;
31
import java.lang.reflect.Field;
32
import java.lang.reflect.Method;
33
import java.lang.reflect.Modifier;
34
import java.net.MalformedURLException;
35
import java.net.URL;
36
import java.net.URLClassLoader;
37
import java.nio.charset.Charset;
38
import java.security.AccessControlException;
39
import java.security.AccessController;
40
import java.security.CodeSource;
41
import java.security.Permission;
42
import java.security.PermissionCollection;
43
import java.security.Policy;
44
import java.security.PrivilegedAction;
45
import java.security.ProtectionDomain;
46
import java.util.ArrayList;
47
import java.util.Collection;
48
import java.util.Collections;
49
import java.util.ConcurrentModificationException;
50
import java.util.Enumeration;
51
import java.util.HashMap;
52
import java.util.Iterator;
53
import java.util.LinkedHashMap;
54
import java.util.LinkedHashSet;
55
import java.util.List;
56
import java.util.Map;
57
import java.util.ResourceBundle;
58
import java.util.Set;
59
import java.util.concurrent.CopyOnWriteArrayList;
60
import java.util.concurrent.ThreadPoolExecutor;
61
import java.util.jar.Attributes;
62
import java.util.jar.Attributes.Name;
63
import java.util.jar.JarEntry;
64
import java.util.jar.JarFile;
65
import java.util.jar.Manifest;
66
67
import javax.naming.Binding;
68
import javax.naming.NameClassPair;
69
import javax.naming.NamingEnumeration;
70
import javax.naming.NamingException;
71
import javax.naming.directory.DirContext;
72
73
import org.apache.catalina.Globals;
74
import org.apache.catalina.Lifecycle;
75
import org.apache.catalina.LifecycleException;
76
import org.apache.catalina.LifecycleListener;
77
import org.apache.catalina.LifecycleState;
78
import org.apache.naming.JndiPermission;
79
import org.apache.naming.resources.ProxyDirContext;
80
import org.apache.naming.resources.Resource;
81
import org.apache.naming.resources.ResourceAttributes;
82
import org.apache.tomcat.InstrumentableClassLoader;
83
import org.apache.tomcat.util.ExceptionUtils;
84
import org.apache.tomcat.util.IntrospectionUtils;
85
import org.apache.tomcat.util.compat.JreCompat;
86
import org.apache.tomcat.util.res.StringManager;
87
88
/**
89
 * Specialized web application class loader.
90
 * <p>
91
 * This class loader is a full reimplementation of the
92
 * <code>URLClassLoader</code> from the JDK. It is designed to be fully
93
 * compatible with a normal <code>URLClassLoader</code>, although its internal
94
 * behavior may be completely different.
95
 * <p>
96
 * <strong>IMPLEMENTATION NOTE</strong> - By default, this class loader follows
97
 * the delegation model required by the specification. The system class
98
 * loader will be queried first, then the local repositories, and only then
99
 * delegation to the parent class loader will occur. This allows the web
100
 * application to override any shared class except the classes from J2SE.
101
 * Special handling is provided from the JAXP XML parser interfaces, the JNDI
102
 * interfaces, and the classes from the servlet API, which are never loaded
103
 * from the webapp repositories. The <code>delegate</code> property
104
 * allows an application to modify this behavior to move the parent class loader
105
 * ahead of the local repositories.
106
 * <p>
107
 * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper
108
 * compilation technology, any repository which contains classes from
109
 * the servlet API will be ignored by the class loader.
110
 * <p>
111
 * <strong>IMPLEMENTATION NOTE</strong> - The class loader generates source
112
 * URLs which include the full JAR URL when a class is loaded from a JAR file,
113
 * which allows setting security permission at the class level, even when a
114
 * class is contained inside a JAR.
115
 * <p>
116
 * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in
117
 * the order they are added via the initial constructor and/or any subsequent
118
 * calls to <code>addRepository()</code> or <code>addJar()</code>.
119
 * <p>
120
 * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
121
 * security is made unless a security manager is present.
122
 * <p>
123
 * TODO: Is there any requirement to provide a proper Lifecycle implementation
124
 *       rather than the current stubbed implementation?
125
 * <p>
126
 * <strong>IMPLEMENTATION NOTE</strong> - As of 7.0.64/8.0, this class
127
 * loader implements {@link InstrumentableClassLoader}, permitting web
128
 * application classes to instrument other classes in the same web
129
 * application. It does not permit instrumentation of system or container
130
 * classes or classes in other web apps.
131
 * <p>
132
 * <strong>IMPLEMENTATION NOTE</strong> - As of 7.0.64, this class loader
133
 * can be registered as parallel capable if running on JRE7 or above.
134
 * @author Remy Maucherat
135
 * @author Craig R. McClanahan
136
 * @author Huxing Zhang (huxing.zhx@alibaba-inc.com)
137
 */
138
public abstract class WebappClassLoaderBase extends URLClassLoader
139
        implements Lifecycle, InstrumentableClassLoader {
140
141
    private static final org.apache.juli.logging.Log log=
142
            org.apache.juli.logging.LogFactory.getLog( WebappClassLoaderBase.class );
143
144
    private static final Charset CHARSET_UTF8 = Charset.forName("UTF-8");
145
    /**
146
     * List of ThreadGroup names to ignore when scanning for web application
147
     * started threads that need to be shut down.
148
     */
149
    private static final List<String> JVM_THREAD_GROUP_NAMES =
150
            new ArrayList<String>();
151
152
    private static final String JVM_THREAD_GROUP_SYSTEM = "system";
153
154
    private static final String SERVICES_PREFIX = "META-INF/services/";
155
156
    private static final String CLASS_FILE_SUFFIX = ".class";
157
158
    static {
159
        try {
160
            if (JreCompat.getInstance().isJre7Available()) {
161
                // parallel class loading capable
162
                final Method registerParallel =
163
                        ClassLoader.class.getDeclaredMethod("registerAsParallelCapable");
164
                AccessController.doPrivileged(new PrivilegedAction<Object>() {
165
                    public Object run() {
166
                        registerParallel.setAccessible(true);
167
                        return null;
168
                    }
169
                });
170
                registerParallel.invoke(null);
171
            }
172
        } catch (Exception e) {
173
            // ignore
174
        }
175
        JVM_THREAD_GROUP_NAMES.add(JVM_THREAD_GROUP_SYSTEM);
176
        JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
177
    }
178
179
180
    protected class PrivilegedFindResourceByName
181
            implements PrivilegedAction<ResourceEntry> {
182
183
        protected String name;
184
        protected String path;
185
        protected boolean manifestRequired;
186
187
        PrivilegedFindResourceByName(String name, String path, boolean manifestRequired) {
188
            this.name = name;
189
            this.path = path;
190
            this.manifestRequired = manifestRequired;
191
        }
192
193
        @Override
194
        public ResourceEntry run() {
195
            return findResourceInternal(name, path, manifestRequired);
196
        }
197
198
    }
199
200
201
    protected static final class PrivilegedGetClassLoader
202
            implements PrivilegedAction<ClassLoader> {
203
204
        public Class<?> clazz;
205
206
        public PrivilegedGetClassLoader(Class<?> clazz){
207
            this.clazz = clazz;
208
        }
209
210
        @Override
211
        public ClassLoader run() {
212
            return clazz.getClassLoader();
213
        }
214
    }
215
216
217
    // ------------------------------------------------------- Static Variables
218
219
220
    /**
221
     * The set of trigger classes that will cause a proposed repository not
222
     * to be added if this class is visible to the class loader that loaded
223
     * this factory class.  Typically, trigger classes will be listed for
224
     * components that have been integrated into the JDK for later versions,
225
     * but where the corresponding JAR files are required to run on
226
     * earlier versions.
227
     */
228
    protected static final String[] triggers = {
229
            "javax.servlet.Servlet", "javax.el.Expression"       // Servlet API
230
    };
231
232
233
    /**
234
     * Set of package names which are not allowed to be loaded from a webapp
235
     * class loader without delegating first.
236
     */
237
    protected static final String[] packageTriggers = {
238
    };
239
240
241
    /**
242
     * The string manager for this package.
243
     */
244
    protected static final StringManager sm =
245
            StringManager.getManager(Constants.Package);
246
247
248
    /**
249
     * Use anti JAR locking code, which does URL rerouting when accessing
250
     * resources.
251
     */
252
    boolean antiJARLocking = false;
253
254
    // ----------------------------------------------------------- Constructors
255
256
257
    /**
258
     * Construct a new ClassLoader with no defined repositories and no
259
     * parent ClassLoader.
260
     */
261
    public WebappClassLoaderBase() {
262
263
        super(new URL[0]);
264
265
        ClassLoader p = getParent();
266
        if (p == null) {
267
            p = getSystemClassLoader();
268
        }
269
        this.parent = p;
270
271
        ClassLoader j = String.class.getClassLoader();
272
        if (j == null) {
273
            j = getSystemClassLoader();
274
            while (j.getParent() != null) {
275
                j = j.getParent();
276
            }
277
        }
278
        this.j2seClassLoader = j;
279
280
        securityManager = System.getSecurityManager();
281
        if (securityManager != null) {
282
            refreshPolicy();
283
        }
284
    }
285
286
287
    /**
288
     * Construct a new ClassLoader with no defined repositories and the given
289
     * parent ClassLoader.
290
     * <p>
291
     * Method is used via reflection -
292
     * see {@link WebappLoader#createClassLoader()}
293
     *
294
     * @param parent Our parent class loader
295
     */
296
    public WebappClassLoaderBase(ClassLoader parent) {
297
298
        super(new URL[0], parent);
299
300
        ClassLoader p = getParent();
301
        if (p == null) {
302
            p = getSystemClassLoader();
303
        }
304
        this.parent = p;
305
306
        ClassLoader j = String.class.getClassLoader();
307
        if (j == null) {
308
            j = getSystemClassLoader();
309
            while (j.getParent() != null) {
310
                j = j.getParent();
311
            }
312
        }
313
        this.j2seClassLoader = j;
314
315
        securityManager = System.getSecurityManager();
316
        if (securityManager != null) {
317
            refreshPolicy();
318
        }
319
    }
320
321
322
    // ----------------------------------------------------- Instance Variables
323
324
325
    /**
326
     * Associated directory context giving access to the resources in this
327
     * webapp.
328
     */
329
    protected DirContext resources = null;
330
331
332
    /**
333
     * The cache of ResourceEntry for classes and resources we have loaded,
334
     * keyed by resource name.
335
     */
336
    protected HashMap<String, ResourceEntry> resourceEntries = new HashMap<String, ResourceEntry>();
337
338
339
    /**
340
     * The list of not found resources.
341
     */
342
    protected HashMap<String, String> notFoundResources =
343
            new LinkedHashMap<String, String>() {
344
                private static final long serialVersionUID = 1L;
345
                @Override
346
                protected boolean removeEldestEntry(
347
                        Map.Entry<String, String> eldest) {
348
                    return size() > 1000;
349
                }
350
            };
351
352
353
    /**
354
     * Should this class loader delegate to the parent class loader
355
     * <strong>before</strong> searching its own repositories (i.e. the
356
     * usual Java2 delegation model)?  If set to <code>false</code>,
357
     * this class loader will search its own repositories first, and
358
     * delegate to the parent only if the class or resource is not
359
     * found locally. Note that the default, <code>false</code>, is
360
     * the behavior called for by the servlet specification.
361
     */
362
    protected boolean delegate = false;
363
364
365
    /**
366
     * Last time a JAR was accessed.
367
     */
368
    protected long lastJarAccessed = 0L;
369
370
371
    /**
372
     * The list of local repositories, in the order they should be searched
373
     * for locally loaded classes or resources.
374
     */
375
    protected String[] repositories = new String[0];
376
377
378
    /**
379
     * Repositories URLs, used to cache the result of getURLs.
380
     */
381
    protected URL[] repositoryURLs = null;
382
383
384
    /**
385
     * Repositories translated as path in the work directory (for Jasper
386
     * originally), but which is used to generate fake URLs should getURLs be
387
     * called.
388
     */
389
    protected File[] files = new File[0];
390
391
392
    /**
393
     * The list of JARs, in the order they should be searched
394
     * for locally loaded classes or resources.
395
     */
396
    protected JarFile[] jarFiles = new JarFile[0];
397
398
399
    /**
400
     * The list of JARs, in the order they should be searched
401
     * for locally loaded classes or resources.
402
     */
403
    protected File[] jarRealFiles = new File[0];
404
405
406
    /**
407
     * The path which will be monitored for added Jar files.
408
     */
409
    protected String jarPath = null;
410
411
412
    /**
413
     * The list of JARs, in the order they should be searched
414
     * for locally loaded classes or resources.
415
     */
416
    protected String[] jarNames = new String[0];
417
418
419
    /**
420
     * The list of JARs last modified dates, in the order they should be
421
     * searched for locally loaded classes or resources.
422
     */
423
    protected long[] lastModifiedDates = new long[0];
424
425
426
    /**
427
     * The list of resources which should be checked when checking for
428
     * modifications.
429
     */
430
    protected String[] paths = new String[0];
431
432
433
    /**
434
     * A list of read File and Jndi Permission's required if this loader
435
     * is for a web application context.
436
     */
437
    protected ArrayList<Permission> permissionList =
438
            new ArrayList<Permission>();
439
440
441
    /**
442
     * Path where resources loaded from JARs will be extracted.
443
     */
444
    protected File loaderDir = null;
445
    protected String canonicalLoaderDir = null;
446
447
    /**
448
     * The PermissionCollection for each CodeSource for a web
449
     * application context.
450
     */
451
    protected HashMap<String, PermissionCollection> loaderPC = new HashMap<String, PermissionCollection>();
452
453
454
    /**
455
     * Instance of the SecurityManager installed.
456
     */
457
    protected SecurityManager securityManager = null;
458
459
460
    /**
461
     * The parent class loader.
462
     */
463
    protected ClassLoader parent = null;
464
465
466
    /**
467
     * The system class loader.
468
     */
469
    protected ClassLoader system = null;
470
471
472
    /**
473
     * The bootstrap class loader used to load the JavaSE classes. In some
474
     * implementations this class loader is always <code>null</null> and in
475
     * those cases {@link ClassLoader#getParent()} will be called recursively on
476
     * the system class loader and the last non-null result used.
477
     */
478
    protected ClassLoader j2seClassLoader;
479
480
481
    /**
482
     * Has this component been started?
483
     */
484
    protected boolean started = false;
485
486
487
    /**
488
     * Has external repositories.
489
     */
490
    protected boolean hasExternalRepositories = false;
491
492
    /**
493
     * Search external repositories first
494
     */
495
    protected boolean searchExternalFirst = false;
496
497
    /**
498
     * need conversion for properties files
499
     */
500
    protected boolean needConvert = false;
501
502
503
    /**
504
     * All permission.
505
     */
506
    protected Permission allPermission = new java.security.AllPermission();
507
508
509
    /**
510
     * Should Tomcat attempt to null out any static or final fields from loaded
511
     * classes when a web application is stopped as a work around for apparent
512
     * garbage collection bugs and application coding errors? There have been
513
     * some issues reported with log4j when this option is true. Applications
514
     * without memory leaks using recent JVMs should operate correctly with this
515
     * option set to <code>false</code>. If not specified, the default value of
516
     * <code>false</code> will be used.
517
     */
518
    private boolean clearReferencesStatic = false;
519
520
    /**
521
     * Should Tomcat attempt to terminate threads that have been started by the
522
     * web application? Stopping threads is performed via the deprecated (for
523
     * good reason) <code>Thread.stop()</code> method and is likely to result in
524
     * instability. As such, enabling this should be viewed as an option of last
525
     * resort in a development environment and is not recommended in a
526
     * production environment. If not specified, the default value of
527
     * <code>false</code> will be used.
528
     */
529
    private boolean clearReferencesStopThreads = false;
530
531
    /**
532
     * Should Tomcat attempt to terminate any {@link java.util.TimerThread}s
533
     * that have been started by the web application? If not specified, the
534
     * default value of <code>false</code> will be used.
535
     */
536
    private boolean clearReferencesStopTimerThreads = false;
537
538
    /**
539
     * Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()}
540
     * when the class loader is stopped? If not specified, the default value
541
     * of <code>true</code> is used. Changing the default setting is likely to
542
     * lead to memory leaks and other issues.
543
     */
544
    private boolean clearReferencesLogFactoryRelease = true;
545
546
    /**
547
     * If an HttpClient keep-alive timer thread has been started by this web
548
     * application and is still running, should Tomcat change the context class
549
     * loader from the current {@link WebappClassLoader} to
550
     * {@link WebappClassLoader#parent} to prevent a memory leak? Note that the
551
     * keep-alive timer thread will stop on its own once the keep-alives all
552
     * expire however, on a busy system that might not happen for some time.
553
     */
554
    private boolean clearReferencesHttpClientKeepAliveThread = true;
555
556
    /**
557
     * Name of associated context used with logging and JMX to associate with
558
     * the right web application. Particularly useful for the clear references
559
     * messages. Defaults to unknown but if standard Tomcat components are used
560
     * it will be updated during initialisation from the resources.
561
     */
562
    private String contextName = "unknown";
563
564
    /**
565
     * Holds the class file transformers decorating this class loader. The
566
     * CopyOnWriteArrayList is thread safe. It is expensive on writes, but
567
     * those should be rare. It is very fast on reads, since synchronization
568
     * is not actually used. Importantly, the ClassLoader will never block
569
     * iterating over the transformers while loading a class.
570
     */
571
    private final List<ClassFileTransformer> transformers = new CopyOnWriteArrayList<ClassFileTransformer>();
572
573
    /**
574
     * Code base to use for classes loaded from WEB-INF/classes.
575
     */
576
    private URL webInfClassesCodeBase = null;
577
578
    // ------------------------------------------------------------- Properties
579
580
581
    /**
582
     * Get associated resources.
583
     */
584
    public DirContext getResources() {
585
586
        return this.resources;
587
588
    }
589
590
591
    /**
592
     * Set associated resources.
593
     */
594
    public void setResources(DirContext resources) {
595
596
        this.resources = resources;
597
598
        if (resources instanceof ProxyDirContext) {
599
            contextName = ((ProxyDirContext) resources).getContextName();
600
        }
601
    }
602
603
604
    /**
605
     * Return the context name for this class loader.
606
     */
607
    public String getContextName() {
608
609
        return (this.contextName);
610
611
    }
612
613
614
    /**
615
     * Return the "delegate first" flag for this class loader.
616
     */
617
    public boolean getDelegate() {
618
619
        return (this.delegate);
620
621
    }
622
623
624
    /**
625
     * Set the "delegate first" flag for this class loader.
626
     * If this flag is true, this class loader delegates
627
     * to the parent class loader
628
     * <strong>before</strong> searching its own repositories, as
629
     * in an ordinary (non-servlet) chain of Java class loaders.
630
     * If set to <code>false</code> (the default),
631
     * this class loader will search its own repositories first, and
632
     * delegate to the parent only if the class or resource is not
633
     * found locally, as per the servlet specification.
634
     *
635
     * @param delegate The new "delegate first" flag
636
     */
637
    public void setDelegate(boolean delegate) {
638
639
        this.delegate = delegate;
640
641
    }
642
643
644
    /**
645
     * @return Returns the antiJARLocking.
646
     */
647
    public boolean getAntiJARLocking() {
648
        return antiJARLocking;
649
    }
650
651
652
    /**
653
     * @param antiJARLocking The antiJARLocking to set.
654
     */
655
    public void setAntiJARLocking(boolean antiJARLocking) {
656
        this.antiJARLocking = antiJARLocking;
657
    }
658
659
    /**
660
     * @return Returns the searchExternalFirst.
661
     */
662
    public boolean getSearchExternalFirst() {
663
        return searchExternalFirst;
664
    }
665
666
    /**
667
     * @param searchExternalFirst Whether external repositories should be searched first
668
     */
669
    public void setSearchExternalFirst(boolean searchExternalFirst) {
670
        this.searchExternalFirst = searchExternalFirst;
671
    }
672
673
674
    /**
675
     * If there is a Java SecurityManager create a read FilePermission
676
     * or JndiPermission for the file directory path.
677
     *
678
     * @param filepath file directory path
679
     */
680
    public void addPermission(String filepath) {
681
        if (filepath == null) {
682
            return;
683
        }
684
685
        String path = filepath;
686
687
        if (securityManager != null) {
688
            Permission permission = null;
689
            if (path.startsWith("jndi:") || path.startsWith("jar:jndi:")) {
690
                if (!path.endsWith("/")) {
691
                    path = path + "/";
692
                }
693
                permission = new JndiPermission(path + "*");
694
                addPermission(permission);
695
            } else {
696
                if (!path.endsWith(File.separator)) {
697
                    permission = new FilePermission(path, "read");
698
                    addPermission(permission);
699
                    path = path + File.separator;
700
                }
701
                permission = new FilePermission(path + "-", "read");
702
                addPermission(permission);
703
            }
704
        }
705
    }
706
707
708
    /**
709
     * If there is a Java SecurityManager create a read FilePermission
710
     * or JndiPermission for URL.
711
     *
712
     * @param url URL for a file or directory on local system
713
     */
714
    public void addPermission(URL url) {
715
        if (url != null) {
716
            addPermission(url.toString());
717
        }
718
    }
719
720
721
    /**
722
     * If there is a Java SecurityManager create a Permission.
723
     *
724
     * @param permission The permission
725
     */
726
    public void addPermission(Permission permission) {
727
        if ((securityManager != null) && (permission != null)) {
728
            permissionList.add(permission);
729
        }
730
    }
731
732
733
    /**
734
     * Return the JAR path.
735
     */
736
    public String getJarPath() {
737
738
        return this.jarPath;
739
740
    }
741
742
743
    /**
744
     * Change the Jar path.
745
     */
746
    public void setJarPath(String jarPath) {
747
748
        this.jarPath = jarPath;
749
750
    }
751
752
753
    /**
754
     * Change the work directory.
755
     */
756
    public void setWorkDir(File workDir) {
757
        this.loaderDir = new File(workDir, "loader");
758
        if (loaderDir == null) {
759
            canonicalLoaderDir = null;
760
        } else {
761
            try {
762
                canonicalLoaderDir = loaderDir.getCanonicalPath();
763
                if (!canonicalLoaderDir.endsWith(File.separator)) {
764
                    canonicalLoaderDir += File.separator;
765
                }
766
            } catch (IOException ioe) {
767
                canonicalLoaderDir = null;
768
            }
769
        }
770
    }
771
772
    /**
773
     * Utility method for use in subclasses.
774
     * Must be called before Lifecycle methods to have any effect.
775
     *
776
     * @deprecated Will be removed in 8.0.x onwards.
777
     */
778
    @Deprecated
779
    protected void setParentClassLoader(ClassLoader pcl) {
780
        parent = pcl;
781
    }
782
783
    /**
784
     * Return the clearReferencesStatic flag for this Context.
785
     */
786
    public boolean getClearReferencesStatic() {
787
        return (this.clearReferencesStatic);
788
    }
789
790
791
    /**
792
     * Set the clearReferencesStatic feature for this Context.
793
     *
794
     * @param clearReferencesStatic The new flag value
795
     */
796
    public void setClearReferencesStatic(boolean clearReferencesStatic) {
797
        this.clearReferencesStatic = clearReferencesStatic;
798
    }
799
800
801
    /**
802
     * Return the clearReferencesStopThreads flag for this Context.
803
     */
804
    public boolean getClearReferencesStopThreads() {
805
        return (this.clearReferencesStopThreads);
806
    }
807
808
809
    /**
810
     * Set the clearReferencesStopThreads feature for this Context.
811
     *
812
     * @param clearReferencesStopThreads The new flag value
813
     */
814
    public void setClearReferencesStopThreads(
815
            boolean clearReferencesStopThreads) {
816
        this.clearReferencesStopThreads = clearReferencesStopThreads;
817
    }
818
819
820
    /**
821
     * Return the clearReferencesStopTimerThreads flag for this Context.
822
     */
823
    public boolean getClearReferencesStopTimerThreads() {
824
        return (this.clearReferencesStopTimerThreads);
825
    }
826
827
828
    /**
829
     * Set the clearReferencesStopTimerThreads feature for this Context.
830
     *
831
     * @param clearReferencesStopTimerThreads The new flag value
832
     */
833
    public void setClearReferencesStopTimerThreads(
834
            boolean clearReferencesStopTimerThreads) {
835
        this.clearReferencesStopTimerThreads = clearReferencesStopTimerThreads;
836
    }
837
838
839
    /**
840
     * Return the clearReferencesLogFactoryRelease flag for this Context.
841
     */
842
    public boolean getClearReferencesLogFactoryRelease() {
843
        return (this.clearReferencesLogFactoryRelease);
844
    }
845
846
847
    /**
848
     * Set the clearReferencesLogFactoryRelease feature for this Context.
849
     *
850
     * @param clearReferencesLogFactoryRelease The new flag value
851
     */
852
    public void setClearReferencesLogFactoryRelease(
853
            boolean clearReferencesLogFactoryRelease) {
854
        this.clearReferencesLogFactoryRelease =
855
                clearReferencesLogFactoryRelease;
856
    }
857
858
859
    /**
860
     * Return the clearReferencesHttpClientKeepAliveThread flag for this
861
     * Context.
862
     */
863
    public boolean getClearReferencesHttpClientKeepAliveThread() {
864
        return (this.clearReferencesHttpClientKeepAliveThread);
865
    }
866
867
868
    /**
869
     * Set the clearReferencesHttpClientKeepAliveThread feature for this
870
     * Context.
871
     *
872
     * @param clearReferencesHttpClientKeepAliveThread The new flag value
873
     */
874
    public void setClearReferencesHttpClientKeepAliveThread(
875
            boolean clearReferencesHttpClientKeepAliveThread) {
876
        this.clearReferencesHttpClientKeepAliveThread =
877
                clearReferencesHttpClientKeepAliveThread;
878
    }
879
880
881
    // ------------------------------------------------------- Reloader Methods
882
883
    /**
884
     * Adds the specified class file transformer to this class loader. The
885
     * transformer will then be able to modify the bytecode of any classes
886
     * loaded by this class loader after the invocation of this method.
887
     *
888
     * @param transformer The transformer to add to the class loader
889
     */
890
    @Override
891
    public void addTransformer(ClassFileTransformer transformer) {
892
893
        if (transformer == null) {
894
            throw new IllegalArgumentException(sm.getString(
895
                    "webappClassLoader.addTransformer.illegalArgument", getContextName()));
896
        }
897
898
        if (this.transformers.contains(transformer)) {
899
            // if the same instance of this transformer was already added, bail out
900
            log.warn(sm.getString("webappClassLoader.addTransformer.duplicate",
901
                    transformer, getContextName()));
902
            return;
903
        }
904
        this.transformers.add(transformer);
905
906
        log.info(sm.getString("webappClassLoader.addTransformer", transformer, getContextName()));
907
908
    }
909
910
    /**
911
     * Removes the specified class file transformer from this class loader.
912
     * It will no longer be able to modify the byte code of any classes
913
     * loaded by the class loader after the invocation of this method.
914
     * However, any classes already modified by this transformer will
915
     * remain transformed.
916
     *
917
     * @param transformer The transformer to remove
918
     */
919
    @Override
920
    public void removeTransformer(ClassFileTransformer transformer) {
921
922
        if (transformer == null) {
923
            return;
924
        }
925
926
        if (this.transformers.remove(transformer)) {
927
            log.info(sm.getString("webappClassLoader.removeTransformer",
928
                    transformer, getContextName()));
929
            return;
930
        }
931
932
    }
933
934
    protected void copyStateWithoutTransformers(WebappClassLoaderBase base) {
935
        base.antiJARLocking = this.antiJARLocking;
936
        base.resources = this.resources;
937
        base.files = this.files;
938
        base.delegate = this.delegate;
939
        base.lastJarAccessed = this.lastJarAccessed;
940
        base.repositories = this.repositories;
941
        base.jarPath = this.jarPath;
942
        base.loaderDir = this.loaderDir;
943
        base.canonicalLoaderDir = this.canonicalLoaderDir;
944
        base.clearReferencesStatic = this.clearReferencesStatic;
945
        base.clearReferencesStopThreads = this.clearReferencesStopThreads;
946
        base.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads;
947
        base.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease;
948
        base.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread;
949
        base.repositoryURLs = this.repositoryURLs.clone();
950
        base.jarFiles = this.jarFiles.clone();
951
        base.jarRealFiles = this.jarRealFiles.clone();
952
        base.jarNames = this.jarNames.clone();
953
        base.lastModifiedDates = this.lastModifiedDates.clone();
954
        base.paths = this.paths.clone();
955
        base.notFoundResources.putAll(this.notFoundResources);
956
        base.permissionList.addAll(this.permissionList);
957
        base.loaderPC.putAll(this.loaderPC);
958
        base.contextName = this.contextName;
959
        base.hasExternalRepositories = this.hasExternalRepositories;
960
        base.searchExternalFirst = this.searchExternalFirst;
961
    }
962
963
    /**
964
     * Add a new repository to the set of places this ClassLoader can look for
965
     * classes to be loaded.
966
     *
967
     * @param repository Name of a source of classes to be loaded, such as a
968
     *  directory pathname, a JAR file pathname, or a ZIP file pathname
969
     *
970
     * @exception IllegalArgumentException if the specified repository is
971
     *  invalid or does not exist
972
     */
973
    public void addRepository(String repository) {
974
975
        // Ignore any of the standard repositories, as they are set up using
976
        // either addJar or addRepository
977
        if (repository.startsWith("/WEB-INF/lib")
978
                || repository.startsWith("/WEB-INF/classes"))
979
            return;
980
981
        // Add this repository to our underlying class loader
982
        try {
983
            URL url = new URL(repository);
984
            super.addURL(url);
985
            hasExternalRepositories = true;
986
            repositoryURLs = null;
987
        } catch (MalformedURLException e) {
988
            IllegalArgumentException iae = new IllegalArgumentException
989
                    ("Invalid repository: " + repository);
990
            iae.initCause(e);
991
            throw iae;
992
        }
993
994
    }
995
996
997
    /**
998
     * Add a new repository to the set of places this ClassLoader can look for
999
     * classes to be loaded.
1000
     *
1001
     * @param repository Name of a source of classes to be loaded, such as a
1002
     *  directory pathname, a JAR file pathname, or a ZIP file pathname
1003
     *
1004
     * @exception IllegalArgumentException if the specified repository is
1005
     *  invalid or does not exist
1006
     */
1007
    synchronized void addRepository(String repository, File file) {
1008
1009
        // Note : There should be only one (of course), but I think we should
1010
        // keep this a bit generic
1011
1012
        if (repository == null)
1013
            return;
1014
1015
        if (log.isDebugEnabled())
1016
            log.debug("addRepository(" + repository + ")");
1017
1018
        int i;
1019
1020
        // Add this repository to our internal list
1021
        String[] result = new String[repositories.length + 1];
1022
        for (i = 0; i < repositories.length; i++) {
1023
            result[i] = repositories[i];
1024
        }
1025
        result[repositories.length] = repository;
1026
        repositories = result;
1027
1028
        // Add the file to the list
1029
        File[] result2 = new File[files.length + 1];
1030
        for (i = 0; i < files.length; i++) {
1031
            result2[i] = files[i];
1032
        }
1033
        result2[files.length] = file;
1034
        files = result2;
1035
1036
    }
1037
1038
1039
    synchronized void addJar(String jar, JarFile jarFile, File file)
1040
            throws IOException {
1041
1042
        if (jar == null)
1043
            return;
1044
        if (jarFile == null)
1045
            return;
1046
        if (file == null)
1047
            return;
1048
1049
        if (log.isDebugEnabled())
1050
            log.debug("addJar(" + jar + ")");
1051
1052
        int i;
1053
1054
        if ((jarPath != null) && (jar.startsWith(jarPath))) {
1055
1056
            String jarName = jar.substring(jarPath.length());
1057
            while (jarName.startsWith("/"))
1058
                jarName = jarName.substring(1);
1059
1060
            String[] result = new String[jarNames.length + 1];
1061
            for (i = 0; i < jarNames.length; i++) {
1062
                result[i] = jarNames[i];
1063
            }
1064
            result[jarNames.length] = jarName;
1065
            jarNames = result;
1066
1067
        }
1068
1069
        try {
1070
1071
            // Register the JAR for tracking
1072
1073
            long lastModified =
1074
                    ((ResourceAttributes) resources.getAttributes(jar))
1075
                            .getLastModified();
1076
1077
            String[] result = new String[paths.length + 1];
1078
            for (i = 0; i < paths.length; i++) {
1079
                result[i] = paths[i];
1080
            }
1081
            result[paths.length] = jar;
1082
            paths = result;
1083
1084
            long[] result3 = new long[lastModifiedDates.length + 1];
1085
            for (i = 0; i < lastModifiedDates.length; i++) {
1086
                result3[i] = lastModifiedDates[i];
1087
            }
1088
            result3[lastModifiedDates.length] = lastModified;
1089
            lastModifiedDates = result3;
1090
1091
        } catch (NamingException e) {
1092
            // Ignore
1093
        }
1094
1095
        // If the JAR currently contains invalid classes, don't actually use it
1096
        // for classloading
1097
        if (!validateJarFile(file))
1098
            return;
1099
1100
        JarFile[] result2 = new JarFile[jarFiles.length + 1];
1101
        for (i = 0; i < jarFiles.length; i++) {
1102
            result2[i] = jarFiles[i];
1103
        }
1104
        result2[jarFiles.length] = jarFile;
1105
        jarFiles = result2;
1106
1107
        // Add the file to the list
1108
        File[] result4 = new File[jarRealFiles.length + 1];
1109
        for (i = 0; i < jarRealFiles.length; i++) {
1110
            result4[i] = jarRealFiles[i];
1111
        }
1112
        result4[jarRealFiles.length] = file;
1113
        jarRealFiles = result4;
1114
    }
1115
1116
1117
    /**
1118
     * Return a String array of the current repositories for this class
1119
     * loader.  If there are no repositories, a zero-length array is
1120
     * returned.For security reason, returns a clone of the Array (since
1121
     * String are immutable).
1122
     */
1123
    public String[] findRepositories() {
1124
1125
        return (repositories.clone());
1126
1127
    }
1128
1129
1130
    /**
1131
     * Have one or more classes or resources been modified so that a reload
1132
     * is appropriate?
1133
     */
1134
    public boolean modified() {
1135
1136
        if (log.isDebugEnabled())
1137
            log.debug("modified()");
1138
1139
        // Checking for modified loaded resources
1140
        int length = paths.length;
1141
1142
        // A rare race condition can occur in the updates of the two arrays
1143
        // It's totally ok if the latest class added is not checked (it will
1144
        // be checked the next time
1145
        int length2 = lastModifiedDates.length;
1146
        if (length > length2)
1147
            length = length2;
1148
1149
        for (int i = 0; i < length; i++) {
1150
            try {
1151
                long lastModified =
1152
                        ((ResourceAttributes) resources.getAttributes(paths[i]))
1153
                                .getLastModified();
1154
                if (lastModified != lastModifiedDates[i]) {
1155
                    if( log.isDebugEnabled() )
1156
                        log.debug("  Resource '" + paths[i]
1157
                                + "' was modified; Date is now: "
1158
                                + new java.util.Date(lastModified) + " Was: "
1159
                                + new java.util.Date(lastModifiedDates[i]));
1160
                    return (true);
1161
                }
1162
            } catch (NamingException e) {
1163
                log.error("    Resource '" + paths[i] + "' is missing");
1164
                return (true);
1165
            }
1166
        }
1167
1168
        length = jarNames.length;
1169
1170
        // Check if JARs have been added or removed
1171
        if (getJarPath() != null) {
1172
1173
            try {
1174
                NamingEnumeration<Binding> enumeration =
1175
                        resources.listBindings(getJarPath());
1176
                int i = 0;
1177
                while (enumeration.hasMoreElements() && (i < length)) {
1178
                    NameClassPair ncPair = enumeration.nextElement();
1179
                    String name = ncPair.getName();
1180
                    // Ignore non JARs present in the lib folder
1181
                    if (!name.endsWith(".jar"))
1182
                        continue;
1183
                    if (!name.equals(jarNames[i])) {
1184
                        // Missing JAR
1185
                        log.info("    Additional JARs have been added : '"
1186
                                + name + "'");
1187
                        return (true);
1188
                    }
1189
                    i++;
1190
                }
1191
                if (enumeration.hasMoreElements()) {
1192
                    while (enumeration.hasMoreElements()) {
1193
                        NameClassPair ncPair = enumeration.nextElement();
1194
                        String name = ncPair.getName();
1195
                        // Additional non-JAR files are allowed
1196
                        if (name.endsWith(".jar")) {
1197
                            // There was more JARs
1198
                            log.info("    Additional JARs have been added");
1199
                            return (true);
1200
                        }
1201
                    }
1202
                } else if (i < jarNames.length) {
1203
                    // There was less JARs
1204
                    log.info("    Additional JARs have been added");
1205
                    return (true);
1206
                }
1207
            } catch (NamingException e) {
1208
                if (log.isDebugEnabled())
1209
                    log.debug("    Failed tracking modifications of '"
1210
                            + getJarPath() + "'");
1211
            } catch (ClassCastException e) {
1212
                log.error("    Failed tracking modifications of '"
1213
                        + getJarPath() + "' : " + e.getMessage());
1214
            }
1215
1216
        }
1217
1218
        // No classes have been modified
1219
        return (false);
1220
1221
    }
1222
1223
1224
    /**
1225
     * Render a String representation of this object.
1226
     */
1227
    @Override
1228
    public String toString() {
1229
1230
        StringBuilder sb = new StringBuilder("WebappClassLoader\r\n");
1231
        sb.append("  context: ");
1232
        sb.append(contextName);
1233
        sb.append("\r\n");
1234
        sb.append("  delegate: ");
1235
        sb.append(delegate);
1236
        sb.append("\r\n");
1237
        sb.append("  repositories:\r\n");
1238
        if (repositories != null) {
1239
            for (int i = 0; i < repositories.length; i++) {
1240
                sb.append("    ");
1241
                sb.append(repositories[i]);
1242
                sb.append("\r\n");
1243
            }
1244
        }
1245
        if (this.parent != null) {
1246
            sb.append("----------> Parent Classloader:\r\n");
1247
            sb.append(this.parent.toString());
1248
            sb.append("\r\n");
1249
        }
1250
        if (this.transformers.size() > 0) {
1251
            sb.append("----------> Class file transformers:\r\n");
1252
            for (ClassFileTransformer transformer : this.transformers) {
1253
                sb.append(transformer).append("\r\n");
1254
            }
1255
        }
1256
        return (sb.toString());
1257
1258
    }
1259
1260
1261
    // ---------------------------------------------------- ClassLoader Methods
1262
1263
1264
    /**
1265
     * Add the specified URL to the classloader.
1266
     */
1267
    @Override
1268
    protected void addURL(URL url) {
1269
        super.addURL(url);
1270
        hasExternalRepositories = true;
1271
        repositoryURLs = null;
1272
    }
1273
1274
1275
    /**
1276
     * Expose this method for use by the unit tests.
1277
     */
1278
    protected final Class<?> doDefineClass(String name, byte[] b, int off, int len,
1279
                                           ProtectionDomain protectionDomain) {
1280
        return super.defineClass(name, b, off, len, protectionDomain);
1281
    }
1282
1283
    /**
1284
     * Find the specified class in our local repositories, if possible.  If
1285
     * not found, throw <code>ClassNotFoundException</code>.
1286
     *
1287
     * @param name Name of the class to be loaded
1288
     *
1289
     * @exception ClassNotFoundException if the class was not found
1290
     */
1291
    @Override
1292
    public Class<?> findClass(String name) throws ClassNotFoundException {
1293
1294
        if (log.isDebugEnabled())
1295
            log.debug("    findClass(" + name + ")");
1296
1297
        // Cannot load anything from local repositories if class loader is stopped
1298
        if (!started) {
1299
            throw new ClassNotFoundException(name);
1300
        }
1301
1302
        // (1) Permission to define this class when using a SecurityManager
1303
        if (securityManager != null) {
1304
            int i = name.lastIndexOf('.');
1305
            if (i >= 0) {
1306
                try {
1307
                    if (log.isTraceEnabled())
1308
                        log.trace("      securityManager.checkPackageDefinition");
1309
                    securityManager.checkPackageDefinition(name.substring(0,i));
1310
                } catch (Exception se) {
1311
                    if (log.isTraceEnabled())
1312
                        log.trace("      -->Exception-->ClassNotFoundException", se);
1313
                    throw new ClassNotFoundException(name, se);
1314
                }
1315
            }
1316
        }
1317
1318
        // Ask our superclass to locate this class, if possible
1319
        // (throws ClassNotFoundException if it is not found)
1320
        Class<?> clazz = null;
1321
        try {
1322
            if (log.isTraceEnabled())
1323
                log.trace("      findClassInternal(" + name + ")");
1324
            if (hasExternalRepositories && searchExternalFirst) {
1325
                try {
1326
                    clazz = super.findClass(name);
1327
                } catch(ClassNotFoundException cnfe) {
1328
                    // Ignore - will search internal repositories next
1329
                } catch(AccessControlException ace) {
1330
                    log.warn("WebappClassLoader.findClassInternal(" + name
1331
                            + ") security exception: " + ace.getMessage(), ace);
1332
                    throw new ClassNotFoundException(name, ace);
1333
                } catch (RuntimeException e) {
1334
                    if (log.isTraceEnabled())
1335
                        log.trace("      -->RuntimeException Rethrown", e);
1336
                    throw e;
1337
                }
1338
            }
1339
            if ((clazz == null)) {
1340
                try {
1341
                    clazz = findClassInternal(name);
1342
                } catch(ClassNotFoundException cnfe) {
1343
                    if (!hasExternalRepositories || searchExternalFirst) {
1344
                        throw cnfe;
1345
                    }
1346
                } catch(AccessControlException ace) {
1347
                    log.warn("WebappClassLoader.findClassInternal(" + name
1348
                            + ") security exception: " + ace.getMessage(), ace);
1349
                    throw new ClassNotFoundException(name, ace);
1350
                } catch (RuntimeException e) {
1351
                    if (log.isTraceEnabled())
1352
                        log.trace("      -->RuntimeException Rethrown", e);
1353
                    throw e;
1354
                }
1355
            }
1356
            if ((clazz == null) && hasExternalRepositories && !searchExternalFirst) {
1357
                try {
1358
                    clazz = super.findClass(name);
1359
                } catch(AccessControlException ace) {
1360
                    log.warn("WebappClassLoader.findClassInternal(" + name
1361
                            + ") security exception: " + ace.getMessage(), ace);
1362
                    throw new ClassNotFoundException(name, ace);
1363
                } catch (RuntimeException e) {
1364
                    if (log.isTraceEnabled())
1365
                        log.trace("      -->RuntimeException Rethrown", e);
1366
                    throw e;
1367
                }
1368
            }
1369
            if (clazz == null) {
1370
                if (log.isDebugEnabled())
1371
                    log.debug("    --> Returning ClassNotFoundException");
1372
                throw new ClassNotFoundException(name);
1373
            }
1374
        } catch (ClassNotFoundException e) {
1375
            if (log.isTraceEnabled())
1376
                log.trace("    --> Passing on ClassNotFoundException");
1377
            throw e;
1378
        }
1379
1380
        // Return the class we have located
1381
        if (log.isTraceEnabled())
1382
            log.debug("      Returning class " + clazz);
1383
1384
        if (log.isTraceEnabled()) {
1385
            ClassLoader cl;
1386
            if (Globals.IS_SECURITY_ENABLED){
1387
                cl = AccessController.doPrivileged(
1388
                        new PrivilegedGetClassLoader(clazz));
1389
            } else {
1390
                cl = clazz.getClassLoader();
1391
            }
1392
            log.debug("      Loaded by " + cl.toString());
1393
        }
1394
        return (clazz);
1395
1396
    }
1397
1398
1399
    /**
1400
     * Find the specified resource in our local repository, and return a
1401
     * <code>URL</code> referring to it, or <code>null</code> if this resource
1402
     * cannot be found.
1403
     *
1404
     * @param name Name of the resource to be found
1405
     */
1406
    @Override
1407
    public URL findResource(final String name) {
1408
1409
        if (log.isDebugEnabled())
1410
            log.debug("    findResource(" + name + ")");
1411
1412
        URL url = null;
1413
1414
        if (hasExternalRepositories && searchExternalFirst)
1415
            url = super.findResource(name);
1416
1417
        if (url == null) {
1418
            ResourceEntry entry = resourceEntries.get(name);
1419
            if (entry == null) {
1420
                if (securityManager != null) {
1421
                    PrivilegedAction<ResourceEntry> dp =
1422
                            new PrivilegedFindResourceByName(name, name, false);
1423
                    entry = AccessController.doPrivileged(dp);
1424
                } else {
1425
                    entry = findResourceInternal(name, name, false);
1426
                }
1427
            }
1428
            if (entry != null) {
1429
                url = entry.source;
1430
            }
1431
        }
1432
1433
        if ((url == null) && hasExternalRepositories && !searchExternalFirst)
1434
            url = super.findResource(name);
1435
1436
        if (log.isDebugEnabled()) {
1437
            if (url != null)
1438
                log.debug("    --> Returning '" + url.toString() + "'");
1439
            else
1440
                log.debug("    --> Resource not found, returning null");
1441
        }
1442
        return (url);
1443
1444
    }
1445
1446
1447
    /**
1448
     * Return an enumeration of <code>URLs</code> representing all of the
1449
     * resources with the given name.  If no resources with this name are
1450
     * found, return an empty enumeration.
1451
     *
1452
     * @param name Name of the resources to be found
1453
     *
1454
     * @exception IOException if an input/output error occurs
1455
     */
1456
    @Override
1457
    public Enumeration<URL> findResources(String name) throws IOException {
1458
1459
        if (log.isDebugEnabled())
1460
            log.debug("    findResources(" + name + ")");
1461
1462
        //we use a LinkedHashSet instead of a Vector to avoid duplicates with virtualmappings
1463
        LinkedHashSet<URL> result = new LinkedHashSet<URL>();
1464
1465
        int jarFilesLength = jarFiles.length;
1466
        int repositoriesLength = repositories.length;
1467
1468
        int i;
1469
1470
        // Adding the results of a call to the superclass
1471
        if (hasExternalRepositories && searchExternalFirst) {
1472
1473
            Enumeration<URL> otherResourcePaths = super.findResources(name);
1474
1475
            while (otherResourcePaths.hasMoreElements()) {
1476
                result.add(otherResourcePaths.nextElement());
1477
            }
1478
1479
        }
1480
        // Looking at the repositories
1481
        for (i = 0; i < repositoriesLength; i++) {
1482
            try {
1483
                String fullPath = repositories[i] + name;
1484
                resources.lookup(fullPath);
1485
                // Note : Not getting an exception here means the resource was
1486
                // found
1487
                try {
1488
                    result.add(getURI(new File(files[i], name)));
1489
                } catch (MalformedURLException e) {
1490
                    // Ignore
1491
                }
1492
            } catch (NamingException e) {
1493
                // Ignore
1494
            }
1495
        }
1496
1497
        // Looking at the JAR files
1498
        synchronized (jarFiles) {
1499
            if (openJARs()) {
1500
                for (i = 0; i < jarFilesLength; i++) {
1501
                    JarEntry jarEntry = jarFiles[i].getJarEntry(name);
1502
                    if (jarEntry != null) {
1503
                        try {
1504
                            String jarFakeUrl = getURI(jarRealFiles[i]).toString();
1505
                            jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name;
1506
                            result.add(new URL(jarFakeUrl));
1507
                        } catch (MalformedURLException e) {
1508
                            // Ignore
1509
                        }
1510
                    }
1511
                }
1512
            }
1513
        }
1514
1515
        // Adding the results of a call to the superclass
1516
        if (hasExternalRepositories && !searchExternalFirst) {
1517
1518
            Enumeration<URL> otherResourcePaths = super.findResources(name);
1519
1520
            while (otherResourcePaths.hasMoreElements()) {
1521
                result.add(otherResourcePaths.nextElement());
1522
            }
1523
1524
        }
1525
1526
        return Collections.enumeration(result);
1527
    }
1528
1529
1530
    /**
1531
     * Find the resource with the given name.  A resource is some data
1532
     * (images, audio, text, etc.) that can be accessed by class code in a
1533
     * way that is independent of the location of the code.  The name of a
1534
     * resource is a "/"-separated path name that identifies the resource.
1535
     * If the resource cannot be found, return <code>null</code>.
1536
     * <p>
1537
     * This method searches according to the following algorithm, returning
1538
     * as soon as it finds the appropriate URL.  If the resource cannot be
1539
     * found, returns <code>null</code>.
1540
     * <ul>
1541
     * <li>If the <code>delegate</code> property is set to <code>true</code>,
1542
     *     call the <code>getResource()</code> method of the parent class
1543
     *     loader, if any.</li>
1544
     * <li>Call <code>findResource()</code> to find this resource in our
1545
     *     locally defined repositories.</li>
1546
     * <li>Call the <code>getResource()</code> method of the parent class
1547
     *     loader, if any.</li>
1548
     * </ul>
1549
     *
1550
     * @param name Name of the resource to return a URL for
1551
     */
1552
    @Override
1553
    public URL getResource(String name) {
1554
1555
        if (log.isDebugEnabled())
1556
            log.debug("getResource(" + name + ")");
1557
        URL url = null;
1558
1559
        // (1) Delegate to parent if requested
1560
        if (delegate) {
1561
            if (log.isDebugEnabled())
1562
                log.debug("  Delegating to parent classloader " + parent);
1563
            url = parent.getResource(name);
1564
            if (url != null) {
1565
                if (log.isDebugEnabled())
1566
                    log.debug("  --> Returning '" + url.toString() + "'");
1567
                return (url);
1568
            }
1569
        }
1570
1571
        // (2) Search local repositories
1572
        url = findResource(name);
1573
        if (url != null) {
1574
            // Locating the repository for special handling in the case
1575
            // of a JAR
1576
            if (antiJARLocking) {
1577
                ResourceEntry entry = resourceEntries.get(name);
1578
                try {
1579
                    String repository = entry.codeBase.toString();
1580
                    if ((repository.endsWith(".jar"))
1581
                            && (!(name.endsWith(CLASS_FILE_SUFFIX)))) {
1582
                        // Copy binary content to the work directory if not present
1583
                        File resourceFile = new File(loaderDir, name);
1584
                        url = getURI(resourceFile);
1585
                    }
1586
                } catch (Exception e) {
1587
                    // Ignore
1588
                }
1589
            }
1590
            if (log.isDebugEnabled())
1591
                log.debug("  --> Returning '" + url.toString() + "'");
1592
            return (url);
1593
        }
1594
1595
        // (3) Delegate to parent unconditionally if not already attempted
1596
        if( !delegate ) {
1597
            url = parent.getResource(name);
1598
            if (url != null) {
1599
                if (log.isDebugEnabled())
1600
                    log.debug("  --> Returning '" + url.toString() + "'");
1601
                return (url);
1602
            }
1603
        }
1604
1605
        // (4) Resource was not found
1606
        if (log.isDebugEnabled())
1607
            log.debug("  --> Resource not found, returning null");
1608
        return (null);
1609
1610
    }
1611
1612
1613
    /**
1614
     * Find the resource with the given name, and return an input stream
1615
     * that can be used for reading it.  The search order is as described
1616
     * for <code>getResource()</code>, after checking to see if the resource
1617
     * data has been previously cached.  If the resource cannot be found,
1618
     * return <code>null</code>.
1619
     *
1620
     * @param name Name of the resource to return an input stream for
1621
     */
1622
    @Override
1623
    public InputStream getResourceAsStream(String name) {
1624
1625
        if (log.isDebugEnabled())
1626
            log.debug("getResourceAsStream(" + name + ")");
1627
        InputStream stream = null;
1628
1629
        // (0) Check for a cached copy of this resource
1630
        stream = findLoadedResource(name);
1631
        if (stream != null) {
1632
            if (log.isDebugEnabled())
1633
                log.debug("  --> Returning stream from cache");
1634
            return (stream);
1635
        }
1636
1637
        // (1) Delegate to parent if requested
1638
        if (delegate) {
1639
            if (log.isDebugEnabled())
1640
                log.debug("  Delegating to parent classloader " + parent);
1641
            stream = parent.getResourceAsStream(name);
1642
            if (stream != null) {
1643
                // FIXME - cache???
1644
                if (log.isDebugEnabled())
1645
                    log.debug("  --> Returning stream from parent");
1646
                return (stream);
1647
            }
1648
        }
1649
1650
        // (2) Search local repositories
1651
        if (log.isDebugEnabled())
1652
            log.debug("  Searching local repositories");
1653
        URL url = findResource(name);
1654
        if (url != null) {
1655
            // FIXME - cache???
1656
            if (log.isDebugEnabled())
1657
                log.debug("  --> Returning stream from local");
1658
            stream = findLoadedResource(name);
1659
            try {
1660
                if (hasExternalRepositories && (stream == null))
1661
                    stream = url.openStream();
1662
            } catch (IOException e) {
1663
                // Ignore
1664
            }
1665
            if (stream != null)
1666
                return (stream);
1667
        }
1668
1669
        // (3) Delegate to parent unconditionally
1670
        if (!delegate) {
1671
            if (log.isDebugEnabled())
1672
                log.debug("  Delegating to parent classloader unconditionally " + parent);
1673
            stream = parent.getResourceAsStream(name);
1674
            if (stream != null) {
1675
                // FIXME - cache???
1676
                if (log.isDebugEnabled())
1677
                    log.debug("  --> Returning stream from parent");
1678
                return (stream);
1679
            }
1680
        }
1681
1682
        // (4) Resource was not found
1683
        if (log.isDebugEnabled())
1684
            log.debug("  --> Resource not found, returning null");
1685
        return (null);
1686
1687
    }
1688
1689
1690
    /**
1691
     * Load the class with the specified name.  This method searches for
1692
     * classes in the same manner as <code>loadClass(String, boolean)</code>
1693
     * with <code>false</code> as the second argument.
1694
     *
1695
     * @param name Name of the class to be loaded
1696
     *
1697
     * @exception ClassNotFoundException if the class was not found
1698
     */
1699
    @Override
1700
    public Class<?> loadClass(String name) throws ClassNotFoundException {
1701
1702
        return (loadClass(name, false));
1703
1704
    }
1705
1706
1707
    /**
1708
     * Load the class with the specified name, searching using the following
1709
     * algorithm until it finds and returns the class.  If the class cannot
1710
     * be found, returns <code>ClassNotFoundException</code>.
1711
     * <ul>
1712
     * <li>Call <code>findLoadedClass(String)</code> to check if the
1713
     *     class has already been loaded.  If it has, the same
1714
     *     <code>Class</code> object is returned.</li>
1715
     * <li>If the <code>delegate</code> property is set to <code>true</code>,
1716
     *     call the <code>loadClass()</code> method of the parent class
1717
     *     loader, if any.</li>
1718
     * <li>Call <code>findClass()</code> to find this class in our locally
1719
     *     defined repositories.</li>
1720
     * <li>Call the <code>loadClass()</code> method of our parent
1721
     *     class loader, if any.</li>
1722
     * </ul>
1723
     * If the class was found using the above steps, and the
1724
     * <code>resolve</code> flag is <code>true</code>, this method will then
1725
     * call <code>resolveClass(Class)</code> on the resulting Class object.
1726
     *
1727
     * @param name Name of the class to be loaded
1728
     * @param resolve If <code>true</code> then resolve the class
1729
     *
1730
     * @exception ClassNotFoundException if the class was not found
1731
     */
1732
    @Override
1733
    public Class<?> loadClass(String name, boolean resolve)
1734
            throws ClassNotFoundException {
1735
1736
        synchronized (getClassLoadingLockInternal(name)) {
1737
            if (log.isDebugEnabled())
1738
                log.debug("loadClass(" + name + ", " + resolve + ")");
1739
            Class<?> clazz = null;
1740
1741
            // Log access to stopped classloader
1742
            if (!started) {
1743
                try {
1744
                    throw new IllegalStateException();
1745
                } catch (IllegalStateException e) {
1746
                    log.info(sm.getString("webappClassLoader.stopped", name), e);
1747
                }
1748
            }
1749
1750
            // (0) Check our previously loaded local class cache
1751
            clazz = findLoadedClass0(name);
1752
            if (clazz != null) {
1753
                if (log.isDebugEnabled())
1754
                    log.debug("  Returning class from cache");
1755
                if (resolve)
1756
                    resolveClass(clazz);
1757
                return (clazz);
1758
            }
1759
1760
            // (0.1) Check our previously loaded class cache
1761
            clazz = findLoadedClass(name);
1762
            if (clazz != null) {
1763
                if (log.isDebugEnabled())
1764
                    log.debug("  Returning class from cache");
1765
                if (resolve)
1766
                    resolveClass(clazz);
1767
                return (clazz);
1768
            }
1769
1770
            // (0.2) Try loading the class with the system class loader, to prevent
1771
            //       the webapp from overriding J2SE classes
1772
            try {
1773
                clazz = j2seClassLoader.loadClass(name);
1774
                if (clazz != null) {
1775
                    if (resolve)
1776
                        resolveClass(clazz);
1777
                    return (clazz);
1778
                }
1779
            } catch (ClassNotFoundException e) {
1780
                // Ignore
1781
            }
1782
1783
            // (0.5) Permission to access this class when using a SecurityManager
1784
            if (securityManager != null) {
1785
                int i = name.lastIndexOf('.');
1786
                if (i >= 0) {
1787
                    try {
1788
                        securityManager.checkPackageAccess(name.substring(0, i));
1789
                    } catch (SecurityException se) {
1790
                        String error = "Security Violation, attempt to use " +
1791
                                "Restricted Class: " + name;
1792
                        log.info(error, se);
1793
                        throw new ClassNotFoundException(error, se);
1794
                    }
1795
                }
1796
            }
1797
1798
            boolean delegateLoad = delegate || filter(name);
1799
1800
            // (1) Delegate to our parent if requested
1801
            if (delegateLoad) {
1802
                if (log.isDebugEnabled())
1803
                    log.debug("  Delegating to parent classloader1 " + parent);
1804
                try {
1805
                    clazz = Class.forName(name, false, parent);
1806
                    if (clazz != null) {
1807
                        if (log.isDebugEnabled())
1808
                            log.debug("  Loading class from parent");
1809
                        if (resolve)
1810
                            resolveClass(clazz);
1811
                        return (clazz);
1812
                    }
1813
                } catch (ClassNotFoundException e) {
1814
                    // Ignore
1815
                }
1816
            }
1817
1818
            // (2) Search local repositories
1819
            if (log.isDebugEnabled())
1820
                log.debug("  Searching local repositories");
1821
            try {
1822
                clazz = findClass(name);
1823
                if (clazz != null) {
1824
                    if (log.isDebugEnabled())
1825
                        log.debug("  Loading class from local repository");
1826
                    if (resolve)
1827
                        resolveClass(clazz);
1828
                    return (clazz);
1829
                }
1830
            } catch (ClassNotFoundException e) {
1831
                // Ignore
1832
            }
1833
1834
            // (3) Delegate to parent unconditionally
1835
            if (!delegateLoad) {
1836
                if (log.isDebugEnabled())
1837
                    log.debug("  Delegating to parent classloader at end: " + parent);
1838
                try {
1839
                    clazz = Class.forName(name, false, parent);
1840
                    if (clazz != null) {
1841
                        if (log.isDebugEnabled())
1842
                            log.debug("  Loading class from parent");
1843
                        if (resolve)
1844
                            resolveClass(clazz);
1845
                        return (clazz);
1846
                    }
1847
                } catch (ClassNotFoundException e) {
1848
                    // Ignore
1849
                }
1850
            }
1851
        }
1852
1853
        throw new ClassNotFoundException(name);
1854
    }
1855
1856
    protected Object getClassLoadingLockInternal(String className) {
1857
        Object lock = this;
1858
        if (JreCompat.getInstance().isJre7Available()) {
1859
            try {
1860
                Method getClassLoadingLock =
1861
                        ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class);
1862
                lock = getClassLoadingLock.invoke(this, className);
1863
            } catch (Exception e) {
1864
                // ignore
1865
            }
1866
        }
1867
        return lock;
1868
    }
1869
1870
1871
    /**
1872
     * Get the Permissions for a CodeSource.  If this instance
1873
     * of WebappClassLoader is for a web application context,
1874
     * add read FilePermission or JndiPermissions for the base
1875
     * directory (if unpacked),
1876
     * the context URL, and jar file resources.
1877
     *
1878
     * @param codeSource where the code was loaded from
1879
     * @return PermissionCollection for CodeSource
1880
     */
1881
    @Override
1882
    protected PermissionCollection getPermissions(CodeSource codeSource) {
1883
1884
        String codeUrl = codeSource.getLocation().toString();
1885
        PermissionCollection pc;
1886
        if ((pc = loaderPC.get(codeUrl)) == null) {
1887
            pc = super.getPermissions(codeSource);
1888
            if (pc != null) {
1889
                Iterator<Permission> perms = permissionList.iterator();
1890
                while (perms.hasNext()) {
1891
                    Permission p = perms.next();
1892
                    pc.add(p);
1893
                }
1894
                loaderPC.put(codeUrl,pc);
1895
            }
1896
        }
1897
        return (pc);
1898
1899
    }
1900
1901
1902
    /**
1903
     * Returns the search path of URLs for loading classes and resources.
1904
     * This includes the original list of URLs specified to the constructor,
1905
     * along with any URLs subsequently appended by the addURL() method.
1906
     * @return the search path of URLs for loading classes and resources.
1907
     */
1908
    @Override
1909
    public URL[] getURLs() {
1910
1911
        if (repositoryURLs != null) {
1912
            return repositoryURLs.clone();
1913
        }
1914
1915
        URL[] external = super.getURLs();
1916
1917
        int filesLength = files.length;
1918
        int jarFilesLength = jarRealFiles.length;
1919
        int externalsLength = external.length;
1920
        int off = 0;
1921
        int i;
1922
1923
        try {
1924
1925
            URL[] urls = new URL[filesLength + jarFilesLength + externalsLength];
1926
            if (searchExternalFirst) {
1927
                for (i = 0; i < externalsLength; i++) {
1928
                    urls[i] = external[i];
1929
                }
1930
                off = externalsLength;
1931
            }
1932
            for (i = 0; i < filesLength; i++) {
1933
                urls[off + i] = getURI(files[i]);
1934
            }
1935
            off += filesLength;
1936
            for (i = 0; i < jarFilesLength; i++) {
1937
                urls[off + i] = getURI(jarRealFiles[i]);
1938
            }
1939
            off += jarFilesLength;
1940
            if (!searchExternalFirst) {
1941
                for (i = 0; i < externalsLength; i++) {
1942
                    urls[off + i] = external[i];
1943
                }
1944
            }
1945
1946
            repositoryURLs = urls;
1947
1948
        } catch (MalformedURLException e) {
1949
            repositoryURLs = new URL[0];
1950
        }
1951
1952
        return repositoryURLs.clone();
1953
1954
    }
1955
1956
1957
    // ------------------------------------------------------ Lifecycle Methods
1958
1959
1960
    /**
1961
     * Add a lifecycle event listener to this component.
1962
     *
1963
     * @param listener The listener to add
1964
     */
1965
    @Override
1966
    public void addLifecycleListener(LifecycleListener listener) {
1967
        // NOOP
1968
    }
1969
1970
1971
    /**
1972
     * Get the lifecycle listeners associated with this lifecycle. If this
1973
     * Lifecycle has no listeners registered, a zero-length array is returned.
1974
     */
1975
    @Override
1976
    public LifecycleListener[] findLifecycleListeners() {
1977
        return new LifecycleListener[0];
1978
    }
1979
1980
1981
    /**
1982
     * Remove a lifecycle event listener from this component.
1983
     *
1984
     * @param listener The listener to remove
1985
     */
1986
    @Override
1987
    public void removeLifecycleListener(LifecycleListener listener) {
1988
        // NOOP
1989
    }
1990
1991
1992
    /**
1993
     * Obtain the current state of the source component.
1994
     *
1995
     * @return The current state of the source component.
1996
     */
1997
    @Override
1998
    public LifecycleState getState() {
1999
        return LifecycleState.NEW;
2000
    }
2001
2002
2003
    /**
2004
     * {@inheritDoc}
2005
     */
2006
    @Override
2007
    public String getStateName() {
2008
        return getState().toString();
2009
    }
2010
2011
2012
    @Override
2013
    public void init() {
2014
        // NOOP
2015
    }
2016
2017
2018
    /**
2019
     * Start the class loader.
2020
     *
2021
     * @exception LifecycleException if a lifecycle error occurs
2022
     */
2023
    @Override
2024
    public void start() throws LifecycleException {
2025
2026
        started = true;
2027
        String encoding = null;
2028
        try {
2029
            encoding = System.getProperty("file.encoding");
2030
        } catch (SecurityException e) {
2031
            return;
2032
        }
2033
        if (encoding.indexOf("EBCDIC")!=-1) {
2034
            needConvert = true;
2035
        }
2036
2037
        for (int i = 0; i < repositories.length; i++) {
2038
            if (repositories[i].equals("/WEB-INF/classes/")) {
2039
                try {
2040
                    webInfClassesCodeBase = files[i].toURI().toURL();
2041
                } catch (MalformedURLException e) {
2042
                    // Ignore - leave it as null
2043
                }
2044
                break;
2045
            }
2046
        }
2047
2048
    }
2049
2050
2051
    public boolean isStarted() {
2052
        return started;
2053
    }
2054
2055
    /**
2056
     * Stop the class loader.
2057
     *
2058
     * @exception LifecycleException if a lifecycle error occurs
2059
     */
2060
    @Override
2061
    public void stop() throws LifecycleException {
2062
2063
        // Clearing references should be done before setting started to
2064
        // false, due to possible side effects
2065
        clearReferences();
2066
2067
        started = false;
2068
2069
        int length = files.length;
2070
        for (int i = 0; i < length; i++) {
2071
            files[i] = null;
2072
        }
2073
2074
        length = jarFiles.length;
2075
        for (int i = 0; i < length; i++) {
2076
            try {
2077
                if (jarFiles[i] != null) {
2078
                    jarFiles[i].close();
2079
                }
2080
            } catch (IOException e) {
2081
                // Ignore
2082
            }
2083
            jarFiles[i] = null;
2084
        }
2085
2086
        notFoundResources.clear();
2087
        resourceEntries.clear();
2088
        resources = null;
2089
        repositories = null;
2090
        repositoryURLs = null;
2091
        files = null;
2092
        jarFiles = null;
2093
        jarRealFiles = null;
2094
        jarPath = null;
2095
        jarNames = null;
2096
        lastModifiedDates = null;
2097
        paths = null;
2098
        hasExternalRepositories = false;
2099
        parent = null;
2100
        webInfClassesCodeBase = null;
2101
2102
        permissionList.clear();
2103
        loaderPC.clear();
2104
2105
        if (loaderDir != null) {
2106
            deleteDir(loaderDir);
2107
        }
2108
2109
    }
2110
2111
2112
    @Override
2113
    public void destroy() {
2114
        // NOOP
2115
    }
2116
2117
2118
    /**
2119
     * Used to periodically signal to the classloader to release
2120
     * JAR resources.
2121
     */
2122
    public void closeJARs(boolean force) {
2123
        if (jarFiles.length > 0) {
2124
            synchronized (jarFiles) {
2125
                if (force || (System.currentTimeMillis()
2126
                        > (lastJarAccessed + 90000))) {
2127
                    for (int i = 0; i < jarFiles.length; i++) {
2128
                        try {
2129
                            if (jarFiles[i] != null) {
2130
                                jarFiles[i].close();
2131
                                jarFiles[i] = null;
2132
                            }
2133
                        } catch (IOException e) {
2134
                            if (log.isDebugEnabled()) {
2135
                                log.debug("Failed to close JAR", e);
2136
                            }
2137
                        }
2138
                    }
2139
                }
2140
            }
2141
        }
2142
    }
2143
2144
2145
    // ------------------------------------------------------ Protected Methods
2146
2147
    protected ClassLoader getJavaseClassLoader() {
2148
        return j2seClassLoader;
2149
    }
2150
2151
    protected void setJavaseClassLoader(ClassLoader classLoader) {
2152
        if (classLoader == null) {
2153
            throw new IllegalArgumentException(
2154
                    sm.getString("webappClassLoader.javaseClassLoaderNull"));
2155
        }
2156
        j2seClassLoader = classLoader;
2157
    }
2158
2159
    /**
2160
     * Clear references.
2161
     */
2162
    protected void clearReferences() {
2163
2164
        // De-register any remaining JDBC drivers
2165
        clearReferencesJdbc();
2166
2167
        // Stop any threads the web application started
2168
        clearReferencesThreads();
2169
2170
        // Check for leaks triggered by ThreadLocals loaded by this class loader
2171
        checkThreadLocalsForLeaks();
2172
2173
        // Clear RMI Targets loaded by this class loader
2174
        clearReferencesRmiTargets();
2175
2176
        // Null out any static or final fields from loaded classes,
2177
        // as a workaround for apparent garbage collection bugs
2178
        if (clearReferencesStatic) {
2179
            clearReferencesStaticFinal();
2180
        }
2181
2182
        // Clear the IntrospectionUtils cache.
2183
        IntrospectionUtils.clear();
2184
2185
        // Clear the classloader reference in common-logging
2186
        if (clearReferencesLogFactoryRelease) {
2187
            org.apache.juli.logging.LogFactory.release(this);
2188
        }
2189
2190
        // Clear the resource bundle cache
2191
        // This shouldn't be necessary, the cache uses weak references but
2192
        // it has caused leaks. Oddly, using the leak detection code in
2193
        // standard host allows the class loader to be GC'd. This has been seen
2194
        // on Sun but not IBM JREs. Maybe a bug in Sun's GC impl?
2195
        clearReferencesResourceBundles();
2196
2197
        // Clear the classloader reference in the VM's bean introspector
2198
        java.beans.Introspector.flushCaches();
2199
2200
    }
2201
2202
2203
    /**
2204
     * Deregister any JDBC drivers registered by the webapp that the webapp
2205
     * forgot. This is made unnecessary complex because a) DriverManager
2206
     * checks the class loader of the calling class (it would be much easier
2207
     * if it checked the context class loader) b) using reflection would
2208
     * create a dependency on the DriverManager implementation which can,
2209
     * and has, changed.
2210
     *
2211
     * We can't just create an instance of JdbcLeakPrevention as it will be
2212
     * loaded by the common class loader (since it's .class file is in the
2213
     * $CATALINA_HOME/lib directory). This would fail DriverManager's check
2214
     * on the class loader of the calling class. So, we load the bytes via
2215
     * our parent class loader but define the class with this class loader
2216
     * so the JdbcLeakPrevention looks like a webapp class to the
2217
     * DriverManager.
2218
     *
2219
     * If only apps cleaned up after themselves...
2220
     */
2221
    private final void clearReferencesJdbc() {
2222
        InputStream is = getResourceAsStream(
2223
                "org/apache/catalina/loader/JdbcLeakPrevention.class");
2224
        // We know roughly how big the class will be (~ 1K) so allow 2k as a
2225
        // starting point
2226
        byte[] classBytes = new byte[2048];
2227
        int offset = 0;
2228
        try {
2229
            int read = is.read(classBytes, offset, classBytes.length-offset);
2230
            while (read > -1) {
2231
                offset += read;
2232
                if (offset == classBytes.length) {
2233
                    // Buffer full - double size
2234
                    byte[] tmp = new byte[classBytes.length * 2];
2235
                    System.arraycopy(classBytes, 0, tmp, 0, classBytes.length);
2236
                    classBytes = tmp;
2237
                }
2238
                read = is.read(classBytes, offset, classBytes.length-offset);
2239
            }
2240
            Class<?> lpClass =
2241
                    defineClass("org.apache.catalina.loader.JdbcLeakPrevention",
2242
                            classBytes, 0, offset, this.getClass().getProtectionDomain());
2243
            Object obj = lpClass.newInstance();
2244
            @SuppressWarnings("unchecked") // clearJdbcDriverRegistrations() returns List<String>
2245
                    List<String> driverNames = (List<String>) obj.getClass().getMethod(
2246
                    "clearJdbcDriverRegistrations").invoke(obj);
2247
            for (String name : driverNames) {
2248
                log.error(sm.getString("webappClassLoader.clearJdbc",
2249
                        contextName, name));
2250
            }
2251
        } catch (Exception e) {
2252
            // So many things to go wrong above...
2253
            Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
2254
            ExceptionUtils.handleThrowable(t);
2255
            log.warn(sm.getString(
2256
                    "webappClassLoader.jdbcRemoveFailed", contextName), t);
2257
        } finally {
2258
            if (is != null) {
2259
                try {
2260
                    is.close();
2261
                } catch (IOException ioe) {
2262
                    log.warn(sm.getString(
2263
                            "webappClassLoader.jdbcRemoveStreamError",
2264
                            contextName), ioe);
2265
                }
2266
            }
2267
        }
2268
    }
2269
2270
2271
    private final void clearReferencesStaticFinal() {
2272
2273
        @SuppressWarnings("unchecked") // resourceEntries is HashMap<String, ResourceEntry>
2274
                Collection<ResourceEntry> values =
2275
                ((HashMap<String,ResourceEntry>) resourceEntries.clone()).values();
2276
        Iterator<ResourceEntry> loadedClasses = values.iterator();
2277
        //
2278
        // walk through all loaded class to trigger initialization for
2279
        //    any uninitialized classes, otherwise initialization of
2280
        //    one class may call a previously cleared class.
2281
        while(loadedClasses.hasNext()) {
2282
            ResourceEntry entry = loadedClasses.next();
2283
            if (entry.loadedClass != null) {
2284
                Class<?> clazz = entry.loadedClass;
2285
                try {
2286
                    Field[] fields = clazz.getDeclaredFields();
2287
                    for (int i = 0; i < fields.length; i++) {
2288
                        if(Modifier.isStatic(fields[i].getModifiers())) {
2289
                            fields[i].get(null);
2290
                            break;
2291
                        }
2292
                    }
2293
                } catch(Throwable t) {
2294
                    // Ignore
2295
                }
2296
            }
2297
        }
2298
        loadedClasses = values.iterator();
2299
        while (loadedClasses.hasNext()) {
2300
            ResourceEntry entry = loadedClasses.next();
2301
            if (entry.loadedClass != null) {
2302
                Class<?> clazz = entry.loadedClass;
2303
                try {
2304
                    Field[] fields = clazz.getDeclaredFields();
2305
                    for (int i = 0; i < fields.length; i++) {
2306
                        Field field = fields[i];
2307
                        int mods = field.getModifiers();
2308
                        if (field.getType().isPrimitive()
2309
                                || (field.getName().indexOf("$") != -1)) {
2310
                            continue;
2311
                        }
2312
                        if (Modifier.isStatic(mods)) {
2313
                            try {
2314
                                field.setAccessible(true);
2315
                                if (Modifier.isFinal(mods)) {
2316
                                    if (!((field.getType().getName().startsWith("java."))
2317
                                            || (field.getType().getName().startsWith("javax.")))) {
2318
                                        nullInstance(field.get(null));
2319
                                    }
2320
                                } else {
2321
                                    field.set(null, null);
2322
                                    if (log.isDebugEnabled()) {
2323
                                        log.debug("Set field " + field.getName()
2324
                                                + " to null in class " + clazz.getName());
2325
                                    }
2326
                                }
2327
                            } catch (Throwable t) {
2328
                                ExceptionUtils.handleThrowable(t);
2329
                                if (log.isDebugEnabled()) {
2330
                                    log.debug("Could not set field " + field.getName()
2331
                                            + " to null in class " + clazz.getName(), t);
2332
                                }
2333
                            }
2334
                        }
2335
                    }
2336
                } catch (Throwable t) {
2337
                    ExceptionUtils.handleThrowable(t);
2338
                    if (log.isDebugEnabled()) {
2339
                        log.debug("Could not clean fields for class " + clazz.getName(), t);
2340
                    }
2341
                }
2342
            }
2343
        }
2344
2345
    }
2346
2347
2348
    private void nullInstance(Object instance) {
2349
        if (instance == null) {
2350
            return;
2351
        }
2352
        Field[] fields = instance.getClass().getDeclaredFields();
2353
        for (int i = 0; i < fields.length; i++) {
2354
            Field field = fields[i];
2355
            int mods = field.getModifiers();
2356
            if (field.getType().isPrimitive()
2357
                    || (field.getName().indexOf("$") != -1)) {
2358
                continue;
2359
            }
2360
            try {
2361
                field.setAccessible(true);
2362
                if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) {
2363
                    // Doing something recursively is too risky
2364
                    continue;
2365
                }
2366
                Object value = field.get(instance);
2367
                if (null != value) {
2368
                    Class<? extends Object> valueClass = value.getClass();
2369
                    if (!loadedByThisOrChild(valueClass)) {
2370
                        if (log.isDebugEnabled()) {
2371
                            log.debug("Not setting field " + field.getName() +
2372
                                    " to null in object of class " +
2373
                                    instance.getClass().getName() +
2374
                                    " because the referenced object was of type " +
2375
                                    valueClass.getName() +
2376
                                    " which was not loaded by this WebappClassLoader.");
2377
                        }
2378
                    } else {
2379
                        field.set(instance, null);
2380
                        if (log.isDebugEnabled()) {
2381
                            log.debug("Set field " + field.getName()
2382
                                    + " to null in class " + instance.getClass().getName());
2383
                        }
2384
                    }
2385
                }
2386
            } catch (Throwable t) {
2387
                ExceptionUtils.handleThrowable(t);
2388
                if (log.isDebugEnabled()) {
2389
                    log.debug("Could not set field " + field.getName()
2390
                            + " to null in object instance of class "
2391
                            + instance.getClass().getName(), t);
2392
                }
2393
            }
2394
        }
2395
    }
2396
2397
2398
    @SuppressWarnings("deprecation") // thread.stop()
2399
    private void clearReferencesThreads() {
2400
        Thread[] threads = getThreads();
2401
        List<Thread> executorThreadsToStop = new ArrayList<Thread>();
2402
2403
        // Iterate over the set of threads
2404
        for (Thread thread : threads) {
2405
            if (thread != null) {
2406
                ClassLoader ccl = thread.getContextClassLoader();
2407
                if (ccl == this) {
2408
                    // Don't warn about this thread
2409
                    if (thread == Thread.currentThread()) {
2410
                        continue;
2411
                    }
2412
2413
                    // JVM controlled threads
2414
                    ThreadGroup tg = thread.getThreadGroup();
2415
                    if (tg != null &&
2416
                            JVM_THREAD_GROUP_NAMES.contains(tg.getName())) {
2417
2418
                        // HttpClient keep-alive threads
2419
                        if (clearReferencesHttpClientKeepAliveThread &&
2420
                                thread.getName().equals("Keep-Alive-Timer")) {
2421
                            thread.setContextClassLoader(parent);
2422
                            log.debug(sm.getString(
2423
                                    "webappClassLoader.checkThreadsHttpClient"));
2424
                        }
2425
2426
                        // Don't warn about remaining JVM controlled threads
2427
                        continue;
2428
                    }
2429
2430
                    // Skip threads that have already died
2431
                    if (!thread.isAlive()) {
2432
                        continue;
2433
                    }
2434
2435
                    // TimerThread can be stopped safely so treat separately
2436
                    // "java.util.TimerThread" in Sun/Oracle JDK
2437
                    // "java.util.Timer$TimerImpl" in Apache Harmony and in IBM JDK
2438
                    if (thread.getClass().getName().startsWith("java.util.Timer") &&
2439
                            clearReferencesStopTimerThreads) {
2440
                        clearReferencesStopTimerThread(thread);
2441
                        continue;
2442
                    }
2443
2444
                    if (isRequestThread(thread)) {
2445
                        log.error(sm.getString("webappClassLoader.warnRequestThread",
2446
                                contextName, thread.getName()));
2447
                    } else {
2448
                        log.error(sm.getString("webappClassLoader.warnThread",
2449
                                contextName, thread.getName()));
2450
                    }
2451
2452
                    // Don't try an stop the threads unless explicitly
2453
                    // configured to do so
2454
                    if (!clearReferencesStopThreads) {
2455
                        continue;
2456
                    }
2457
2458
                    // If the thread has been started via an executor, try
2459
                    // shutting down the executor
2460
                    boolean usingExecutor = false;
2461
                    try {
2462
2463
                        // Runnable wrapped by Thread
2464
                        // "target" in Sun/Oracle JDK
2465
                        // "runnable" in IBM JDK
2466
                        // "action" in Apache Harmony
2467
                        Object target = null;
2468
                        for (String fieldName : new String[] { "target",
2469
                                "runnable", "action" }) {
2470
                            try {
2471
                                Field targetField = thread.getClass()
2472
                                        .getDeclaredField(fieldName);
2473
                                targetField.setAccessible(true);
2474
                                target = targetField.get(thread);
2475
                                break;
2476
                            } catch (NoSuchFieldException nfe) {
2477
                                continue;
2478
                            }
2479
                        }
2480
2481
                        // "java.util.concurrent" code is in public domain,
2482
                        // so all implementations are similar
2483
                        if (target != null &&
2484
                                target.getClass().getCanonicalName() != null
2485
                                && target.getClass().getCanonicalName().equals(
2486
                                "java.util.concurrent.ThreadPoolExecutor.Worker")) {
2487
                            Field executorField =
2488
                                    target.getClass().getDeclaredField("this$0");
2489
                            executorField.setAccessible(true);
2490
                            Object executor = executorField.get(target);
2491
                            if (executor instanceof ThreadPoolExecutor) {
2492
                                ((ThreadPoolExecutor) executor).shutdownNow();
2493
                                usingExecutor = true;
2494
                            }
2495
                        }
2496
                    } catch (SecurityException e) {
2497
                        log.warn(sm.getString(
2498
                                "webappClassLoader.stopThreadFail",
2499
                                thread.getName(), contextName), e);
2500
                    } catch (NoSuchFieldException e) {
2501
                        log.warn(sm.getString(
2502
                                "webappClassLoader.stopThreadFail",
2503
                                thread.getName(), contextName), e);
2504
                    } catch (IllegalArgumentException e) {
2505
                        log.warn(sm.getString(
2506
                                "webappClassLoader.stopThreadFail",
2507
                                thread.getName(), contextName), e);
2508
                    } catch (IllegalAccessException e) {
2509
                        log.warn(sm.getString(
2510
                                "webappClassLoader.stopThreadFail",
2511
                                thread.getName(), contextName), e);
2512
                    }
2513
2514
                    if (usingExecutor) {
2515
                        // Executor may take a short time to stop all the
2516
                        // threads. Make a note of threads that should be
2517
                        // stopped and check them at the end of the method.
2518
                        executorThreadsToStop.add(thread);
2519
                    } else {
2520
                        // This method is deprecated and for good reason. This
2521
                        // is very risky code but is the only option at this
2522
                        // point. A *very* good reason for apps to do this
2523
                        // clean-up themselves.
2524
                        thread.stop();
2525
                    }
2526
                }
2527
            }
2528
        }
2529
2530
        // If thread stopping is enabled, executor threads should have been
2531
        // stopped above when the executor was shut down but that depends on the
2532
        // thread correctly handling the interrupt. Give all the executor
2533
        // threads a few seconds shutdown and if they are still running
2534
        // Give threads up to 2 seconds to shutdown
2535
        int count = 0;
2536
        for (Thread t : executorThreadsToStop) {
2537
            while (t.isAlive() && count < 100) {
2538
                try {
2539
                    Thread.sleep(20);
2540
                } catch (InterruptedException e) {
2541
                    // Quit the while loop
2542
                    break;
2543
                }
2544
                count++;
2545
            }
2546
            if (t.isAlive()) {
2547
                // This method is deprecated and for good reason. This is
2548
                // very risky code but is the only option at this point.
2549
                // A *very* good reason for apps to do this clean-up
2550
                // themselves.
2551
                t.stop();
2552
            }
2553
        }
2554
    }
2555
2556
2557
    /*
2558
     * Look at a threads stack trace to see if it is a request thread or not. It
2559
     * isn't perfect, but it should be good-enough for most cases.
2560
     */
2561
    private boolean isRequestThread(Thread thread) {
2562
2563
        StackTraceElement[] elements = thread.getStackTrace();
2564
2565
        if (elements == null || elements.length == 0) {
2566
            // Must have stopped already. Too late to ignore it. Assume not a
2567
            // request processing thread.
2568
            return false;
2569
        }
2570
2571
        // Step through the methods in reverse order looking for calls to any
2572
        // CoyoteAdapter method. All request threads will have this unless
2573
        // Tomcat has been heavily modified - in which case there isn't much we
2574
        // can do.
2575
        for (int i = 0; i < elements.length; i++) {
2576
            StackTraceElement element = elements[elements.length - (i+1)];
2577
            if ("org.apache.catalina.connector.CoyoteAdapter".equals(
2578
                    element.getClassName())) {
2579
                return true;
2580
            }
2581
        }
2582
        return false;
2583
    }
2584
2585
2586
    private void clearReferencesStopTimerThread(Thread thread) {
2587
2588
        // Need to get references to:
2589
        // in Sun/Oracle JDK:
2590
        // - newTasksMayBeScheduled field (in java.util.TimerThread)
2591
        // - queue field
2592
        // - queue.clear()
2593
        // in IBM JDK, Apache Harmony:
2594
        // - cancel() method (in java.util.Timer$TimerImpl)
2595
2596
        try {
2597
2598
            try {
2599
                Field newTasksMayBeScheduledField =
2600
                        thread.getClass().getDeclaredField("newTasksMayBeScheduled");
2601
                newTasksMayBeScheduledField.setAccessible(true);
2602
                Field queueField = thread.getClass().getDeclaredField("queue");
2603
                queueField.setAccessible(true);
2604
2605
                Object queue = queueField.get(thread);
2606
2607
                Method clearMethod = queue.getClass().getDeclaredMethod("clear");
2608
                clearMethod.setAccessible(true);
2609
2610
                synchronized(queue) {
2611
                    newTasksMayBeScheduledField.setBoolean(thread, false);
2612
                    clearMethod.invoke(queue);
2613
                    queue.notify();  // In case queue was already empty.
2614
                }
2615
2616
            }catch (NoSuchFieldException nfe){
2617
                Method cancelMethod = thread.getClass().getDeclaredMethod("cancel");
2618
                synchronized(thread) {
2619
                    cancelMethod.setAccessible(true);
2620
                    cancelMethod.invoke(thread);
2621
                }
2622
            }
2623
2624
            log.error(sm.getString("webappClassLoader.warnTimerThread",
2625
                    contextName, thread.getName()));
2626
2627
        } catch (Exception e) {
2628
            // So many things to go wrong above...
2629
            Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
2630
            ExceptionUtils.handleThrowable(t);
2631
            log.warn(sm.getString(
2632
                    "webappClassLoader.stopTimerThreadFail",
2633
                    thread.getName(), contextName), t);
2634
        }
2635
    }
2636
2637
    private void checkThreadLocalsForLeaks() {
2638
        Thread[] threads = getThreads();
2639
2640
        try {
2641
            // Make the fields in the Thread class that store ThreadLocals
2642
            // accessible
2643
            Field threadLocalsField =
2644
                    Thread.class.getDeclaredField("threadLocals");
2645
            threadLocalsField.setAccessible(true);
2646
            Field inheritableThreadLocalsField =
2647
                    Thread.class.getDeclaredField("inheritableThreadLocals");
2648
            inheritableThreadLocalsField.setAccessible(true);
2649
            // Make the underlying array of ThreadLoad.ThreadLocalMap.Entry objects
2650
            // accessible
2651
            Class<?> tlmClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
2652
            Field tableField = tlmClass.getDeclaredField("table");
2653
            tableField.setAccessible(true);
2654
            Method expungeStaleEntriesMethod = tlmClass.getDeclaredMethod("expungeStaleEntries");
2655
            expungeStaleEntriesMethod.setAccessible(true);
2656
2657
            for (int i = 0; i < threads.length; i++) {
2658
                Object threadLocalMap;
2659
                if (threads[i] != null) {
2660
2661
                    // Clear the first map
2662
                    threadLocalMap = threadLocalsField.get(threads[i]);
2663
                    if (null != threadLocalMap){
2664
                        expungeStaleEntriesMethod.invoke(threadLocalMap);
2665
                        checkThreadLocalMapForLeaks(threadLocalMap, tableField);
2666
                    }
2667
2668
                    // Clear the second map
2669
                    threadLocalMap =inheritableThreadLocalsField.get(threads[i]);
2670
                    if (null != threadLocalMap){
2671
                        expungeStaleEntriesMethod.invoke(threadLocalMap);
2672
                        checkThreadLocalMapForLeaks(threadLocalMap, tableField);
2673
                    }
2674
                }
2675
            }
2676
        } catch (Throwable t) {
2677
            ExceptionUtils.handleThrowable(t);
2678
            log.warn(sm.getString(
2679
                    "webappClassLoader.checkThreadLocalsForLeaksFail",
2680
                    getContextName()), t);
2681
        }
2682
    }
2683
2684
2685
    /**
2686
     * Analyzes the given thread local map object. Also pass in the field that
2687
     * points to the internal table to save re-calculating it on every
2688
     * call to this method.
2689
     */
2690
    private void checkThreadLocalMapForLeaks(Object map,
2691
                                             Field internalTableField) throws IllegalAccessException,
2692
            NoSuchFieldException {
2693
        if (map != null) {
2694
            Object[] table = (Object[]) internalTableField.get(map);
2695
            if (table != null) {
2696
                for (int j =0; j < table.length; j++) {
2697
                    Object obj = table[j];
2698
                    if (obj != null) {
2699
                        boolean potentialLeak = false;
2700
                        // Check the key
2701
                        Object key = ((Reference<?>) obj).get();
2702
                        if (this.equals(key) || loadedByThisOrChild(key)) {
2703
                            potentialLeak = true;
2704
                        }
2705
                        // Check the value
2706
                        Field valueField =
2707
                                obj.getClass().getDeclaredField("value");
2708
                        valueField.setAccessible(true);
2709
                        Object value = valueField.get(obj);
2710
                        if (this.equals(value) || loadedByThisOrChild(value)) {
2711
                            potentialLeak = true;
2712
                        }
2713
                        if (potentialLeak) {
2714
                            Object[] args = new Object[5];
2715
                            args[0] = contextName;
2716
                            if (key != null) {
2717
                                args[1] = getPrettyClassName(key.getClass());
2718
                                try {
2719
                                    args[2] = key.toString();
2720
                                } catch (Exception e) {
2721
                                    log.error(sm.getString(
2722
                                            "webappClassLoader.checkThreadLocalsForLeaks.badKey",
2723
                                            args[1]), e);
2724
                                    args[2] = sm.getString(
2725
                                            "webappClassLoader.checkThreadLocalsForLeaks.unknown");
2726
                                }
2727
                            }
2728
                            if (value != null) {
2729
                                args[3] = getPrettyClassName(value.getClass());
2730
                                try {
2731
                                    args[4] = value.toString();
2732
                                } catch (Exception e) {
2733
                                    log.error(sm.getString(
2734
                                            "webappClassLoader.checkThreadLocalsForLeaks.badValue",
2735
                                            args[3]), e);
2736
                                    args[4] = sm.getString(
2737
                                            "webappClassLoader.checkThreadLocalsForLeaks.unknown");
2738
                                }
2739
                            }
2740
                            if (value == null) {
2741
                                if (log.isDebugEnabled()) {
2742
                                    log.debug(sm.getString(
2743
                                            "webappClassLoader.checkThreadLocalsForLeaksDebug",
2744
                                            args));
2745
                                }
2746
                            } else {
2747
                                log.error(sm.getString(
2748
                                        "webappClassLoader.checkThreadLocalsForLeaks",
2749
                                        args));
2750
                            }
2751
                        }
2752
                    }
2753
                }
2754
            }
2755
        }
2756
    }
2757
2758
    private String getPrettyClassName(Class<?> clazz) {
2759
        String name = clazz.getCanonicalName();
2760
        if (name==null){
2761
            name = clazz.getName();
2762
        }
2763
        return name;
2764
    }
2765
2766
    /**
2767
     * @param o object to test, may be null
2768
     * @return <code>true</code> if o has been loaded by the current classloader
2769
     * or one of its descendants.
2770
     */
2771
    private boolean loadedByThisOrChild(Object o) {
2772
        if (o == null) {
2773
            return false;
2774
        }
2775
2776
        Class<?> clazz;
2777
        if (o instanceof Class) {
2778
            clazz = (Class<?>) o;
2779
        } else {
2780
            clazz = o.getClass();
2781
        }
2782
2783
        ClassLoader cl = clazz.getClassLoader();
2784
        while (cl != null) {
2785
            if (cl == this) {
2786
                return true;
2787
            }
2788
            cl = cl.getParent();
2789
        }
2790
2791
        if (o instanceof Collection<?>) {
2792
            Iterator<?> iter = ((Collection<?>) o).iterator();
2793
            try {
2794
                while (iter.hasNext()) {
2795
                    Object entry = iter.next();
2796
                    if (loadedByThisOrChild(entry)) {
2797
                        return true;
2798
                    }
2799
                }
2800
            } catch (ConcurrentModificationException e) {
2801
                log.warn(sm.getString(
2802
                                "webappClassLoader", clazz.getName(), getContextName()),
2803
                        e);
2804
            }
2805
        }
2806
        return false;
2807
    }
2808
2809
    /*
2810
     * Get the set of current threads as an array.
2811
     */
2812
    private Thread[] getThreads() {
2813
        // Get the current thread group
2814
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
2815
        // Find the root thread group
2816
        try {
2817
            while (tg.getParent() != null) {
2818
                tg = tg.getParent();
2819
            }
2820
        } catch (SecurityException se) {
2821
            String msg = sm.getString(
2822
                    "webappClassLoader.getThreadGroupError", tg.getName());
2823
            if (log.isDebugEnabled()) {
2824
                log.debug(msg, se);
2825
            } else {
2826
                log.warn(msg);
2827
            }
2828
        }
2829
2830
        int threadCountGuess = tg.activeCount() + 50;
2831
        Thread[] threads = new Thread[threadCountGuess];
2832
        int threadCountActual = tg.enumerate(threads);
2833
        // Make sure we don't miss any threads
2834
        while (threadCountActual == threadCountGuess) {
2835
            threadCountGuess *=2;
2836
            threads = new Thread[threadCountGuess];
2837
            // Note tg.enumerate(Thread[]) silently ignores any threads that
2838
            // can't fit into the array
2839
            threadCountActual = tg.enumerate(threads);
2840
        }
2841
2842
        return threads;
2843
    }
2844
2845
2846
    /**
2847
     * This depends on the internals of the Sun JVM so it does everything by
2848
     * reflection.
2849
     */
2850
    private void clearReferencesRmiTargets() {
2851
        try {
2852
            // Need access to the ccl field of sun.rmi.transport.Target
2853
            Class<?> objectTargetClass =
2854
                    Class.forName("sun.rmi.transport.Target");
2855
            Field cclField = objectTargetClass.getDeclaredField("ccl");
2856
            cclField.setAccessible(true);
2857
2858
            // Clear the objTable map
2859
            Class<?> objectTableClass =
2860
                    Class.forName("sun.rmi.transport.ObjectTable");
2861
            Field objTableField = objectTableClass.getDeclaredField("objTable");
2862
            objTableField.setAccessible(true);
2863
            Object objTable = objTableField.get(null);
2864
            if (objTable == null) {
2865
                return;
2866
            }
2867
2868
            // Iterate over the values in the table
2869
            if (objTable instanceof Map<?,?>) {
2870
                Iterator<?> iter = ((Map<?,?>) objTable).values().iterator();
2871
                while (iter.hasNext()) {
2872
                    Object obj = iter.next();
2873
                    Object cclObject = cclField.get(obj);
2874
                    if (this == cclObject) {
2875
                        iter.remove();
2876
                    }
2877
                }
2878
            }
2879
2880
            // Clear the implTable map
2881
            Field implTableField = objectTableClass.getDeclaredField("implTable");
2882
            implTableField.setAccessible(true);
2883
            Object implTable = implTableField.get(null);
2884
            if (implTable == null) {
2885
                return;
2886
            }
2887
2888
            // Iterate over the values in the table
2889
            if (implTable instanceof Map<?,?>) {
2890
                Iterator<?> iter = ((Map<?,?>) implTable).values().iterator();
2891
                while (iter.hasNext()) {
2892
                    Object obj = iter.next();
2893
                    Object cclObject = cclField.get(obj);
2894
                    if (this == cclObject) {
2895
                        iter.remove();
2896
                    }
2897
                }
2898
            }
2899
        } catch (ClassNotFoundException e) {
2900
            log.info(sm.getString("webappClassLoader.clearRmiInfo",
2901
                    contextName), e);
2902
        } catch (SecurityException e) {
2903
            log.warn(sm.getString("webappClassLoader.clearRmiFail",
2904
                    contextName), e);
2905
        } catch (NoSuchFieldException e) {
2906
            log.warn(sm.getString("webappClassLoader.clearRmiFail",
2907
                    contextName), e);
2908
        } catch (IllegalArgumentException e) {
2909
            log.warn(sm.getString("webappClassLoader.clearRmiFail",
2910
                    contextName), e);
2911
        } catch (IllegalAccessException e) {
2912
            log.warn(sm.getString("webappClassLoader.clearRmiFail",
2913
                    contextName), e);
2914
        }
2915
    }
2916
2917
2918
    /**
2919
     * Clear the {@link ResourceBundle} cache of any bundles loaded by this
2920
     * class loader or any class loader where this loader is a parent class
2921
     * loader. Whilst {@link ResourceBundle#clearCache()} could be used there
2922
     * are complications around the
2923
     * {@link org.apache.jasper.servlet.JasperLoader} that mean a reflection
2924
     * based approach is more likely to be complete.
2925
     *
2926
     * The ResourceBundle is using WeakReferences so it shouldn't be pinning the
2927
     * class loader in memory. However, it is. Therefore clear ou the
2928
     * references.
2929
     */
2930
    private void clearReferencesResourceBundles() {
2931
        // Get a reference to the cache
2932
        try {
2933
            Field cacheListField =
2934
                    ResourceBundle.class.getDeclaredField("cacheList");
2935
            cacheListField.setAccessible(true);
2936
2937
            // Java 6 uses ConcurrentMap
2938
            // Java 5 uses SoftCache extends Abstract Map
2939
            // So use Map and it *should* work with both
2940
            Map<?,?> cacheList = (Map<?,?>) cacheListField.get(null);
2941
2942
            // Get the keys (loader references are in the key)
2943
            Set<?> keys = cacheList.keySet();
2944
2945
            Field loaderRefField = null;
2946
2947
            // Iterate over the keys looking at the loader instances
2948
            Iterator<?> keysIter = keys.iterator();
2949
2950
            int countRemoved = 0;
2951
2952
            while (keysIter.hasNext()) {
2953
                Object key = keysIter.next();
2954
2955
                if (loaderRefField == null) {
2956
                    loaderRefField =
2957
                            key.getClass().getDeclaredField("loaderRef");
2958
                    loaderRefField.setAccessible(true);
2959
                }
2960
                WeakReference<?> loaderRef =
2961
                        (WeakReference<?>) loaderRefField.get(key);
2962
2963
                ClassLoader loader = (ClassLoader) loaderRef.get();
2964
2965
                while (loader != null && loader != this) {
2966
                    loader = loader.getParent();
2967
                }
2968
2969
                if (loader != null) {
2970
                    keysIter.remove();
2971
                    countRemoved++;
2972
                }
2973
            }
2974
2975
            if (countRemoved > 0 && log.isDebugEnabled()) {
2976
                log.debug(sm.getString(
2977
                        "webappClassLoader.clearReferencesResourceBundlesCount",
2978
                        Integer.valueOf(countRemoved), contextName));
2979
            }
2980
        } catch (SecurityException e) {
2981
            log.error(sm.getString(
2982
                    "webappClassLoader.clearReferencesResourceBundlesFail",
2983
                    contextName), e);
2984
        } catch (NoSuchFieldException e) {
2985
            if (Globals.IS_ORACLE_JVM) {
2986
                log.error(sm.getString(
2987
                        "webappClassLoader.clearReferencesResourceBundlesFail",
2988
                        getContextName()), e);
2989
            } else {
2990
                log.debug(sm.getString(
2991
                        "webappClassLoader.clearReferencesResourceBundlesFail",
2992
                        getContextName()), e);
2993
            }
2994
        } catch (IllegalArgumentException e) {
2995
            log.error(sm.getString(
2996
                    "webappClassLoader.clearReferencesResourceBundlesFail",
2997
                    contextName), e);
2998
        } catch (IllegalAccessException e) {
2999
            log.error(sm.getString(
3000
                    "webappClassLoader.clearReferencesResourceBundlesFail",
3001
                    contextName), e);
3002
        }
3003
    }
3004
3005
3006
    /**
3007
     * Used to periodically signal to the classloader to release JAR resources.
3008
     */
3009
    protected boolean openJARs() {
3010
        if (started && (jarFiles.length > 0)) {
3011
            lastJarAccessed = System.currentTimeMillis();
3012
            if (jarFiles[0] == null) {
3013
                for (int i = 0; i < jarFiles.length; i++) {
3014
                    try {
3015
                        jarFiles[i] = new JarFile(jarRealFiles[i]);
3016
                    } catch (IOException e) {
3017
                        if (log.isDebugEnabled()) {
3018
                            log.debug("Failed to open JAR", e);
3019
                        }
3020
                        return false;
3021
                    }
3022
                }
3023
            }
3024
        }
3025
        return true;
3026
    }
3027
3028
3029
    /**
3030
     * Find specified class in local repositories.
3031
     *
3032
     * @return the loaded class, or null if the class isn't found
3033
     */
3034
    protected Class<?> findClassInternal(String name)
3035
            throws ClassNotFoundException {
3036
3037
        if (!validate(name))
3038
            throw new ClassNotFoundException(name);
3039
3040
        String tempPath = name.replace('.', '/');
3041
        String classPath = tempPath + CLASS_FILE_SUFFIX;
3042
3043
        ResourceEntry entry = null;
3044
3045
        if (securityManager != null) {
3046
            PrivilegedAction<ResourceEntry> dp =
3047
                    new PrivilegedFindResourceByName(name, classPath, true);
3048
            entry = AccessController.doPrivileged(dp);
3049
        } else {
3050
            entry = findResourceInternal(name, classPath, true);
3051
        }
3052
3053
        if (entry == null)
3054
            throw new ClassNotFoundException(name);
3055
3056
        Class<?> clazz = entry.loadedClass;
3057
        if (clazz != null)
3058
            return clazz;
3059
3060
        synchronized (this) {
3061
            clazz = entry.loadedClass;
3062
            if (clazz != null)
3063
                return clazz;
3064
3065
            if (entry.binaryContent == null)
3066
                throw new ClassNotFoundException(name);
3067
3068
            // Looking up the package
3069
            String packageName = null;
3070
            int pos = name.lastIndexOf('.');
3071
            if (pos != -1)
3072
                packageName = name.substring(0, pos);
3073
3074
            Package pkg = null;
3075
3076
            if (packageName != null) {
3077
                pkg = getPackage(packageName);
3078
                // Define the package (if null)
3079
                if (pkg == null) {
3080
                    try {
3081
                        if (entry.manifest == null) {
3082
                            definePackage(packageName, null, null, null, null,
3083
                                    null, null, null);
3084
                        } else {
3085
                            definePackage(packageName, entry.manifest,
3086
                                    entry.codeBase);
3087
                        }
3088
                    } catch (IllegalArgumentException e) {
3089
                        // Ignore: normal error due to dual definition of package
3090
                    }
3091
                    pkg = getPackage(packageName);
3092
                }
3093
            }
3094
3095
            if (securityManager != null) {
3096
3097
                // Checking sealing
3098
                if (pkg != null) {
3099
                    boolean sealCheck = true;
3100
                    if (pkg.isSealed()) {
3101
                        sealCheck = pkg.isSealed(entry.codeBase);
3102
                    } else {
3103
                        sealCheck = (entry.manifest == null)
3104
                                || !isPackageSealed(packageName, entry.manifest);
3105
                    }
3106
                    if (!sealCheck)
3107
                        throw new SecurityException
3108
                                ("Sealing violation loading " + name + " : Package "
3109
                                        + packageName + " is sealed.");
3110
                }
3111
3112
            }
3113
3114
            try {
3115
                clazz = defineClass(name, entry.binaryContent, 0,
3116
                        entry.binaryContent.length,
3117
                        new CodeSource(entry.codeBase, entry.certificates));
3118
            } catch (UnsupportedClassVersionError ucve) {
3119
                throw new UnsupportedClassVersionError(
3120
                        ucve.getLocalizedMessage() + " " +
3121
                                sm.getString("webappClassLoader.wrongVersion",
3122
                                        name));
3123
            }
3124
            entry.loadedClass = clazz;
3125
            entry.binaryContent = null;
3126
            entry.source = null;
3127
            entry.codeBase = null;
3128
            entry.manifest = null;
3129
            entry.certificates = null;
3130
        }
3131
3132
        return clazz;
3133
3134
    }
3135
3136
    /**
3137
     * Find specified resource in local repositories.
3138
     *
3139
     * @return the loaded resource, or null if the resource isn't found
3140
     */
3141
    protected ResourceEntry findResourceInternal(File file, String path){
3142
        ResourceEntry entry = new ResourceEntry();
3143
        try {
3144
            entry.source = getURI(new File(file, path));
3145
            String sourceString = entry.source.toString();
3146
            if (sourceString.startsWith(webInfClassesCodeBase.toString()) &&
3147
                    sourceString.endsWith(CLASS_FILE_SUFFIX)) {
3148
                entry.codeBase = webInfClassesCodeBase;
3149
            } else {
3150
                entry.codeBase = entry.source;
3151
            }
3152
        } catch (MalformedURLException e) {
3153
            return null;
3154
        }
3155
        return entry;
3156
    }
3157
3158
3159
    /**
3160
     * Find specified resource in local repositories.
3161
     *
3162
     * @return the loaded resource, or null if the resource isn't found
3163
     */
3164
    protected ResourceEntry findResourceInternal(final String name, final String path,
3165
                                                 final boolean manifestRequired) {
3166
3167
        if (!started) {
3168
            log.info(sm.getString("webappClassLoader.stopped", name));
3169
            return null;
3170
        }
3171
3172
        if ((name == null) || (path == null))
3173
            return null;
3174
3175
        ResourceEntry entry = resourceEntries.get(name);
3176
        if (entry != null)
3177
            return entry;
3178
3179
        int contentLength = -1;
3180
        InputStream binaryStream = null;
3181
        boolean isClassResource = path.endsWith(CLASS_FILE_SUFFIX);
3182
        boolean isCacheable = isClassResource;
3183
        if (!isCacheable) {
3184
            isCacheable = path.startsWith(SERVICES_PREFIX);
3185
        }
3186
3187
        int jarFilesLength = jarFiles.length;
3188
        int repositoriesLength = repositories.length;
3189
3190
        int i;
3191
3192
        Resource resource = null;
3193
3194
        boolean fileNeedConvert = false;
3195
3196
        for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
3197
            try {
3198
3199
                String fullPath = repositories[i] + path;
3200
3201
                Object lookupResult = resources.lookup(fullPath);
3202
                if (lookupResult instanceof Resource) {
3203
                    resource = (Resource) lookupResult;
3204
                }
3205
3206
                // Note : Not getting an exception here means the resource was
3207
                // found
3208
3209
                ResourceAttributes attributes =
3210
                        (ResourceAttributes) resources.getAttributes(fullPath);
3211
                contentLength = (int) attributes.getContentLength();
3212
                String canonicalPath = attributes.getCanonicalPath();
3213
                if (canonicalPath != null) {
3214
                    // we create the ResourceEntry based on the information returned
3215
                    // by the DirContext rather than just using the path to the
3216
                    // repository. This allows to have smart DirContext implementations
3217
                    // that "virtualize" the docbase (e.g. Eclipse WTP)
3218
                    entry = findResourceInternal(new File(canonicalPath), "");
3219
                } else {
3220
                    // probably a resource not in the filesystem (e.g. in a
3221
                    // packaged war)
3222
                    entry = findResourceInternal(files[i], path);
3223
                }
3224
                entry.lastModified = attributes.getLastModified();
3225
3226
                if (resource != null) {
3227
3228
3229
                    try {
3230
                        binaryStream = resource.streamContent();
3231
                    } catch (IOException e) {
3232
                        return null;
3233
                    }
3234
3235
                    if (needConvert) {
3236
                        if (path.endsWith(".properties")) {
3237
                            fileNeedConvert = true;
3238
                        }
3239
                    }
3240
3241
                    // Register the full path for modification checking
3242
                    // Note: Only syncing on a 'constant' object is needed
3243
                    synchronized (allPermission) {
3244
3245
                        int j;
3246
3247
                        long[] result2 =
3248
                                new long[lastModifiedDates.length + 1];
3249
                        for (j = 0; j < lastModifiedDates.length; j++) {
3250
                            result2[j] = lastModifiedDates[j];
3251
                        }
3252
                        result2[lastModifiedDates.length] = entry.lastModified;
3253
                        lastModifiedDates = result2;
3254
3255
                        String[] result = new String[paths.length + 1];
3256
                        for (j = 0; j < paths.length; j++) {
3257
                            result[j] = paths[j];
3258
                        }
3259
                        result[paths.length] = fullPath;
3260
                        paths = result;
3261
3262
                    }
3263
3264
                }
3265
3266
            } catch (NamingException e) {
3267
                // Ignore
3268
            }
3269
        }
3270
3271
        if ((entry == null) && (notFoundResources.containsKey(name)))
3272
            return null;
3273
3274
        JarEntry jarEntry = null;
3275
3276
        synchronized (jarFiles) {
3277
3278
            try {
3279
                if (!openJARs()) {
3280
                    return null;
3281
                }
3282
                for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
3283
3284
                    jarEntry = jarFiles[i].getJarEntry(path);
3285
3286
                    if (jarEntry != null) {
3287
3288
                        entry = new ResourceEntry();
3289
                        try {
3290
                            entry.codeBase = getURI(jarRealFiles[i]);
3291
                            String jarFakeUrl = entry.codeBase.toString();
3292
                            jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
3293
                            entry.source = new URL(jarFakeUrl);
3294
                            entry.lastModified = jarRealFiles[i].lastModified();
3295
                        } catch (MalformedURLException e) {
3296
                            return null;
3297
                        }
3298
                        contentLength = (int) jarEntry.getSize();
3299
                        try {
3300
                            if (manifestRequired) {
3301
                                entry.manifest = jarFiles[i].getManifest();
3302
                            }
3303
                            binaryStream = jarFiles[i].getInputStream(jarEntry);
3304
                        } catch (IOException e) {
3305
                            return null;
3306
                        }
3307
3308
                        // Extract resources contained in JAR to the workdir
3309
                        if (antiJARLocking && !(path.endsWith(CLASS_FILE_SUFFIX))) {
3310
                            byte[] buf = new byte[1024];
3311
                            File resourceFile = new File
3312
                                    (loaderDir, jarEntry.getName());
3313
                            if (!resourceFile.exists()) {
3314
                                Enumeration<JarEntry> entries =
3315
                                        jarFiles[i].entries();
3316
                                while (entries.hasMoreElements()) {
3317
                                    JarEntry jarEntry2 =  entries.nextElement();
3318
                                    if (!(jarEntry2.isDirectory())
3319
                                            && (!jarEntry2.getName().endsWith
3320
                                            (CLASS_FILE_SUFFIX))) {
3321
                                        resourceFile = new File
3322
                                                (loaderDir, jarEntry2.getName());
3323
                                        try {
3324
                                            if (!resourceFile.getCanonicalPath().startsWith(
3325
                                                    canonicalLoaderDir)) {
3326
                                                throw new IllegalArgumentException(
3327
                                                        sm.getString("webappClassLoader.illegalJarPath",
3328
                                                                jarEntry2.getName()));
3329
                                            }
3330
                                        } catch (IOException ioe) {
3331
                                            throw new IllegalArgumentException(
3332
                                                    sm.getString("webappClassLoader.validationErrorJarPath",
3333
                                                            jarEntry2.getName()), ioe);
3334
                                        }
3335
                                        File parentFile = resourceFile.getParentFile();
3336
                                        if (!parentFile.mkdirs() && !parentFile.exists()) {
3337
                                            // Ignore the error (like the IOExceptions below)
3338
                                        }
3339
                                        FileOutputStream os = null;
3340
                                        InputStream is = null;
3341
                                        try {
3342
                                            is = jarFiles[i].getInputStream
3343
                                                    (jarEntry2);
3344
                                            os = new FileOutputStream
3345
                                                    (resourceFile);
3346
                                            while (true) {
3347
                                                int n = is.read(buf);
3348
                                                if (n <= 0) {
3349
                                                    break;
3350
                                                }
3351
                                                os.write(buf, 0, n);
3352
                                            }
3353
                                            resourceFile.setLastModified(
3354
                                                    jarEntry2.getTime());
3355
                                        } catch (IOException e) {
3356
                                            // Ignore
3357
                                        } finally {
3358
                                            try {
3359
                                                if (is != null) {
3360
                                                    is.close();
3361
                                                }
3362
                                            } catch (IOException e) {
3363
                                                // Ignore
3364
                                            }
3365
                                            try {
3366
                                                if (os != null) {
3367
                                                    os.close();
3368
                                                }
3369
                                            } catch (IOException e) {
3370
                                                // Ignore
3371
                                            }
3372
                                        }
3373
                                    }
3374
                                }
3375
                            }
3376
                        }
3377
3378
                    }
3379
3380
                }
3381
3382
                if (entry == null) {
3383
                    synchronized (notFoundResources) {
3384
                        notFoundResources.put(name, name);
3385
                    }
3386
                    return null;
3387
                }
3388
3389
                /* Only cache the binary content if there is some content
3390
                 * available one of the following is true:
3391
                 * a) It is a class file since the binary content is only cached
3392
                 *    until the class has been loaded
3393
                 *    or
3394
                 * b) The file needs conversion to address encoding issues (see
3395
                 *    below)
3396
                 *    or
3397
                 * c) The resource is a service provider configuration file located
3398
                 *    under META=INF/services
3399
                 *
3400
                 * In all other cases do not cache the content to prevent
3401
                 * excessive memory usage if large resources are present (see
3402
                 * https://bz.apache.org/bugzilla/show_bug.cgi?id=53081).
3403
                 */
3404
                if (binaryStream != null &&
3405
                        (isCacheable || fileNeedConvert)) {
3406
3407
                    byte[] binaryContent = new byte[contentLength];
3408
3409
                    int pos = 0;
3410
                    try {
3411
3412
                        while (true) {
3413
                            int n = binaryStream.read(binaryContent, pos,
3414
                                    binaryContent.length - pos);
3415
                            if (n <= 0)
3416
                                break;
3417
                            pos += n;
3418
                        }
3419
                    } catch (IOException e) {
3420
                        log.error(sm.getString("webappClassLoader.readError", name), e);
3421
                        return null;
3422
                    }
3423
                    if (fileNeedConvert) {
3424
                        // Workaround for certain files on platforms that use
3425
                        // EBCDIC encoding, when they are read through FileInputStream.
3426
                        // See commit message of rev.303915 for details
3427
                        // http://svn.apache.org/viewvc?view=revision&revision=303915
3428
                        String str = new String(binaryContent,0,pos);
3429
                        try {
3430
                            binaryContent = str.getBytes(CHARSET_UTF8);
3431
                        } catch (Exception e) {
3432
                            return null;
3433
                        }
3434
                    }
3435
                    entry.binaryContent = binaryContent;
3436
3437
                    // The certificates are only available after the JarEntry
3438
                    // associated input stream has been fully read
3439
                    if (jarEntry != null) {
3440
                        entry.certificates = jarEntry.getCertificates();
3441
                    }
3442
3443
                }
3444
            } finally {
3445
                if (binaryStream != null) {
3446
                    try {
3447
                        binaryStream.close();
3448
                    } catch (IOException e) { /* Ignore */}
3449
                }
3450
            }
3451
        }
3452
3453
        if (isClassResource && entry.binaryContent != null &&
3454
                this.transformers.size() > 0) {
3455
            // If the resource is a class just being loaded, decorate it
3456
            // with any attached transformers
3457
            String className = name.endsWith(CLASS_FILE_SUFFIX) ?
3458
                    name.substring(0, name.length() - CLASS_FILE_SUFFIX.length()) : name;
3459
            String internalName = className.replace(".", "/");
3460
3461
            for (ClassFileTransformer transformer : this.transformers) {
3462
                try {
3463
                    byte[] transformed = transformer.transform(
3464
                            this, internalName, null, null, entry.binaryContent
3465
                    );
3466
                    if (transformed != null) {
3467
                        entry.binaryContent = transformed;
3468
                    }
3469
                } catch (IllegalClassFormatException e) {
3470
                    log.error(sm.getString("webappClassLoader.transformError", name), e);
3471
                    return null;
3472
                }
3473
            }
3474
        }
3475
3476
        // Add the entry in the local resource repository
3477
        synchronized (resourceEntries) {
3478
            // Ensures that all the threads which may be in a race to load
3479
            // a particular class all end up with the same ResourceEntry
3480
            // instance
3481
            ResourceEntry entry2 = resourceEntries.get(name);
3482
            if (entry2 == null) {
3483
                resourceEntries.put(name, entry);
3484
            } else {
3485
                entry = entry2;
3486
            }
3487
        }
3488
3489
        return entry;
3490
3491
    }
3492
3493
3494
    /**
3495
     * Returns true if the specified package name is sealed according to the
3496
     * given manifest.
3497
     */
3498
    protected boolean isPackageSealed(String name, Manifest man) {
3499
3500
        String path = name.replace('.', '/') + '/';
3501
        Attributes attr = man.getAttributes(path);
3502
        String sealed = null;
3503
        if (attr != null) {
3504
            sealed = attr.getValue(Name.SEALED);
3505
        }
3506
        if (sealed == null) {
3507
            if ((attr = man.getMainAttributes()) != null) {
3508
                sealed = attr.getValue(Name.SEALED);
3509
            }
3510
        }
3511
        return "true".equalsIgnoreCase(sealed);
3512
3513
    }
3514
3515
3516
    /**
3517
     * Finds the resource with the given name if it has previously been
3518
     * loaded and cached by this class loader, and return an input stream
3519
     * to the resource data.  If this resource has not been cached, return
3520
     * <code>null</code>.
3521
     *
3522
     * @param name Name of the resource to return
3523
     */
3524
    protected InputStream findLoadedResource(String name) {
3525
3526
        ResourceEntry entry = resourceEntries.get(name);
3527
        if (entry != null) {
3528
            if (entry.binaryContent != null)
3529
                return new ByteArrayInputStream(entry.binaryContent);
3530
            else {
3531
                try {
3532
                    return entry.source.openStream();
3533
                } catch (IOException ioe) {
3534
                    // Ignore
3535
                }
3536
            }
3537
        }
3538
        return null;
3539
3540
    }
3541
3542
3543
    /**
3544
     * Finds the class with the given name if it has previously been
3545
     * loaded and cached by this class loader, and return the Class object.
3546
     * If this class has not been cached, return <code>null</code>.
3547
     *
3548
     * @param name Name of the resource to return
3549
     */
3550
    protected Class<?> findLoadedClass0(String name) {
3551
3552
        ResourceEntry entry = resourceEntries.get(name);
3553
        if (entry != null) {
3554
            return entry.loadedClass;
3555
        }
3556
        return (null);  // FIXME - findLoadedResource()
3557
3558
    }
3559
3560
3561
    /**
3562
     * Refresh the system policy file, to pick up eventual changes.
3563
     */
3564
    protected void refreshPolicy() {
3565
3566
        try {
3567
            // The policy file may have been modified to adjust
3568
            // permissions, so we're reloading it when loading or
3569
            // reloading a Context
3570
            Policy policy = Policy.getPolicy();
3571
            policy.refresh();
3572
        } catch (AccessControlException e) {
3573
            // Some policy files may restrict this, even for the core,
3574
            // so this exception is ignored
3575
        }
3576
3577
    }
3578
3579
3580
    /**
3581
     * Filter classes.
3582
     *
3583
     * @param name class name
3584
     * @return true if the class should be filtered
3585
     */
3586
    protected boolean filter(String name) {
3587
3588
        if (name == null)
3589
            return false;
3590
3591
        // Looking up the package
3592
        String packageName = null;
3593
        int pos = name.lastIndexOf('.');
3594
        if (pos != -1)
3595
            packageName = name.substring(0, pos);
3596
        else
3597
            return false;
3598
3599
        for (int i = 0; i < packageTriggers.length; i++) {
3600
            if (packageName.startsWith(packageTriggers[i]))
3601
                return true;
3602
        }
3603
3604
        return false;
3605
3606
    }
3607
3608
3609
    /**
3610
     * Validate a classname. As per SRV.9.7.2, we must restrict loading of
3611
     * classes from J2SE (java.*) and most classes of the servlet API
3612
     * (javax.servlet.*). That should enhance robustness and prevent a number
3613
     * of user error (where an older version of servlet.jar would be present
3614
     * in /WEB-INF/lib).
3615
     *
3616
     * @param name class name
3617
     * @return true if the name is valid
3618
     */
3619
    protected boolean validate(String name) {
3620
3621
        // Need to be careful with order here
3622
        if (name == null) {
3623
            // Can't load a class without a name
3624
            return false;
3625
        }
3626
        if (name.startsWith("java.")) {
3627
            // Must never load java.* classes
3628
            return false;
3629
        }
3630
        if (name.startsWith("javax.servlet.jsp.jstl")) {
3631
            // OK for web apps to package JSTL
3632
            return true;
3633
        }
3634
        if (name.startsWith("javax.servlet.")) {
3635
            // Web apps should never package any other Servlet or JSP classes
3636
            return false;
3637
        }
3638
        if (name.startsWith("javax.el")) {
3639
            // Must never load javax.el.* classes
3640
            return false;
3641
        }
3642
3643
        // Assume everything else is OK
3644
        return true;
3645
3646
    }
3647
3648
3649
    /**
3650
     * Check the specified JAR file, and return <code>true</code> if it does
3651
     * not contain any of the trigger classes.
3652
     *
3653
     * @param file  The JAR file to be checked
3654
     *
3655
     * @exception IOException if an input/output error occurs
3656
     */
3657
    protected boolean validateJarFile(File file)
3658
            throws IOException {
3659
3660
        if (triggers == null)
3661
            return (true);
3662
3663
        JarFile jarFile = null;
3664
        try {
3665
            jarFile = new JarFile(file);
3666
            for (int i = 0; i < triggers.length; i++) {
3667
                Class<?> clazz = null;
3668
                try {
3669
                    if (parent != null) {
3670
                        clazz = parent.loadClass(triggers[i]);
3671
                    } else {
3672
                        clazz = Class.forName(triggers[i]);
3673
                    }
3674
                } catch (Exception e) {
3675
                    clazz = null;
3676
                }
3677
                if (clazz == null)
3678
                    continue;
3679
                String name = triggers[i].replace('.', '/') + CLASS_FILE_SUFFIX;
3680
                if (log.isDebugEnabled())
3681
                    log.debug(" Checking for " + name);
3682
                JarEntry jarEntry = jarFile.getJarEntry(name);
3683
                if (jarEntry != null) {
3684
                    log.info("validateJarFile(" + file +
3685
                            ") - jar not loaded. See Servlet Spec 3.0, "
3686
                            + "section 10.7.2. Offending class: " + name);
3687
                    return false;
3688
                }
3689
            }
3690
            return true;
3691
        } finally {
3692
            if (jarFile != null) {
3693
                try {
3694
                    jarFile.close();
3695
                } catch (IOException ioe) {
3696
                    // Ignore
3697
                }
3698
            }
3699
        }
3700
    }
3701
3702
3703
    /**
3704
     * Get URL.
3705
     * @deprecated Use {@link #getURI(File)} instead
3706
     */
3707
    @Deprecated
3708
    protected URL getURL(File file, boolean encoded)
3709
            throws MalformedURLException {
3710
3711
        File realFile = file;
3712
        try {
3713
            realFile = realFile.getCanonicalFile();
3714
        } catch (IOException e) {
3715
            // Ignore
3716
        }
3717
        if(encoded) {
3718
            return getURI(realFile);
3719
        }
3720
3721
        return realFile.toURI().toURL();
3722
    }
3723
3724
3725
    /**
3726
     * Get the URI for the given file.
3727
     */
3728
    protected URL getURI(File file)
3729
            throws MalformedURLException {
3730
3731
3732
        File realFile = file;
3733
        try {
3734
            realFile = realFile.getCanonicalFile();
3735
        } catch (IOException e) {
3736
            // Ignore
3737
        }
3738
        return realFile.toURI().toURL();
3739
3740
    }
3741
3742
3743
    /**
3744
     * Delete the specified directory, including all of its contents and
3745
     * subdirectories recursively.
3746
     *
3747
     * @param dir File object representing the directory to be deleted
3748
     */
3749
    protected static void deleteDir(File dir) {
3750
3751
        String files[] = dir.list();
3752
        if (files == null) {
3753
            files = new String[0];
3754
        }
3755
        for (int i = 0; i < files.length; i++) {
3756
            File file = new File(dir, files[i]);
3757
            if (file.isDirectory()) {
3758
                deleteDir(file);
3759
            } else {
3760
                file.delete();
3761
            }
3762
        }
3763
        dir.delete();
3764
3765
    }
3766
3767
3768
}
(-)java/org/apache/catalina/loader/WebappLoader.java (-4 / +4 lines)
Lines 122-128 public class WebappLoader extends LifecycleMBeanBase Link Here
122
    /**
122
    /**
123
     * The class loader being managed by this Loader component.
123
     * The class loader being managed by this Loader component.
124
     */
124
     */
125
    private WebappClassLoader classLoader = null;
125
    private WebappClassLoaderBase classLoader = null;
126
126
127
127
128
    /**
128
    /**
Lines 715-725 public class WebappLoader extends LifecycleMBeanBase Link Here
715
    /**
715
    /**
716
     * Create associated classLoader.
716
     * Create associated classLoader.
717
     */
717
     */
718
    private WebappClassLoader createClassLoader()
718
    private WebappClassLoaderBase createClassLoader()
719
        throws Exception {
719
        throws Exception {
720
720
721
        Class<?> clazz = Class.forName(loaderClass);
721
        Class<?> clazz = Class.forName(loaderClass);
722
        WebappClassLoader classLoader = null;
722
        WebappClassLoaderBase classLoader = null;
723
723
724
        if (parentClassLoader == null) {
724
        if (parentClassLoader == null) {
725
            parentClassLoader = container.getParentClassLoader();
725
            parentClassLoader = container.getParentClassLoader();
Lines 727-733 public class WebappLoader extends LifecycleMBeanBase Link Here
727
        Class<?>[] argTypes = { ClassLoader.class };
727
        Class<?>[] argTypes = { ClassLoader.class };
728
        Object[] args = { parentClassLoader };
728
        Object[] args = { parentClassLoader };
729
        Constructor<?> constr = clazz.getConstructor(argTypes);
729
        Constructor<?> constr = clazz.getConstructor(argTypes);
730
        classLoader = (WebappClassLoader) constr.newInstance(args);
730
        classLoader = (WebappClassLoaderBase) constr.newInstance(args);
731
731
732
        return classLoader;
732
        return classLoader;
733
733
(-)java/org/apache/catalina/startup/Bootstrap.java (-18 / +1 lines)
Lines 146-169 public final class Bootstrap { Link Here
146
            }
146
            }
147
        }
147
        }
148
148
149
        ClassLoader classLoader = ClassLoaderFactory.createClassLoader
149
        return ClassLoaderFactory.createClassLoader(repositories, parent);
150
            (repositories, parent);
151
152
        // Retrieving MBean server
153
        MBeanServer mBeanServer = null;
154
        if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
155
            mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
156
        } else {
157
            mBeanServer = ManagementFactory.getPlatformMBeanServer();
158
        }
159
160
        // Register the server classloader
161
        ObjectName objectName =
162
            new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
163
        mBeanServer.registerMBean(classLoader, objectName);
164
165
        return classLoader;
166
167
    }
150
    }
168
151
169
    /**
152
    /**
(-)java/org/apache/catalina/startup/ClassLoaderFactory.java (-9 / +9 lines)
Lines 22-27 package org.apache.catalina.startup; Link Here
22
import java.io.File;
22
import java.io.File;
23
import java.io.IOException;
23
import java.io.IOException;
24
import java.net.URL;
24
import java.net.URL;
25
import java.net.URLClassLoader;
25
import java.security.AccessController;
26
import java.security.AccessController;
26
import java.security.PrivilegedAction;
27
import java.security.PrivilegedAction;
27
import java.util.LinkedHashSet;
28
import java.util.LinkedHashSet;
Lines 29-35 import java.util.List; Link Here
29
import java.util.Locale;
30
import java.util.Locale;
30
import java.util.Set;
31
import java.util.Set;
31
32
32
import org.apache.catalina.loader.StandardClassLoader;
33
import org.apache.juli.logging.Log;
33
import org.apache.juli.logging.Log;
34
import org.apache.juli.logging.LogFactory;
34
import org.apache.juli.logging.LogFactory;
35
35
Lines 124-136 public final class ClassLoaderFactory { Link Here
124
        // Construct the class loader itself
124
        // Construct the class loader itself
125
        final URL[] array = set.toArray(new URL[set.size()]);
125
        final URL[] array = set.toArray(new URL[set.size()]);
126
        return AccessController.doPrivileged(
126
        return AccessController.doPrivileged(
127
                new PrivilegedAction<StandardClassLoader>() {
127
                new PrivilegedAction<URLClassLoader>() {
128
                    @Override
128
                    @Override
129
                    public StandardClassLoader run() {
129
                    public URLClassLoader run() {
130
                        if (parent == null)
130
                        if (parent == null)
131
                            return new StandardClassLoader(array);
131
                            return new URLClassLoader(array);
132
                        else
132
                        else
133
                            return new StandardClassLoader(array, parent);
133
                            return new URLClassLoader(array, parent);
134
                    }
134
                    }
135
                });
135
                });
136
    }
136
    }
Lines 222-234 public final class ClassLoaderFactory { Link Here
222
            }
222
            }
223
223
224
        return AccessController.doPrivileged(
224
        return AccessController.doPrivileged(
225
                new PrivilegedAction<StandardClassLoader>() {
225
                new PrivilegedAction<URLClassLoader>() {
226
                    @Override
226
                    @Override
227
                    public StandardClassLoader run() {
227
                    public URLClassLoader run() {
228
                        if (parent == null)
228
                        if (parent == null)
229
                            return new StandardClassLoader(array);
229
                            return new URLClassLoader(array);
230
                        else
230
                        else
231
                            return new StandardClassLoader(array, parent);
231
                            return new URLClassLoader(array, parent);
232
                    }
232
                    }
233
                });
233
                });
234
    }
234
    }
(-)java/org/apache/tomcat/util/compat/JreCompat.java (-2 / +11 lines)
Lines 34-39 public class JreCompat { Link Here
34
    private static StringManager sm =
34
    private static StringManager sm =
35
            StringManager.getManager(JreCompat.class.getPackage().getName());
35
            StringManager.getManager(JreCompat.class.getPackage().getName());
36
    private static final boolean jre8Available;
36
    private static final boolean jre8Available;
37
    private static final boolean jre7Available;
37
    
38
    
38
    
39
    
39
    static {
40
    static {
Lines 43-53 public class JreCompat { Link Here
43
        if (Jre8Compat.isSupported()) {
44
        if (Jre8Compat.isSupported()) {
44
            instance = new Jre8Compat();
45
            instance = new Jre8Compat();
45
            jre8Available = true;
46
            jre8Available = true;
47
            jre7Available = true;
46
        } else if (Jre7Compat.isSupported()) {
48
        } else if (Jre7Compat.isSupported()) {
47
            instance = new Jre7Compat();
49
            instance = new Jre7Compat();
48
            jre8Available = false;
50
            jre8Available = false;
51
            jre7Available = true;
49
        } else {
52
        } else {
50
            instance = new JreCompat();
53
            instance = new JreCompat();
54
            jre7Available = false;
51
            jre8Available = false;
55
            jre8Available = false;
52
        }
56
        }
53
    }
57
    }
Lines 106-113 public class JreCompat { Link Here
106
    public static boolean isJre8Available() {
110
    public static boolean isJre8Available() {
107
        return jre8Available;
111
        return jre8Available;
108
    }
112
    }
109
    
113
110
    
111
    @SuppressWarnings("unused")
114
    @SuppressWarnings("unused")
112
    public void setUseServerCipherSuitesOrder(SSLServerSocket socket,
115
    public void setUseServerCipherSuitesOrder(SSLServerSocket socket,
113
            boolean useCipherSuitesOrder) {
116
            boolean useCipherSuitesOrder) {
Lines 121-124 public class JreCompat { Link Here
121
        throw new UnsupportedOperationException(sm.getString("jreCompat.noServerCipherSuiteOrder"));
124
        throw new UnsupportedOperationException(sm.getString("jreCompat.noServerCipherSuiteOrder"));
122
    }
125
    }
123
126
127
    // JAVA 7 methods
128
129
    public static boolean isJre7Available() {
130
        return jre7Available;
131
    }
132
124
}
133
}
(-)test/org/apache/catalina/loader/TestParallelWebappClassLoader.java (+140 lines)
Line 0 Link Here
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 org.apache.catalina.Context;
22
import org.apache.catalina.startup.Tomcat;
23
import org.apache.catalina.startup.TomcatBaseTest;
24
import org.apache.tomcat.util.compat.JreCompat;
25
import org.junit.Test;
26
27
import javax.servlet.ServletException;
28
import javax.servlet.http.HttpServlet;
29
import javax.servlet.http.HttpServletRequest;
30
import javax.servlet.http.HttpServletResponse;
31
import java.io.IOException;
32
import java.lang.reflect.Method;
33
34
import static org.junit.Assert.assertNotEquals;
35
import static org.junit.Assert.assertNotNull;
36
import static org.junit.Assert.assertNull;
37
import static org.junit.Assert.assertTrue;
38
import static org.junit.Assert.fail;
39
40
/**
41
 * @author Huxing Zhang (huxing.zhx@alibaba-inc.com)
42
 */
43
public class TestParallelWebappClassLoader extends TomcatBaseTest {
44
45
    private static final String PARALLEL_CLASSLOADER =
46
            "org.apache.catalina.loader.ParallelWebappClassLoader";
47
    private static final String DUMMY_SERVLET =
48
            "org.apache.catalina.loader.DummyServlet";
49
50
    @Test
51
    public void testParallelCapableOnJre7() {
52
        if (!JreCompat.getInstance().isJre7Available()) {
53
            // ignore on Jre6 or lower
54
            return;
55
        }
56
        try {
57
            Tomcat tomcat = getTomcatInstance();
58
            Context ctx = tomcat.addContext("", null);
59
60
            WebappLoader webappLoader = new WebappLoader();
61
            webappLoader.setLoaderClass(PARALLEL_CLASSLOADER);
62
            ctx.setLoader(webappLoader);
63
64
            tomcat.start();
65
66
            ClassLoader classloader = ctx.getLoader().getClassLoader();
67
68
            assertTrue(classloader instanceof ParallelWebappClassLoader);
69
70
            // parallel class loading capable
71
            Method getClassLoadingLock =
72
                    getDeclaredMethod(classloader.getClass(), "getClassLoadingLock", String.class);
73
            // make sure we have getClassLoadingLock on JRE7.
74
            assertNotNull(getClassLoadingLock);
75
            // give us permission to access protected method
76
            getClassLoadingLock.setAccessible(true);
77
78
            Object lock = getClassLoadingLock.invoke(classloader, DUMMY_SERVLET);
79
            // make sure it is not a ParallelWebappClassLoader object lock
80
            assertNotEquals(lock, classloader);
81
        } catch (Exception e) {
82
            e.printStackTrace();
83
            fail("testParallelCapableOnJre7 fails.");
84
        }
85
    }
86
87
    @Test
88
    public void testParallelIncapableOnJre6() {
89
        if (JreCompat.getInstance().isJre7Available()) {
90
            // ignore on Jre7 or above
91
            return;
92
        }
93
        try {
94
            Tomcat tomcat = getTomcatInstance();
95
            // Must have a real docBase - just use temp
96
            Context ctx = tomcat.addContext("",
97
                    System.getProperty("java.io.tmpdir"));
98
99
            WebappLoader webappLoader = new WebappLoader();
100
            webappLoader.setLoaderClass(PARALLEL_CLASSLOADER);
101
            ctx.setLoader(webappLoader);
102
103
            tomcat.start();
104
105
            ClassLoader classloader = ctx.getLoader().getClassLoader();
106
107
            assertTrue(classloader instanceof ParallelWebappClassLoader);
108
109
            // parallel class loading capable
110
            Method getClassLoadingLock =
111
                    getDeclaredMethod(classloader.getClass(), "getClassLoadingLock", String.class);
112
            // make sure we don't have getClassLoadingLock on JRE6.
113
            assertNull(getClassLoadingLock);
114
        } catch (Exception e) {
115
            e.printStackTrace();
116
            fail("testParallelIncapableOnJre6 fails.");
117
        }
118
    }
119
120
    private Method getDeclaredMethod(Class clazz, String name, Class<?>... parameterTypes) {
121
        if (clazz == null) return null;
122
        for (Method method: clazz.getDeclaredMethods()) {
123
            if (method.getName().equals(name)) {
124
                return method;
125
            }
126
        }
127
        // find from super class
128
        return getDeclaredMethod(clazz.getSuperclass(), name, parameterTypes);
129
    }
130
131
    private static final class DummyServlet extends HttpServlet {
132
133
        @Override
134
        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
135
                throws ServletException, IOException {
136
            // do nothing
137
        }
138
139
    }
140
}

Return to bug 57681