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-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org