From 09ffc1f09499ca855b0d15560cf46631670efff2 Mon Sep 17 00:00:00 2001 From: markt Date: Tue, 23 Jun 2009 14:02:48 +0000 Subject: [PATCH] Align TLD scanning code between Jasper & Catalina (make Catalina like Jasper) - Still two copies (no easy way to avoid that) but at least only a single algorithm - Adds support for JAR URLs (useful when embedding) to TldConfig - Add comments to remind people to keep code in sync - JSP TCK passes git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@787678 13f79535-47bb-0310-9956-ffa450edef68 --- java/org/apache/catalina/startup/TldConfig.java | 436 +++++++-------------- .../apache/jasper/compiler/TldLocationsCache.java | 9 + 2 files changed, 159 insertions(+), 286 deletions(-) diff --git a/java/org/apache/catalina/startup/TldConfig.java b/java/org/apache/catalina/startup/TldConfig.java index e8b1bf670..838406eb1 100644 --- a/java/org/apache/catalina/startup/TldConfig.java +++ b/java/org/apache/catalina/startup/TldConfig.java @@ -19,28 +19,21 @@ package org.apache.catalina.startup; -import java.io.File; -import java.io.IOException; import java.io.InputStream; -import java.net.URISyntaxException; +import java.net.JarURLConnection; import java.net.URL; import java.net.URLClassLoader; +import java.net.URLConnection; import java.util.ArrayList; import java.util.Enumeration; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import javax.naming.NameClassPair; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.DirContext; -import javax.servlet.ServletException; +import javax.servlet.ServletContext; import org.apache.catalina.Context; import org.apache.catalina.Lifecycle; @@ -49,6 +42,7 @@ import org.apache.catalina.LifecycleListener; import org.apache.catalina.core.StandardContext; import org.apache.catalina.core.StandardHost; import org.apache.catalina.util.StringManager; +import org.apache.jasper.JasperException; import org.apache.tomcat.util.digester.Digester; import org.xml.sax.InputSource; @@ -81,9 +75,14 @@ public final class TldConfig implements LifecycleListener { private static Digester[] tldDigesters = new Digester[4]; private static final TldRuleSet tldRuleSet = new TldRuleSet(); - + + private static final String FILE_PROTOCOL = "file:"; + private static final String JAR_FILE_SUFFIX = ".jar"; + /* * Initializes the set of JARs that are known not to contain any TLDs + * + * Keep in sync with o.a.jasper.compiler.TldLocationsCache */ static { noTldJars = new HashSet(); @@ -273,8 +272,9 @@ public final class TldConfig implements LifecycleListener { } public void addApplicationListener( String s ) { - //if(log.isDebugEnabled()) + if(log.isDebugEnabled()) { log.debug( "Add tld listener " + s); + } listeners.add(s); } @@ -294,30 +294,8 @@ public final class TldConfig implements LifecycleListener { public void execute() throws Exception { long t1=System.currentTimeMillis(); - /* - * Acquire the list of TLD resource paths, possibly embedded in JAR - * files, to be processed - */ - Set resourcePaths = tldScanResourcePaths(); - Map jarPaths = getJarPaths(); - - // Scan each accumulated resource path for TLDs to be processed - Iterator paths = resourcePaths.iterator(); - while (paths.hasNext()) { - String path = paths.next(); - if (path.endsWith(".jar")) { - tldScanJar(path); - } else { - tldScanTld(path); - } - } - - if (jarPaths != null) { - Iterator files = jarPaths.values().iterator(); - while (files.hasNext()) { - tldScanJar(files.next()); - } - } + scanJars(); + processTldsInFileSystem("/WEB-INF/"); String list[] = getTldListeners(); @@ -337,89 +315,155 @@ public final class TldConfig implements LifecycleListener { // -------------------------------------------------------- Private Methods /** - * Scan the JAR file at the specified resource path for TLDs in the - * META-INF subdirectory, and scan each TLD for application - * event listeners that need to be registered. + * Scans all JARs accessible to the webapp's classloader and its + * parent classloaders for TLDs. + * + * The list of JARs always includes the JARs under WEB-INF/lib, as well as + * all shared JARs in the classloader delegation chain of the webapp's + * classloader. * - * @param resourcePath Resource path of the JAR file to scan + * Considering JARs in the classloader delegation chain constitutes a + * Tomcat-specific extension to the TLD search + * order defined in the JSP spec. It allows tag libraries packaged as JAR + * files to be shared by web applications by simply dropping them in a + * location that all web applications have access to (e.g., + * /common/lib). * - * @exception Exception if an exception occurs while scanning this JAR + * The set of shared JARs to be scanned for TLDs is narrowed down by + * the noTldJars class variable, which contains the names of JARs + * that are known not to contain any TLDs. + * + * Keep in sync with o.a.jasper.compiler.TldLocationsCache */ - private void tldScanJar(String resourcePath) throws Exception { + private void scanJars() throws Exception { - if (log.isDebugEnabled()) { - log.debug(" Scanning JAR at resource path '" + resourcePath + "'"); - } + ClassLoader webappLoader + = Thread.currentThread().getContextClassLoader(); + ClassLoader loader = webappLoader; - URL url = context.getServletContext().getResource(resourcePath); - if (url == null) { - throw new IllegalArgumentException - (sm.getString("contextConfig.tldResourcePath", - resourcePath)); - } + while (loader != null) { + if (loader instanceof URLClassLoader) { + URL[] urls = ((URLClassLoader) loader).getURLs(); + for (int i=0; i element. * - * @param file JAR file whose TLD entries are scanned for application - * listeners + * @param conn The JarURLConnection to the JAR file to scan + * + * Keep in sync with o.a.jasper.compiler.TldLocationsCache */ - private void tldScanJar(File file) throws Exception { + private void scanJar(JarURLConnection conn) throws JasperException { JarFile jarFile = null; - String name = null; + String resourcePath = conn.getJarFileURL().toString(); - String jarPath = file.getAbsolutePath(); + if (log.isDebugEnabled()) { + log.debug("Scanning JAR at resource path '" + resourcePath + "'"); + } try { - jarFile = new JarFile(file); + conn.setUseCaches(false); + jarFile = conn.getJarFile(); Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); - name = entry.getName(); - if (!name.startsWith("META-INF/")) { - continue; - } - if (!name.endsWith(".tld")) { - continue; - } + String name = entry.getName(); + if (!name.startsWith("META-INF/")) continue; + if (!name.endsWith(".tld")) continue; + InputStream stream = jarFile.getInputStream(entry); if (log.isTraceEnabled()) { log.trace(" Processing TLD at '" + name + "'"); } try { - tldScanStream(new InputSource(jarFile.getInputStream(entry))); + tldScanStream( + new InputSource(jarFile.getInputStream(entry))); } catch (Exception e) { log.error(sm.getString("contextConfig.tldEntryException", - name, jarPath, context.getPath()), - e); + name, resourcePath, context.getPath()), e); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Throwable t) { + // do nothing + } + } } } - } catch (Exception e) { + } catch (Exception ex) { log.error(sm.getString("contextConfig.tldJarException", - jarPath, context.getPath()), - e); + resourcePath, context.getPath()), ex); + } finally { if (jarFile != null) { try { jarFile.close(); } catch (Throwable t) { - // Ignore + // ignore + } + } + } + } + + + /** + * Searches the filesystem under /WEB-INF for any TLD files, and adds + * an implicit map entry to the taglib map for any TLD that has a + * element. + * + * Keep in sync with o.a.jasper.compiler.TldLocationsCache + */ + private void processTldsInFileSystem(String startPath) + throws Exception { + + ServletContext ctxt = context.getServletContext(); + + Set dirList = ctxt.getResourcePaths(startPath); + if (dirList != null) { + Iterator it = dirList.iterator(); + while (it.hasNext()) { + String path = it.next(); + if (path.endsWith("/")) { + processTldsInFileSystem(path); + } + if (!path.endsWith(".tld")) { + continue; + } + InputStream stream = ctxt.getResourceAsStream(path); + try { + tldScanStream(new InputSource(stream)); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Throwable t) { + // do nothing + } + } } } } @@ -449,213 +493,33 @@ public final class TldConfig implements LifecycleListener { } - /** - * Scan the TLD contents at the specified resource path, and register - * any application event listeners found there. - * - * @param resourcePath Resource path being scanned - * - * @exception Exception if an exception occurs while scanning this TLD - */ - private void tldScanTld(String resourcePath) throws Exception { - - if (log.isDebugEnabled()) { - log.debug(" Scanning TLD at resource path '" + resourcePath + "'"); - } - - InputSource inputSource = null; - try { - InputStream stream = - context.getServletContext().getResourceAsStream(resourcePath); - if (stream == null) { - throw new IllegalArgumentException - (sm.getString("contextConfig.tldResourcePath", - resourcePath)); - } - inputSource = new InputSource(stream); - tldScanStream(inputSource); - } catch (Exception e) { - throw new ServletException - (sm.getString("contextConfig.tldFileException", resourcePath, - context.getPath()), - e); - } - - } - - /** - * Accumulate and return a Set of resource paths to be analyzed for - * tag library descriptors. Each element of the returned set will be - * the context-relative path to either a tag library descriptor file, - * or to a JAR file that may contain tag library descriptors in its - * META-INF subdirectory. - * - * @exception IOException if an input/output error occurs while - * accumulating the list of resource paths - */ - private Set tldScanResourcePaths() throws IOException { - if (log.isDebugEnabled()) { - log.debug(" Accumulating TLD resource paths"); - } - Set resourcePaths = new HashSet(); - - // Accumulate resource paths explicitly listed in the web application - // deployment descriptor - if (log.isTraceEnabled()) { - log.trace(" Scanning elements in web.xml"); - } - String taglibs[] = context.findTaglibs(); - for (int i = 0; i < taglibs.length; i++) { - String resourcePath = context.findTaglib(taglibs[i]); - // FIXME - Servlet 2.4 DTD implies that the location MUST be - // a context-relative path starting with '/'? - if (!resourcePath.startsWith("/")) { - resourcePath = "/WEB-INF/" + resourcePath; - } - if (log.isTraceEnabled()) { - log.trace(" Adding path '" + resourcePath + - "' for URI '" + taglibs[i] + "'"); - } - resourcePaths.add(resourcePath); - } - - DirContext resources = context.getResources(); - if (resources != null) { - tldScanResourcePathsWebInf(resources, "/WEB-INF", resourcePaths); - } - - // Return the completed set - return (resourcePaths); - - } - /* - * Scans the web application's subdirectory identified by rootPath, - * along with its subdirectories, for TLDs. - * - * Initially, rootPath equals /WEB-INF. The /WEB-INF/classes and - * /WEB-INF/lib subdirectories are excluded from the search, as per the - * JSP 2.0 spec. - * - * @param resources The web application's resources - * @param rootPath The path whose subdirectories are to be searched for - * TLDs - * @param tldPaths The set of TLD resource paths to add to - */ - private void tldScanResourcePathsWebInf(DirContext resources, - String rootPath, - Set tldPaths) - throws IOException { - - if (log.isTraceEnabled()) { - log.trace(" Scanning TLDs in " + rootPath + " subdirectory"); - } - - try { - NamingEnumeration items = resources.list(rootPath); - while (items.hasMoreElements()) { - NameClassPair item = items.nextElement(); - String resourcePath = rootPath + "/" + item.getName(); - if (!resourcePath.endsWith(".tld") - && (resourcePath.startsWith("/WEB-INF/classes") - || resourcePath.startsWith("/WEB-INF/lib"))) { - continue; - } - if (resourcePath.endsWith(".tld")) { - if (log.isTraceEnabled()) { - log.trace(" Adding path '" + resourcePath + "'"); - } - tldPaths.add(resourcePath); - } else { - tldScanResourcePathsWebInf(resources, resourcePath, - tldPaths); - } - } - } catch (NamingException e) { - // Silent catch: it's valid that no /WEB-INF directory exists - } - } - - /** - * Returns a map of the paths to all JAR files that are accessible to the - * webapp and will be scanned for TLDs. + * Determines if the JAR file with the given jarPath needs to be + * scanned for TLDs. * - * The map always includes all the JARs under WEB-INF/lib, as well as - * shared JARs in the classloader delegation chain of the webapp's - * classloader. + * @param loader The current classloader in the parent chain + * @param webappLoader The webapp classloader + * @param jarPath The JAR file path * - * The latter constitutes a Tomcat-specific extension to the TLD search - * order defined in the JSP spec. It allows tag libraries packaged as JAR - * files to be shared by web applications by simply dropping them in a - * location that all web applications have access to (e.g., - * /common/lib). - * - * The set of shared JARs to be scanned for TLDs is narrowed down by - * the noTldJars class variable, which contains the names of JARs - * that are known not to contain any TLDs. - * - * @return Map of JAR file paths + * @return TRUE if the JAR file identified by jarPath needs to be + * scanned for TLDs, FALSE otherwise + * + * Keep in sync with o.a.jasper.compiler.TldLocationsCache */ - private Map getJarPaths() { - - HashMap jarPathMap = null; - - ClassLoader webappLoader = Thread.currentThread().getContextClassLoader(); - ClassLoader loader = webappLoader; - while (loader != null) { - if (loader instanceof URLClassLoader) { - URL[] urls = ((URLClassLoader) loader).getURLs(); - for (int i=0; i(); - jarPathMap.put(path, file); - } else if (!jarPathMap.containsKey(path)) { - jarPathMap.put(path, file); - } - } - } + private boolean needScanJar(ClassLoader loader, ClassLoader webappLoader, + String jarPath) { + if (loader == webappLoader) { + // JARs under WEB-INF/lib must be scanned unconditionally according + // to the spec. + return true; + } else { + String jarName = jarPath; + int slash = jarPath.lastIndexOf('/'); + if (slash >= 0) { + jarName = jarPath.substring(slash + 1); } - loader = loader.getParent(); + return (!noTldJars.contains(jarName)); } - - return jarPathMap; } public void lifecycleEvent(LifecycleEvent event) { diff --git a/java/org/apache/jasper/compiler/TldLocationsCache.java b/java/org/apache/jasper/compiler/TldLocationsCache.java index b86d23104..aae08490f 100644 --- a/java/org/apache/jasper/compiler/TldLocationsCache.java +++ b/java/org/apache/jasper/compiler/TldLocationsCache.java @@ -112,6 +112,7 @@ public class TldLocationsCache { /* * Initializes the set of JARs that are known not to contain any TLDs + * Keep in sync with o.a.c.startup.TldConfig */ static { noTldJars = new HashSet(); @@ -345,6 +346,8 @@ public class TldLocationsCache { * @param conn The JarURLConnection to the JAR file to scan * @param ignore true if any exceptions raised when processing the given * JAR should be ignored, false otherwise + * + * Keep in sync with o.a.c.startup.TldConfig */ private void scanJar(JarURLConnection conn, boolean ignore) throws JasperException { @@ -412,6 +415,8 @@ public class TldLocationsCache { * Searches the filesystem under /WEB-INF for any TLD files, and adds * an implicit map entry to the taglib map for any TLD that has a * element. + * + * Keep in sync with o.a.c.startup.TldConfig */ private void processTldsInFileSystem(String startPath) throws Exception { @@ -486,6 +491,8 @@ public class TldLocationsCache { * The set of shared JARs to be scanned for TLDs is narrowed down by * the noTldJars class variable, which contains the names of JARs * that are known not to contain any TLDs. + * + * Keep in sync with o.a.c.startup.TldConfig */ private void scanJars() throws Exception { @@ -530,6 +537,8 @@ public class TldLocationsCache { * * @return TRUE if the JAR file identified by jarPath needs to be * scanned for TLDs, FALSE otherwise + * + * Keep in sync with o.a.c.startup.TldConfig */ private boolean needScanJar(ClassLoader loader, ClassLoader webappLoader, String jarPath) { -- 2.11.0