Refactor the TLD JAR scanning. This a) reduces duplication between Catalina and Jaspe...
authormarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Tue, 22 Sep 2009 14:54:28 +0000 (14:54 +0000)
committermarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Tue, 22 Sep 2009 14:54:28 +0000 (14:54 +0000)
The JSP TCK passes with this patch applied

git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@817685 13f79535-47bb-0310-9956-ffa450edef68

13 files changed:
java/org/apache/catalina/Context.java
java/org/apache/catalina/core/StandardContext.java
java/org/apache/catalina/startup/ContextRuleSet.java
java/org/apache/catalina/startup/DefaultJarScanner.java [new file with mode: 0644]
java/org/apache/catalina/startup/LocalStrings.properties
java/org/apache/catalina/startup/TldConfig.java
java/org/apache/jasper/compiler/JarScannerFactory.java [new file with mode: 0644]
java/org/apache/jasper/compiler/TldLocationsCache.java
java/org/apache/jasper/resources/LocalStrings.properties
java/org/apache/tomcat/JarScanner.java [new file with mode: 0644]
java/org/apache/tomcat/JarScannerCallback.java [new file with mode: 0644]
webapps/docs/config/jar-scanner.xml [new file with mode: 0644]
webapps/docs/config/project.xml

index 0f6c20a..b8478f4 100644 (file)
@@ -21,6 +21,7 @@ package org.apache.catalina;
 
 import javax.servlet.ServletContext;
 
+import org.apache.tomcat.JarScanner;
 import org.apache.tomcat.util.http.mapper.Mapper;
 
 import org.apache.catalina.deploy.ApplicationParameter;
@@ -1081,5 +1082,19 @@ public interface Context extends Container {
      */
     public String getRealPath(String path);
 
+    /**
+     * Get the Jar Scanner to be used to scan for JAR resources for this
+     * context.
+     * @return  The Jar Scanner configured for this context.
+     */
+    public JarScanner getJarScanner();
+
+    /**
+     * Set the Jar Scanner to be used to scan for JAR resources for this
+     * context.
+     * @param jarScanner    The Jar Scanner to be used for this context.
+     */
+    public void setJarScanner(JarScanner jarScanner);
+
 }
 
index d291f5d..910861b 100644 (file)
@@ -85,6 +85,7 @@ import org.apache.catalina.deploy.SecurityConstraint;
 import org.apache.catalina.loader.WebappLoader;
 import org.apache.catalina.session.StandardManager;
 import org.apache.catalina.startup.ContextConfig;
+import org.apache.catalina.startup.DefaultJarScanner;
 import org.apache.catalina.startup.TldConfig;
 import org.apache.catalina.util.CharsetMapper;
 import org.apache.catalina.util.ExtensionValidator;
@@ -99,6 +100,7 @@ import org.apache.naming.resources.FileDirContext;
 import org.apache.naming.resources.ProxyDirContext;
 import org.apache.naming.resources.WARDirContext;
 import org.apache.tomcat.InstanceManager;
+import org.apache.tomcat.JarScanner;
 import org.apache.tomcat.util.modeler.Registry;
 
 /**
@@ -725,12 +727,30 @@ public class StandardContext
      */
     private boolean useHttpOnly = true;
 
+    /**
+     * The Jar scanner to use to search for Jars that might contain
+     * configuration information such as TLDs or web-fragment.xml files. 
+     */
+    private JarScanner jarScanner = null;
 
 
 
     // ----------------------------------------------------- Context Properties
 
 
+    public JarScanner getJarScanner() {
+        if (jarScanner == null) {
+            jarScanner = new DefaultJarScanner();
+        }
+        return jarScanner;
+    }
+
+
+    public void setJarScanner(JarScanner jarScanner) {
+        this.jarScanner = jarScanner;
+    }
+
+     
     public InstanceManager getInstanceManager() {
        return instanceManager;
     }
@@ -4459,6 +4479,11 @@ public class StandardContext
             
             // Create context attributes that will be required
             if (ok) {
+                getServletContext().setAttribute(
+                        JarScanner.class.getName(), getJarScanner());
+            }
+
+            if (ok) {
                 postWelcomeFiles();
             }
             
index 9f176e9..762ae73 100644 (file)
@@ -203,6 +203,14 @@ public class ContextRuleSet extends RuleSetBase {
         digester.addCallMethod(prefix + "Context/WrapperListener",
                                "addWrapperListener", 0);
 
+        digester.addObjectCreate(prefix + "Context/JarScanner",
+                                 "org.apache.catalina.deploy.DefaultJarScanner",
+                                 "className");
+        digester.addSetProperties(prefix + "Context/JarScanner");
+        digester.addSetNext(prefix + "Context/JarScanner",
+                            "setJarScanner",
+                            "org.apache.tomcat.JarScanner");
+
     }
 
 }
diff --git a/java/org/apache/catalina/startup/DefaultJarScanner.java b/java/org/apache/catalina/startup/DefaultJarScanner.java
new file mode 100644 (file)
index 0000000..cfb728f
--- /dev/null
@@ -0,0 +1,214 @@
+package org.apache.catalina.startup;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLConnection;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+
+import org.apache.tomcat.JarScanner;
+import org.apache.tomcat.JarScannerCallback;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * The default {@link JarScanner} implementation scans the WEB-INF/lib directory
+ * followed by the provided classloader and then works up the classloader
+ * hierarchy. This implementation is sufficient to meet the requirements of the
+ * Servlet 3.0 specification as well is to provide a number of Tomcat specific
+ * extensions. The extensions are:
+ * <ul>
+ *   <li>Scanning the classloader hierarchy (enabled by default)</li>
+ *   <li>Testing all files to see if they are JARs (disabled by default)</li>
+ *   <li>Testing all directories to see if they are exploded JARs
+ *       (disabled by default)</li>
+ * </ul>
+ * All of the extenions may be controlled via configuration.
+ */
+public class DefaultJarScanner implements JarScanner {
+
+    private static final String JAR_EXT = ".jar";
+    private static final String WEB_INF_LIB = "/WEB-INF/lib/";
+
+    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);
+
+
+    /**
+     * Controls the classpath scanning extenion.
+     */
+    private boolean scanClassPath = true;
+    public boolean isScanClassPath() {
+        return scanClassPath;
+    }
+    public void setScanClassPath(boolean scanClassPath) {
+        this.scanClassPath = scanClassPath;
+    }
+
+    /**
+     * Controls the testing all files to see of they are JAR files extenion.
+     */
+    private boolean scanAllFiles = false;
+    public boolean isScanAllFiles() {
+        return scanAllFiles;
+    }
+    public void setScanAllFiles(boolean scanAllFiles) {
+        this.scanAllFiles = scanAllFiles;
+    }
+
+    /**
+     * Controls the testing all directories to see of they are exploded JAR
+     * files extenion.
+     */
+    private boolean scanAllDirectories = false;
+    public boolean isScanAllDirectories() {
+        return scanAllDirectories;
+    }
+    public void setScanAllDirectories(boolean scanAllDirectories) {
+        this.scanAllDirectories = scanAllDirectories;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void scan(ServletContext context, ClassLoader classloader,
+            JarScannerCallback callback, Set<String> jarsToSkip) {
+
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("jarScan.webinflibStart"));
+        }
+
+        // Scan WEB-INF/lib
+        Set<String> dirList = context.getResourcePaths(WEB_INF_LIB);
+        if (dirList != null) {
+            Iterator<String> it = dirList.iterator();
+            while (it.hasNext()) {
+                String path = it.next();
+                if (path.endsWith(JAR_EXT) &&
+                        !jarsToSkip.contains(
+                                path.substring(path.lastIndexOf('/')))) {
+                    // Need to scan this JAR
+                    URL url = null;
+                    try {
+                        url = context.getResource(path);
+                        process(callback, url);
+                    } catch (IOException e) {
+                        log.warn(sm.getString("jarScan.webinflibFail", url), e);
+                    }
+                }
+            }
+        }
+        
+        // Scan the classpath
+        if (scanClassPath) {
+            if (log.isTraceEnabled()) {
+                log.trace(sm.getString("jarScan.classloaderStart"));
+            }
+
+            ClassLoader loader = 
+                Thread.currentThread().getContextClassLoader();
+            
+            while (loader != null) {
+                if (loader instanceof URLClassLoader) {
+                    URL[] urls = ((URLClassLoader) loader).getURLs();
+                    for (int i=0; i<urls.length; i++) {
+                        // Extract the jarName if there is one to be found
+                        String jarName = getJarName(urls[i]);
+                        
+                        // Skip JARs with known not to be interesting and JARs
+                        // in WEB-INF/lib we have already scanned
+                        if (!(jarsToSkip.contains(jarName) ||
+                                urls[i].toString().contains(
+                                        WEB_INF_LIB + jarName))) {
+                            try {
+                                process(callback, urls[i]);
+                            } catch (IOException ioe) {
+                                log.warn(sm.getString(
+                                        "jarScan.classloaderFail",urls[i]), ioe);
+                            }
+                        }
+                    }
+                }
+                loader = loader.getParent();
+            }
+
+        }
+    }
+
+    /*
+     * Scan a URL for JARs with the optional extensions to look at all files
+     * and all directories.
+     */
+    private void process(JarScannerCallback callback, URL url)
+            throws IOException {
+
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("jarScan.jarUrlStart", url));
+        }
+
+        URLConnection conn = url.openConnection();
+        if (conn instanceof JarURLConnection) {
+            callback.scan((JarURLConnection) conn);
+        } else {
+            String urlStr = url.toString();
+            if (urlStr.startsWith("file:")) {
+                if (urlStr.endsWith(JAR_EXT)) {
+                    URL jarURL = new URL("jar:" + urlStr + "!/");
+                    callback.scan((JarURLConnection) jarURL.openConnection());
+                } else {
+                    File f;
+                    try {
+                        f = new File(url.toURI());
+                        if (f.isFile() && scanAllFiles) {
+                            // Treat this file as a JAR
+                            URL jarURL = new URL("jar:" + urlStr + "!/");
+                            callback.scan((JarURLConnection) jarURL.openConnection());
+                        } else if (f.isDirectory() && scanAllDirectories) {
+                            File metainf = new File(f.getAbsoluteFile() +
+                                    File.separator + "META-INF");
+                            if (metainf.isDirectory()) {
+                                callback.scan(f);
+                            }
+                        }
+                    } catch (URISyntaxException e) {
+                        // Wrap the exception and re-throw
+                        IOException ioe = new IOException();
+                        ioe.initCause(e);
+                        throw ioe;
+                    }
+                }
+            }
+        }
+        
+    }
+
+    /*
+     * 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;
+    }
+
+}
index 594f4e4..e69fd10 100644 (file)
@@ -83,20 +83,21 @@ hostConfig.stop=HostConfig: Processing STOP
 hostConfig.undeploy=Undeploying context [{0}]
 hostConfig.undeploy.error=Error undeploying web application at context path {0}
 hostConfig.undeploying=Undeploying deployed web applications
+jarScan.classloaderFail=Failed to scan [{0}] from classloader hierarchy
+jarScan.classloaderStart=Scanning for JARs in classloader hierarchy
+jarScan.jarUrlStart=Scanning JAR at URL [{0}]
+jarScan.webinflibFail=Failed to scan JAR [{0}] from WEB-INF/lib
+jarScan.webinflibStart=Scanning WEB-INF/lib for JARs
 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.dirScan=Scanning for TLD files in directory ''{0}''
+tldConfig.dirFail=Failed to process directory [{0}] for TLD files
+tldConfig.dirScan=Scanning for TLD files in directory [{0}]
 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.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 <taglib> elements in web.xml
 userConfig.database=Exception loading user database
 userConfig.deploy=Deploying web application for user {0}
index 530bb71..1dd6d95 100644 (file)
@@ -24,10 +24,6 @@ import java.io.FileInputStream;
 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.net.URLConnection;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashSet;
@@ -45,6 +41,8 @@ import org.apache.catalina.LifecycleEvent;
 import org.apache.catalina.LifecycleListener;
 import org.apache.catalina.core.StandardContext;
 import org.apache.catalina.core.StandardHost;
+import org.apache.tomcat.JarScanner;
+import org.apache.tomcat.JarScannerCallback;
 import org.apache.tomcat.util.res.StringManager;
 import org.apache.tomcat.util.digester.Digester;
 import org.xml.sax.InputSource;
@@ -61,27 +59,10 @@ import org.xml.sax.SAXException;
  */
 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();
-
-    private static final boolean SCAN_ALL_FILES = Boolean.valueOf(
-            System.getProperty(
-                "org.apache.jasper.compiler.TldLocationsCache.SCAN_ALL_FILES",
-                "false")).booleanValue();
-
-    private static final boolean SCAN_ALL_DIRS = Boolean.valueOf(
-            System.getProperty(
-                "org.apache.jasper.compiler.TldLocationsCache.SCAN_ALL_DIRS",
-                "false")).booleanValue();
-
     // Names of JARs that are known not to contain any TLDs
     private static HashSet<String> noTldJars;
 
@@ -327,7 +308,18 @@ public final class TldConfig  implements LifecycleListener {
     /**
      * Scan for and configure all tag library descriptors found in this
      * web application.
+     * 
+     * This supports 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>/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.
+     * 
      * @exception Exception if a fatal input/output or parsing error occurs
      */
     public void execute() throws Exception {
@@ -349,14 +341,12 @@ public final class TldConfig  implements LifecycleListener {
         // Stage 3a - TLDs under WEB-INF (not lib or classes)
         tldScanResourcePaths(WEB_INF);
 
-        // Stage 3b - .jar files in WEB-INF/lib/
-        tldScanWebInfLib();
+        // Stages 3b & 4
+        JarScanner jarScanner = context.getJarScanner();
+        jarScanner.scan(context.getServletContext(),
+                context.getLoader().getClassLoader(),
+                new TldJarScannerCallback(), noTldJars);
         
-        // Stage 4 - Additional entries from the container
-        if (SCAN_CLASSPATH) {
-            tldScanClassloaders();
-        }
-
         // Now add all the listeners we found to the listeners for this context
         String list[] = getTldListeners();
 
@@ -375,6 +365,22 @@ public final class TldConfig  implements LifecycleListener {
 
     }
 
+    private class TldJarScannerCallback implements JarScannerCallback {
+
+        @Override
+        public void scan(JarURLConnection urlConn) throws IOException {
+            tldScanJar(urlConn);
+        }
+
+        @Override
+        public void scan(File file) {
+            File metaInf = new File(file, "META-INF");
+            if (metaInf.isDirectory()) {
+                tldScanDir(metaInf);
+            }
+        }
+    }
+
     // -------------------------------------------------------- Private Methods
 
 
@@ -478,136 +484,6 @@ public final class TldConfig  implements LifecycleListener {
     }
     
     /*
-     * 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 tldScanWebInfLib() {
-
-        if (log.isTraceEnabled()) {
-            log.trace(sm.getString("tldConfig.webinflibStart"));
-        }
-        ServletContext ctxt = context.getServletContext();
-        
-        Set<String> dirList = ctxt.getResourcePaths(WEB_INF_LIB);
-        if (dirList != null) {
-            Iterator<String> 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);
-                    }
-                }
-            }
-        }
-    }
-
-    /*
-     * 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.
-     *
-     * 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>/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.
-     * 
-     * Keep in sync with o.a.j.comiler.TldLocationsCache
-     */
-    private void tldScanClassloaders() {
-
-        if (log.isTraceEnabled()) {
-            log.trace(sm.getString("tldConfig.classloaderStart"));
-        }
-
-        ClassLoader loader = 
-            Thread.currentThread().getContextClassLoader();
-        
-        while (loader != null) {
-            if (loader instanceof URLClassLoader) {
-                URL[] urls = ((URLClassLoader) loader).getURLs();
-                for (int i=0; i<urls.length; i++) {
-                    // Extract the jarName if there is one to be found
-                    String jarName = getJarName(urls[i]);
-                    
-                    // Skip JARs with known not to contain TLDs and JARs in
-                    // WEB-INF/lib we have already scanned
-                    if (!(noTldJars.contains(jarName) ||
-                            urls[i].toString().contains(
-                                    WEB_INF_LIB + jarName))) {
-                        try {
-                            tldScanJar(urls[i]);
-                        } catch (IOException ioe) {
-                            log.warn(sm.getString(
-                                    "tldConfig.classloaderFail",urls[i]), ioe);
-                        }
-                    }
-                }
-            }
-            loader = loader.getParent();
-        }
-    }
-
-    /*
-     * Keep in sync with o.a.j.comiler.TldLocationsCache
-     */
-    private void tldScanJar(URL url) throws IOException {
-        if (log.isTraceEnabled()) {
-            log.trace(sm.getString("tldConfig.jarUrlStart", url));
-        }
-
-        URLConnection conn = url.openConnection();
-        if (conn instanceof JarURLConnection) {
-            tldScanJar((JarURLConnection) conn);
-        } else {
-            String urlStr = url.toString();
-            if (urlStr.startsWith("file:")) {
-                if (urlStr.endsWith(JAR_EXT)) {
-                    URL jarURL = new URL("jar:" + urlStr + "!/");
-                    tldScanJar((JarURLConnection) jarURL.openConnection());
-                } else {
-                    File f;
-                    try {
-                        f = new File(url.toURI());
-                        if (f.isFile() && SCAN_ALL_FILES) {
-                            // Treat this file as a JAR
-                            URL jarURL = new URL("jar:" + urlStr + "!/");
-                            tldScanJar((JarURLConnection) jarURL.openConnection());
-                        } else if (f.isDirectory() && SCAN_ALL_DIRS) {
-                            File metainf = new File(f.getAbsoluteFile() +
-                                    File.separator + "META-INF");
-                            if (metainf.isDirectory()) {
-                                tldScanDir(metainf);
-                            }
-                        }
-                    } catch (URISyntaxException e) {
-                        // Wrap the exception and re-throw
-                        IOException ioe = new IOException();
-                        ioe.initCause(e);
-                        throw ioe;
-                    }
-                }
-            }
-        }
-    }
-
-    /*
      * Scans the directory identified by startPath, along with its
      * sub-directories, for TLDs.
      *
@@ -684,25 +560,6 @@ public final class TldConfig  implements LifecycleListener {
 
 
     /*
-     * 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.  <b>NOTE</b> - This 
      * method ensure that the InputStream is correctly closed.
@@ -779,4 +636,5 @@ public final class TldConfig  implements LifecycleListener {
             tldDigester = createTldDigester(tldNamespaceAware, tldValidation);
         }
     }
+
 }
diff --git a/java/org/apache/jasper/compiler/JarScannerFactory.java b/java/org/apache/jasper/compiler/JarScannerFactory.java
new file mode 100644 (file)
index 0000000..d7f1664
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+package org.apache.jasper.compiler;
+
+import javax.servlet.ServletContext;
+
+import org.apache.tomcat.JarScanner;
+
+/**
+ * Provide a mechanism for Jasper to obtain a reference to the JarScanner
+ * impementation.
+ */
+public class JarScannerFactory {
+
+    /*
+     * Don't want any instances so hide the default constructor.
+     */
+    private JarScannerFactory() {
+    }
+
+    /**
+     * Obtain the {@link JarScanner} associated with the specificed {@link
+     * ServletContext}. It is obtained via a context parameter.
+     */
+    public static JarScanner getJarScanner(ServletContext ctxt) {
+        JarScanner jarScanner = 
+               (JarScanner) ctxt.getAttribute(JarScanner.class.getName());
+        if (jarScanner == null) {
+            ctxt.log(Localizer.getMessage("jsp.warning.noJarScanner"));
+        }
+        return jarScanner;
+    }
+
+}
index 40a57fe..167d900 100644 (file)
@@ -23,10 +23,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.JarURLConnection;
 import java.net.MalformedURLException;
-import java.net.URISyntaxException;
 import java.net.URL;
-import java.net.URLClassLoader;
-import java.net.URLConnection;
 import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.HashSet;
@@ -45,6 +42,8 @@ import org.apache.jasper.xmlparser.ParserUtils;
 import org.apache.jasper.xmlparser.TreeNode;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.JarScanner;
+import org.apache.tomcat.JarScannerCallback;
 
 /**
  * A container for all tag libraries that are defined "globally"
@@ -98,22 +97,6 @@ public class TldLocationsCache {
     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();
-
-    private static final boolean SCAN_ALL_FILES = Boolean.valueOf(
-            System.getProperty(
-                "org.apache.jasper.compiler.TldLocationsCache.SCAN_ALL_FILES",
-                "false")).booleanValue();
-
-    private static final boolean SCAN_ALL_DIRS = Boolean.valueOf(
-            System.getProperty(
-                "org.apache.jasper.compiler.TldLocationsCache.SCAN_ALL_DIRS",
-                "false")).booleanValue();
-
     // Names of JARs that are known not to contain any TLDs
     private static HashSet<String> noTldJars;
 
@@ -257,16 +240,28 @@ public class TldLocationsCache {
 
     /*
      * Keep processing order in sync with o.a.c.startup.TldConfig
+     *
+     * This supports 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>/lib). It also supports some of the weird and
+     * wonderful arrangements present when Tomcat gets embedded.
+     *
      */
     private void init() throws JasperException {
         if (initialized) return;
         try {
             tldScanWebXml();
             tldScanResourcePaths(WEB_INF);
-            tldScanWebInfLib();
-            if (SCAN_CLASSPATH) {
-                tldScanClassloaders();
+            
+            JarScanner jarScanner = JarScannerFactory.getJarScanner(ctxt);
+            if (jarScanner != null) {
+                jarScanner.scan(ctxt,
+                        Thread.currentThread().getContextClassLoader(),
+                        new TldJarScannerCallback(), noTldJars);
             }
+
             initialized = true;
         } catch (Exception ex) {
             throw new JasperException(Localizer.getMessage(
@@ -274,6 +269,22 @@ public class TldLocationsCache {
         }
     }
 
+    private class TldJarScannerCallback implements JarScannerCallback {
+
+        @Override
+        public void scan(JarURLConnection urlConn) throws IOException {
+            tldScanJar(urlConn);
+        }
+
+        @Override
+        public void scan(File file) throws IOException {
+            File metaInf = new File(file, "META-INF");
+            if (metaInf.isDirectory()) {
+                tldScanDir(metaInf);
+            }
+        }
+    }
+
     /*
      * Populates taglib map described in web.xml.
      * 
@@ -414,115 +425,6 @@ public class TldLocationsCache {
     }
 
     /*
-     * 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 void tldScanWebInfLib() throws Exception {
-        
-        Set<String> dirList = ctxt.getResourcePaths(WEB_INF_LIB);
-        if (dirList != null) {
-            Iterator<String> 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);
-                }
-            }
-        }
-    }
-
-    /*
-     * 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.
-     *
-     * 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>/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.
-     * 
-     * Keep in sync with o.a.c.startup.TldConfig
-     */
-    private void tldScanClassloaders() throws Exception {
-
-        ClassLoader loader =
-            Thread.currentThread().getContextClassLoader();
-
-        while (loader != null) {
-            if (loader instanceof URLClassLoader) {
-                URL[] urls = ((URLClassLoader) loader).getURLs();
-                for (int i=0; i<urls.length; i++) {
-                    // Extract the jarName if there is one to be found
-                    String jarName = getJarName(urls[i]);
-
-                    // Skip JARs with known not to contain TLDs and JARs in
-                    // WEB-INF/lib we have already scanned
-                    if (!(noTldJars.contains(jarName) ||
-                            urls[i].toString().contains(
-                                    "WEB-INF/lib/" + jarName))) {
-                        tldScanJar(urls[i]);
-                    }
-                }
-            }
-            loader = loader.getParent();
-        }
-    }
-
-    /*
-     * Keep in sync with o.a.c.startup.TldConfig
-     */
-    private void tldScanJar(URL url) throws IOException {
-        URLConnection conn = url.openConnection();
-        if (conn instanceof JarURLConnection) {
-            tldScanJar((JarURLConnection) conn);
-        } else {
-            String urlStr = url.toString();
-            if (urlStr.startsWith("file:")) {
-                if (urlStr.endsWith(JAR_EXT)) {
-                    URL jarURL = new URL("jar:" + urlStr + "!/");
-                    tldScanJar((JarURLConnection) jarURL.openConnection());
-                } else {
-                    File f;
-                    try {
-                        f = new File(url.toURI());
-                        if (f.isFile() && SCAN_ALL_FILES) {
-                            // Treat this file as a JAR
-                            URL jarURL = new URL("jar:" + urlStr + "!/");
-                            tldScanJar((JarURLConnection) jarURL.openConnection());
-                            tldScanJar((JarURLConnection) jarURL.openConnection());
-                        } else if (f.isDirectory() && SCAN_ALL_DIRS) {
-                            File metainf = new File(f.getAbsoluteFile() +
-                                    File.separator + "META-INF");
-                            if (metainf.isDirectory()) {
-                                tldScanDir(metainf);
-                            }
-                        }
-                    } catch (URISyntaxException e) {
-                        // Wrap the exception and re-throw
-                        IOException ioe = new IOException();
-                        ioe.initCause(e);
-                        throw ioe;
-                    }
-                }
-            }
-        }
-    }
-
-    /*
      * Scans the directory identified by startPath, along with its
      * sub-directories, for TLDs.
      *
@@ -593,25 +495,6 @@ public class TldLocationsCache {
     }
 
     /*
-     * 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;
-    }
-
-    /*
      * Scan the TLD contents in the specified input stream and add any new URIs
      * to the map.
      * 
index 2b8bab4..29b5cba 100644 (file)
@@ -454,3 +454,6 @@ jsp.error.page.invalid.trimdirectivewhitespaces=Page directive: invalid value fo
 jsp.error.tag.invalid.trimdirectivewhitespaces=Tag directive: invalid value for trimDirectiveWhitespaces
 jsp.error.page.conflict.trimdirectivewhitespaces=Page directive: illegal to have multiple occurrences of 'trimDirectiveWhitespaces' with different values (old: {0}, new: {1})
 jsp.error.tag.conflict.trimdirectivewhitespaces=Tag directive: illegal to have multiple occurrences of 'trimDirectiveWhitespaces' with different values (old: {0}, new: {1})
+
+# JarScanner
+jsp.warning.noJarScanner=Warning: No org.apache.tomcat.JarScanner set in ServletContext. Jar scanning for TLDs and web-fragment.xml files is disabled
\ No newline at end of file
diff --git a/java/org/apache/tomcat/JarScanner.java b/java/org/apache/tomcat/JarScanner.java
new file mode 100644 (file)
index 0000000..978b725
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+package org.apache.tomcat;
+
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+
+/**
+ * Scans a web application and classloader hierarchy for JAR files. Uses
+ * include TLD scanning and web-fragment.xml scanning. Uses a call-back
+ * mechanism so the caller can process each JAR found. 
+ */
+public interface JarScanner {
+
+    /**
+     * Scan the provided ServletContext and classloader for JAR files. Each JAR
+     * file found will be passed to the callback handler to be processed.
+     *  
+     * @param context       The ServletContext - used to locate and access
+     *                      WEB-INF/lib
+     * @param classloader   The classloader - used to access JARs not in
+     *                      WEB-INF/lib
+     * @param callback      The handler to process any JARs found
+     * @param jarsToSkip    List of JARs to ignore
+     */
+    public void scan(ServletContext context, ClassLoader classloader,
+            JarScannerCallback callback, Set<String> jarsToSkip);
+    
+}
diff --git a/java/org/apache/tomcat/JarScannerCallback.java b/java/org/apache/tomcat/JarScannerCallback.java
new file mode 100644 (file)
index 0000000..4d31fdf
--- /dev/null
@@ -0,0 +1,18 @@
+package org.apache.tomcat;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.JarURLConnection;
+
+public interface JarScannerCallback {
+
+    /**
+     * 
+     * @param urlConn
+     * @throws IOException
+     */
+    public void scan(JarURLConnection urlConn) throws IOException;
+    
+    public void scan(File file) throws IOException ;
+
+}
diff --git a/webapps/docs/config/jar-scanner.xml b/webapps/docs/config/jar-scanner.xml
new file mode 100644 (file)
index 0000000..1c4377a
--- /dev/null
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!DOCTYPE document [
+  <!ENTITY project SYSTEM "project.xml">
+]>
+<document url="jar-scanner.html">
+
+  &project;
+
+  <properties>
+    <title>The Jar Scanner Component</title>
+  </properties>
+
+<body>
+
+
+<section name="Introduction">
+
+  <p>The <strong>Jar Scanner</strong> element represents the component that is
+  used to scan the web application for JAR files. It is typically used during
+  web application start to identify configuration files such as TLDs or
+  web-fragment.xml files that must be processed as part of the web application
+  initialisation.</p>
+
+  <p>A Jar Scanner element MAY be nested inside a
+  <a href="context.html">Context</a> component.  If it is not included,
+  a default Jar Scanner configuration will be created automatically, which
+  is sufficient for most requirements.</p>
+
+</section>
+
+
+<section name="Attributes">
+
+  <subsection name="Common Attributes">
+
+    <p>All implementations of <strong>Jar Scanner</strong>
+    support the following attributes:</p>
+
+    <attributes>
+
+      <attribute name="className" required="false">
+        <p>Java class name of the implementation to use.  This class must
+        implement the <code>org.apache.tomcat.JarScanner</code> interface.
+        If not specified, the standard value (defined below) will be used.</p>
+      </attribute>
+
+    </attributes>
+
+  </subsection>
+
+
+  <subsection name="Standard Implementation">
+
+    <p>The standard implementation of <strong>Jar Scanner</strong> is
+    <strong>org.apache.catalina.deploy.DefaultJarScanner</strong>.
+    It supports the following additional attributes (in addition to the
+    common attributes listed above):</p>
+
+    <attributes>
+
+      <attribute name="scanClassPath" required="false">
+       <p>If true, the full web application classpath, including the shared and
+       common classloaders will be scanned for Jar files in addition to the
+       web application. The default is <code>true</code>.</p>
+      </attribute>
+
+      <attribute name="scanAllFiles" required="false">
+       <p>If true, any files found on the classpath will be checked to see if
+       they are Jar files rather than relying on the file extension being
+       <code>.jar</code>. The default is <code>false</code></p>
+      </attribute>
+
+      <attribute name="scanAllDirectories" required="false">
+       <p>If true, any directories found on the classpath will be checked to see
+       if are expanded Jar files. The default is <code>false</code></p>
+      </attribute>
+
+    </attributes>
+
+  </subsection>
+
+
+</section>
+
+
+<section name="Nested Components">
+  <p>No components may be nested inside a <strong>Jar Scanner</strong> element.
+  </p>
+</section>
+
+
+<section name="Special Features">
+  <p>No special features are associated with a <strong>Jar Scanner</strong>
+  element.</p>
+</section>
+
+</body>
+
+</document>
index 4b4cff5..1b63be0 100644 (file)
@@ -57,6 +57,7 @@
         <item name="Listeners"             href="listeners.html"/>
         <item name="Global Resources"      href="globalresources.html"/>
         <item name="Loader"                href="loader.html"/>
+        <item name="JarScanner"            href="jar-scanner.html"/>
         <item name="Manager"               href="manager.html"/> 
         <item name="Realm"                 href="realm.html"/>
         <item name="Resources"             href="resources.html"/>