You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2009/07/17 18:57:30 UTC

svn commit: r795143 - in /tomcat/trunk/java/org/apache/catalina/startup: LocalStrings.properties TldConfig.java

Author: markt
Date: Fri Jul 17 16:57:30 2009
New Revision: 795143

URL: http://svn.apache.org/viewvc?rev=795143&view=rev
Log:
Part 1 of a series of commits to align the Catalina and Jasper TLD scanning code and to provide additional features when embedding. This first commit modifies the Catalina code to:
- better match the spec
- improve logging
The JSP TCK passes with this patch applied

Modified:
    tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties
    tomcat/trunk/java/org/apache/catalina/startup/TldConfig.java

Modified: tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties?rev=795143&r1=795142&r2=795143&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties (original)
+++ tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties Fri Jul 17 16:57:30 2009
@@ -90,8 +90,19 @@
 hostConfig.undeploy=Undeploying context [{0}]
 hostConfig.undeploy.error=Error undeploying web application at context path {0}
 hostConfig.undeploying=Undeploying deployed web applications
+tdlConfig.addListeners=Adding {0} listeners from TLD files
 tldConfig.cce=Lifecycle event data object {0} is not a Context
 tldConfig.execute=Error processing TLD files for context path {0}
+tldConfig.jarStart=Scanning JAR ''{0}'' for TLDs
+tldConfig.processingTld=Processing TLD found at ''{0}''
+tldConfig.webinflibStart=Scanning WEB-INF/lib for JARs containing META-INF/**/*.TLD
+tldConfig.webinflibJarFail=Failed to scan JAR ''{0}'' for TLDs
+tldConfig.webinfFail=Failed to process TLD found at ''{0}''
+tldConfig.webinfScan=Scanning WEB-INF for TLD files in ''{0}''
+tldConfig.webxmlStart=Scanning <taglib> elements in web.xml
+tldConfig.webxmlAdd=Adding path ''{0}'' for URI ''{1}''
+tldConfig.webxmlSkip=Path ''{1}'' skipped since URI ''{0}'' is a duplicate
+tldConfig.webxmlFail=Failed to process TLD with path ''{1}'' and URI ''{0}''
 userConfig.database=Exception loading user database
 userConfig.deploy=Deploying web application for user {0}
 userConfig.deploying=Deploying user web applications

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=795143&r1=795142&r2=795143&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/startup/TldConfig.java (original)
+++ tomcat/trunk/java/org/apache/catalina/startup/TldConfig.java Fri Jul 17 16:57:30 2009
@@ -22,15 +22,13 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.JarURLConnection;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLClassLoader;
 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;
@@ -63,6 +61,11 @@
  */
 public final class TldConfig  implements LifecycleListener {
 
+    private static final String JAR_EXT = ".jar";
+    private static final String TLD_EXT = ".tld";
+    private static final String WEB_INF = "/WEB-INF/";
+    private static final String WEB_INF_LIB = "/WEB-INF/lib/";
+    
     // Names of JARs that are known not to contain any TLDs
     private static HashSet<String> noTldJars;
 
@@ -315,34 +318,32 @@
         long t1=System.currentTimeMillis();
 
         /*
-         * Acquire the list of TLD resource paths, possibly embedded in JAR
-         * files, to be processed
+         * Priority order of URIs required by spec is:
+         * 1. J2EE platform taglibs - Tomcat doesn't provide these
+         * 2. web.xml entries
+         * 3. JARS in WEB-INF/lib & TLDs under WEB-INF (equal priority)
+         * 4. Additional entries from the container
          */
-        Set<String> resourcePaths = tldScanResourcePaths();
-        Map<String, File> jarPaths = getJarPaths();
+        
+        // Stage 2 - web.xml entries
+        tldScanWebXml();
+        
+        // Stage 3a - TLDs under WEB-INF (not lib or classes)
+        tldScanResourcePathsWebInf(context.getResources(), WEB_INF);
 
-        // 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);
-            }
-        }
+        // Stage 3b - .jar files in WEB-INF/lib/
+        tldScanWebInfLib();
         
-        if (jarPaths != null) {
-            Iterator<File> files  = jarPaths.values().iterator();
-            while (files.hasNext()) {
-                tldScanJar(files.next());
-            }
-        }
+        // Stage 4 - Additional entries from the container
+        tldScanClassloaders();
 
+        // Now add all the listeners we found to the listeners for this context
         String list[] = getTldListeners();
 
         if( log.isDebugEnabled() )
-            log.debug( "Adding tld listeners:" + list.length);
+            log.debug(sm.getString("tdlConfig.addListeners",
+                    Integer.valueOf(list.length)));
+
         for( int i=0; list!=null && i<list.length; i++ ) {
             context.addApplicationListener(list[i]);
         }
@@ -356,6 +357,126 @@
 
     // -------------------------------------------------------- Private Methods
 
+
+    /**
+     * Get the taglib entries from web.xml and add them to the map.
+     */
+    private void tldScanWebXml() {
+        
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("tldConfig.webxmlStart"));
+        }
+     
+        String taglibs[] = context.findTaglibs();
+        for (int i = 0; i < taglibs.length; i++) {
+            String resourcePath = context.findTaglib(taglibs[i]);
+            // Note: Whilst the Servlet 2.4 DTD implies that the location must
+            // be a context-relative path starting with '/', JSP.7.3.6.1 states
+            // explicitly how paths that do not start with '/' should be
+            // handled.
+            if (!resourcePath.startsWith("/")) {
+                resourcePath = WEB_INF + resourcePath;
+            }
+            if (taglibUris.contains(taglibs[i])) {
+                log.warn(sm.getString("tldConfig.webxmlSkip", resourcePath,
+                        taglibs[i]));
+            } else {
+                if (log.isTraceEnabled()) {
+                    log.trace(sm.getString("tldConfig.webxmlAdd", resourcePath,
+                            taglibs[i]));
+                }
+                try {
+                    tldScanTld(resourcePath);
+                    taglibUris.add(taglibs[i]);
+                } catch (Exception e) {
+                    log.warn(sm.getString("tldConfig.webxmlFail", resourcePath,
+                            taglibs[i]), e);
+                }
+            }
+        }
+    }
+    
+    /*
+     * 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
+     */
+    private void tldScanResourcePathsWebInf(DirContext resources,
+                                            String rootPath) {
+
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("tldConfig.webinfScan", rootPath));
+        }
+
+        try {
+            NamingEnumeration<NameClassPair> items = resources.list(rootPath);
+            while (items.hasMoreElements()) {
+                NameClassPair item = items.nextElement();
+                String resourcePath = rootPath + item.getName();
+                if (!resourcePath.endsWith(TLD_EXT)
+                        && (resourcePath.startsWith("/WEB-INF/classes/")
+                            || resourcePath.startsWith("/WEB-INF/lib/"))) {
+                    continue;
+                }
+                if (resourcePath.endsWith(TLD_EXT)) {
+                    if (resourcePath.startsWith("/WEB-INF/tags") &&
+                            !resourcePath.endsWith("implicit.tld")) {
+                        continue;
+                    }
+                    try {
+                        tldScanTld(resourcePath);
+                    } catch (Exception e) {
+                        log.warn(sm.getString(
+                                "tldConfig.webinfFail", resourcePath),e);
+                    }
+                } else {
+                    tldScanResourcePathsWebInf(resources, resourcePath + '/');
+                }
+            }
+        } catch (NamingException e) {
+            // Silent catch: it's valid that no /WEB-INF directory exists
+        }
+    }
+    
+    /**
+     * Scan the JARs in the WEB-INF/lib directory. Skip the JARs known not to
+     * have any TLDs in them.
+     */
+    private void tldScanWebInfLib() {
+
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("tldConfig.webinflibStart"));
+        }
+
+        DirContext resources = context.getResources();
+        try {
+            NamingEnumeration<NameClassPair> items =
+                resources.list(WEB_INF_LIB);
+            
+            while (items.hasMoreElements()) {
+                NameClassPair item = items.nextElement();
+                String name = item.getName();
+                if (name.endsWith(JAR_EXT) && !noTldJars.contains(name)) {
+                    // Need to scan this JAR for TLDs
+                    try {
+                        tldScanJar(WEB_INF_LIB + name);
+                    } catch (Exception e) {
+                        log.warn(sm.getString("tldConfig.webinflibJarFail"), e);
+                    }
+                }
+            }
+        } catch (NamingException e) {
+            // Silent catch: it's valid that no /WEB-INF/lib directory exists
+        }
+    }
+
     /**
      * Scan the JAR file at the specified resource path for TLDs in the
      * <code>META-INF</code> subdirectory, and scan each TLD for application
@@ -367,10 +488,6 @@
      */
     private void tldScanJar(String resourcePath) throws Exception {
 
-        if (log.isDebugEnabled()) {
-            log.debug(" Scanning JAR at resource path '" + resourcePath + "'");
-        }
-
         URL url = context.getServletContext().getResource(resourcePath);
         if (url == null) {
             throw new IllegalArgumentException
@@ -400,36 +517,14 @@
      * @param file JAR file whose TLD entries are scanned for application
      * listeners
      */
-    private void tldScanJar(File file) throws Exception {
+    private void tldScanJar(File file) {
 
         JarFile jarFile = null;
-        String name = null;
-
         String jarPath = file.getAbsolutePath();
 
         try {
             jarFile = new JarFile(file);
-            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;
-                }
-                if (log.isTraceEnabled()) {
-                    log.trace("  Processing TLD at '" + name + "'");
-                }
-                try {
-                    tldScanStream(new InputSource(jarFile.getInputStream(entry)));
-                } catch (Exception e) {
-                    log.error(sm.getString("contextConfig.tldEntryException",
-                                           name, jarPath, context.getPath()),
-                              e);
-                }
-            }
+            tldScanJar(jarFile, jarPath);
         } catch (Exception e) {
             log.error(sm.getString("contextConfig.tldJarException",
                                    jarPath, context.getPath()),
@@ -445,6 +540,38 @@
         }
     }
 
+    private void tldScanJar(JarFile jarFile, String jarLocation) {
+
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("tldConfig.jarStart", jarLocation));
+        }
+
+        String name = null;
+        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_EXT)) {
+                continue;
+            }
+            if (log.isTraceEnabled()) {
+                log.trace(sm.getString("tldConfig.processingTld", name));
+            }
+            try{
+                tldScanStream(
+                        new InputSource(jarFile.getInputStream(entry)));
+            } catch (Exception e) {
+                log.error(sm.getString("contextConfig.tldEntryException",
+                                       name, jarLocation, context.getPath()),
+                          e);
+            }
+        }
+    }
+
+
     /**
      * Scan the TLD contents in the specified input stream, and register
      * any application event listeners found there.  <b>NOTE</b> - It is
@@ -479,8 +606,8 @@
      */
     private void tldScanTld(String resourcePath) throws Exception {
 
-        if (log.isDebugEnabled()) {
-            log.debug(" Scanning TLD at resource path '" + resourcePath + "'");
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("tldConfig.processingTld", resourcePath));
         }
 
         InputSource inputSource = null;
@@ -504,123 +631,23 @@
     }
 
     /**
-     * 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]);
-            // Note: Whilst the Servlet 2.4 DTD implies that the location must
-            // be a context-relative path starting with '/', JSP.7.3.6.1 states
-            // explicitly how paths that do not start with '/' should be
-            // handled.
-            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.
+     * Scan the classloader hierarchy for JARs and, optionally, for JARs where
+     * the name doesn't end in .jar and directories that represent exploded
+     * JARs. The JARs under WEB-INF/lib will be skipped as they have been
+     * scanned previously.
      *
-     * @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
+     * This represents 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).
+     * <CATALINA_HOME>/common/lib). It also supports some of the weird and
+     * wonderful arrangements present when Tomcat gets embedded.
      *
      * 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;
+    private void tldScanClassloaders() {
 
         ClassLoader webappLoader = Thread.currentThread().getContextClassLoader();
         ClassLoader loader = webappLoader;
@@ -628,20 +655,60 @@
             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
+                    URL url = urls[i];
+                    
+                    // Extract the jarName if there is one to be found
+                    String jarName = getJarName(url);
+                    
+                    // Skip JARs in WEB-INF/lib - we already scanned them
+                    if (jarName != null &&
+                            url.getPath().contains(WEB_INF_LIB + jarName)) {
+                        continue;
+                    }
                     
-                    // Check that the URL is using file protocol, else ignore it
-                    if (!"file".equals(urls[i].getProtocol())) {
+                    // Skip JARs we know we don't want to scan
+                    if (jarName != null && noTldJars.contains(jarName)) {
+                        continue;
+                    }
+
+                    // Handle JAR URLs
+                    if ("jar".equals(url.getProtocol())) {
+                        JarFile jarFile = null;
+                        try {
+                            JarURLConnection conn =
+                                (JarURLConnection) url.openConnection();
+                            // Avoid the possibility of locking the JAR
+                            conn.setUseCaches(false);
+                            jarFile = conn.getJarFile(); 
+                            tldScanJar(jarFile, conn.getJarFileURL().toString());
+                        } catch (Exception e) {
+                            log.error(sm.getString("contextConfig.tldJarException",
+                                                   url, context.getPath()),
+                                      e);
+                        } finally {
+                            if (jarFile != null) {
+                                try {
+                                    jarFile.close();
+                                } catch (Throwable t) {
+                                    // ignore
+                                }
+                            }
+                        }
+                        
+                        // Move on to the next URL
+                        continue;
+                    }
+
+                    // At this point, if it isn't a file URL - can't handle it
+                    if (!"file".equals(url.getProtocol())) {
                         continue;
                     }
                     
+                    // File URLs may %xx encoded or not depending on the class
+                    // loader
                     File file = null;
                     try {
-                        file = new File(urls[i].toURI());
+                        file = new File(url.toURI());
                     } catch (URISyntaxException e) {
                         // Ignore, probably an unencoded char
                         file = new File(urls[i].getFile());
@@ -655,29 +722,30 @@
                         continue;
                     }
                     String path = file.getAbsolutePath();
-                    if (!path.endsWith(".jar")) {
+                    if (!path.endsWith(JAR_EXT)) {
                         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);
-                        }
-                    }
+
+                    tldScanJar(file);
                 }
             }
             loader = loader.getParent();
         }
+    }
 
-        return jarPathMap;
+    // Extract the JAR name, if present, from a URL
+    private String getJarName(URL url) {
+        
+        String name = null;
+        
+        String path = url.getPath();
+        int end = path.indexOf(JAR_EXT);
+        if (end != -1) {
+            int start = path.lastIndexOf('/', end);
+            name = path.substring(start + 1, end + 4);
+        }
+        
+        return name;
     }
 
     public void lifecycleEvent(LifecycleEvent event) {



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