~ubuntu-branches/debian/squeeze/tomcat6/squeeze

« back to all changes in this revision

Viewing changes to .pc/0010-avoid-deadlock-in-WebappClassLoader.patch/java/org/apache/catalina/loader/WebappClassLoader.java

  • Committer: Bazaar Package Importer
  • Author(s): Torsten Werner
  • Date: 2010-06-28 21:41:31 UTC
  • Revision ID: james.westby@ubuntu.com-20100628214131-tubi81tw9dg0rill
Tags: 6.0.26-5
* Convert patches to dep3 format.
* Backport security fix from trunk to fix CVE-2010-1157. (Closes: #587447)
* Set urgency to medium due to the security fix.

Show diffs side-by-side

added added

removed removed

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