Property changes on: . ___________________________________________________________________ Modified: svn:mergeinfo Merged /tomcat/trunk:r647344,751502,793621,795143,795767,795819,795822,795824,795838,795860,795902,796016-796017,796030,801601,801637 Index: java/org/apache/catalina/startup/ContextConfig.java =================================================================== --- java/org/apache/catalina/startup/ContextConfig.java (revision 801738) +++ java/org/apache/catalina/startup/ContextConfig.java (working copy) @@ -144,27 +144,21 @@ * The Digester we will use to process web application * deployment descriptor files. */ - protected static Digester webDigester = null; + protected Digester webDigester = null; /** - * The Rule used to parse the web.xml + * The Digesters available to process web application + * deployment descriptor files. */ - protected static WebRuleSet webRuleSet = new WebRuleSet(); + protected static Digester[] webDigesters = new Digester[4]; /** - * Attribute value used to turn on/off XML validation + * The Rule used to parse the web.xml */ - protected static boolean xmlValidation = false; + protected static WebRuleSet webRuleSet = new WebRuleSet(); - /** - * Attribute value used to turn on/off XML namespace awarenes. - */ - protected static boolean xmlNamespaceAware = false; - - - /** * Deployment count. */ protected static long deploymentCount = 0L; @@ -517,25 +511,37 @@ * Create (if necessary) and return a Digester configured to process the * web application deployment descriptor (web.xml). */ - protected static Digester createWebDigester() { - Digester webDigester = - createWebXmlDigester(xmlNamespaceAware, xmlValidation); - return webDigester; - } - - - /** - * Create (if necessary) and return a Digester configured to process the - * web application deployment descriptor (web.xml). - */ public static Digester createWebXmlDigester(boolean namespaceAware, boolean validation) { - Digester webDigester = DigesterFactory.newDigester(xmlValidation, - xmlNamespaceAware, - webRuleSet); - return webDigester; + Digester digester = null; + if (!namespaceAware && !validation) { + if (webDigesters[0] == null) { + webDigesters[0] = DigesterFactory.newDigester(validation, + namespaceAware, webRuleSet); } + digester = webDigesters[0]; + } else if (!namespaceAware && validation) { + if (webDigesters[1] == null) { + webDigesters[1] = DigesterFactory.newDigester(validation, + namespaceAware, webRuleSet); + } + digester = webDigesters[1]; + } else if (namespaceAware && !validation) { + if (webDigesters[2] == null) { + webDigesters[2] = DigesterFactory.newDigester(validation, + namespaceAware, webRuleSet); + } + digester = webDigesters[2]; + } else { + if (webDigesters[3] == null) { + webDigesters[3] = DigesterFactory.newDigester(validation, + namespaceAware, webRuleSet); + } + digester = webDigesters[3]; + } + return digester; + } /** @@ -991,11 +997,6 @@ protected void init() { // Called from StandardContext.init() - if (webDigester == null){ - webDigester = createWebDigester(); - webDigester.getParser(); - } - if (contextDigester == null){ contextDigester = createContextDigester(); contextDigester.getParser(); @@ -1040,28 +1041,36 @@ if (log.isDebugEnabled()) log.debug(sm.getString("contextConfig.start")); - // Set properties based on DefaultContext + // Process the default and application web.xml files + // Set properties based on default context + boolean useXmlValidation = context.getXmlValidation(); + boolean useXmlNamespaceAware = context.getXmlNamespaceAware(); + Container container = context.getParent(); + // Use the value from the host if: + // - override is false on the context + // - value has been set to false / not set on the context if( !context.getOverride() ) { if( container instanceof Host ) { - // Reset the value only if the attribute wasn't - // set on the context. - xmlValidation = context.getXmlValidation(); - if (!xmlValidation) { - xmlValidation = ((Host)container).getXmlValidation(); + if (!useXmlValidation) { + useXmlValidation = ((Host)container).getXmlValidation(); } - xmlNamespaceAware = context.getXmlNamespaceAware(); - if (!xmlNamespaceAware){ - xmlNamespaceAware + if (!useXmlNamespaceAware){ + useXmlNamespaceAware = ((Host)container).getXmlNamespaceAware(); } - container = container.getParent(); } } - // Process the default and application web.xml files + if (log.isDebugEnabled()) { + log.debug(sm.getString("contextConfig.xmlSettings", + context.getName(), Boolean.valueOf(useXmlValidation), + Boolean.valueOf(useXmlNamespaceAware))); + } + webDigester = createWebXmlDigester(useXmlNamespaceAware, useXmlValidation); + defaultWebConfig(); applicationWebConfig(); if (!context.getIgnoreAnnotations()) { Index: java/org/apache/catalina/startup/LocalStrings.properties =================================================================== --- java/org/apache/catalina/startup/LocalStrings.properties (revision 801738) +++ java/org/apache/catalina/startup/LocalStrings.properties (working copy) @@ -46,6 +46,7 @@ contextConfig.tldFileException=Exception processing TLD at resource path {0} in context {1} contextConfig.tldJarException=Exception processing JAR at resource path {0} in context {1} contextConfig.tldResourcePath=Invalid TLD resource path {0} +contextConfig.xmlSettings=Context [{0}] will parse web.xml and web-fragment.xml files with validation:{1} and namespaceAware:{2} contextConfig.unavailable=Marking this application unavailable due to previous error(s) contextConfig.altDDNotFound=alt-dd file {0} not found embedded.alreadyStarted=Embedded service has already been started @@ -87,8 +88,20 @@ hostConfig.undeploy=Undeploying context [{0}] hostConfig.undeploy.error=Error undeploying web application at context path {0} hostConfig.undeploying=Undeploying deployed web applications +tldConfig.addListeners=Adding {0} listeners from TLD files tldConfig.cce=Lifecycle event data object {0} is not a Context +tldConfig.classloaderFail=Failed to process ''{0}'' for TLDs. +tldConfig.classloaderStart=Scanning for TLDs in classloader hierarchy tldConfig.execute=Error processing TLD files for context path {0} +tldConfig.jarUrlStart=Scanning for TLD files in URL ''{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.webxmlAdd=Adding path ''{0}'' for URI ''{1}'' +tldConfig.webxmlFail=Failed to process TLD with path ''{1}'' and URI ''{0}'' +tldConfig.webxmlSkip=Path ''{1}'' skipped since URI ''{0}'' is a duplicate +tldConfig.webxmlStart=Scanning elements in web.xml userConfig.database=Exception loading user database userConfig.deploy=Deploying web application for user {0} userConfig.deploying=Deploying user web applications Index: java/org/apache/catalina/startup/TldConfig.java =================================================================== --- java/org/apache/catalina/startup/TldConfig.java (revision 801738) +++ java/org/apache/catalina/startup/TldConfig.java (working copy) @@ -19,35 +19,24 @@ package org.apache.catalina.startup; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -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.Globals; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleListener; @@ -56,7 +45,9 @@ import org.apache.catalina.util.StringManager; import org.apache.tomcat.util.digester.Digester; import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + /** * Startup event listener for a Context that configures application * listeners configured in any TLD files. @@ -67,12 +58,34 @@ */ 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/"; + + // Configuration properties + private static final boolean SCAN_CLASSPATH = Boolean.valueOf( + System.getProperty( + "org.apache.jasper.compiler.TldLocationsCache.SCAN_CLASSPATH", + "true")).booleanValue(); + // Names of JARs that are known not to contain any TLDs private static HashSet noTldJars; private static org.apache.juli.logging.Log log= org.apache.juli.logging.LogFactory.getLog( TldConfig.class ); + /** + * The string resources for this package. + */ + private static final StringManager sm = + StringManager.getManager(Constants.Package); + + /** + * The Digesters available to process tld files. + */ + private static Digester[] tldDigesters = new Digester[4]; + /* * Initializes the set of JARs that are known not to contain any TLDs */ @@ -124,7 +137,43 @@ noTldJars.add("sunpkcs11.jar"); } + /** + * Create (if necessary) and return a Digester configured to process the + * tld. + */ + private static Digester createTldDigester(boolean namespaceAware, + boolean validation) { + Digester digester = null; + if (!namespaceAware && !validation) { + if (tldDigesters[0] == null) { + tldDigesters[0] = DigesterFactory.newDigester(validation, + namespaceAware, new TldRuleSet()); + } + digester = tldDigesters[0]; + } else if (!namespaceAware && validation) { + if (tldDigesters[1] == null) { + tldDigesters[1] = DigesterFactory.newDigester(validation, + namespaceAware, new TldRuleSet()); + } + digester = tldDigesters[1]; + } else if (namespaceAware && !validation) { + if (tldDigesters[2] == null) { + tldDigesters[2] = DigesterFactory.newDigester(validation, + namespaceAware, new TldRuleSet()); + } + digester = tldDigesters[2]; + } else { + if (tldDigesters[3] == null) { + tldDigesters[3] = DigesterFactory.newDigester(validation, + namespaceAware, new TldRuleSet()); + } + digester = tldDigesters[3]; + } + return digester; + } + + // ----------------------------------------------------- Instance Variables /** @@ -134,36 +183,51 @@ /** - * The string resources for this package. - */ - private static final StringManager sm = - StringManager.getManager(Constants.Package); - - /** * The Digester we will use to process tag library * descriptor files. */ - private static Digester tldDigester = null; + private Digester tldDigester = null; /** * Attribute value used to turn on/off TLD validation */ - private static boolean tldValidation = false; + private boolean tldValidation = false; /** * Attribute value used to turn on/off TLD namespace awarenes. */ - private static boolean tldNamespaceAware = false; + private boolean tldNamespaceAware = false; private boolean rescan=true; + /** + * Set of URIs discovered for the associated context. Used to enforce the + * correct processing priority. Only the TLD associated with the first + * instance of any URI will be processed. + */ + private Set taglibUris = new HashSet(); + private ArrayList listeners = new ArrayList(); // --------------------------------------------------------- Public Methods /** + * Adds a taglib URI to the list of known URIs. + */ + public void addTaglibUri(String uri) { + taglibUris.add(uri); + } + + /** + * Determines if the provided URI is a known taglib URI. + */ + public boolean isKnownTaglibUri(String uri) { + return taglibUris.contains(uri); + } + + /** * Sets the list of JARs that are known not to contain any TLDs. * * @param jarNames List of comma-separated names of JAR files that are @@ -185,7 +249,7 @@ * @param tldValidation true to enable xml instance validation */ public void setTldValidation(boolean tldValidation){ - TldConfig.tldValidation = tldValidation; + this.tldValidation = tldValidation; } /** @@ -194,7 +258,7 @@ * */ public boolean getTldValidation(){ - return tldValidation; + return this.tldValidation; } /** @@ -203,7 +267,7 @@ * */ public boolean getTldNamespaceAware(){ - return tldNamespaceAware; + return this.tldNamespaceAware; } @@ -213,7 +277,7 @@ * @param tldNamespaceAware true to enable namespace awareness */ public void setTldNamespaceAware(boolean tldNamespaceAware){ - TldConfig.tldNamespaceAware = tldNamespaceAware; + this.tldNamespaceAware = tldNamespaceAware; } @@ -255,73 +319,37 @@ public void execute() throws Exception { long t1=System.currentTimeMillis(); - File tldCache=null; - - if (context instanceof StandardContext) { - File workDir= (File) - ((StandardContext)context).getServletContext().getAttribute(Globals.WORK_DIR_ATTR); - //tldCache=new File( workDir, "tldCache.ser"); - } - - // Option to not rescan - if( ! rescan ) { - // find the cache - if( tldCache!= null && tldCache.exists()) { - // just read it... - processCache(tldCache); - return; - } - } - /* - * 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 + * + * Keep processing order in sync with o.a.j.compiler.TldLocationsCache */ - Set resourcePaths = tldScanResourcePaths(); - Map jarPaths = getJarPaths(); - // Check to see if we can use cached listeners - if (tldCache != null && tldCache.exists()) { - long lastModified = getLastModified(resourcePaths, jarPaths); - if (lastModified < tldCache.lastModified()) { - processCache(tldCache); - return; - } - } + // Stage 2 - web.xml entries + tldScanWebXml(); + + // Stage 3a - TLDs under WEB-INF (not lib or classes) + tldScanResourcePaths(WEB_INF); - // Scan each accumulated resource path for TLDs to be processed - Iterator paths = resourcePaths.iterator(); - while (paths.hasNext()) { - String path = (String) paths.next(); - if (path.endsWith(".jar")) { - tldScanJar(path); - } else { - tldScanTld(path); - } + // Stage 3b - .jar files in WEB-INF/lib/ + tldScanWebInfLib(); + + // Stage 4 - Additional entries from the container + if (SCAN_CLASSPATH) { + tldScanClassloaders(); } - if (jarPaths != null) { - paths = jarPaths.values().iterator(); - while (paths.hasNext()) { - tldScanJar((File) paths.next()); - } - } + // Now add all the listeners we found to the listeners for this context String list[] = getTldListeners(); - if( tldCache!= null ) { - log.debug( "Saving tld cache: " + tldCache + " " + list.length); - try { - FileOutputStream out=new FileOutputStream(tldCache); - ObjectOutputStream oos=new ObjectOutputStream( out ); - oos.writeObject( list ); - oos.close(); - } catch( IOException ex ) { - ex.printStackTrace(); - } - } - if( log.isDebugEnabled() ) - log.debug( "Adding tld listeners:" + list.length); + log.debug(sm.getString("tldConfig.addListeners", + Integer.valueOf(list.length))); + for( int i=0; list!=null && i lastModified) lastModified = lastM; - if (log.isDebugEnabled()) { - log.debug( "Last modified " + path + " " + lastM); - } - } - - if (jarPaths != null) { - paths = jarPaths.values().iterator(); - while (paths.hasNext()) { - File jarFile = (File) paths.next(); - long lastM = jarFile.lastModified(); - if (lastM > lastModified) lastModified = lastM; - if (log.isDebugEnabled()) { - log.debug("Last modified " + jarFile.getAbsolutePath() - + " " + lastM); - } - } - } - - return lastModified; - } - - private void processCache(File tldCache ) throws IOException { - // read the cache and return; - try { - FileInputStream in=new FileInputStream(tldCache); - ObjectInputStream ois=new ObjectInputStream( in ); - String list[]=(String [])ois.readObject(); - if( log.isDebugEnabled() ) - log.debug("Reusing tldCache " + tldCache + " " + list.length); - for( int i=0; list!=null && iMETA-INF subdirectory, and scan each TLD for application - * event listeners that need to be registered. - * - * @param resourcePath Resource path of the JAR file to scan - * - * @exception Exception if an exception occurs while scanning this JAR + * Get the taglib entries from web.xml and add them to the map. + * + * This is not kept in sync with o.a.j.compiler.TldLocationsCache as this + * code needs to scan the TLDs listed in web.xml whereas Jasper only needs + * the URI to TLD mappings. */ - private void tldScanJar(String resourcePath) throws Exception { - - if (log.isDebugEnabled()) { - log.debug(" Scanning JAR at resource path '" + resourcePath + "'"); + private void tldScanWebXml() { + + if (log.isTraceEnabled()) { + log.trace(sm.getString("tldConfig.webxmlStart")); } - - URL url = context.getServletContext().getResource(resourcePath); - if (url == null) { - throw new IllegalArgumentException - (sm.getString("contextConfig.tldResourcePath", - resourcePath)); - } - - 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 - } - 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 - */ - private void tldScanJar(File file) throws Exception { - - JarFile jarFile = null; - String name = null; - - String jarPath = file.getAbsolutePath(); - - try { - jarFile = new JarFile(file); - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = (JarEntry) entries.nextElement(); - name = entry.getName(); - if (!name.startsWith("META-INF/")) { - continue; - } - if (!name.endsWith(".tld")) { - continue; - } + + 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(" Processing TLD at '" + name + "'"); + log.trace(sm.getString("tldConfig.webxmlAdd", resourcePath, + taglibs[i])); } try { - tldScanStream(new InputSource(jarFile.getInputStream(entry))); - } catch (Exception e) { - log.error(sm.getString("contextConfig.tldEntryException", - name, jarPath, context.getPath()), - e); + InputStream stream = context.getServletContext( + ).getResourceAsStream(resourcePath); + tldScanStream(stream); + taglibUris.add(taglibs[i]); + } catch (IOException ioe) { + log.warn(sm.getString("tldConfig.webxmlFail", resourcePath, + taglibs[i]), ioe); } } - } catch (Exception e) { - log.error(sm.getString("contextConfig.tldJarException", - jarPath, context.getPath()), - e); - } finally { - if (jarFile != null) { - try { - jarFile.close(); - } catch (Throwable t) { - // Ignore - } - } } } - - /** - * Scan the TLD contents in the specified input stream, and register - * any application event listeners found there. NOTE - It is - * the responsibility of the caller to close the InputStream after this - * method returns. + + /* + * Scans the web application's sub-directory identified by startPath, + * along with its sub-directories, for TLDs. * - * @param resourceStream InputStream containing a tag library descriptor + * Initially, rootPath equals /WEB-INF/. The /WEB-INF/classes and + * /WEB-INF/lib sub-directories are excluded from the search, as per the + * JSP 2.0 spec. * - * @exception Exception if an exception occurs while scanning this TLD + * Keep in sync with o.a.j.comiler.TldLocationsCache */ - private void tldScanStream(InputSource resourceStream) - throws Exception { + private void tldScanResourcePaths(String startPath) { - synchronized (tldDigester) { - try { - tldDigester.push(this); - tldDigester.parse(resourceStream); - } finally { - tldDigester.reset(); - } + if (log.isTraceEnabled()) { + log.trace(sm.getString("tldConfig.webinfScan", startPath)); } - } + ServletContext ctxt = context.getServletContext(); - /** - * 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)); + Set dirList = ctxt.getResourcePaths(startPath); + if (dirList != null) { + Iterator it = dirList.iterator(); + while (it.hasNext()) { + String path = it.next(); + if (!path.endsWith(TLD_EXT) + && (path.startsWith(WEB_INF_LIB) + || path.startsWith("/WEB-INF/classes/"))) { + continue; + } + if (path.endsWith(TLD_EXT)) { + if (path.startsWith("/WEB-INF/tags/") && + !path.endsWith("implicit.tld")) { + continue; + } + InputStream stream = ctxt.getResourceAsStream(path); + try { + tldScanStream(stream); + } catch (IOException ioe) { + log.warn(sm.getString("tldConfig.webinfFail", path), + ioe); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Throwable t) { + // do nothing + } + } + } + } else { + tldScanResourcePaths(path); + } } - inputSource = new InputSource(stream); - if (inputSource == null) { - throw new IllegalArgumentException - (sm.getString("contextConfig.tldResourcePath", - resourcePath)); - } - 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 + * Scan the JARs in the WEB-INF/lib directory. Skip the JARs known not to + * have any TLDs in them. + * + * Keep in sync with o.a.j.comiler.TldLocationsCache */ - private void tldScanResourcePathsWebInf(DirContext resources, - String rootPath, - Set tldPaths) - throws IOException { + private void tldScanWebInfLib() { if (log.isTraceEnabled()) { - log.trace(" Scanning TLDs in " + rootPath + " subdirectory"); + log.trace(sm.getString("tldConfig.webinflibStart")); } + ServletContext ctxt = context.getServletContext(); - try { - NamingEnumeration items = resources.list(rootPath); - while (items.hasMoreElements()) { - NameClassPair item = (NameClassPair) 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 + "'"); + Set dirList = ctxt.getResourcePaths(WEB_INF_LIB); + if (dirList != null) { + Iterator it = dirList.iterator(); + while (it.hasNext()) { + String path = it.next(); + if (path.endsWith(JAR_EXT) && + !noTldJars.contains( + path.substring(path.lastIndexOf('/')))) { + // Need to scan this JAR for TLDs + URL url = null; + try { + url = ctxt.getResource(path); + tldScanJar(url); + } catch (IOException e) { + log.warn(sm.getString("tldConfig.webinflibJarFail"), e); } - 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. + /* + * 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. * - * 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., - * /common/lib). + * /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 noTldJars class variable, which contains the names of JARs * that are known not to contain any TLDs. - * - * @return Map of JAR file paths + * + * Keep in sync with o.a.j.comiler.TldLocationsCache */ - private Map getJarPaths() { + private void tldScanClassloaders() { - HashMap jarPathMap = null; + if (log.isTraceEnabled()) { + log.trace(sm.getString("tldConfig.classloaderStart")); + } - ClassLoader webappLoader = Thread.currentThread().getContextClassLoader(); - ClassLoader loader = webappLoader; + ClassLoader loader = + Thread.currentThread().getContextClassLoader(); + while (loader != null) { if (loader instanceof URLClassLoader) { URL[] urls = ((URLClassLoader) loader).getURLs(); for (int i=0; i entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (!name.startsWith("META-INF/")) continue; + if (!name.endsWith(".tld")) continue; + InputStream stream = jarFile.getInputStream(entry); + tldScanStream(stream); + } + } finally { + if (jarFile != null) { + try { + jarFile.close(); + } catch (Throwable t) { + // ignore + } + } } } + + + /* + * Extract the JAR name, if present, from a URL + * + * Keep in sync with o.a.j.comiler.TldLocationsCache + */ + 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; + } + + /* + * Scan the TLD contents in the specified input stream, and register + * any application event listeners found there. NOTE - This + * method ensure that the InputStream is correctly closed. + * + * @param resourceStream InputStream containing a tag library descriptor + * + * @throws IOException If the file cannot be read + */ + private void tldScanStream(InputStream resourceStream) throws IOException { + + InputSource source = new InputSource(resourceStream); + + synchronized (tldDigester) { + try { + tldDigester.push(this); + tldDigester.parse(source); + } catch (SAXException s) { + // Hack - makes exception handling simpler + IOException ioe = new IOException(); + ioe.initCause(s); + throw ioe; + } finally { + tldDigester.reset(); + if (resourceStream != null) { + try { + resourceStream.close(); + } catch (Throwable t) { + // do nothing + } } } - loader = loader.getParent(); } - - return jarPathMap; } public void lifecycleEvent(LifecycleEvent event) { @@ -752,7 +689,8 @@ setTldNamespaceAware(context.getTldNamespaceAware()); // (2) if the attribute wasn't defined on the context - // try the host. + // and override is not set on the context try the host. + if (!context.getOverride()) { if (!tldValidation) { setTldValidation( ((StandardHost) context.getParent()).getXmlValidation()); @@ -762,11 +700,8 @@ setTldNamespaceAware( ((StandardHost) context.getParent()).getXmlNamespaceAware()); } - - tldDigester = DigesterFactory.newDigester(tldValidation, - tldNamespaceAware, - new TldRuleSet()); - tldDigester.getParser(); } + tldDigester = createTldDigester(tldNamespaceAware, tldValidation); } } +} Index: java/org/apache/catalina/startup/TldRuleSet.java =================================================================== --- java/org/apache/catalina/startup/TldRuleSet.java (revision 801738) +++ java/org/apache/catalina/startup/TldRuleSet.java (working copy) @@ -20,6 +20,7 @@ import org.apache.tomcat.util.digester.Digester; +import org.apache.tomcat.util.digester.Rule; import org.apache.tomcat.util.digester.RuleSetBase; @@ -87,10 +88,99 @@ */ public void addRuleInstances(Digester digester) { - digester.addCallMethod(prefix + "taglib/listener/listener-class", - "addApplicationListener", 0); + // Note the sharing of state between rules + TaglibUriRule taglibUriRule = new TaglibUriRule(); + digester.addRule(prefix + "taglib", new TaglibRule(taglibUriRule)); + + digester.addRule(prefix + "taglib/uri", taglibUriRule); + + digester.addRule(prefix + "taglib/listener/listener-class", + new TaglibListenerRule(taglibUriRule)); + } } + +/* + * This rule only exists to reset the duplicateUri flag on the TaglibUriRule. + */ +final class TaglibRule extends Rule { + private final TaglibUriRule taglibUriRule; + + public TaglibRule(TaglibUriRule taglibUriRule) { + this.taglibUriRule = taglibUriRule; + } + + public void body(String namespace, String name, String text) + throws Exception { + taglibUriRule.setDuplicateUri(false); + } + +} + +final class TaglibUriRule extends Rule { + + // This is set to false for each file processed by the TaglibRule + private boolean duplicateUri; + + public TaglibUriRule() { + } + + @Override + public void body(String namespace, String name, String text) + throws Exception { + TldConfig tldConfig = + (TldConfig) digester.peek(digester.getCount() - 1); + if (tldConfig.isKnownTaglibUri(text)) { + // Already seen this URI + duplicateUri = true; + // This is expected if the URI was defined in web.xml + // Log message at debug in this case + if (tldConfig.getContext().findTaglib(text) == null) { + digester.getLogger().info( + "TLD skipped. URI: " + text + " is already defined"); + } else { + if (digester.getLogger().isDebugEnabled()) { + digester.getLogger().debug( + "TLD skipped. URI: " + text + " is already defined"); + } + } + } else { + // New URI. Add it to known list and carry on + tldConfig.addTaglibUri(text); + } + } + + public boolean isDuplicateUri() { + return duplicateUri; + } + + public void setDuplicateUri(boolean duplciateUri) { + this.duplicateUri = duplciateUri; + } + +} + +final class TaglibListenerRule extends Rule { + + private final TaglibUriRule taglibUriRule; + + public TaglibListenerRule(TaglibUriRule taglibUriRule) { + this.taglibUriRule = taglibUriRule; + } + + @Override + public void body(String namespace, String name, String text) + throws Exception { + TldConfig tldConfig = + (TldConfig) digester.peek(digester.getCount() - 1); + + // Only process the listener if the URI is not a duplicate + if (!taglibUriRule.isDuplicateUri()) { + tldConfig.addApplicationListener(text); + } + } + +} \ No newline at end of file Index: java/org/apache/jasper/compiler/TagLibraryInfoImpl.java =================================================================== --- java/org/apache/jasper/compiler/TagLibraryInfoImpl.java (revision 801738) +++ java/org/apache/jasper/compiler/TagLibraryInfoImpl.java (working copy) @@ -149,8 +149,8 @@ } try { - if (!location[0].endsWith("jar")) { - // Location points to TLD file + if (location[1] == null) { + // Location points directly to TLD file try { in = getResourceAsStream(location[0]); if (in == null) { @@ -320,7 +320,7 @@ String[] location = new String[2]; location[0] = uri; - if (location[0].endsWith("jar")) { + if (location[0].endsWith(".jar")) { URL url = null; try { url = ctxt.getResource(location[0]); Index: java/org/apache/jasper/compiler/TldLocationsCache.java =================================================================== --- java/org/apache/jasper/compiler/TldLocationsCache.java (revision 801738) +++ java/org/apache/jasper/compiler/TldLocationsCache.java (working copy) @@ -17,6 +17,7 @@ package org.apache.jasper.compiler; +import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; import java.net.MalformedURLException; @@ -87,10 +88,19 @@ public static final int ROOT_REL_URI = 1; public static final int NOROOT_REL_URI = 2; + private static final String WEB_INF = "/WEB-INF/"; + private static final String WEB_INF_LIB = "/WEB-INF/lib/"; private static final String WEB_XML = "/WEB-INF/web.xml"; private static final String FILE_PROTOCOL = "file:"; - private static final String JAR_FILE_SUFFIX = ".jar"; + private static final String JAR_EXT = ".jar"; + private static final String TLD_EXT = ".tld"; + // Configuration properties + private static final boolean SCAN_CLASSPATH = Boolean.valueOf( + System.getProperty( + "org.apache.jasper.compiler.TldLocationsCache.SCAN_CLASSPATH", + "true")).booleanValue(); + // Names of JARs that are known not to contain any TLDs private static HashSet noTldJars; @@ -105,7 +115,6 @@ private boolean initialized; private ServletContext ctxt; - private boolean redeployMode; //********************************************************************* // Constructor and Initilizations @@ -161,10 +170,6 @@ noTldJars.add("sunpkcs11.jar"); } - public TldLocationsCache(ServletContext ctxt) { - this(ctxt, true); - } - /** Constructor. * * @param ctxt the servlet context of the web application in which Jasper @@ -175,9 +180,8 @@ * because of JDK bug 4211817 fixed in this release. * If redeployMode is false, a faster but less capable mode will be used. */ - public TldLocationsCache(ServletContext ctxt, boolean redeployMode) { + public TldLocationsCache(ServletContext ctxt) { this.ctxt = ctxt; - this.redeployMode = redeployMode; mappings = new Hashtable(); initialized = false; } @@ -237,12 +241,18 @@ } } + /* + * Keep processing order in sync with o.a.c.startup.TldConfig + */ private void init() throws JasperException { if (initialized) return; try { - processWebDotXml(); - scanJars(); - processTldsInFileSystem("/WEB-INF/"); + tldScanWebXml(); + tldScanResourcePaths(WEB_INF); + tldScanWebInfLib(); + if (SCAN_CLASSPATH) { + tldScanClassloaders(); + } initialized = true; } catch (Exception ex) { throw new JasperException(Localizer.getMessage( @@ -252,8 +262,12 @@ /* * Populates taglib map described in web.xml. + * + * This is not kept in sync with o.a.c.startup.TldConfig as the Jasper only + * needs the URI to TLD mappings from scan web.xml whereas TldConfig needs + * to scan the actual TLD files. */ - private void processWebDotXml() throws Exception { + private void tldScanWebXml() throws Exception { InputStream is = null; @@ -322,7 +336,7 @@ if (uriType(tagLoc) == NOROOT_REL_URI) tagLoc = "/WEB-INF/" + tagLoc; String tagLoc2 = null; - if (tagLoc.endsWith(JAR_FILE_SUFFIX)) { + if (tagLoc.endsWith(JAR_EXT)) { tagLoc = ctxt.getResource(tagLoc).toString(); tagLoc2 = "META-INF/taglib.tld"; } @@ -337,83 +351,18 @@ } } - /** - * 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 element. - * - * @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 - */ - private void scanJar(JarURLConnection conn, boolean ignore) - throws JasperException { - - JarFile jarFile = null; - String resourcePath = conn.getJarFileURL().toString(); - try { - if (redeployMode) { - conn.setUseCaches(false); - } - jarFile = conn.getJarFile(); - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = (JarEntry) entries.nextElement(); - String name = entry.getName(); - if (!name.startsWith("META-INF/")) continue; - if (!name.endsWith(".tld")) continue; - InputStream stream = jarFile.getInputStream(entry); - try { - String uri = getUriFromTld(resourcePath, stream); - // Add implicit map entry only if its uri is not already - // present in the map - if (uri != null && mappings.get(uri) == null) { - mappings.put(uri, new String[]{ resourcePath, name }); - } - } finally { - if (stream != null) { - try { - stream.close(); - } catch (Throwable t) { - // do nothing - } - } - } - } - } catch (Exception ex) { - if (!redeployMode) { - // if not in redeploy mode, close the jar in case of an error - if (jarFile != null) { - try { - jarFile.close(); - } catch (Throwable t) { - // ignore - } - } - } - if (!ignore) { - throw new JasperException(ex); - } - } finally { - if (redeployMode) { - // if in redeploy mode, always close the jar - if (jarFile != null) { - try { - jarFile.close(); - } catch (Throwable t) { - // 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. + * Scans the web application's sub-directory identified by startPath, + * along with its sub-directories, for TLDs and adds an implicit map entry + * to the taglib map for any TLD that has a element. + * + * Initially, rootPath equals /WEB-INF/. The /WEB-INF/classes and + * /WEB-INF/lib sub-directories are excluded from the search, as per the + * JSP 2.0 spec. + * + * Keep code in sync with o.a.c.startup.TldConfig */ - private void processTldsInFileSystem(String startPath) + private void tldScanResourcePaths(String startPath) throws Exception { Set dirList = ctxt.getResourcePaths(startPath); @@ -421,129 +370,220 @@ Iterator it = dirList.iterator(); while (it.hasNext()) { String path = (String) it.next(); - if (path.endsWith("/")) { - processTldsInFileSystem(path); - } - if (!path.endsWith(".tld")) { + if (!path.endsWith(TLD_EXT) + && (path.startsWith(WEB_INF_LIB) + || path.startsWith("/WEB-INF/classes/"))) { continue; } - InputStream stream = ctxt.getResourceAsStream(path); - String uri = null; - try { - uri = getUriFromTld(path, stream); - } finally { - if (stream != null) { - try { - stream.close(); - } catch (Throwable t) { - // do nothing + if (path.endsWith(TLD_EXT)) { + if (path.startsWith("/WEB-INF/tags/") && + !path.endsWith("implicit.tld")) { + continue; + } + InputStream stream = ctxt.getResourceAsStream(path); + try { + tldScanStream(path, null, stream); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Throwable t) { + // do nothing + } } } + } else { + tldScanResourcePaths(path); } - // Add implicit map entry only if its uri is not already - // present in the map - if (uri != null && mappings.get(uri) == null) { - mappings.put(uri, new String[] { path, null }); - } } } } /* - * Returns the value of the uri element of the given TLD, or null if the - * given TLD does not contain any such element. + * Scan the JARs in the WEB-INF/lib directory. Skip the JARs known not to + * have any TLDs in them. + * + * Keep in sync with o.a.c.startup.TldConfig */ - private String getUriFromTld(String resourcePath, InputStream in) - throws JasperException - { - // Parse the tag library descriptor at the specified resource path - TreeNode tld = new ParserUtils().parseXMLDocument(resourcePath, in); - TreeNode uri = tld.findChild("uri"); - if (uri != null) { - String body = uri.getBody(); - if (body != null) - return body; - } + private void tldScanWebInfLib() throws Exception { - return null; + Set dirList = ctxt.getResourcePaths(WEB_INF_LIB); + if (dirList != null) { + Iterator it = dirList.iterator(); + while (it.hasNext()) { + String path = it.next(); + if (path.endsWith(JAR_EXT) && + !noTldJars.contains( + path.substring(path.lastIndexOf('/')))) { + // Need to scan this JAR for TLDs + URL url = null; + url = ctxt.getResource(path); + tldScanJar(url); } + } + } + } /* - * Scans all JARs accessible to the webapp's classloader and its - * parent classloaders for TLDs. + * 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. * - * 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. - * - * Considering JARs in the classloader delegation chain 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., - * /common/lib). + * /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 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 { + private void tldScanClassloaders() throws Exception { - ClassLoader webappLoader - = Thread.currentThread().getContextClassLoader(); - ClassLoader loader = webappLoader; + ClassLoader loader = + Thread.currentThread().getContextClassLoader(); while (loader != null) { if (loader instanceof URLClassLoader) { URL[] urls = ((URLClassLoader) loader).getURLs(); for (int i=0; i element. + * + * @param conn The JarURLConnection to the JAR file to scan + * + * Keep in sync with o.a.c.startup.TldConfig + */ + private void tldScanJar(JarURLConnection conn) throws IOException { + + JarFile jarFile = null; + String resourcePath = conn.getJarFileURL().toString(); + try { + conn.setUseCaches(false); + jarFile = conn.getJarFile(); + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (!name.startsWith("META-INF/")) continue; + if (!name.endsWith(".tld")) continue; + InputStream stream = jarFile.getInputStream(entry); + tldScanStream(resourcePath, name, stream); } + } finally { + if (jarFile != null) { + try { + jarFile.close(); + } catch (Throwable t) { + // ignore + } + } + } + } - loader = loader.getParent(); + /* + * Extract the JAR name, if present, from a URL + * + * Keep in sync with o.a.c.startup.TldConfig + */ + 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; } /* - * Determines if the JAR file with the given jarPath needs to be - * scanned for TLDs. + * Scan the TLD contents in the specified input stream and add any new URIs + * to the map. * - * @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 jarPath needs to be - * scanned for TLDs, FALSE otherwise + * @param resourcePath Path of the resource + * @param entryName If the resource is a JAR file, the name of the entry + * in the JAR file + * @param stream The input stream for the resource + * @throws IOException */ - 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); + private void tldScanStream(String resourcePath, String entryName, + InputStream stream) throws IOException { + try { + // Parse the tag library descriptor at the specified resource path + String uri = null; + + TreeNode tld = + new ParserUtils().parseXMLDocument(resourcePath, stream); + TreeNode uriNode = tld.findChild("uri"); + if (uriNode != null) { + String body = uriNode.getBody(); + if (body != null) + uri = body; } - return (!noTldJars.contains(jarName)); + + // Add implicit map entry only if its uri is not already + // present in the map + if (uri != null && mappings.get(uri) == null) { + mappings.put(uri, new String[]{ resourcePath, entryName }); } + } catch (JasperException e) { + // Hack - makes exception handling simpler + IOException ioe = new IOException(); + ioe.initCause(e); + throw ioe; + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Throwable t) { + // do nothing } } + } + } + +} Index: java/org/apache/jasper/JspC.java =================================================================== --- java/org/apache/jasper/JspC.java (revision 801738) +++ java/org/apache/jasper/JspC.java (working copy) @@ -1253,7 +1253,7 @@ context =new JspCServletContext (new PrintWriter(System.out), new URL("file:" + uriRoot.replace('\\','/') + '/')); - tldLocationsCache = new TldLocationsCache(context, true); + tldLocationsCache = new TldLocationsCache(context); } catch (MalformedURLException me) { System.out.println("**" + me); } Index: webapps/docs/config/systemprops.xml =================================================================== --- webapps/docs/config/systemprops.xml (revision 801738) +++ webapps/docs/config/systemprops.xml (working copy) @@ -83,6 +83,15 @@ true will be used.

+ +

When scanning the class path for TLDs, should Jasper scan all JAR files + in the classpath, including those provided by the web application's parent + clasloaders? This is intended for sharing tag libraries between multiple + web applications by making the JAR available via the common or shared + classloader. If not specified, the default value of true will + be used.

+
+

If true, any tag buffer that expands beyond org.apache.jasper.Constants.DEFAULT_TAG_BUFFER_SIZE will be