This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tomcat-jakartaee-migration.git
commit e963951d3f3a7ac0785815dfe44bd008425ccd43 Author: Mark Thomas <ma...@apache.org> AuthorDate: Tue Feb 9 17:00:03 2021 +0000 Fix #3 Add support for excluding files from conversion --- CHANGES.md | 3 +- .../org/apache/tomcat/jakartaee/GlobMatcher.java | 243 +++++++++++++++++++++ .../org/apache/tomcat/jakartaee/Migration.java | 65 +++++- .../org/apache/tomcat/jakartaee/MigrationCLI.java | 7 +- .../org/apache/tomcat/jakartaee/MigrationTask.java | 17 ++ .../tomcat/jakartaee/LocalStrings.properties | 12 +- 6 files changed, 338 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9b23746..4c6b15d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,7 +22,8 @@ - Ensure that all the Manifest attributes are processed during the migration process. (markt) - Include `.properties` and `.json` files in the conversion process. (markt) - Replace `-verbose` with `-logLevel=` to provide more control over logging level. (markt) -- Fix [#13](https://github.com/apache/tomcat-jakartaee-migration/issues/13). Refactor mapping of log messages to log levels. (markt) +- Fix [#13](https://github.com/apache/tomcat-jakartaee-migration/issues/13). Refactor mapping of log messages to log levels. (markt) +- Fix [#3](https://github.com/apache/tomcat-jakartaee-migration/issues/3). Add support for excluding files from conversion. (markt) ## 0.1.0 diff --git a/src/main/java/org/apache/tomcat/jakartaee/GlobMatcher.java b/src/main/java/org/apache/tomcat/jakartaee/GlobMatcher.java new file mode 100644 index 0000000..33dc338 --- /dev/null +++ b/src/main/java/org/apache/tomcat/jakartaee/GlobMatcher.java @@ -0,0 +1,243 @@ +/* + * 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.jakartaee; + +import java.util.Set; + +/** + * <p>This is a utility class to match file globs. + * The class has been derived from + * org.apache.tools.ant.types.selectors.SelectorUtils. + * </p> + * <p>All methods are static.</p> + */ +public final class GlobMatcher { + + /** + * Tests whether or not a given file name matches any file name pattern in + * the given set. The match is performed case-sensitively. + * + * @see #match(String, String, boolean) + * + * @param patternSet The pattern set to match against. Must not be + * <code>null</code>. + * @param fileName The file name to match, as a String. Must not be + * <code>null</code>. It must be just a file name, without + * a path. + * @param caseSensitive Whether or not matching should be performed + * case sensitively. + * + * @return <code>true</code> if any pattern in the set matches against the + * file name, or <code>false</code> otherwise. + */ + public static boolean matchName(Set<String> patternSet, String fileName, boolean caseSensitive) { + char[] fileNameArray = fileName.toCharArray(); + for (String pattern: patternSet) { + if (match(pattern, fileNameArray, caseSensitive)) { + return true; + } + } + return false; + } + + + /** + * 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) { + + return match(pattern, str.toCharArray(), caseSensitive); + } + + + /** + * 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 strArr The character array 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. + */ + private static boolean match(String pattern, char[] strArr, + boolean caseSensitive) { + char[] patArr = pattern.toCharArray(); + int patIdxStart = 0; + int patIdxEnd = patArr.length - 1; + int strIdxStart = 0; + int strIdxEnd = strArr.length - 1; + char ch; + + boolean containsStar = false; + for (char c : patArr) { + if (c == '*') { + 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); + } + +} diff --git a/src/main/java/org/apache/tomcat/jakartaee/Migration.java b/src/main/java/org/apache/tomcat/jakartaee/Migration.java index 153345a..e8c39b6 100644 --- a/src/main/java/org/apache/tomcat/jakartaee/Migration.java +++ b/src/main/java/org/apache/tomcat/jakartaee/Migration.java @@ -26,7 +26,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.jar.JarEntry; import java.util.logging.Level; @@ -49,11 +51,46 @@ public class Migration { private static final Logger logger = Logger.getLogger(Migration.class.getCanonicalName()); private static final StringManager sm = StringManager.getManager(Migration.class); + private static final Set<String> DEFAULT_EXCLUDES = new HashSet<>(); + + static { + // Apache Commons + DEFAULT_EXCLUDES.add("commons-codec-*.jar"); + DEFAULT_EXCLUDES.add("commons-lang-*.jar"); + // Apache HTTP Components + DEFAULT_EXCLUDES.add("httpclient-*.jar"); + DEFAULT_EXCLUDES.add("httpcore-*.jar"); + // ASM + DEFAULT_EXCLUDES.add("asm-*.jar"); + // AspectJ + DEFAULT_EXCLUDES.add("aspectjweaver-*.jar"); + // Bouncy Castle JCE provider + DEFAULT_EXCLUDES.add("bcprov*.jar"); + DEFAULT_EXCLUDES.add("bcpkix*.jar"); + // Hystrix + DEFAULT_EXCLUDES.add("hystrix-core-*.jar"); + DEFAULT_EXCLUDES.add("hystrix-serialization-*.jar"); + // Jackson + DEFAULT_EXCLUDES.add("jackson-annotations-*.jar"); + DEFAULT_EXCLUDES.add("jackson-core-*.jar"); + DEFAULT_EXCLUDES.add("jackson-module-afterburner-*.jar"); + // Logging + DEFAULT_EXCLUDES.add("jul-to-slf4j-*.jar"); + DEFAULT_EXCLUDES.add("log4j-to-slf4j-*.jar"); + DEFAULT_EXCLUDES.add("slf4j-api-*.jar"); + // Spring + DEFAULT_EXCLUDES.add("spring-aop-*.jar"); + DEFAULT_EXCLUDES.add("spring-expression-*.jar"); + DEFAULT_EXCLUDES.add("spring-security-crypto-*.jar"); + DEFAULT_EXCLUDES.add("spring-security-rsa-*.jar"); + } + private EESpecProfile profile = EESpecProfile.TOMCAT; private boolean zipInMemory; private File source; private File destination; private final List<Converter> converters; + private final Set<String> excludes = new HashSet<>(); public Migration() { // Initialise the converters @@ -89,6 +126,10 @@ public class Migration { this.zipInMemory = zipInMemory; } + public void addExclude(String exclude) { + this.excludes.add(exclude); + } + public void setSource(File source) { if (!source.canRead()) { throw new IllegalArgumentException(sm.getString("migration.cannotReadSource", @@ -181,7 +222,7 @@ public class Migration { String destName = profile.convert(sourceName); JarEntry destEntry = new JarEntry(destName); zipOs.putNextEntry(destEntry); - migrateStream(destEntry.getName(), zipIs, zipOs); + migrateStream(sourceName, zipIs, zipOs); } } catch (ZipException ze) { logger.log(Level.SEVERE, sm.getString("migration.archiveFailed", name), ze); @@ -231,7 +272,10 @@ public class Migration { private void migrateStream(String name, InputStream src, OutputStream dest) throws IOException { - if (isArchive(name)) { + if (isExcluded(name)) { + Util.copy(src, dest); + logger.log(Level.INFO, sm.getString("migration.skip", name)); + } else if (isArchive(name)) { if (zipInMemory) { logger.log(Level.INFO, sm.getString("migration.archive.memory", name)); migrateArchiveInMemory(src, dest); @@ -252,11 +296,26 @@ public class Migration { } - private static boolean isArchive(String fileName) { + private boolean isArchive(String fileName) { return fileName.endsWith(".jar") || fileName.endsWith(".war") || fileName.endsWith(".zip"); } + private boolean isExcluded(String name) { + File f = new File(name); + String filename = f.getName(); + + if (GlobMatcher.matchName(DEFAULT_EXCLUDES, filename, true)) { + return true; + } + + if (GlobMatcher.matchName(excludes, filename, true)) { + return true; + } + + return false; + } + private static class RenamableZipArchiveEntry extends ZipArchiveEntry { public RenamableZipArchiveEntry(ZipArchiveEntry entry) throws ZipException { diff --git a/src/main/java/org/apache/tomcat/jakartaee/MigrationCLI.java b/src/main/java/org/apache/tomcat/jakartaee/MigrationCLI.java index 64af9b9..36e9dc5 100644 --- a/src/main/java/org/apache/tomcat/jakartaee/MigrationCLI.java +++ b/src/main/java/org/apache/tomcat/jakartaee/MigrationCLI.java @@ -30,6 +30,7 @@ public class MigrationCLI { private static final StringManager sm = StringManager.getManager(MigrationCLI.class); + private static final String EXCLUDE_ARG = "-exclude="; private static final String LOGLEVEL_ARG = "-logLevel="; private static final String PROFILE_ARG = "-profile="; // Will be removed for 1.0.0 @@ -51,7 +52,11 @@ public class MigrationCLI { Iterator<String> iter = arguments.iterator(); while (iter.hasNext()) { String argument = iter.next(); - if (argument.startsWith(LOGLEVEL_ARG)) { + if (argument.startsWith(EXCLUDE_ARG)) { + iter.remove(); + String exclude = argument.substring(EXCLUDE_ARG.length()); + migration.addExclude(exclude); + } else if (argument.startsWith(LOGLEVEL_ARG)) { iter.remove(); String logLevelName = argument.substring(LOGLEVEL_ARG.length()); Level level = null; diff --git a/src/main/java/org/apache/tomcat/jakartaee/MigrationTask.java b/src/main/java/org/apache/tomcat/jakartaee/MigrationTask.java index c19599e..39f31dd 100644 --- a/src/main/java/org/apache/tomcat/jakartaee/MigrationTask.java +++ b/src/main/java/org/apache/tomcat/jakartaee/MigrationTask.java @@ -33,6 +33,7 @@ public class MigrationTask extends Task { private File dest; private String profile = EESpecProfile.TOMCAT.toString(); private boolean zipInMemory = false; + private String excludes; public void setSrc(File src) { this.src = src; @@ -50,6 +51,16 @@ public class MigrationTask extends Task { this.zipInMemory = zipInMemory; } + /** + * Set exclusion patterns. + * + * @param excludes Comma separated, case sensitive list of glob patterns + * for files to exclude + */ + public void setExcludes(String excludes) { + this.excludes = excludes; + } + @Override public void execute() throws BuildException { // redirect the log messages to Ant @@ -73,6 +84,12 @@ public class MigrationTask extends Task { migration.setDestination(dest); migration.setEESpecProfile(profile); migration.setZipInMemory(zipInMemory); + if (this.excludes != null) { + String[] excludes= this.excludes.split(","); + for (String exclude : excludes) { + migration.addExclude(exclude); + } + } try { migration.execute(); diff --git a/src/main/resources/org/apache/tomcat/jakartaee/LocalStrings.properties b/src/main/resources/org/apache/tomcat/jakartaee/LocalStrings.properties index 5515874..655bc10 100644 --- a/src/main/resources/org/apache/tomcat/jakartaee/LocalStrings.properties +++ b/src/main/resources/org/apache/tomcat/jakartaee/LocalStrings.properties @@ -16,20 +16,24 @@ classConverter.converted=Migrated class [{0}] classConverter.noConversion=No conversion necessary for [{0}] -migration.archive.complete=Migration of archive [{0}] is complete -migration.archive.memory=Migrating archive [{0}] using in memory copy -migration.archive.stream=Migrating archive [{0}] using streaming +migration.archive.complete=Migration finished for archive [{0}] +migration.archive.memory=Migration starting for archive [{0}] using in memory copy +migration.skip=Migration skipped for archive [{0}] because it is excluded (the archive was copied unchanged) +migration.archive.stream=Migration starting for archive [{0}] using streaming migration.archiveFailed=Failed to migrate archive [{0}]. Using the "-zipInMemory" option may help. migration.cannotReadSource=Cannot read source location [{0}] migration.done=Migration completed successfully in [{0}] milliseconds -migration.entry=Migrating Jar entry [{0}] migration.error=Error performing migration migration.execute=Performing migration from source [{0}] to destination [{1}] with Jakarta EE specification profile [{2}] migration.mkdirError=Error creating destination directory [{0}] migration.removeSignature=Remove cryptographic signature for [{0}] +migration.skip=Migration skipped for archive [{0}] because it is excluded (the archive was copied unchanged) migration.skipSignatureFile=Drop cryptographic signature file [{0}] migration.usage=Usage: Migration [options] <source> <destination>\n\ where options includes:\n\ +\ -exclude=<glob pattern to exclude>\n\ +\ This option may be used multiple times. Wild cards '*'\n\ +\ and '?' are supported. Matching is case sensitive.\n\ \ -logLevel=<name of java.util.logging.level enum value>\n\ \ Useful values are INFO (default), FINE or FINEST\n\ \ -profile=<profile name>\n\ --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org