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]>