Author: markt
Date: Tue Jun 23 14:02:48 2009
New Revision: 787678

URL: http://svn.apache.org/viewvc?rev=787678&view=rev
Log:
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

Modified:
    tomcat/trunk/java/org/apache/catalina/startup/TldConfig.java
    tomcat/trunk/java/org/apache/jasper/compiler/TldLocationsCache.java

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=787678&r1=787677&r2=787678&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/startup/TldConfig.java (original)
+++ tomcat/trunk/java/org/apache/catalina/startup/TldConfig.java Tue Jun 23 
14:02:48 2009
@@ -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.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 @@
     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<String>();
@@ -273,8 +272,9 @@
     }
 
     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 void execute() throws Exception {
         long t1=System.currentTimeMillis();
 
-        /*
-         * Acquire the list of TLD resource paths, possibly embedded in JAR
-         * files, to be processed
-         */
-        Set<String> resourcePaths = tldScanResourcePaths();
-        Map<String, File> jarPaths = getJarPaths();
-
-        // Scan each accumulated resource path for TLDs to be processed
-        Iterator<String> paths = resourcePaths.iterator();
-        while (paths.hasNext()) {
-            String path = paths.next();
-            if (path.endsWith(".jar")) {
-                tldScanJar(path);
-            } else {
-                tldScanTld(path);
-            }
-        }
-        
-        if (jarPaths != null) {
-            Iterator<File> files  = jarPaths.values().iterator();
-            while (files.hasNext()) {
-                tldScanJar(files.next());
-            }
-        }
+        scanJars();
+        processTldsInFileSystem("/WEB-INF/");
 
         String list[] = getTldListeners();
 
@@ -337,89 +315,155 @@
     // -------------------------------------------------------- Private Methods
 
     /**
-     * Scan the JAR file at the specified resource path for TLDs in the
-     * <code>META-INF</code> 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.,
+     * <CATALINA_HOME>/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 <tt>noTldJars</tt> 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<urls.length; i++) {
+                    URLConnection conn = urls[i].openConnection();
+                    if (conn instanceof JarURLConnection) {
+                        if (needScanJar(loader, webappLoader,
+                                        ((JarURLConnection) 
conn).getJarFile().getName())) {
+                            scanJar((JarURLConnection) conn);
+                        }
+                    } else {
+                        String urlStr = urls[i].toString();
+                        if (urlStr.startsWith(FILE_PROTOCOL)
+                                && urlStr.endsWith(JAR_FILE_SUFFIX)
+                                && needScanJar(loader, webappLoader, urlStr)) {
+                            URL jarURL = new URL("jar:" + urlStr + "!/");
+                            scanJar((JarURLConnection) 
jarURL.openConnection());
+                        }
+                    }
+                }
+            }
 
-        File file = null;
-        try {
-            file = new File(url.toURI());
-        } catch (URISyntaxException e) {
-            // Ignore, probably an unencoded char
-            file = new File(url.getFile());
-        }
-        try {
-            file = file.getCanonicalFile();
-        } catch (IOException e) {
-            // Ignore
+            loader = loader.getParent();
         }
-        tldScanJar(file);
-
     }
 
     /**
-     * Scans all TLD entries in the given JAR for application listeners.
-     *
-     * @param file JAR file whose TLD entries are scanned for application
-     * listeners
+     * Scans the given JarURLConnection for TLD files located in META-INF
+     * (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
+     * 
+     * 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<JarEntry> 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 <uri>
+     * element.
+     * 
+     * Keep in sync with o.a.jasper.compiler.TldLocationsCache
+     */
+    private void processTldsInFileSystem(String startPath)
+            throws Exception {
+
+        ServletContext ctxt = context.getServletContext();
+
+        Set<String> dirList = ctxt.getResourcePaths(startPath);
+        if (dirList != null) {
+            Iterator<String> 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 @@
 
     }
 
-    /**
-     * 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
-     * <code>META-INF</code> subdirectory.
-     *
-     * @exception IOException if an input/output error occurs while
-     *  accumulating the list of resource paths
-     */
-    private Set<String> tldScanResourcePaths() throws IOException {
-        if (log.isDebugEnabled()) {
-            log.debug(" Accumulating TLD resource paths");
-        }
-        Set<String> resourcePaths = new HashSet<String>();
-
-        // Accumulate resource paths explicitly listed in the web application
-        // deployment descriptor
-        if (log.isTraceEnabled()) {
-            log.trace("  Scanning <taglib> 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.
+     * Determines if the JAR file with the given <tt>jarPath</tt> needs to be
+     * scanned 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<String> tldPaths) 
-            throws IOException {
-
-        if (log.isTraceEnabled()) {
-            log.trace("  Scanning TLDs in " + rootPath + " subdirectory");
-        }
-
-        try {
-            NamingEnumeration<NameClassPair> 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.
-     *
-     * 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.
-     *
-     * 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.,
-     * <CATALINA_HOME>/common/lib).
-     *
-     * The set of shared JARs to be scanned for TLDs is narrowed down by
-     * the <tt>noTldJars</tt> class variable, which contains the names of JARs
-     * that are known not to contain any TLDs.
-     *
-     * @return Map of JAR file paths
-     */
-    private Map<String, File> getJarPaths() {
-
-        HashMap<String, File> 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<urls.length; i++) {
-                    // Expect file URLs, these are %xx encoded or not depending
-                    // on the class loader
-                    // This is definitely not as clean as using JAR URLs either
-                    // over file or the custom jndi handler, but a lot less
-                    // buggy overall
-                    
-                    // Check that the URL is using file protocol, else ignore 
it
-                    if (!"file".equals(urls[i].getProtocol())) {
-                        continue;
-                    }
-                    
-                    File file = null;
-                    try {
-                        file = new File(urls[i].toURI());
-                    } catch (URISyntaxException e) {
-                        // Ignore, probably an unencoded char
-                        file = new File(urls[i].getFile());
-                    }
-                    try {
-                        file = file.getCanonicalFile();
-                    } catch (IOException e) {
-                        // Ignore
-                    }
-                    if (!file.exists()) {
-                        continue;
-                    }
-                    String path = file.getAbsolutePath();
-                    if (!path.endsWith(".jar")) {
-                        continue;
-                    }
-                    /*
-                     * Scan all JARs from WEB-INF/lib, plus any shared JARs
-                     * that are not known not to contain any TLDs
-                     */
-                    if (loader == webappLoader
-                            || noTldJars == null
-                            || !noTldJars.contains(file.getName())) {
-                        if (jarPathMap == null) {
-                            jarPathMap = new HashMap<String, File>();
-                            jarPathMap.put(path, file);
-                        } else if (!jarPathMap.containsKey(path)) {
-                            jarPathMap.put(path, file);
-                        }
-                    }
-                }
+     * @param loader The current classloader in the parent chain
+     * @param webappLoader The webapp classloader
+     * @param jarPath The JAR file path
+     *
+     * @return TRUE if the JAR file identified by <tt>jarPath</tt> needs to be
+     * scanned for TLDs, FALSE otherwise
+     * 
+     * Keep in sync with o.a.jasper.compiler.TldLocationsCache
+     */
+    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) {

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=787678&r1=787677&r2=787678&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/jasper/compiler/TldLocationsCache.java 
(original)
+++ tomcat/trunk/java/org/apache/jasper/compiler/TldLocationsCache.java Tue Jun 
23 14:02:48 2009
@@ -112,6 +112,7 @@
 
     /*
      * 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<String>();
@@ -345,6 +346,8 @@
      * @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 @@
      * 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 <uri>
      * element.
+     *
+     * Keep in sync with o.a.c.startup.TldConfig
      */
     private void processTldsInFileSystem(String startPath)
             throws Exception {
@@ -486,6 +491,8 @@
      * The set of shared JARs to be scanned for TLDs is narrowed down by
      * the <tt>noTldJars</tt> 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 @@
      *
      * @return TRUE if the JAR file identified by <tt>jarPath</tt> 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) {



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to