DO NOT REPLY TO THIS EMAIL, BUT PLEASE POST YOUR BUG 
RELATED COMMENTS THROUGH THE WEB INTERFACE AVAILABLE AT
<http://nagoya.apache.org/bugzilla/show_bug.cgi?id=15557>.
ANY REPLY MADE TO THIS MESSAGE WILL NOT BE COLLECTED AND 
INSERTED IN THE BUG DATABASE.

http://nagoya.apache.org/bugzilla/show_bug.cgi?id=15557

Remove use of URL.setURLStreamHandlerFactory()

           Summary: Remove use of URL.setURLStreamHandlerFactory()
           Product: Tomcat 4
           Version: 4.1.18
          Platform: All
        OS/Version: All
            Status: NEW
          Severity: Enhancement
          Priority: Other
         Component: Catalina
        AssignedTo: [EMAIL PROTECTED]
        ReportedBy: [EMAIL PROTECTED]


o.a.c.loader.WebappLoader calls URL.setURLStreamHandlerFactory() with an
instance of o.a.n.resources.DirContextURLStreamHandlerFactory in its start()
method. This factory returns URL stream handlers for the "jndi" protocol used
for web app resources. With the URL stream handler factory in place for this
protocol, URL instances can be created for URLs like
"jar:jndi:/WEB-INF/lib/foo.jar!/" and calling openConnection on such a URL
returns a JarURLConnection. This feature is used in some places in Catalina and
Jasper to scan for JAR entries.

However, registering a URL stream handler factory with
URL.setURLStreamHandlerFactory() has nasty side effects. I'm working on a
slimmed down Tomcat distribution package and among other things it will support
upgrades and feature additions in a live Tomcat instance by replacing the
classloaders for all server classes. This fails when a URL stream handler has
been registered this way, since the URL class keeps a reference to the stream
handler factory instance that was loaded by the original classloader. Since the
setURLStreamHandlerFactory() can only be called once per JVM instance, using it
for Tomcat internals also prevents an application that embeds Tomcat to use it.

I therefore suggest a different approach. The o.a.c.c.ApplicationContext already
returns URL instances that are created with an instance of
DirContextURLStreamHandler as its stream handler. This means that calling
openStream() on a URL returned by ServletContext.getResource() returns a stream
to the underlying JNDI resource. Wrapping a java.util.jar.JarInputStream around
this stream provides access to the JAR entries. A positive side-effect of this
approach is that it solves the problem with closing JarFile that is mentioned in
various places; there's no JarFile to close with this approach and closing the
JarInputStream works fine.

In more detail, this is what needs to be done to avoid the side effects of
setURLStreamHandlerFactory():
1) In o.a.c.loader.WebappLoader#start(), remove the call to
URL.setURLStreamHandlerFactory()
2) Remove the o.a.n.resources.DirContextURLStreamHandlerFactory class
3) In all places where URLs like "jar:jndi:/WEB-INF/lib/foo.jar!/" are created
to access JAR entries, replace it with code that instead wraps a JarInputStream
around the stream retrieved from the ServletContext.getResourceAsStream(). As
far as I can tell, there are three places in Catalina and Jasper (I have not
looked as non-essential code, such as the various management applications):
  + o.a.c.startup.ContextConfig
  + o.a.j.compiler.TagLibrayInfoImpl
  + o.a.j.compiler.TldLocationsCache

I have made these changes for my distribution, but it would be really nice to
see the same changes made in the Tomcat source. Here is an updated version of
the affected o.a.c.startup.ContextConfig code:

    private void tldScanJar(String resourcePath, SAXParser saxParser,
                            DefaultHandler tldHandler) throws Exception {

        if (debug >= 1) {
            log(" Scanning JAR at resource path '" + resourcePath + "'");
        }

        String name = null;
        JarInputStream jarIS = null;
        try {
            URL url = context.getServletContext().getResource(resourcePath);
            if (url == null) {
                throw new IllegalArgumentException
                    (sm.getString("contextConfig.tldResourcePath",
                                  resourcePath));
            }
            jarIS = new JarInputStream(url.openStream());
            JarEntry entry = null;
            while ((entry = jarIS.getNextJarEntry()) != null) {
                name = entry.getName();
                if (debug >= 2) {
                    log(" Looking at '" + name + "' in '" + resourcePath + "'");
                }
                if (!name.startsWith("META-INF/")) {
                    continue;
                }
                if (!name.endsWith(".tld")) {
                    continue;
                }
                if (debug >= 2) {
                    log("  Processing TLD at '" + name + "'");
                }
                tldScanStream(jarIS, saxParser, tldHandler);
            }
        } catch (Exception e) {
            if (name == null) {
                log(sm.getString("contextConfig.tldJarException",
                                 resourcePath), e);
            } else {
                log(sm.getString("contextConfig.tldEntryException",
                                 name, resourcePath), e);
            }
        } finally {
            if (jarIS != null) {
                try {
                    jarIS.close();
                } catch (Throwable t) {
                    ;
                }
            }
        }
    }

    private void tldScanStream(InputStream resourceStream, SAXParser saxParser,
                               DefaultHandler tldHandler)
        throws Exception {

        /*
         * Create a wrapper around the stream so that it doesn't close
         * when the parser calls close(). Unless this is done, parsing
         * a JarInputStream fails when looking for the next entry.
         */
        InputStream is = new InputStreamWrapper(resourceStream);
        saxParser.parse(is, tldHandler);
    }

    private static class InputStreamWrapper extends InputStream {
        private InputStream is;
        public InputStreamWrapper(InputStream is) {
            this.is = is;
        }
        public int read() throws IOException {return is.read();}
        public int read(byte[] b) throws IOException {return is.read(b);}
        public int read(byte[] b, int off, int len) throws IOException {
            return is.read(b, off, len);
        }
        public long skip(long n) throws IOException {return is.skip(n);}
        public int available() throws IOException {return is.available();}
        public void close() throws IOException {
            // A no-op in this wrapper
        }
        public void mark(int readLimit) {is.mark(readLimit);}
        public void reset() throws IOException {is.reset();}
        public boolean markSupported() {return is.markSupported();}
    }

The changes needed in o.a.j.compiler.TagLibrayInfoImpl and
o.a.j.compiler.TldLocationsCache are very similar, but if you want them, I'll
post them as well.

A possible alternative to this solution is to create a new 
DirContextJarURLStreamHandler that returns a JarURLConnection from
openConnection() and let o.a.c.c.ApplicationContext provide an instance of this
class for all URLs that represent JAR files in the JNDI context. All places
where JAR files are scanned must still be changed, so I think it's pretty much
the same amount of work. I have not tested this alternative and I'm not sure if
it has any advantages over the approach I describe here.

--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to