/* * 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.util.scan; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.StringTokenizer; import javax.servlet.ServletContext; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.JarScanner; import org.apache.tomcat.JarScannerCallback; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.buf.UriUtil; import org.apache.tomcat.util.file.Matcher; 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 as 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 extensions may be controlled via configuration. */ public class StandardJarScanner implements JarScanner { private static final Log log = LogFactory.getLog(StandardJarScanner.class); private static final Set<String> defaultJarsToSkip = new HashSet<String>(); /** * The string resources for this package. */ private static final StringManager sm = StringManager.getManager(Constants.Package); static { String jarList = System.getProperty(Constants.SKIP_JARS_PROPERTY); if (jarList != null) { StringTokenizer tokenizer = new StringTokenizer(jarList, ","); while (tokenizer.hasMoreElements()) { String token = tokenizer.nextToken().trim(); if (token.length() > 0) { defaultJarsToSkip.add(token); } } } } /** * Controls the classpath scanning extension. */ 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 extension. */ 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 extension. */ private boolean scanAllDirectories = false; public boolean isScanAllDirectories() { return scanAllDirectories; } public void setScanAllDirectories(boolean scanAllDirectories) { this.scanAllDirectories = scanAllDirectories; } /** * Controls the testing of the bootstrap classpath which consists of the * runtime classes provided by the JVM and any installed system extensions. */ private boolean scanBootstrapClassPath = false; public boolean isScanBootstrapClassPath() { return scanBootstrapClassPath; } public void setScanBootstrapClassPath(boolean scanBootstrapClassPath) { this.scanBootstrapClassPath = scanBootstrapClassPath; } /** * 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. If this list is null, a * default list will be read from the system property * defined by {@link Constants#SKIP_JARS_PROPERTY} */ @Override public void scan(ServletContext context, ClassLoader classloader, JarScannerCallback callback, Set<String> jarsToSkip) { if (log.isTraceEnabled()) { log.trace(sm.getString("jarScan.webinflibStart")); } final Set<String> ignoredJars; if (jarsToSkip == null) { ignoredJars = defaultJarsToSkip; } else { ignoredJars = jarsToSkip; } // Scan WEB-INF/lib Set<String> dirList = context.getResourcePaths(Constants.WEB_INF_LIB); if (dirList != null) { Iterator<String> it = dirList.iterator(); while (it.hasNext()) { String path = it.next(); if (path.endsWith(Constants.JAR_EXT) && !Matcher.matchName(ignoredJars, path.substring(path.lastIndexOf('/')+1))) { // Need to scan this JAR if (log.isDebugEnabled()) { log.debug(sm.getString("jarScan.webinflibJarScan", path)); } URL url = null; try { // File URLs are always faster to work with so use them // if available. String realPath = context.getRealPath(path); if (realPath == null) { url = context.getResource(path); } else { url = (new File(realPath)).toURI().toURL(); } process(callback, url); } catch (IOException e) { log.warn(sm.getString("jarScan.webinflibFail", url), e); } } else { if (log.isTraceEnabled()) { log.trace(sm.getString("jarScan.webinflibJarNoScan", path)); } } } } // Scan the classpath if (scanClassPath && classloader != null) { if (log.isTraceEnabled()) { log.trace(sm.getString("jarScan.classloaderStart")); } ClassLoader loader = classloader; ClassLoader stopLoader = null; if (!scanBootstrapClassPath) { // Stop when we reach the bootstrap class loader stopLoader = ClassLoader.getSystemClassLoader().getParent(); } while (loader != null && loader != stopLoader) { 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 known not to be interesting and JARs // in WEB-INF/lib we have already scanned if (jarName != null && !(Matcher.matchName(ignoredJars, jarName) || urls[i].toString().contains( Constants.WEB_INF_LIB + jarName))) { if (log.isDebugEnabled()) { log.debug(sm.getString("jarScan.classloaderJarScan", urls[i])); } try { process(callback, urls[i]); } catch (IOException ioe) { log.warn(sm.getString( "jarScan.classloaderFail",urls[i]), ioe); } } else { if (log.isTraceEnabled()) { log.trace(sm.getString("jarScan.classloaderJarNoScan", urls[i])); } } } } 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:") || urlStr.startsWith("jndi:") || urlStr.startsWith("http:") || urlStr.startsWith("https:")) { if (urlStr.endsWith(Constants.JAR_EXT)) { URL jarURL = UriUtil.buildJarUrl(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 = UriUtil.buildJarUrl(f); 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 (Throwable t) { ExceptionUtils.handleThrowable(t); // Wrap the exception and re-throw IOException ioe = new IOException(); ioe.initCause(t); 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(Constants.JAR_EXT); if (end != -1) { int start = path.lastIndexOf('/', end); name = path.substring(start + 1, end + 4); } else if (isScanAllDirectories()){ int start = path.lastIndexOf('/'); name = path.substring(start + 1); } return name; } }