Author: rjung Date: Tue Aug 3 17:09:55 2010 New Revision: 981967 URL: http://svn.apache.org/viewvc?rev=981967&view=rev Log: Allow glob patterns in the jarsToSkip configuration and add some debug logging to the jar scanner.
Still need to localize debug log messages. ecj Jar in catalina.properties now uses a glob, did not yet add globs to all the commons jars in the default configuration. Added: tomcat/trunk/java/org/apache/tomcat/util/file/ tomcat/trunk/java/org/apache/tomcat/util/file/Matcher.java (with props) Modified: tomcat/trunk/conf/catalina.properties tomcat/trunk/java/org/apache/tomcat/util/scan/StandardJarScanner.java tomcat/trunk/webapps/docs/changelog.xml Modified: tomcat/trunk/conf/catalina.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/conf/catalina.properties?rev=981967&r1=981966&r2=981967&view=diff ============================================================================== --- tomcat/trunk/conf/catalina.properties (original) +++ tomcat/trunk/conf/catalina.properties Tue Aug 3 17:09:55 2010 @@ -89,7 +89,7 @@ tomcat.util.scan.DefaultJarScanner.jarsT bootstrap.jar,commons-daemon.jar,tomcat-juli.jar,\ annotations-api.jar,el-api.jar,jsp-api.jar,servlet-api.jar,\ catalina.jar,catalina-ant.jar,catalina-ha.jar,catalina-tribes.jar,\ -jasper.jar,jasper-el.jar,ecj-3.6.jar,\ +jasper.jar,jasper-el.jar,ecj-*.jar,\ tomcat-api.jar,tomcat-util.jar,tomcat-coyote.jar,tomcat-dbcp.jar,\ tomcat-i18n-en.jar,tomcat-i18n-es.jar,tomcat-i18n-fr.jar,tomcat-i18n-ja.jar,\ commons-beanutils.jar,commons-collections.jar,commons-dbcp.jar,\ Added: tomcat/trunk/java/org/apache/tomcat/util/file/Matcher.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/file/Matcher.java?rev=981967&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/util/file/Matcher.java (added) +++ tomcat/trunk/java/org/apache/tomcat/util/file/Matcher.java Tue Aug 3 17:09:55 2010 @@ -0,0 +1,571 @@ +/* + * 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.file; + +import java.io.File; +import java.util.Locale; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * <p>This is a utility class used by selectors and DirectoryScanner. The + * functionality more properly belongs just to selectors, but unfortunately + * DirectoryScanner exposed these as protected methods. Thus we have to + * support any subclasses of DirectoryScanner that may access these methods. + * </p> + * <p>This is a Singleton.</p> + */ +public final class Matcher { + + /** + * The pattern that matches an arbitrary number of directories. + */ + public static final String DEEP_TREE_MATCH = "**"; + + private static final Matcher instance = new Matcher(); + private static final String OS_NAME = + System.getProperty("os.name").toLowerCase(Locale.ENGLISH); + private static final String PATH_SEP = + System.getProperty("path.separator"); + private static final boolean ON_NETWARE = isNetware(); + private static final boolean ON_DOS = isDos(); + + /** + * Private Constructor + */ + private Matcher() { + } + + /** + * Retrieves the instance of the Singleton. + * @return singleton instance + */ + public static Matcher getInstance() { + return instance; + } + + /** + * Tests whether or not a given path matches any pattern in the given set. + * + * If you need to call this method multiple times with the same + * pattern you should rather use TokenizedPath + * + * @see TokenizedPath + * + * @param patternSet The pattern set to match against. Must not be + * <code>null</code>. + * @param str The path to match, as a String. Must not be + * <code>null</code>. + * + * @return <code>true</code> if any pattern in the set matches against the string, + * or <code>false</code> otherwise. + */ + public static boolean matchPath(Set<String[]> patternSet, String str) { + boolean result; + for (String[] patternTokens: patternSet) { + if (matchPath(patternTokens, tokenizePathAsArray(str), true)) { + return true; + } + } + return false; + } + + /** + * Tests whether or not a given path matches a given pattern. + * + * If you need to call this method multiple times with the same + * pattern you should rather use TokenizedPath + * + * @see TokenizedPath + * + * @param pattern The pattern to match against. Must not be + * <code>null</code>. + * @param str The path to match, as a String. Must not be + * <code>null</code>. + * + * @return <code>true</code> if the pattern matches against the string, + * or <code>false</code> otherwise. + */ + public static boolean matchPath(String pattern, String str) { + String[] patDirs = tokenizePathAsArray(pattern); + return matchPath(patDirs, tokenizePathAsArray(str), true); + } + + /** + * Tests whether or not a given path matches a given pattern. + * + * If you need to call this method multiple times with the same + * pattern you should rather use TokenizedPattern + * + * @see TokenizedPattern + * + * @param pattern The pattern to match against. Must not be + * <code>null</code>. + * @param str The path to match, as a String. Must not be + * <code>null</code>. + * @param isCaseSensitive Whether or not matching should be performed + * case sensitively. + * + * @return <code>true</code> if the pattern matches against the string, + * or <code>false</code> otherwise. + */ + public static boolean matchPath(String pattern, String str, + boolean isCaseSensitive) { + String[] patDirs = tokenizePathAsArray(pattern); + return matchPath(patDirs, tokenizePathAsArray(str), isCaseSensitive); + } + + /** + * Core implementation of matchPath. It is isolated so that it + * can be called from TokenizedPattern. + */ + static boolean matchPath(String[] tokenizedPattern, String[] strDirs, + boolean isCaseSensitive) { + int patIdxStart = 0; + int patIdxEnd = tokenizedPattern.length - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.length - 1; + + // up to first '**' + while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { + String patDir = tokenizedPattern[patIdxStart]; + if (patDir.equals(DEEP_TREE_MATCH)) { + break; + } + if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) { + return false; + } + patIdxStart++; + strIdxStart++; + } + if (strIdxStart > strIdxEnd) { + // String is exhausted + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { + return false; + } + } + return true; + } else { + if (patIdxStart > patIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } + } + + // up to last '**' + while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { + String patDir = tokenizedPattern[patIdxEnd]; + if (patDir.equals(DEEP_TREE_MATCH)) { + break; + } + if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) { + return false; + } + patIdxEnd--; + strIdxEnd--; + } + if (strIdxStart > strIdxEnd) { + // String is exhausted + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { + return false; + } + } + return true; + } + + while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { + int patIdxTmp = -1; + for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { + if (tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == patIdxStart + 1) { + // '**/**' situation, so skip one + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - patIdxStart - 1); + int strLength = (strIdxEnd - strIdxStart + 1); + int foundIdx = -1; + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = tokenizedPattern[patIdxStart + j + 1]; + String subStr = strDirs[strIdxStart + i + j]; + if (!match(subPat, subStr, isCaseSensitive)) { + continue strLoop; + } + } + + foundIdx = strIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { + return false; + } + } + + return true; + } + + /** + * Tests whether or not a string matches against a pattern. + * The pattern may contain two special characters:<br> + * '*' means zero or more characters<br> + * '?' means one and only one character + * + * @param pattern The pattern to match against. + * Must not be <code>null</code>. + * @param str The string which must be matched against the pattern. + * Must not be <code>null</code>. + * + * @return <code>true</code> if the string matches against the pattern, + * or <code>false</code> otherwise. + */ + public static boolean match(String pattern, String str) { + return match(pattern, str, true); + } + + /** + * Tests whether or not a string matches against a pattern. + * The pattern may contain two special characters:<br> + * '*' means zero or more characters<br> + * '?' means one and only one character + * + * @param pattern The pattern to match against. + * Must not be <code>null</code>. + * @param str The string which must be matched against the pattern. + * Must not be <code>null</code>. + * @param caseSensitive Whether or not matching should be performed + * case sensitively. + * + * + * @return <code>true</code> if the string matches against the pattern, + * or <code>false</code> otherwise. + */ + public static boolean match(String pattern, String str, + boolean caseSensitive) { + char[] patArr = pattern.toCharArray(); + char[] strArr = str.toCharArray(); + int patIdxStart = 0; + int patIdxEnd = patArr.length - 1; + int strIdxStart = 0; + int strIdxEnd = strArr.length - 1; + char ch; + + boolean containsStar = false; + for (int i = 0; i < patArr.length; i++) { + if (patArr[i] == '*') { + containsStar = true; + break; + } + } + + if (!containsStar) { + // No '*'s, so we make a shortcut + if (patIdxEnd != strIdxEnd) { + return false; // Pattern and string do not have the same size + } + for (int i = 0; i <= patIdxEnd; i++) { + ch = patArr[i]; + if (ch != '?') { + if (different(caseSensitive, ch, strArr[i])) { + return false; // Character mismatch + } + } + } + return true; // String matches against pattern + } + + if (patIdxEnd == 0) { + return true; // Pattern contains only '*', which matches anything + } + + // Process characters before first star + while (true) { + ch = patArr[patIdxStart]; + if (ch == '*' || strIdxStart > strIdxEnd) { + break; + } + if (ch != '?') { + if (different(caseSensitive, ch, strArr[strIdxStart])) { + return false; // Character mismatch + } + } + patIdxStart++; + strIdxStart++; + } + if (strIdxStart > strIdxEnd) { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + return allStars(patArr, patIdxStart, patIdxEnd); + } + + // Process characters after last star + while (true) { + ch = patArr[patIdxEnd]; + if (ch == '*' || strIdxStart > strIdxEnd) { + break; + } + if (ch != '?') { + if (different(caseSensitive, ch, strArr[strIdxEnd])) { + return false; // Character mismatch + } + } + patIdxEnd--; + strIdxEnd--; + } + if (strIdxStart > strIdxEnd) { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + return allStars(patArr, patIdxStart, patIdxEnd); + } + + // process pattern between stars. padIdxStart and patIdxEnd point + // always to a '*'. + while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { + int patIdxTmp = -1; + for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { + if (patArr[i] == '*') { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == patIdxStart + 1) { + // Two stars next to each other, skip the first one. + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - patIdxStart - 1); + int strLength = (strIdxEnd - strIdxStart + 1); + int foundIdx = -1; + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + ch = patArr[patIdxStart + j + 1]; + if (ch != '?') { + if (different(caseSensitive, ch, + strArr[strIdxStart + i + j])) { + continue strLoop; + } + } + } + + foundIdx = strIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + // All characters in the string are used. Check if only '*'s are left + // in the pattern. If so, we succeeded. Otherwise failure. + return allStars(patArr, patIdxStart, patIdxEnd); + } + + private static boolean allStars(char[] chars, int start, int end) { + for (int i = start; i <= end; ++i) { + if (chars[i] != '*') { + return false; + } + } + return true; + } + + private static boolean different( + boolean caseSensitive, char ch, char other) { + return caseSensitive + ? ch != other + : Character.toUpperCase(ch) != Character.toUpperCase(other); + } + + /** + * Breaks a path up into a Vector of path elements, tokenizing on + * <code>File.separator</code>. + * + * @param path Path to tokenize. Must not be <code>null</code>. + * + * @return a String array of path elements from the tokenized path + */ + public static String[] tokenizePathAsArray(String path) { + String root = null; + if (isAbsolutePath(path)) { + String[] s = dissect(path); + root = s[0]; + path = s[1]; + } + char sep = File.separatorChar; + int start = 0; + int len = path.length(); + int count = 0; + for (int pos = 0; pos < len; pos++) { + if (path.charAt(pos) == sep) { + if (pos != start) { + count++; + } + start = pos + 1; + } + } + if (len != start) { + count++; + } + String[] l = new String[count + ((root == null) ? 0 : 1)]; + + if (root != null) { + l[0] = root; + count = 1; + } else { + count = 0; + } + start = 0; + for (int pos = 0; pos < len; pos++) { + if (path.charAt(pos) == sep) { + if (pos != start) { + String tok = path.substring(start, pos); + l[count++] = tok; + } + start = pos + 1; + } + } + if (len != start) { + String tok = path.substring(start); + l[count/*++*/] = tok; + } + return l; + } + + /** + * Dissect the specified absolute path. + * @param path the path to dissect (must be absolute). + * @return String[] {root, remaining path}. + * @throws java.lang.NullPointerException if path is null. + */ + private static String[] dissect(String path) { + char sep = File.separatorChar; + path = path.replace('/', sep).replace('\\', sep); + + String root = null; + int colon = path.indexOf(':'); + if (colon > 0 && (ON_DOS || ON_NETWARE)) { + + int next = colon + 1; + root = path.substring(0, next); + char[] ca = path.toCharArray(); + root += sep; + //remove the initial separator; the root has it. + next = (ca[next] == sep) ? next + 1 : next; + + StringBuffer sbPath = new StringBuffer(); + // Eliminate consecutive slashes after the drive spec: + for (int i = next; i < ca.length; i++) { + if (ca[i] != sep || ca[i - 1] != sep) { + sbPath.append(ca[i]); + } + } + path = sbPath.toString(); + } else if (path.length() > 1 && path.charAt(1) == sep) { + // UNC drive + int nextsep = path.indexOf(sep, 2); + nextsep = path.indexOf(sep, nextsep + 1); + root = (nextsep > 2) ? path.substring(0, nextsep + 1) : path; + path = path.substring(root.length()); + } else { + root = File.separator; + path = path.substring(1); + } + return new String[] {root, path}; + } + + /** + * Verifies that the specified filename represents an absolute path. + * Differs from new java.io.File("filename").isAbsolute() in that a path + * beginning with a double file separator--signifying a Windows UNC--must + * at minimum match "\\a\b" to be considered an absolute path. + * @param filename the filename to be checked. + * @return true if the filename represents an absolute path. + * @throws java.lang.NullPointerException if filename is null. + */ + private static boolean isAbsolutePath(String filename) { + int len = filename.length(); + if (len == 0) { + return false; + } + char sep = File.separatorChar; + filename = filename.replace('/', sep).replace('\\', sep); + char c = filename.charAt(0); + if (!(ON_DOS || ON_NETWARE)) { + return (c == sep); + } + if (c == sep) { + // CheckStyle:MagicNumber OFF + if (!(ON_DOS && len > 4 && filename.charAt(1) == sep)) { + return false; + } + // CheckStyle:MagicNumber ON + int nextsep = filename.indexOf(sep, 2); + return nextsep > 2 && nextsep + 1 < len; + } + int colon = filename.indexOf(':'); + return (Character.isLetter(c) && colon == 1 + && filename.length() > 2 && filename.charAt(2) == sep) + || (ON_NETWARE && colon > 0); + } + + /** + * Determines if our OS is Netware. + * + * @return true if we run on Netware + */ + private static boolean isNetware() { + return OS_NAME.indexOf("netware") > -1; + } + + /** + * Determines if our OS is DOS. + * + * @return true if we run on DOS + */ + private static boolean isDos() { + return PATH_SEP.equals(";") && !isNetware(); + } +} Propchange: tomcat/trunk/java/org/apache/tomcat/util/file/Matcher.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: tomcat/trunk/java/org/apache/tomcat/util/file/Matcher.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Modified: tomcat/trunk/java/org/apache/tomcat/util/scan/StandardJarScanner.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/scan/StandardJarScanner.java?rev=981967&r1=981966&r2=981967&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/util/scan/StandardJarScanner.java (original) +++ tomcat/trunk/java/org/apache/tomcat/util/scan/StandardJarScanner.java Tue Aug 3 17:09:55 2010 @@ -35,6 +35,7 @@ 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.file.Matcher; import org.apache.tomcat.util.res.StringManager; /** @@ -134,6 +135,13 @@ public class StandardJarScanner implemen } else { ignoredJars = jarsToSkip; } + Set<String[]> ignoredJarsTokens = new HashSet<String[]>(); + for (String pattern: ignoredJars) { + if (log.isDebugEnabled()) { + log.debug("Tokenizing " + pattern); + } + ignoredJarsTokens.add(Matcher.tokenizePathAsArray(pattern)); + } // Scan WEB-INF/lib Set<String> dirList = context.getResourcePaths(Constants.WEB_INF_LIB); @@ -141,10 +149,16 @@ public class StandardJarScanner implemen Iterator<String> it = dirList.iterator(); while (it.hasNext()) { String path = it.next(); + if (log.isDebugEnabled()) { + log.debug("Matching path '" + path + "'"); + } if (path.endsWith(Constants.JAR_EXT) && - !ignoredJars.contains( - path.substring(path.lastIndexOf('/')+1))) { + !Matcher.matchPath(ignoredJarsTokens, + path.substring(path.lastIndexOf('/')+1))) { // Need to scan this JAR + if (log.isDebugEnabled()) { + log.debug("Scanning jar " + path); + } URL url = null; try { url = context.getResource(path); @@ -152,6 +166,10 @@ public class StandardJarScanner implemen } catch (IOException e) { log.warn(sm.getString("jarScan.webinflibFail", url), e); } + } else { + if (log.isDebugEnabled()) { + log.debug("Didn't scan jar " + path); + } } } } @@ -174,15 +192,26 @@ public class StandardJarScanner implemen // Skip JARs with known not to be interesting and JARs // in WEB-INF/lib we have already scanned - if (!(ignoredJars.contains(jarName) || + if (log.isDebugEnabled()) { + log.debug("Matching jar '" + jarName + "'"); + } + if (jarName != null && + !(Matcher.matchPath(ignoredJarsTokens, jarName) || urls[i].toString().contains( Constants.WEB_INF_LIB + jarName))) { + if (log.isDebugEnabled()) { + log.debug("Scanning jar " + jarName); + } try { process(callback, urls[i]); } catch (IOException ioe) { log.warn(sm.getString( "jarScan.classloaderFail",urls[i]), ioe); } + } else { + if (log.isDebugEnabled()) { + log.debug("Didn't scan jar " + jarName); + } } } } Modified: tomcat/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=981967&r1=981966&r2=981967&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/changelog.xml (original) +++ tomcat/trunk/webapps/docs/changelog.xml Tue Aug 3 17:09:55 2010 @@ -42,6 +42,10 @@ Fix repgrssion that prevented running with a security manager enabled. (markt) </fix> + <update> + Allow glob patterns in the <code>jarsToSkip</code> configuration and add + some debug logging to the jar scanner. (rjung) + </update> </changelog> </subsection> <subsection name="Web applications"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org