Author: markt Date: Wed Apr 20 11:28:53 2011 New Revision: 1095367 URL: http://svn.apache.org/viewvc?rev=1095367&view=rev Log: Switch JAR scanning to use JarInputStream rather JarFile for significant startup performance improvements
Added: tomcat/trunk/java/org/apache/tomcat/util/scan/NonClosingJarInputStream.java (with props) Modified: tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java tomcat/trunk/java/org/apache/catalina/startup/TldConfig.java tomcat/trunk/java/org/apache/jasper/compiler/TldLocationsCache.java tomcat/trunk/webapps/docs/changelog.xml Modified: tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java?rev=1095367&r1=1095366&r2=1095367&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java (original) +++ tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java Wed Apr 20 11:28:53 2011 @@ -44,8 +44,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.zip.ZipEntry; +import java.util.jar.JarInputStream; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; @@ -1376,17 +1375,28 @@ public class ContextConfig for (WebXml fragment : fragments) { URL url = fragment.getURL(); - JarFile jarFile = null; + JarInputStream jarInputStream = null; InputStream is = null; ServletContainerInitializer sci = null; try { if ("jar".equals(url.getProtocol())) { - JarURLConnection conn = + JarURLConnection jarConn = (JarURLConnection) url.openConnection(); - jarFile = conn.getJarFile(); - ZipEntry entry = jarFile.getEntry(SCI_LOCATION); + URL resourceURL = jarConn.getJarFileURL(); + URLConnection resourceConn = resourceURL.openConnection(); + resourceConn.setUseCaches(false); + + jarInputStream = + new JarInputStream(resourceConn.getInputStream()); + JarEntry entry = jarInputStream.getNextJarEntry(); + while (entry != null) { + if (SCI_LOCATION.equals(entry.getName())) { + break; + } + entry = jarInputStream.getNextJarEntry(); + } if (entry != null) { - is = jarFile.getInputStream(entry); + is = jarInputStream; } } else if ("file".equals(url.getProtocol())) { String path = url.getPath(); @@ -1412,9 +1422,9 @@ public class ContextConfig // Ignore } } - if (jarFile != null) { + if (jarInputStream != null) { try { - jarFile.close(); + jarInputStream.close(); } catch (IOException e) { // Ignore } @@ -1504,14 +1514,24 @@ public class ContextConfig protected void processResourceJARs(Set<WebXml> fragments) { for (WebXml fragment : fragments) { URL url = fragment.getURL(); - JarFile jarFile = null; + JarInputStream jarInputStream = null; try { // Note: Ignore file URLs for now since only jar URLs will be accepted if ("jar".equals(url.getProtocol())) { - JarURLConnection conn = + JarURLConnection jarConn = (JarURLConnection) url.openConnection(); - jarFile = conn.getJarFile(); - ZipEntry entry = jarFile.getEntry("META-INF/resources/"); + URL resourceURL = jarConn.getJarFileURL(); + URLConnection resourceConn = resourceURL.openConnection(); + resourceConn.setUseCaches(false); + jarInputStream = + new JarInputStream(resourceConn.getInputStream()); + JarEntry entry = jarInputStream.getNextJarEntry(); + while (entry != null) { + if ("META-INF/resources/".equals(entry.getName())) { + break; + } + entry = jarInputStream.getNextJarEntry(); + } if (entry != null) { context.addResourceJarUrl(url); } @@ -1520,9 +1540,9 @@ public class ContextConfig log.error(sm.getString("contextConfig.resourceJarFail", url, context.getName())); } finally { - if (jarFile != null) { + if (jarInputStream != null) { try { - jarFile.close(); + jarInputStream.close(); } catch (IOException e) { // Ignore } @@ -1780,50 +1800,42 @@ public class ContextConfig protected void processAnnotationsJar(URL url, WebXml fragment) { - JarFile jarFile = null; + JarInputStream jarInputStream = null; try { URLConnection urlConn = url.openConnection(); - JarURLConnection jarUrlConn; + JarURLConnection jarConn; if (!(urlConn instanceof JarURLConnection)) { // This should never happen sm.getString("contextConfig.jarUrl", url); return; } - jarUrlConn = (JarURLConnection) urlConn; - jarUrlConn.setUseCaches(false); - jarFile = jarUrlConn.getJarFile(); - - Enumeration<JarEntry> jarEntries = jarFile.entries(); - while (jarEntries.hasMoreElements()) { - JarEntry jarEntry = jarEntries.nextElement(); - String entryName = jarEntry.getName(); + jarConn = (JarURLConnection) urlConn; + jarConn.setUseCaches(false); + URL resourceURL = jarConn.getJarFileURL(); + URLConnection resourceConn = resourceURL.openConnection(); + + jarInputStream = new JarInputStream(resourceConn.getInputStream()); + + JarEntry entry = jarInputStream.getNextJarEntry(); + while (entry != null) { + String entryName = entry.getName(); if (entryName.endsWith(".class")) { - InputStream is = null; try { - is = jarFile.getInputStream(jarEntry); - processAnnotationsStream(is, fragment); + processAnnotationsStream(jarInputStream, fragment); } catch (IOException e) { log.error(sm.getString("contextConfig.inputStreamJar", entryName, url),e); - } finally { - if (is != null) { - try { - is.close(); - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - } - } } } } } catch (IOException e) { log.error(sm.getString("contextConfig.jarFile", url), e); } finally { - if (jarFile != null) { + if (jarInputStream != null) { try { - jarFile.close(); + jarInputStream.close(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } @@ -2302,45 +2314,48 @@ public class ContextConfig private Map<String,WebXml> fragments = new HashMap<String,WebXml>(); @Override - public void scan(JarURLConnection urlConn) throws IOException { + public void scan(JarURLConnection jarConn) throws IOException { - JarFile jarFile = null; - InputStream stream = null; + // JarURLConnection#getJarFile() creates temporary copies of the JAR + // if the underlying resource is not a file URL. That can be slow so + // the InputStream for the resource is used + URL resourceURL = jarConn.getJarFileURL(); + + JarInputStream jarInputStream = null; WebXml fragment = new WebXml(); try { - urlConn.setUseCaches(false); - jarFile = urlConn.getJarFile(); - JarEntry fragmentEntry = - jarFile.getJarEntry(FRAGMENT_LOCATION); - if (fragmentEntry == null) { + URLConnection resourceConn = resourceURL.openConnection(); + resourceConn.setUseCaches(false); + jarInputStream = + new JarInputStream(resourceConn.getInputStream()); + JarEntry entry = jarInputStream.getNextJarEntry(); + while (entry != null) { + if (FRAGMENT_LOCATION.equals(entry.getName())) { + break; + } + entry = jarInputStream.getNextJarEntry(); + } + + if (entry == null) { // If there is no web.xml, normal JAR no impact on // distributable fragment.setDistributable(true); } else { - stream = jarFile.getInputStream(fragmentEntry); InputSource source = new InputSource( - urlConn.getJarFileURL().toString() + - "!/" + FRAGMENT_LOCATION); - source.setByteStream(stream); + resourceURL.toString() + "!/" + FRAGMENT_LOCATION); + source.setByteStream(jarInputStream); parseWebXml(source, fragment, true); } } finally { - if (jarFile != null) { + if (jarInputStream != null) { try { - jarFile.close(); - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - } - } - if (stream != null) { - try { - stream.close(); + jarInputStream.close(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } } - fragment.setURL(urlConn.getURL()); + fragment.setURL(jarConn.getURL()); if (fragment.getName() == null) { fragment.setName(fragment.getURL().toString()); } Modified: tomcat/trunk/java/org/apache/catalina/startup/TldConfig.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/TldConfig.java?rev=1095367&r1=1095366&r2=1095367&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/TldConfig.java (original) +++ tomcat/trunk/java/org/apache/catalina/startup/TldConfig.java Wed Apr 20 11:28:53 2011 @@ -21,15 +21,15 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; -import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.JarEntry; -import java.util.jar.JarFile; import javax.servlet.ServletContext; import javax.servlet.descriptor.TaglibDescriptor; @@ -44,6 +44,7 @@ import org.apache.tomcat.JarScannerCallb import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.digester.Digester; import org.apache.tomcat.util.res.StringManager; +import org.apache.tomcat.util.scan.NonClosingJarInputStream; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -377,9 +378,10 @@ public final class TldConfig implements log.trace(sm.getString("tldConfig.webxmlAdd", resourcePath, descriptor.getTaglibURI())); } + InputStream stream = null; try { - InputStream stream = context.getServletContext( - ).getResourceAsStream(resourcePath); + stream = context.getServletContext().getResourceAsStream( + resourcePath); XmlErrorHandler handler = tldScanStream(stream); handler.logFindings(log, resourcePath); taglibUris.add(descriptor.getTaglibURI()); @@ -387,6 +389,14 @@ public final class TldConfig implements } catch (IOException ioe) { log.warn(sm.getString("tldConfig.webxmlFail", resourcePath, descriptor.getTaglibURI()), ioe); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Throwable t) { + ExceptionUtils.handleThrowable(t); + } + } } } } @@ -494,34 +504,41 @@ public final class TldConfig implements * Scans the given JarURLConnection for TLD files located in META-INF * (or a sub-directory of it). * - * @param conn The JarURLConnection to the JAR file to scan + * @param jarConn The JarURLConnection to the JAR file to scan * * Keep in sync with o.a.j.comiler.TldLocationsCache */ - private void tldScanJar(JarURLConnection conn) { + private void tldScanJar(JarURLConnection jarConn) { - JarFile jarFile = null; + // JarURLConnection#getJarFile() creates temporary copies of the JAR if + // the underlying resource is not a file URL. That can be slow so the + // InputStream for the resource is used + URL resourceURL = jarConn.getJarFileURL(); + NonClosingJarInputStream jarInputStream = null; String name = null; + try { - conn.setUseCaches(false); - jarFile = conn.getJarFile(); - Enumeration<JarEntry> entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); + URLConnection resourceConn = resourceURL.openConnection(); + resourceConn.setUseCaches(false); + jarInputStream = + new NonClosingJarInputStream(resourceConn.getInputStream()); + + JarEntry entry = jarInputStream.getNextJarEntry(); + while (entry != null) { name = entry.getName(); - if (!name.startsWith("META-INF/")) continue; - if (!name.endsWith(".tld")) continue; - InputStream stream = jarFile.getInputStream(entry); - XmlErrorHandler handler = tldScanStream(stream); - handler.logFindings(log, conn.getURL() + name); + if (name.startsWith("META-INF/") && name.endsWith(".tld")) { + XmlErrorHandler handler = tldScanStream(jarInputStream); + handler.logFindings(log, jarConn.getURL() + name); + } + entry = jarInputStream.getNextJarEntry(); } } catch (IOException ioe) { - log.warn(sm.getString("tldConfig.jarFail", conn.getURL() + name), + log.warn(sm.getString("tldConfig.jarFail", jarConn.getURL() + name), ioe); } finally { - if (jarFile != null) { + if (jarInputStream != null) { try { - jarFile.close(); + jarInputStream.reallyClose(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } @@ -556,13 +573,6 @@ public final class TldConfig implements throw new IOException(s); } finally { tldDigester.reset(); - if (resourceStream != null) { - try { - resourceStream.close(); - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - } - } } return result; } Modified: tomcat/trunk/java/org/apache/jasper/compiler/TldLocationsCache.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/jasper/compiler/TldLocationsCache.java?rev=1095367&r1=1095366&r2=1095367&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/jasper/compiler/TldLocationsCache.java (original) +++ tomcat/trunk/java/org/apache/jasper/compiler/TldLocationsCache.java Wed Apr 20 11:28:53 2011 @@ -21,14 +21,14 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; -import java.util.Enumeration; +import java.net.URL; +import java.net.URLConnection; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.JarEntry; -import java.util.jar.JarFile; import javax.servlet.ServletContext; @@ -40,6 +40,7 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.JarScanner; import org.apache.tomcat.JarScannerCallback; +import org.apache.tomcat.util.scan.NonClosingJarInputStream; /** @@ -392,32 +393,39 @@ public class TldLocationsCache { * (or a subdirectory of it), adding an implicit map entry to the taglib * map for any TLD that has a <uri> element. * - * @param conn The JarURLConnection to the JAR file to scan + * @param jarConn The JarURLConnection to the JAR file to scan * * Keep in sync with o.a.c.startup.TldConfig */ - private void tldScanJar(JarURLConnection conn) throws IOException { + private void tldScanJar(JarURLConnection jarConn) throws IOException { - JarFile jarFile = null; - String resourcePath = conn.getJarFileURL().toString(); + // JarURLConnection#getJarFile() creates temporary copies of the JAR if + // the underlying resource is not a file URL. That can be slow so the + // InputStream for the resource is used + URL resourceURL = jarConn.getJarFileURL(); + String resourcePath = resourceURL.toString(); + + NonClosingJarInputStream jarInputStream = null; + boolean foundTld = false; try { - conn.setUseCaches(false); - jarFile = conn.getJarFile(); - Enumeration<JarEntry> entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); + URLConnection resourceConn = resourceURL.openConnection(); + resourceConn.setUseCaches(false); + jarInputStream = + new NonClosingJarInputStream(resourceConn.getInputStream()); + JarEntry entry = jarInputStream.getNextJarEntry(); + while (entry != null) { String name = entry.getName(); - if (!name.startsWith("META-INF/")) continue; - if (!name.endsWith(".tld")) continue; - foundTld = true; - InputStream stream = jarFile.getInputStream(entry); - tldScanStream(resourcePath, name, stream); + if (name.startsWith("META-INF/") && name.endsWith(".tld")) { + foundTld = true; + tldScanStream(resourcePath, name, jarInputStream); + } + entry = jarInputStream.getNextJarEntry(); } } finally { - if (jarFile != null) { + if (jarInputStream != null) { try { - jarFile.close(); + jarInputStream.reallyClose(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } @@ -468,14 +476,6 @@ public class TldLocationsCache { } catch (JasperException e) { // Hack - makes exception handling simpler throw new IOException(e); - } finally { - if (stream != null) { - try { - stream.close(); - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - } - } } } Added: tomcat/trunk/java/org/apache/tomcat/util/scan/NonClosingJarInputStream.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/scan/NonClosingJarInputStream.java?rev=1095367&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/util/scan/NonClosingJarInputStream.java (added) +++ tomcat/trunk/java/org/apache/tomcat/util/scan/NonClosingJarInputStream.java Wed Apr 20 11:28:53 2011 @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.util.scan; + +import java.io.IOException; +import java.io.InputStream; +import java.util.jar.JarInputStream; + +/** + * When using a {@link JarInputStream} with an XML parser, the stream will be + * closed by the parser. This causes problems if multiple entries from the JAR + * need to be parsed. This implementation makes {{@link #close()} a NO-OP and + * adds {@link #reallyClose()} that will close the stream. + */ +public class NonClosingJarInputStream extends JarInputStream { + + public NonClosingJarInputStream(InputStream in, boolean verify) + throws IOException { + super(in, verify); + } + + public NonClosingJarInputStream(InputStream in) throws IOException { + super(in); + } + + @Override + public void close() throws IOException { + // Make this a NO-OP so that further entries can be read from the stream + } + + public void reallyClose() throws IOException { + super.close(); + } +} Propchange: tomcat/trunk/java/org/apache/tomcat/util/scan/NonClosingJarInputStream.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: tomcat/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1095367&r1=1095366&r2=1095367&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/changelog.xml (original) +++ tomcat/trunk/webapps/docs/changelog.xml Wed Apr 20 11:28:53 2011 @@ -79,6 +79,13 @@ fragments to the list of JARs to skip when scanning for TLDs and web fragments. (markt) </add> + <fix> + While scanning JARs for TLDs and fragments, avoid using JarFile and use + JarInputStream as in most circumstances where JARs are scanned, JarFile + will create a temporary copy of the JAR rather than using the resource + directly. This change significantly improves startup performance for + applications with lots of JARs to be scanned. (markt) + </fix> </changelog> </subsection> <subsection name="Coyote"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org