Move FileID from deploy to util

+ Move static methods from other
  places to FileID or URIUtil
  - MultiReleaseJarFile
  - Resource
  - MetaInfConfiguration
+ Improve testing of URIUtil and FileID
This commit is contained in:
Joakim Erdfelt 2022-07-29 14:07:54 -05:00
parent 3006994b14
commit dd7dda2c53
No known key found for this signature in database
GPG Key ID: 2D0E1FB8FE4B68B4
37 changed files with 1184 additions and 947 deletions

View File

@ -20,9 +20,9 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.eclipse.jetty.deploy.util.FileID;
import org.eclipse.jetty.ee.Deployable;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.FileID;
/**
* The information about an App that is managed by the {@link DeploymentManager}.

View File

@ -30,10 +30,10 @@ import java.util.function.Supplier;
import java.util.stream.Stream;
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.util.FileID;
import org.eclipse.jetty.ee.Deployable;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;

View File

@ -29,8 +29,8 @@ import java.util.stream.Collectors;
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppProvider;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.util.FileID;
import org.eclipse.jetty.ee.Deployable;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;

View File

@ -1,89 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.deploy.util;
import java.io.File;
import java.nio.file.Path;
import java.util.Locale;
/**
* Simple, yet surprisingly common utility methods for identifying various file types commonly seen and worked with in a
* deployment scenario.
*/
public class FileID
{
/**
* Is the path a Web Archive File (not directory)
*
* @param file the path to test.
* @return True if a .war or .jar file.
*/
public static boolean isWebArchive(File file)
{
return isWebArchive(file.getName());
}
/**
* Is the path a Web Archive File (not directory)
*
* @param path the path to test.
* @return True if a .war or .jar file.
*/
public static boolean isWebArchive(Path path)
{
return isWebArchive(path.getFileName().toString());
}
/**
* Is the filename a WAR file.
*
* @param filename the filename to test.
* @return True if a .war or .jar file.
*/
public static boolean isWebArchive(String filename)
{
String name = filename.toLowerCase(Locale.ENGLISH);
return (name.endsWith(".war"));
}
public static boolean isXml(File path)
{
return isXml(path.getName());
}
public static boolean isXml(Path path)
{
return isXml(path.getFileName().toString());
}
public static boolean isXml(String filename)
{
return filename.toLowerCase(Locale.ENGLISH).endsWith(".xml");
}
/**
* Retrieve the basename of a path. This is the name of the
* last segment of the path, with any dot suffix (e.g. ".war") removed
* @param path The string path
* @return The last segment of the path without any dot suffix
*/
public static String getBasename(Path path)
{
String basename = path.getFileName().toString();
int dot = basename.lastIndexOf('.');
if (dot >= 0)
basename = basename.substring(0, dot);
return basename;
}
}

View File

@ -1,18 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
/**
* Jetty Deploy : Utilities
*/
package org.eclipse.jetty.deploy.util;

View File

@ -16,9 +16,9 @@ package org.eclipse.jetty.deploy;
import java.io.File;
import java.nio.file.Path;
import org.eclipse.jetty.deploy.util.FileID;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.Environment;
@ -60,9 +60,9 @@ public class MockAppProvider extends AbstractLifeCycle implements AppProvider
String name = app.getPath().toString();
name = name.substring(name.lastIndexOf("-") + 1);
File war = new File(webappsDir, name);
Path war = new File(webappsDir, name).toPath();
String path = war.getName();
String path = war.toString();
if (FileID.isWebArchive(war))
{

View File

@ -27,6 +27,7 @@ import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Disabled;
@ -69,7 +70,7 @@ public class RangeWriterTest
{
Path exampleJar = MavenTestingUtils.getTestResourcePathFile("example.jar");
URI jarFileUri = Resource.toJarFileUri(exampleJar.toUri());
URI jarFileUri = URIUtil.toJarFileUri(exampleJar.toUri());
// close prior one (if it exists)
IO.close(zipfs);

View File

@ -0,0 +1,382 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
/**
* Simple, yet surprisingly common utility methods for identifying various file types commonly seen and worked with in a
* deployment scenario.
*/
public class FileID
{
/**
* Does the provided path have a directory segment with
* the configured name.
*
* @param path the path to search
* @param directoryName the directory name to look for (case insensitive lookup)
* @return true if the directory name exists in path, false if otherwise
*/
public static boolean containsDirectory(Path path, String directoryName)
{
if (path == null)
return false;
int segmentCount = path.getNameCount();
for (int i = segmentCount - 1; i > 0; i--)
{
Path segment = path.getName(i);
if (segment.getFileName().toString().equalsIgnoreCase(directoryName))
return true;
}
return false;
}
/**
* Retrieve the basename of a path. This is the name of the
* last segment of the path, with any dot suffix (e.g. ".war") removed
*
* @param path The string path
* @return The last segment of the path without any dot suffix
*/
public static String getBasename(Path path)
{
String basename = path.getFileName().toString();
int dot = basename.lastIndexOf('.');
if (dot >= 0)
basename = basename.substring(0, dot);
return basename;
}
/**
* Retrieve the extension of a file path (not a directory).
* This is the name of the last segment of the file path with a substring
* for the extension (if any), including the dot, lower-cased.
*
* @param path The string path
* @return The last segment extension, or null if not a file, or null if there is no extension present
*/
public static String getExtension(Path path)
{
if (path == null)
return null; // no path
if (!Files.isRegularFile(path))
return null; // not a file
return getExtension(path.getFileName().toString());
}
/**
* Retrieve the extension of a file path (not a directory).
* This is the extension of filename of the last segment of the file path with a substring
* for the extension (if any), including the dot, lower-cased.
*
* @param filename The string path
* @return The last segment extension, or null if not a file, or null if there is no extension present
*/
public static String getExtension(String filename)
{
if (filename == null)
return null; // no filename
if (filename.endsWith("/") || filename.endsWith("\\"))
return null; // not a filename
int lastSlash = filename.lastIndexOf(File.separator);
if (lastSlash >= 0)
filename = filename.substring(lastSlash + 1);
int lastDot = filename.lastIndexOf('.');
if (lastDot < 0)
return null; // no extension
return filename.substring(lastDot).toLowerCase(Locale.ENGLISH);
}
/**
* Test if Path is any supported Java Archive type (ends in {@code .jar}, {@code .war}, or {@code .zip}).
*
* @param path the path to test
* @return true if path is a file, and an extension of {@code .jar}, {@code .war}, or {@code .zip}
* @see #getExtension(Path)
*/
public static boolean isArchive(Path path)
{
String ext = getExtension(path);
if (ext == null)
return false;
return (ext.equals(".jar") || ext.equals(".war") || ext.equals(".zip"));
}
/**
* Test if filename is any supported Java Archive type (ends in {@code .jar}, {@code .war}, or {@code .zip}).
*
* @param filename the filename to test
* @return true if path is a file and name ends with {@code .jar}, {@code .war}, or {@code .zip}
* @see #getExtension(String)
*/
public static boolean isArchive(String filename)
{
String ext = getExtension(filename);
if (ext == null)
return false;
return (ext.equals(".jar") || ext.equals(".war") || ext.equals(".zip"));
}
/**
* Test if URI is any supported Java Archive type.
*
* @param uri the URI to test
* @return true if the URI has a path that seems to point to a ({@code .jar}, {@code .war}, or {@code .zip}).
* @see #isArchive(String)
*/
public static boolean isArchive(URI uri)
{
if (uri == null)
return false;
if (uri.getScheme() == null)
return false;
if (uri.getScheme().equalsIgnoreCase("jar"))
{
URI sspUri = URI.create(uri.getRawSchemeSpecificPart());
if (!sspUri.getScheme().equalsIgnoreCase("file"))
{
return false; // not a `jar:file:` based URI
}
String path = sspUri.getPath();
int jarEnd = path.indexOf("!/");
if (jarEnd >= 0)
{
return isArchive(path.substring(0, jarEnd));
}
return isArchive(path);
}
String path = uri.getPath();
// look for `!/` split
int jarEnd = path.indexOf("!/");
if (jarEnd >= 0)
{
return isArchive(path.substring(0, jarEnd));
}
return isArchive(path);
}
/**
* Predicate to select all class files
*
* @param path the path to test
* @return true if the filename ends with {@code .class}
*/
public static boolean isClassFile(Path path)
{
String filename = path.getFileName().toString();
// has to end in ".class"
if (!filename.toLowerCase(Locale.ENGLISH).endsWith(".class"))
return false;
// is it a valid class filename?
int start = 0;
int end = filename.length() - 6; // drop ".class"
if (end <= start) // if the filename is only ".class"
return false;
// Test first character
if (!Character.isJavaIdentifierStart(filename.charAt(0)))
return false;
// Test rest
for (int i = start + 1; i < end; i++)
{
if (!Character.isJavaIdentifierPart(filename.codePointAt(i)))
{
// not a java identifier
return false;
}
}
return true;
}
/**
* Predicate useful for {@code Stream<Path>} to exclude hidden paths following
* filesystem rules for hidden directories and files.
*
* @param base the base path to search from (anything above this path is not evaluated)
* @param path the path to evaluate
* @return true if hidden by FileSystem rules, false if not
* @see Files#isHidden(Path)
*/
public static boolean isHidden(Path base, Path path)
{
// Work with the path in relative form, from the base onwards to the path
Path relative = base.relativize(path);
int count = relative.getNameCount();
for (int i = 0; i < count; i++)
{
try
{
if (Files.isHidden(relative.getName(i)))
return true;
}
catch (IOException ignore)
{
// ignore, if filesystem gives us an error, we cannot make the call on hidden status
}
}
return false;
}
/**
* Is the path a Java Archive (JAR) File (not directory)
*
* @param path the path to test.
* @return True if a .jar file.
*/
public static boolean isJavaArchive(Path path)
{
return ".jar".equals(getExtension(path));
}
/**
* Is the filename a JAR file.
*
* @param filename the filename to test.
* @return True if a .jar file.
*/
public static boolean isJavaArchive(String filename)
{
return ".jar".equals(getExtension(filename));
}
/**
* Predicate to filter on {@code META-INF/versions/*} tree in walk/stream results.
*
* <p>
* This only works with a zipfs based FileSystem
* </p>
*
* @param path the path to test
* @return true if path is in {@code META-INF/versions/*} tree
*/
public static boolean isMetaInfVersions(Path path)
{
if (path.getNameCount() < 3)
return false;
Path path0 = path.getName(0);
Path path1 = path.getName(1);
Path path2 = path.getName(2);
return (path0.toString().equals("META-INF") &&
path1.toString().equals("versions") &&
path2.getFileName().toString().matches("[0-9]+"));
}
/**
* Is the path a TLD File
*
* @param path the path to test.
* @return True if a .war file.
*/
public static boolean isTldFile(Path path)
{
if (path == null)
return false;
if (path.getNameCount() < 2)
return false;
if (!".tld".equals(getExtension(path)))
return false;
return containsDirectory(path, "META-INF");
}
/**
* Is the path a Web Archive File (not directory)
*
* @param path the path to test.
* @return True if a .war file.
*/
public static boolean isWebArchive(Path path)
{
return ".war".equals(getExtension(path));
}
/**
* Is the filename a WAR file.
*
* @param filename the filename to test.
* @return True if a .war file.
*/
public static boolean isWebArchive(String filename)
{
return ".war".equals(getExtension(filename));
}
/**
* Is the Path a file that ends in XML?
*
* @param path the path to test
* @return true if a .xml, false otherwise
*/
public static boolean isXml(Path path)
{
return ".xml".equals(getExtension(path));
}
/**
* Is the Path a file that ends in XML?
*
* @param filename the filename to test
* @return true if a .xml, false otherwise
*/
public static boolean isXml(String filename)
{
return ".xml".equals(getExtension(filename));
}
/**
* Predicate to skip {@code META-INF/versions/*} tree from walk/stream results.
*
* <p>
* This only works with a zipfs based FileSystem
* </p>
*
* @param path the path to test
* @return true if not in {@code META-INF/versions/*} tree
*/
public static boolean skipMetaInfVersions(Path path)
{
return !isMetaInfVersions(path);
}
/**
* Predicate to skip {@code module-info.class} files.
*
* <p>
* This is a simple test against the last path segment using {@link Path#getFileName()}
* </p>
*
* @param path the path to test
* @return true if not a {@code module-info.class} file
*/
public static boolean skipModuleInfoClass(Path path)
{
Path filenameSegment = path.getFileName();
if (filenameSegment == null)
return true;
return !filenameSegment.toString().equalsIgnoreCase("module-info.class");
}
}

View File

@ -17,7 +17,6 @@ import java.io.Closeable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Objects;
import java.util.stream.Stream;
@ -51,8 +50,7 @@ public class MultiReleaseJarFile implements Closeable
if (!Files.isRegularFile(jarFile))
throw new IllegalArgumentException("Not a file: " + jarFile);
// TODO : use FileID.isJar() in future PR
if (!Resource.isArchive(jarFile))
if (!FileID.isJavaArchive(jarFile))
throw new IllegalArgumentException("Not a Jar: " + jarFile);
if (!Files.isReadable(jarFile))
@ -64,133 +62,6 @@ public class MultiReleaseJarFile implements Closeable
LOG.debug("mounting {}", jarResource);
}
/**
* Predicate to skip {@code module-info.class} files.
*
* <p>
* This is a simple test against the last path segment using {@link Path#getFileName()}
* </p>
*
* @param path the path to test
* @return true if not a {@code module-info.class} file
* TODO: move to FileID class in later PR
*/
public static boolean skipModuleInfoClass(Path path)
{
Path filenameSegment = path.getFileName();
if (filenameSegment == null)
return true;
return !filenameSegment.toString().equalsIgnoreCase("module-info.class");
}
/**
* Predicate to skip {@code META-INF/versions/*} tree from walk/stream results.
*
* <p>
* This only works with a zipfs based FileSystem
* </p>
*
* @param path the path to test
* @return true if not in {@code META-INF/versions/*} tree
* TODO: move to FileID class in later PR
*/
public static boolean skipMetaInfVersions(Path path)
{
return !isMetaInfVersions(path);
}
/**
* Predicate to filter on {@code META-INF/versions/*} tree in walk/stream results.
*
* <p>
* This only works with a zipfs based FileSystem
* </p>
*
* @param path the path to test
* @return true if path is in {@code META-INF/versions/*} tree
* TODO: move to FileID class in later PR
*/
public static boolean isMetaInfVersions(Path path)
{
if (path.getNameCount() < 3)
return false;
Path path0 = path.getName(0);
Path path1 = path.getName(1);
Path path2 = path.getName(2);
return (path0.toString().equals("META-INF") &&
path1.toString().equals("versions") &&
path2.getFileName().toString().matches("[0-9]+"));
}
/**
* Predicate to select all class files
*
* @param path the path to test
* @return true if the filename ends with {@code .class}
* TODO: move to FileID class in later PR
*/
public static boolean isClassFile(Path path)
{
String filename = path.getFileName().toString();
// has to end in ".class"
if (!filename.toLowerCase(Locale.ENGLISH).endsWith(".class"))
return false;
// is it a valid class filename?
int start = 0;
int end = filename.length() - 6; // drop ".class"
if (end <= start) // if the filename is only ".class"
return false;
// Test first character
if (!Character.isJavaIdentifierStart(filename.charAt(0)))
return false;
// Test rest
for (int i = start + 1; i < end; i++)
{
if (!Character.isJavaIdentifierPart(filename.codePointAt(i)))
{
if (LOG.isDebugEnabled())
LOG.debug("Not a java identifier: {}", filename);
return false;
}
}
return true;
}
/**
* Predicate useful for {@code Stream<Path>} to exclude hidden paths following
* filesystem rules for hidden directories and files.
*
* @param base the base path to search from (anything above this path is not evaluated)
* @param path the path to evaluate
* @return true if hidden by FileSystem rules, false if not
* @see Files#isHidden(Path)
* TODO: move to FileID.isHidden(Path, Path)
*/
public static boolean isHidden(Path base, Path path)
{
// Work with the path in relative form, from the base onwards to the path
Path relative = base.relativize(path);
int count = relative.getNameCount();
for (int i = 0; i < count; i++)
{
try
{
if (Files.isHidden(relative.getName(i)))
return true;
}
catch (IOException ignore)
{
// ignore, if filesystem gives us an error, we cannot make the call on hidden status
}
}
return false;
}
/**
* @return A stream of versioned entries from the jar, excluding {@code META-INF/versions} entries.
*/
@ -201,7 +72,7 @@ public class MultiReleaseJarFile implements Closeable
return Files.walk(rootPath)
// skip the entire META-INF/versions tree
.filter(MultiReleaseJarFile::skipMetaInfVersions);
.filter(FileID::skipMetaInfVersions);
}
@Override

View File

@ -13,15 +13,24 @@
package org.eclipse.jetty.util;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.stream.Stream;
import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
import org.eclipse.jetty.util.resource.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -36,8 +45,7 @@ import org.slf4j.LoggerFactory;
*
* @see UrlEncoded
*/
public class URIUtil
implements Cloneable
public final class URIUtil
{
private static final Logger LOG = LoggerFactory.getLogger(URIUtil.class);
public static final String SLASH = "/";
@ -1647,25 +1655,7 @@ public class URIUtil
return query1 + '&' + query2;
}
public static URI getJarSource(URL url)
{
return getJarSource(URI.create(url.toString()));
}
public static URI getJarSource(URI uri)
{
if (!"jar".equals(uri.getScheme()))
return uri;
// Get SSP (retaining encoded form)
String s = uri.getRawSchemeSpecificPart();
int bangSlash = s.indexOf("!/");
if (bangSlash >= 0)
s = s.substring(0, bangSlash);
return URI.create(s);
}
public static URI fixBadJavaIoFileUrl(URI uri)
public static URI correctBadFileURI(URI uri)
{
if ((uri == null) || (uri.getScheme() == null))
return uri;
@ -1692,6 +1682,142 @@ public class URIUtil
return uri;
}
/**
* Split a string of references, that may be split with {@code ,}, or {@code ;}, or {@code |} into URIs.
* <p>
* Each part of the input string could be path references (unix or windows style), or string URI references.
* </p>
* <p>
* If the result of processing the input segment is a java archive, then its resulting URI will be a mountable URI as `jar:file:...!/`.
* </p>
*
* @param str the input string of references
* @see #toJarFileUri(URI)
*/
public static List<URI> split(String str)
{
List<URI> uris = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(str, ",;|");
while (tokenizer.hasMoreTokens())
{
String reference = tokenizer.nextToken();
try
{
// Is this a glob reference?
if (reference.endsWith("/*") || reference.endsWith("\\*"))
{
String dir = reference.substring(0, reference.length() - 2);
Path pathDir = Paths.get(dir);
// Use directory
if (Files.exists(pathDir) && Files.isDirectory(pathDir))
{
// To obtain the list of entries
try (Stream<Path> listStream = Files.list(pathDir))
{
listStream
.filter(Files::isRegularFile)
.filter(FileID::isArchive)
.sorted(Comparator.naturalOrder())
.forEach(path -> uris.add(toJarFileUri(path.toUri())));
}
catch (IOException e)
{
throw new RuntimeException("Unable to process directory glob listing: " + reference, e);
}
}
}
else
{
// Simple reference
URI refUri = Resource.toURI(reference);
// Is this a Java Archive that can be mounted?
URI jarFileUri = toJarFileUri(refUri);
if (jarFileUri != null)
// add as mountable URI
uris.add(jarFileUri);
else
// add as normal URI
uris.add(refUri);
}
}
catch (Exception e)
{
LOG.warn("Invalid Resource Reference: " + reference);
throw e;
}
}
return uris;
}
/**
* Take an arbitrary URI and provide a URI that is suitable for mounting the URI as a Java FileSystem.
*
* The resulting URI will point to the {@code jar:file://foo.jar!/} said Java Archive (jar, war, or zip)
*
* @param uri the URI to mutate to a {@code jar:file:...} URI.
* @return the <code>jar:${uri_to_java_archive}!/${internal-reference}</code> URI or null if not a Java Archive.
* @see FileID#isArchive(URI)
*/
public static URI toJarFileUri(URI uri)
{
Objects.requireNonNull(uri, "URI");
String scheme = Objects.requireNonNull(uri.getScheme(), "URI scheme");
if (!FileID.isArchive(uri))
return null;
boolean hasInternalReference = uri.getRawSchemeSpecificPart().indexOf("!/") > 0;
if (scheme.equalsIgnoreCase("jar"))
{
if (uri.getRawSchemeSpecificPart().startsWith("file:"))
{
// Looking good as a jar:file: URI
if (hasInternalReference)
return uri; // is all good, no changes needed.
else
// add the internal reference indicator to the root of the archive
return URI.create(uri.toASCIIString() + "!/");
}
}
else if (scheme.equalsIgnoreCase("file"))
{
String rawUri = uri.toASCIIString();
if (hasInternalReference)
return URI.create("jar:" + rawUri);
else
return URI.create("jar:" + rawUri + "!/");
}
// shouldn't be possible to reach this point
throw new IllegalArgumentException("Cannot make %s into `jar:file:` URI".formatted(uri));
}
/**
* Unwrap a URI to expose its container path reference.
*
* Take out the container archive name URI from a {@code jar:file:${container-name}!/} URI.
*
* @param uri the input URI
* @return the container String if a {@code jar} scheme, or just the URI untouched.
*/
public static URI unwrapContainer(URI uri)
{
Objects.requireNonNull(uri);
String scheme = uri.getScheme();
if ((scheme == null) || !scheme.equalsIgnoreCase("jar"))
return uri;
String spec = uri.getRawSchemeSpecificPart();
int sep = spec.indexOf("!/");
if (sep != -1)
spec = spec.substring(0, sep);
return URI.create(spec);
}
/**
* Take a URI and add a deep reference {@code jar:file://foo.jar!/suffix}, replacing
* any existing deep reference on the input URI.
@ -1740,7 +1866,9 @@ public class URIUtil
URL[] urls = urlClassLoader.getURLs();
return Stream.of(urls)
.filter(Objects::nonNull)
.map(URIUtil::getJarSource)
.map(URIUtil::fixBadJavaIoFileUrl);
.map(URL::toString)
.map(URI::create)
.map(URIUtil::unwrapContainer)
.map(URIUtil::correctBadFileURI);
}
}

View File

@ -31,6 +31,7 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
@ -226,7 +227,7 @@ public class FileSystemPool implements Dumpable
private Bucket(URI fsUri, FileSystem fileSystem, Resource.Mount mount)
{
URI containerUri = Resource.unwrapContainer(fsUri);
URI containerUri = URIUtil.unwrapContainer(fsUri);
Path path = Paths.get(containerUri);
long size = -1L;

View File

@ -18,6 +18,8 @@ import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import org.eclipse.jetty.util.URIUtil;
/**
* Java NIO Path Resource with file system pooling. {@link FileSystem} implementations that must be closed
* must use this class, for instance the one handling the `jar` scheme.
@ -29,7 +31,7 @@ public class MountedPathResource extends PathResource
MountedPathResource(URI uri) throws IOException
{
super(uri, true);
containerUri = unwrapContainer(getURI());
containerUri = URIUtil.unwrapContainer(getURI());
}
@Override

View File

@ -36,17 +36,14 @@ import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.Loader;
@ -144,7 +141,7 @@ public abstract class Resource implements ResourceFactory
String scheme = uri.getScheme();
if (scheme == null)
return null;
if (!isArchive(uri))
if (!FileID.isArchive(uri))
return null;
try
{
@ -171,7 +168,7 @@ public abstract class Resource implements ResourceFactory
*/
public static Resource.Mount mount(URI uri) throws IOException
{
if (!isArchive(uri))
if (!FileID.isArchive(uri))
throw new IllegalArgumentException("URI is not a Java Archive: " + uri);
if (!uri.getScheme().equalsIgnoreCase("jar"))
throw new IllegalArgumentException("not an allowed URI: " + uri);
@ -186,12 +183,12 @@ public abstract class Resource implements ResourceFactory
*/
public static Resource.Mount mountJar(Path path) throws IOException
{
if (!isArchive(path))
if (!FileID.isArchive(path))
throw new IllegalArgumentException("Path is not a Java Archive: " + path);
URI pathUri = path.toUri();
if (!pathUri.getScheme().equalsIgnoreCase("file"))
throw new IllegalArgumentException("Not an allowed path: " + path);
URI jarUri = toJarFileUri(pathUri);
URI jarUri = URIUtil.toJarFileUri(pathUri);
if (jarUri == null)
throw new IllegalArgumentException("Not a mountable archive: " + path);
return FileSystemPool.INSTANCE.mount(jarUri);
@ -225,136 +222,6 @@ public abstract class Resource implements ResourceFactory
return new ResourceCollection(List.of(resources));
}
/**
* Test if Path is a Java Archive (ends in {@code .jar}, {@code .war}, or {@code .zip}).
*
* @param path the path to test
* @return true if path is a {@link Files#isRegularFile(Path, LinkOption...)} and name ends with {@code .jar}, {@code .war}, or {@code .zip}
*/
public static boolean isArchive(Path path)
{
if (path == null)
return false;
if (!Files.isRegularFile(path))
return false;
String filename = path.getFileName().toString().toLowerCase(Locale.ENGLISH);
return (filename.endsWith(".jar") || filename.endsWith(".war") || filename.endsWith(".zip"));
}
/**
* Test if URI is a Java Archive. (ends with {@code .jar}, {@code .war}, or {@code .zip}).
*
* @param uri the URI to test
* @return true if the URI has a path that seems to point to a ({@code .jar}, {@code .war}, or {@code .zip}).
*/
public static boolean isArchive(URI uri)
{
if (uri == null)
return false;
if (uri.getScheme() == null)
return false;
String path = uri.getPath();
int idxEnd = path == null ? -1 : path.length();
if (uri.getScheme().equalsIgnoreCase("jar"))
{
String ssp = uri.getRawSchemeSpecificPart();
path = URI.create(ssp).getPath();
idxEnd = path.length();
// look for `!/` split
int jarEnd = path.indexOf("!/");
if (jarEnd >= 0)
idxEnd = jarEnd;
}
if (path == null)
return false;
int idxLastSlash = path.lastIndexOf('/', idxEnd);
if (idxLastSlash < 0)
return false; // no last slash, can't possibly be a valid jar/war/zip
// look for filename suffix
int idxSuffix = path.lastIndexOf('.', idxEnd);
if (idxSuffix < 0)
return false; // no suffix found, can't possibly be a jar/war/zip
if (idxSuffix < idxLastSlash)
return false; // last dot is before last slash, eg ("/path.to/something")
String suffix = path.substring(idxSuffix, idxEnd).toLowerCase(Locale.ENGLISH);
return suffix.equals(".jar") || suffix.equals(".war") || suffix.equals(".zip");
}
/**
* Take an arbitrary URI and provide a URI that is suitable for mounting the URI as a Java FileSystem.
*
* The resulting URI will point to the {@code jar:file://foo.jar!/} said Java Archive (jar, war, or zip)
*
* @param uri the URI to mutate to a {@code jar:file:...} URI.
* @return the <code>jar:${uri_to_java_archive}!/${internal-reference}</code> URI or null if not a Java Archive.
* @see #isArchive(URI)
*/
public static URI toJarFileUri(URI uri)
{
Objects.requireNonNull(uri, "URI");
String scheme = Objects.requireNonNull(uri.getScheme(), "URI scheme");
if (!isArchive(uri))
return null;
boolean hasInternalReference = uri.getRawSchemeSpecificPart().indexOf("!/") > 0;
if (scheme.equalsIgnoreCase("jar"))
{
if (uri.getRawSchemeSpecificPart().startsWith("file:"))
{
// Looking good as a jar:file: URI
if (hasInternalReference)
return uri; // is all good, no changes needed.
else
// add the internal reference indicator to the root of the archive
return URI.create(uri.toASCIIString() + "!/");
}
}
else if (scheme.equalsIgnoreCase("file"))
{
String rawUri = uri.toASCIIString();
if (hasInternalReference)
return URI.create("jar:" + rawUri);
else
return URI.create("jar:" + rawUri + "!/");
}
// shouldn't be possible to reach this point
throw new IllegalArgumentException("Cannot make %s into `jar:file:` URI".formatted(uri));
}
// TODO: will be removed in MultiReleaseJarFile PR, as AnnotationParser is the only thing using this,
// and it doesn't need to recreate the URI that it will already have.
public static String toJarPath(String jarFile, String pathInJar)
{
return "jar:" + jarFile + URIUtil.addPaths("!/", pathInJar);
}
/**
* Unwrap a URI to expose its container path reference.
*
* Take out the container archive name URI from a {@code jar:file:${container-name}!/} URI.
*
* @param uri the input URI
* @return the container String if a {@code jar} scheme, or just the URI untouched.
* TODO: reconcile with URIUtil.getJarSource(URI)
*/
public static URI unwrapContainer(URI uri)
{
Objects.requireNonNull(uri);
String scheme = uri.getScheme();
if ((scheme == null) || !scheme.equalsIgnoreCase("jar"))
return uri;
String spec = uri.getRawSchemeSpecificPart();
int sep = spec.indexOf("!/");
if (sep != -1)
spec = spec.substring(0, sep);
return URI.create(spec);
}
/**
* <p>Convert a String into a URI suitable for use as a Resource.</p>
*
@ -852,6 +719,7 @@ public abstract class Resource implements ResourceFactory
return newResource(resolvedUri);
}
// TODO: move to URIUtil
private static URI uriResolve(URI uri, String subUriPath) throws IOException
{
try
@ -1327,75 +1195,6 @@ public abstract class Resource implements ResourceFactory
}
}
/**
* Split a string of references, that may be split with {@code ,}, or {@code ;}, or {@code |} into URIs.
* <p>
* Each part of the input string could be path references (unix or windows style), or string URI references.
* </p>
* <p>
* If the result of processing the input segment is a java archive, then its resulting URI will be a mountable URI as `jar:file:...!/`.
* </p>
*
* @param str the input string of references
* @see #toJarFileUri(URI)
*/
public static List<URI> split(String str)
{
List<URI> uris = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(str, ",;|");
while (tokenizer.hasMoreTokens())
{
String reference = tokenizer.nextToken();
try
{
// Is this a glob reference?
if (reference.endsWith("/*") || reference.endsWith("\\*"))
{
String dir = reference.substring(0, reference.length() - 2);
Path pathDir = Paths.get(dir);
// Use directory
if (Files.exists(pathDir) && Files.isDirectory(pathDir))
{
// To obtain the list of entries
try (Stream<Path> listStream = Files.list(pathDir))
{
listStream
.filter(Files::isRegularFile)
.filter(Resource::isArchive)
.sorted(Comparator.naturalOrder())
.forEach(path -> uris.add(toJarFileUri(path.toUri())));
}
catch (IOException e)
{
throw new RuntimeException("Unable to process directory glob listing: " + reference, e);
}
}
}
else
{
// Simple reference
URI refUri = toURI(reference);
// Is this a Java Archive that can be mounted?
URI jarFileUri = toJarFileUri(refUri);
if (jarFileUri != null)
// add as mountable URI
uris.add(jarFileUri);
else
// add as normal URI
uris.add(refUri);
}
}
catch (Exception e)
{
LOG.warn("Invalid Resource Reference: " + reference);
throw e;
}
}
return uris;
}
/**
* Certain {@link Resource}s (e.g.: JAR files) require mounting before they can be used. This class is the representation
* of such mount allowing the use of more {@link Resource}s.

View File

@ -0,0 +1,388 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util;
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(WorkDirExtension.class)
public class FileIDTest
{
private static FileSystem metaInfVersionTestFileSystem;
public WorkDir workDir;
@AfterAll
public static void closeFileSystems()
{
IO.close(metaInfVersionTestFileSystem);
}
private Path touchTestPath(String input) throws IOException
{
return touchTestPath(workDir.getPath(), input);
}
private Path touchTestPath(Path base, String input) throws IOException
{
Path path = base.resolve(input);
if (input.endsWith("/"))
{
FS.ensureEmpty(path);
}
else
{
FS.ensureDirExists(path.getParent());
FS.touch(path);
}
return path;
}
/**
* Create an empty ZipFs FileSystem useful for testing skipMetaInfVersions and isMetaInfVersions rules
*/
private static FileSystem getMetaInfVersionTestJar() throws IOException
{
if (metaInfVersionTestFileSystem != null)
return metaInfVersionTestFileSystem;
Path outputJar = MavenTestingUtils.getTargetTestingPath("getMetaInfVersionTestJar").resolve("metainf-versions.jar");
FS.ensureEmpty(outputJar.getParent());
Map<String, String> env = new HashMap<>();
env.put("create", "true");
URI uri = URI.create("jar:" + outputJar.toUri().toASCIIString());
metaInfVersionTestFileSystem = FileSystems.newFileSystem(uri, env);
// this is an empty FileSystem, I don't need to create the files for the skipMetaInfVersions() tests
return metaInfVersionTestFileSystem;
}
public static Stream<Arguments> basenameCases()
{
return Stream.of(
Arguments.of("foo.xml", "foo"),
Arguments.of("dir/foo.xml", "foo"),
Arguments.of("dir/foo", "foo"),
Arguments.of("foo", "foo")
);
}
@ParameterizedTest
@MethodSource("basenameCases")
public void testGetBasename(String input, String expected)
{
Path path = workDir.getEmptyPathDir().resolve(input);
String actual = FileID.getBasename(path);
assertThat(actual, is(expected));
}
public static Stream<Arguments> containsDirectoryTrueCases()
{
return Stream.of(
Arguments.of("path/to/webapps/root.war", "webapps"),
Arguments.of("path/to/webapps/", "webapps"),
Arguments.of("META-INF/services/org.eclipse.jetty.FooService", "META-INF"),
Arguments.of("META-INF/lib/lib-1.jar", "META-INF"),
Arguments.of("deeper/path/to/exploded-jar/META-INF/lib/lib-1.jar", "META-INF")
);
}
@ParameterizedTest
@MethodSource("containsDirectoryTrueCases")
public void testContainsDirectoryTrue(String input, String dirname) throws IOException
{
Path path = touchTestPath(input);
assertTrue(FileID.containsDirectory(path, dirname), "containsDirectory(%s, \"%s\")".formatted(path, dirname));
}
public static Stream<Arguments> containsDirectoryFalseCases()
{
return Stream.of(
Arguments.of("path/to/webapps/root.war", "WEB-INF"),
Arguments.of("path/to/webapps/", "WEB-INF"),
Arguments.of("classes/org.eclipse.jetty.util.Foo", "util"),
Arguments.of("path/lib-a/foo.txt", "lib")
);
}
@ParameterizedTest
@MethodSource("containsDirectoryFalseCases")
public void testContainsDirectoryFalse(String input, String dirname) throws IOException
{
Path path = touchTestPath(input);
assertFalse(FileID.containsDirectory(path, dirname), "containsDirectory(%s, \"%s\")".formatted(path, dirname));
}
public static Stream<Arguments> extensionCases()
{
return Stream.of(
Arguments.of("foo.xml", ".xml"),
Arguments.of("dir/foo.xml", ".xml"),
Arguments.of("foo.jar", ".jar"),
Arguments.of("FOO.WAR", ".war"),
Arguments.of("Foo.Zip", ".zip")
);
}
@ParameterizedTest
@MethodSource("extensionCases")
public void testGetExtension(String input, String expected) throws IOException
{
String actual;
actual = FileID.getExtension(input);
assertThat("getExtension((String) \"%s\")".formatted(input), actual, is(expected));
Path path = touchTestPath(input);
actual = FileID.getExtension(path);
assertThat("getExtension((Path) \"%s\")".formatted(path), actual, is(expected));
}
@ParameterizedTest
@ValueSource(strings = {
"jar:file:/home/user/project/with.jar/in/path/name",
"file:/home/user/project/directory/",
"file:/home/user/hello.ear",
"/home/user/hello.jar",
"/home/user/app.war"
})
public void testIsArchiveUriFalse(String rawUri)
{
assertFalse(FileID.isArchive(URI.create(rawUri)), "Should be detected as a JAR URI: " + rawUri);
}
@ParameterizedTest
@ValueSource(strings = {
"file:/home/user/.m2/repository/com/company/1.0/company-1.0.jar",
"jar:file:/home/user/.m2/repository/com/company/1.0/company-1.0.jar!/",
"jar:file:/home/user/.m2/repository/com/company/1.0/company-1.0.jar",
"file:/home/user/install/jetty-home-12.0.0.zip",
"file:/opt/websites/webapps/company.war",
"jar:file:/home/user/.m2/repository/jakarta/servlet/jakarta.servlet-api/6.0.0/jakarta.servlet-api-6.0.0.jar!/META-INF/resources"
})
public void testIsArchiveUriTrue(String rawUri)
{
assertTrue(FileID.isArchive(URI.create(rawUri)), "Should be detected as a JAR URI: " + rawUri);
}
@ParameterizedTest
@ValueSource(strings = {
// Doesn't end in class
"Foo.txt",
// No name
".class",
// Illegal characters
"tab\tcharacter.class",
// Doesn't start with identifier
"42.class",
"org/eclipse/jetty/demo/123Foo.class",
// Has spaces
"org/eclipse/jetty/demo/A $ Inner.class"
})
public void testIsClassFileFalse(String input) throws IOException
{
Path testPath = touchTestPath(input);
assertFalse(FileID.isClassFile(testPath), "isClassFile(" + testPath + ")");
}
@ParameterizedTest
@ValueSource(strings = {
"Foo.class",
"org/eclipse/jetty/demo/Zed.class",
"org/eclipse/jetty/demo/Zed$Inner.class"
})
public void testIsClassFileTrue(String input) throws IOException
{
Path testPath = touchTestPath(input);
assertTrue(FileID.isClassFile(testPath), "isClassFile(" + testPath + ")");
}
@ParameterizedTest
@ValueSource(strings = {
"dir/foo.txt",
"bar",
"zed.png",
"a/b/c/d/e/f/g.jpeg"
})
public void testIsHiddenFalse(String input) throws IOException
{
Path base = workDir.getEmptyPathDir();
Path path = touchTestPath(base, input);
assertFalse(FileID.isHidden(base, path), "isHidden(" + input + ")");
}
@ParameterizedTest
@ValueSource(strings = {
".dir/foo.txt",
".bar",
"a/b/c/.d/e/f/g.jpeg"
})
@EnabledOnOs({OS.LINUX, OS.MAC})
public void testIsHiddenTrue(String input) throws IOException
{
Path base = workDir.getEmptyPathDir();
Path path = touchTestPath(base, input);
assertTrue(FileID.isHidden(base, path), "isHidden(" + input + ")");
}
@ParameterizedTest
@ValueSource(strings = {
"/META-INF/versions/9/foo.txt",
"/META-INF/versions/17/org/eclipse/demo/Util.class",
"/META-INF/versions/17/WEB-INF/web.xml",
"/META-INF/versions/10/module-info.class"
})
public void testIsMetaInfVersions(String input) throws IOException
{
FileSystem zipfs = getMetaInfVersionTestJar();
Path testPath = zipfs.getPath(input);
assertTrue(FileID.isMetaInfVersions(testPath), "isMetaInfVersions(" + testPath + ")");
assertFalse(FileID.skipMetaInfVersions(testPath), "skipMetaInfVersions(" + testPath + ")");
}
@ParameterizedTest
@ValueSource(strings = {
"foo",
"dir/",
"zed.txt",
"dir/zed.txt",
"dir/bar.war/zed.txt",
"dir/bar.war/",
"cee.jar",
"cee.zip"
})
public void testIsWebArchiveFalse(String input) throws IOException
{
assertFalse(FileID.isWebArchive(input), "isWebArchive((String) \"%s\")".formatted(input));
Path path = touchTestPath(input);
assertFalse(FileID.isWebArchive(path), "isWebArchive((Path) \"%s\")".formatted(path));
}
@ParameterizedTest
@ValueSource(strings = {
"dir/foo.war",
"DIR/FOO.WAR",
"Dir/Foo.War",
"zed.war",
"ZED.WAR",
"Zed.War"
})
public void testIsWebArchiveTrue(String input) throws IOException
{
assertTrue(FileID.isWebArchive(input), "isWebArchive((String) \"%s\")".formatted(input));
Path path = touchTestPath(input);
assertTrue(FileID.isWebArchive(path), "isWebArchive((Path) \"%s\")".formatted(path));
}
@ParameterizedTest
@ValueSource(strings = {
"foo.jar",
"FOO.war",
"Foo.zip",
"dir/zed.xml/",
"dir/zed.xml/bar.txt"
})
public void testIsXmlFalse(String input) throws IOException
{
assertFalse(FileID.isXml(input), "isXml((String) \"%s\")".formatted(input));
Path path = touchTestPath(input);
assertFalse(FileID.isXml(path), "isXml((Path) \"%s\")".formatted(path));
}
@ParameterizedTest
@ValueSource(strings = {
"foo.xml",
"FOO.XML",
"Foo.Xml",
"dir/zed.xml",
"DIR/ZED.XML",
"Dir/Zed.Xml",
})
public void testIsXmlTrue(String input) throws IOException
{
assertTrue(FileID.isXml(input), "isXml((String) \"%s\")".formatted(input));
Path path = touchTestPath(input);
assertTrue(FileID.isXml(path), "isXml((Path) \"%s\")".formatted(path));
}
@ParameterizedTest
@ValueSource(strings = {
"/root.txt",
"/META-INF/MANIFEST.MF",
"/META-INF/services/versions/foo.txt",
"/META-INF/versions/", // root, no version
"/META-INF/versions/Zed.class", // root, no version
"/meta-inf/versions/10/Foo.class", // not following case sensitivity rules in Java spec
"/meta-inf/VERSIONS/10/Zed.class", // not following case sensitivity rules in Java spec
})
public void testNotMetaInfVersions(String input) throws IOException
{
FileSystem zipfs = getMetaInfVersionTestJar();
Path testPath = zipfs.getPath(input);
assertTrue(FileID.skipMetaInfVersions(testPath), "skipMetaInfVersions(" + testPath + ")");
assertFalse(FileID.isMetaInfVersions(testPath), "isMetaInfVersions(" + testPath + ")");
}
@ParameterizedTest
@ValueSource(strings = {
"foo/module-info.class",
"module-info.class",
"Module-Info.Class", // case differences
"META-INF/versions/17/module-info.class"
})
public void testSkipModuleInfoClassFalse(String input) throws IOException
{
Path path = touchTestPath(input);
assertFalse(FileID.skipModuleInfoClass(path), "skipModuleInfoClass(" + path + ")");
}
@ParameterizedTest
@ValueSource(strings = {
"foo/Bar.class",
"Zed.class",
"META-INF/versions/9/foo/Bar.class",
"META-INF/versions/9/foo/module-info.class/Zed.class", // as path segment
"", // no segment
})
public void testSkipModuleInfoClassTrue(String input) throws IOException
{
Path path = touchTestPath(input);
assertTrue(FileID.skipModuleInfoClass(path), "skipModuleInfoClass(" + path + ")");
}
}

View File

@ -32,34 +32,19 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(WorkDirExtension.class)
public class MultiReleaseJarFileTest
{
public WorkDir workDir;
private static FileSystem metaInfVersionTestFileSystem;
@AfterAll
public static void closeFileSystems()
{
IO.close(metaInfVersionTestFileSystem);
}
private void createExampleJar(Path outputJar) throws IOException
{
@ -205,154 +190,4 @@ public class MultiReleaseJarFileTest
assertThat(entries, Matchers.containsInAnyOrder(expected));
}
@ParameterizedTest
@ValueSource(strings = {
"foo/Bar.class",
"Zed.class",
"META-INF/versions/9/foo/Bar.class",
"META-INF/versions/9/foo/module-info.class/Zed.class", // as path segment
"", // no segment
})
public void testSkipModuleInfoClassTrue(String input)
{
Path path = workDir.getPath().resolve(input);
assertTrue(MultiReleaseJarFile.skipModuleInfoClass(path), "skipModuleInfoClass(" + path + ")");
}
@ParameterizedTest
@ValueSource(strings = {
"foo/module-info.class",
"module-info.class",
"Module-Info.Class", // case differences
"META-INF/versions/17/module-info.class"
})
public void testSkipModuleInfoClassFalse(String input)
{
Path path = workDir.getPath().resolve(input);
assertFalse(MultiReleaseJarFile.skipModuleInfoClass(path), "skipModuleInfoClass(" + path + ")");
}
/**
* Create an empty ZipFs FileSystem useful for testing skipMetaInfVersions and isMetaInfVersions rules
*/
private static FileSystem getMetaInfVersionTestJar() throws IOException
{
if (metaInfVersionTestFileSystem != null)
return metaInfVersionTestFileSystem;
Path outputJar = MavenTestingUtils.getTargetTestingPath("getMetaInfVersionTestJar").resolve("metainf-versions.jar");
FS.ensureEmpty(outputJar.getParent());
Map<String, String> env = new HashMap<>();
env.put("create", "true");
URI uri = URI.create("jar:" + outputJar.toUri().toASCIIString());
metaInfVersionTestFileSystem = FileSystems.newFileSystem(uri, env);
// this is an empty FileSystem, I don't need to create the files for the skipMetaInfVersions() tests
return metaInfVersionTestFileSystem;
}
@ParameterizedTest
@ValueSource(strings = {
"/root.txt",
"/META-INF/MANIFEST.MF",
"/META-INF/services/versions/foo.txt",
"/META-INF/versions/", // root, no version
"/META-INF/versions/Zed.class", // root, no version
"/meta-inf/versions/10/Foo.class", // not following case sensitivity rules in Java spec
"/meta-inf/VERSIONS/10/Zed.class", // not following case sensitivity rules in Java spec
})
public void testNotMetaInfVersions(String input) throws IOException
{
FileSystem zipfs = getMetaInfVersionTestJar();
Path testPath = zipfs.getPath(input);
assertTrue(MultiReleaseJarFile.skipMetaInfVersions(testPath), "skipMetaInfVersions(" + testPath + ")");
assertFalse(MultiReleaseJarFile.isMetaInfVersions(testPath), "isMetaInfVersions(" + testPath + ")");
}
@ParameterizedTest
@ValueSource(strings = {
"/META-INF/versions/9/foo.txt",
"/META-INF/versions/17/org/eclipse/demo/Util.class",
"/META-INF/versions/17/WEB-INF/web.xml",
"/META-INF/versions/10/module-info.class"
})
public void testIsMetaInfVersions(String input) throws IOException
{
FileSystem zipfs = getMetaInfVersionTestJar();
Path testPath = zipfs.getPath(input);
assertTrue(MultiReleaseJarFile.isMetaInfVersions(testPath), "isMetaInfVersions(" + testPath + ")");
assertFalse(MultiReleaseJarFile.skipMetaInfVersions(testPath), "skipMetaInfVersions(" + testPath + ")");
}
@ParameterizedTest
@ValueSource(strings = {
"Foo.class",
"org/eclipse/jetty/demo/Zed.class",
"org/eclipse/jetty/demo/Zed$Inner.class"
})
public void testIsClassFileTrue(String input)
{
Path base = MavenTestingUtils.getTargetTestingPath();
Path testPath = base.resolve(input);
assertTrue(MultiReleaseJarFile.isClassFile(testPath), "isClassFile(" + testPath + ")");
}
@ParameterizedTest
@ValueSource(strings = {
// Doesn't end in class
"Foo.txt",
// No name
".class",
// Illegal characters
"tab\tcharacter.class",
// Doesn't start with identifier
"42.class",
"org/eclipse/jetty/demo/123Foo.class",
// Has spaces
"org/eclipse/jetty/demo/A $ Inner.class"
})
public void testIsClassFileFalse(String input)
{
Path base = MavenTestingUtils.getTargetTestingPath();
Path testPath = base.resolve(input);
assertFalse(MultiReleaseJarFile.isClassFile(testPath), "isClassFile(" + testPath + ")");
}
@ParameterizedTest
@ValueSource(strings = {
".dir/foo.txt",
".bar",
"a/b/c/.d/e/f/g.jpeg"
})
@EnabledOnOs({OS.LINUX, OS.MAC})
public void testIsHiddenTrue(String input) throws IOException
{
Path base = MavenTestingUtils.getTargetTestingPath("testIsHiddenTrue");
Path path = base.resolve(input);
FS.ensureDirExists(path.getParent());
FS.touch(path);
assertTrue(MultiReleaseJarFile.isHidden(base, path), "isHidden(" + input + ")");
}
@ParameterizedTest
@ValueSource(strings = {
"dir/foo.txt",
"bar",
"zed.png",
"a/b/c/d/e/f/g.jpeg"
})
public void testIsHiddenFalse(String input) throws IOException
{
Path base = MavenTestingUtils.getTargetTestingPath("testIsHiddenFalse");
Path path = base.resolve(input);
FS.ensureDirExists(path.getParent());
FS.touch(path);
assertFalse(MultiReleaseJarFile.isHidden(base, path), "isHidden(" + input + ")");
}
}

View File

@ -38,11 +38,13 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -518,31 +520,7 @@ public class URIUtilTest
assertTrue(URIUtil.equalsIgnoreEncodings(uriA, uriB));
}
public static Stream<Arguments> getJarSourceStringSource()
{
return Stream.of(
// Common cases
Arguments.of("file:///tmp/", "file:///tmp/"),
Arguments.of("jar:file:///tmp/foo.jar", "file:///tmp/foo.jar"),
Arguments.of("jar:file:///tmp/foo.jar!/some/path", "file:///tmp/foo.jar"),
// Bad File.toURL cases
Arguments.of("file:/tmp/", "file:/tmp/"),
Arguments.of("jar:file:/tmp/foo.jar", "file:/tmp/foo.jar"),
Arguments.of("jar:file:/tmp/foo.jar!/some/path", "file:/tmp/foo.jar")
);
}
@ParameterizedTest
@MethodSource("getJarSourceStringSource")
public void testJarSourceURI(String uri, String expectedJarUri)
{
URI input = URI.create(uri);
URI expected = URI.create(expectedJarUri);
URI actual = URIUtil.getJarSource(input);
assertThat(actual.toASCIIString(), is(expected.toASCIIString()));
}
public static Stream<Arguments> badJavaIoFileUrlCases()
public static Stream<Arguments> correctBadFileURICases()
{
return Stream.of(
// Already valid URIs
@ -573,19 +551,19 @@ public class URIUtilTest
}
@ParameterizedTest
@MethodSource("badJavaIoFileUrlCases")
public void testFixBadJavaIoFileUrl(String input, String expected)
@MethodSource("correctBadFileURICases")
public void testCorrectBadFileURI(String input, String expected)
{
URI inputUri = URI.create(input);
URI actualUri = URIUtil.fixBadJavaIoFileUrl(inputUri);
URI actualUri = URIUtil.correctBadFileURI(inputUri);
URI expectedUri = URI.create(expected);
assertThat(actualUri.toASCIIString(), is(expectedUri.toASCIIString()));
}
@Test
public void testFixBadJavaIoFileUrlActualFile() throws Exception
public void testCorrectBadFileURIActualFile() throws Exception
{
File file = MavenTestingUtils.getTargetFile("testFixBadJavaIoFileUrlActualFile.txt");
File file = MavenTestingUtils.getTargetFile("testCorrectBadFileURIActualFile.txt");
FS.touch(file);
URI expectedUri = file.toPath().toUri();
@ -599,8 +577,8 @@ public class URIUtilTest
assertThat(fileUri.toASCIIString(), not(containsString("://")));
assertThat(fileUrlUri.toASCIIString(), not(containsString("://")));
assertThat(URIUtil.fixBadJavaIoFileUrl(fileUri).toASCIIString(), is(expectedUri.toASCIIString()));
assertThat(URIUtil.fixBadJavaIoFileUrl(fileUrlUri).toASCIIString(), is(expectedUri.toASCIIString()));
assertThat(URIUtil.correctBadFileURI(fileUri).toASCIIString(), is(expectedUri.toASCIIString()));
assertThat(URIUtil.correctBadFileURI(fileUrlUri).toASCIIString(), is(expectedUri.toASCIIString()));
}
public static Stream<Arguments> encodeSpacesSource()
@ -752,7 +730,7 @@ public class URIUtilTest
final String TEST_RESOURCE_JAR = "test-base-resource.jar";
List<Arguments> arguments = new ArrayList<>();
Path testJar = MavenTestingUtils.getTestResourcePathFile(TEST_RESOURCE_JAR);
URI jarFileUri = Resource.toJarFileUri(testJar.toUri());
URI jarFileUri = URIUtil.toJarFileUri(testJar.toUri());
try (Resource.Mount jarMount = Resource.mount(jarFileUri))
{
@ -871,4 +849,174 @@ public class URIUtilTest
final URI inputURI = input == null ? null : URI.create(input);
assertThrows(IllegalArgumentException.class, () -> URIUtil.uriJarPrefix(inputURI, suffix));
}
public static Stream<Arguments> jarFileUriCases()
{
List<Arguments> cases = new ArrayList<>();
String expected = "jar:file:/path/company-1.0.jar!/";
cases.add(Arguments.of("file:/path/company-1.0.jar", expected));
cases.add(Arguments.of("jar:file:/path/company-1.0.jar", expected));
cases.add(Arguments.of("jar:file:/path/company-1.0.jar!/", expected));
cases.add(Arguments.of("jar:file:/path/company-1.0.jar!/META-INF/services", expected + "META-INF/services"));
expected = "jar:file:/opt/jetty/webapps/app.war!/";
cases.add(Arguments.of("file:/opt/jetty/webapps/app.war", expected));
cases.add(Arguments.of("jar:file:/opt/jetty/webapps/app.war", expected));
cases.add(Arguments.of("jar:file:/opt/jetty/webapps/app.war!/", expected));
cases.add(Arguments.of("jar:file:/opt/jetty/webapps/app.war!/WEB-INF/classes", expected + "WEB-INF/classes"));
return cases.stream();
}
@ParameterizedTest
@MethodSource("jarFileUriCases")
public void testToJarFileUri(String inputRawUri, String expectedRawUri)
{
URI actual = URIUtil.toJarFileUri(URI.create(inputRawUri));
assertNotNull(actual);
assertThat(actual.toASCIIString(), is(expectedRawUri));
}
public static Stream<Arguments> unwrapContainerCases()
{
return Stream.of(
Arguments.of("/path/to/foo.jar", "file:///path/to/foo.jar"),
Arguments.of("/path/to/bogus.txt", "file:///path/to/bogus.txt"),
Arguments.of("file:///path/to/zed.jar", "file:///path/to/zed.jar"),
Arguments.of("jar:file:///path/to/bar.jar!/internal.txt", "file:///path/to/bar.jar")
);
}
@ParameterizedTest
@MethodSource("unwrapContainerCases")
public void testUnwrapContainer(String inputRawUri, String expected)
{
URI input = Resource.toURI(inputRawUri);
URI actual = URIUtil.unwrapContainer(input);
assertThat(actual.toASCIIString(), is(expected));
}
@Test
public void testSplitSingleJar()
{
// Bad java file.uri syntax
String input = "file:/home/user/lib/acme.jar";
List<URI> uris = URIUtil.split(input);
String expected = String.format("jar:%s!/", input);
assertThat(uris.get(0).toString(), is(expected));
}
@Test
public void testSplitSinglePath()
{
String input = "/home/user/lib/acme.jar";
List<URI> uris = URIUtil.split(input);
String expected = String.format("jar:file://%s!/", input);
assertThat(uris.get(0).toString(), is(expected));
}
@Test
public void testSplitOnComma()
{
Path base = workDir.getEmptyPathDir();
Path dir = base.resolve("dir");
FS.ensureDirExists(dir);
Path foo = dir.resolve("foo");
FS.ensureDirExists(foo);
Path bar = dir.resolve("bar");
FS.ensureDirExists(bar);
// This represents the user-space raw configuration
String config = String.format("%s,%s,%s", dir, foo, bar);
// Split using commas
List<URI> uris = URIUtil.split(config);
URI[] expected = new URI[] {
dir.toUri(),
foo.toUri(),
bar.toUri()
};
assertThat(uris, contains(expected));
}
@Test
public void testSplitOnPipe()
{
Path base = workDir.getEmptyPathDir();
Path dir = base.resolve("dir");
FS.ensureDirExists(dir);
Path foo = dir.resolve("foo");
FS.ensureDirExists(foo);
Path bar = dir.resolve("bar");
FS.ensureDirExists(bar);
// This represents the user-space raw configuration
String config = String.format("%s|%s|%s", dir, foo, bar);
// Split using commas
List<URI> uris = URIUtil.split(config);
URI[] expected = new URI[] {
dir.toUri(),
foo.toUri(),
bar.toUri()
};
assertThat(uris, contains(expected));
}
@Test
public void testSplitOnSemicolon()
{
Path base = workDir.getEmptyPathDir();
Path dir = base.resolve("dir");
FS.ensureDirExists(dir);
Path foo = dir.resolve("foo");
FS.ensureDirExists(foo);
Path bar = dir.resolve("bar");
FS.ensureDirExists(bar);
// This represents the user-space raw configuration
String config = String.format("%s;%s;%s", dir, foo, bar);
// Split using commas
List<URI> uris = URIUtil.split(config);
URI[] expected = new URI[] {
dir.toUri(),
foo.toUri(),
bar.toUri()
};
assertThat(uris, contains(expected));
}
@Test
public void testSplitOnPipeWithGlob() throws IOException
{
Path base = workDir.getEmptyPathDir();
Path dir = base.resolve("dir");
FS.ensureDirExists(dir);
Path foo = dir.resolve("foo");
FS.ensureDirExists(foo);
Path bar = dir.resolve("bar");
FS.ensureDirExists(bar);
FS.touch(bar.resolve("lib-foo.jar"));
FS.touch(bar.resolve("lib-zed.zip"));
// This represents the user-space raw configuration with a glob
String config = String.format("%s;%s;%s%s*", dir, foo, bar, File.separator);
// Split using commas
List<URI> uris = URIUtil.split(config);
URI[] expected = new URI[] {
dir.toUri(),
foo.toUri(),
// Should see the two archives as `jar:file:` URI entries
URIUtil.toJarFileUri(bar.resolve("lib-foo.jar").toUri()),
URIUtil.toJarFileUri(bar.resolve("lib-zed.zip").toUri())
};
assertThat(uris, contains(expected));
}
}

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.URIUtil;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -172,7 +173,7 @@ public class ResourceCollectionTest
String config = String.format("%s,%s,%s", dir, foo, bar);
// To use this, we need to split it (and optionally honor globs)
List<URI> uris = Resource.split(config);
List<URI> uris = URIUtil.split(config);
// Now let's create a ResourceCollection from this list of URIs
// Since this is user space, we cannot know ahead of time what
// this list contains, so we mount because we assume there
@ -201,7 +202,7 @@ public class ResourceCollectionTest
String config = String.format("%s;%s;%s%s*", dir, foo, bar, File.separator);
// To use this, we need to split it (and optionally honor globs)
List<URI> uris = Resource.split(config);
List<URI> uris = URIUtil.split(config);
// Now let's create a ResourceCollection from this list of URIs
// Since this is user space, we cannot know ahead of time what
// this list contains, so we mount because we assume there

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.URIUtil;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
@ -41,15 +42,11 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -409,205 +406,8 @@ public class ResourceTest
public void testJarReferenceAsURINotYetMounted() throws Exception
{
Path jar = MavenTestingUtils.getTestResourcePathFile("example.jar");
URI jarFileUri = Resource.toJarFileUri(jar.toUri());
URI jarFileUri = URIUtil.toJarFileUri(jar.toUri());
assertNotNull(jarFileUri);
assertThrows(IllegalStateException.class, () -> Resource.newResource(jarFileUri));
}
@ParameterizedTest
@ValueSource(strings = {
"file:/home/user/.m2/repository/com/company/1.0/company-1.0.jar",
"jar:file:/home/user/.m2/repository/com/company/1.0/company-1.0.jar!/",
"jar:file:/home/user/.m2/repository/com/company/1.0/company-1.0.jar",
"file:/home/user/install/jetty-home-12.0.0.zip",
"file:/opt/websites/webapps/company.war",
"jar:file:/home/user/.m2/repository/jakarta/servlet/jakarta.servlet-api/6.0.0/jakarta.servlet-api-6.0.0.jar!/META-INF/resources"
})
public void testIsArchiveUriTrue(String rawUri)
{
assertTrue(Resource.isArchive(URI.create(rawUri)), "Should be detected as a JAR URI: " + rawUri);
}
@ParameterizedTest
@ValueSource(strings = {
"jar:file:/home/user/project/with.jar/in/path/name",
"file:/home/user/project/directory/",
"file:/home/user/hello.ear",
"/home/user/hello.jar",
"/home/user/app.war"
})
public void testIsArchiveUriFalse(String rawUri)
{
assertFalse(Resource.isArchive(URI.create(rawUri)), "Should be detected as a JAR URI: " + rawUri);
}
public static Stream<Arguments> jarFileUriCases()
{
List<Arguments> cases = new ArrayList<>();
String expected = "jar:file:/path/company-1.0.jar!/";
cases.add(Arguments.of("file:/path/company-1.0.jar", expected));
cases.add(Arguments.of("jar:file:/path/company-1.0.jar", expected));
cases.add(Arguments.of("jar:file:/path/company-1.0.jar!/", expected));
cases.add(Arguments.of("jar:file:/path/company-1.0.jar!/META-INF/services", expected + "META-INF/services"));
expected = "jar:file:/opt/jetty/webapps/app.war!/";
cases.add(Arguments.of("file:/opt/jetty/webapps/app.war", expected));
cases.add(Arguments.of("jar:file:/opt/jetty/webapps/app.war", expected));
cases.add(Arguments.of("jar:file:/opt/jetty/webapps/app.war!/", expected));
cases.add(Arguments.of("jar:file:/opt/jetty/webapps/app.war!/WEB-INF/classes", expected + "WEB-INF/classes"));
return cases.stream();
}
@ParameterizedTest
@MethodSource("jarFileUriCases")
public void testToJarFileUri(String inputRawUri, String expectedRawUri)
{
URI actual = Resource.toJarFileUri(URI.create(inputRawUri));
assertNotNull(actual);
assertThat(actual.toASCIIString(), is(expectedRawUri));
}
public static Stream<Arguments> unwrapContainerCases()
{
return Stream.of(
Arguments.of("/path/to/foo.jar", "file:///path/to/foo.jar"),
Arguments.of("/path/to/bogus.txt", "file:///path/to/bogus.txt"),
Arguments.of("file:///path/to/zed.jar", "file:///path/to/zed.jar"),
Arguments.of("jar:file:///path/to/bar.jar!/internal.txt", "file:///path/to/bar.jar")
);
}
@ParameterizedTest
@MethodSource("unwrapContainerCases")
public void testUnwrapContainer(String inputRawUri, String expected)
{
URI input = Resource.toURI(inputRawUri);
URI actual = Resource.unwrapContainer(input);
assertThat(actual.toASCIIString(), is(expected));
}
@Test
public void testSplitSingleJar()
{
// Bad java file.uri syntax
String input = "file:/home/user/lib/acme.jar";
List<URI> uris = Resource.split(input);
String expected = String.format("jar:%s!/", input);
assertThat(uris.get(0).toString(), is(expected));
}
@Test
public void testSplitSinglePath()
{
String input = "/home/user/lib/acme.jar";
List<URI> uris = Resource.split(input);
String expected = String.format("jar:file://%s!/", input);
assertThat(uris.get(0).toString(), is(expected));
}
@Test
public void testSplitOnComma()
{
Path base = workDir.getEmptyPathDir();
Path dir = base.resolve("dir");
FS.ensureDirExists(dir);
Path foo = dir.resolve("foo");
FS.ensureDirExists(foo);
Path bar = dir.resolve("bar");
FS.ensureDirExists(bar);
// This represents the user-space raw configuration
String config = String.format("%s,%s,%s", dir, foo, bar);
// Split using commas
List<URI> uris = Resource.split(config);
URI[] expected = new URI[] {
dir.toUri(),
foo.toUri(),
bar.toUri()
};
assertThat(uris, contains(expected));
}
@Test
public void testSplitOnPipe()
{
Path base = workDir.getEmptyPathDir();
Path dir = base.resolve("dir");
FS.ensureDirExists(dir);
Path foo = dir.resolve("foo");
FS.ensureDirExists(foo);
Path bar = dir.resolve("bar");
FS.ensureDirExists(bar);
// This represents the user-space raw configuration
String config = String.format("%s|%s|%s", dir, foo, bar);
// Split using commas
List<URI> uris = Resource.split(config);
URI[] expected = new URI[] {
dir.toUri(),
foo.toUri(),
bar.toUri()
};
assertThat(uris, contains(expected));
}
@Test
public void testSplitOnSemicolon()
{
Path base = workDir.getEmptyPathDir();
Path dir = base.resolve("dir");
FS.ensureDirExists(dir);
Path foo = dir.resolve("foo");
FS.ensureDirExists(foo);
Path bar = dir.resolve("bar");
FS.ensureDirExists(bar);
// This represents the user-space raw configuration
String config = String.format("%s;%s;%s", dir, foo, bar);
// Split using commas
List<URI> uris = Resource.split(config);
URI[] expected = new URI[] {
dir.toUri(),
foo.toUri(),
bar.toUri()
};
assertThat(uris, contains(expected));
}
@Test
public void testSplitOnPipeWithGlob() throws IOException
{
Path base = workDir.getEmptyPathDir();
Path dir = base.resolve("dir");
FS.ensureDirExists(dir);
Path foo = dir.resolve("foo");
FS.ensureDirExists(foo);
Path bar = dir.resolve("bar");
FS.ensureDirExists(bar);
FS.touch(bar.resolve("lib-foo.jar"));
FS.touch(bar.resolve("lib-zed.zip"));
// This represents the user-space raw configuration with a glob
String config = String.format("%s;%s;%s%s*", dir, foo, bar, File.separator);
// Split using commas
List<URI> uris = Resource.split(config);
URI[] expected = new URI[] {
dir.toUri(),
foo.toUri(),
// Should see the two archives as `jar:file:` URI entries
Resource.toJarFileUri(bar.resolve("lib-foo.jar").toUri()),
Resource.toJarFileUri(bar.resolve("lib-zed.zip").toUri())
};
assertThat(uris, contains(expected));
}
}

View File

@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.MultiReleaseJarFile;
import org.eclipse.jetty.util.StringUtil;
@ -816,8 +817,7 @@ public class AnnotationParser
if (jarResource == null)
return;
// TODO: what if the input is "FOO.JAR" or "Bar.Jar" ? - need to use FileID.isArchive() or FileID.isJar()
if (jarResource.toString().endsWith(".jar"))
if (FileID.isJavaArchive(jarResource.getPath()))
{
if (LOG.isDebugEnabled())
LOG.debug("Scanning jar {}", jarResource);
@ -826,9 +826,9 @@ public class AnnotationParser
try (MultiReleaseJarFile jarFile = new MultiReleaseJarFile(jarResource.getPath());
Stream<Path> jarEntryStream = jarFile.stream()
.filter(Files::isRegularFile)
.filter(MultiReleaseJarFile::skipModuleInfoClass)
.filter(MultiReleaseJarFile::skipMetaInfVersions)
.filter(MultiReleaseJarFile::isClassFile)
.filter(FileID::skipModuleInfoClass)
.filter(FileID::skipMetaInfVersions)
.filter(FileID::isClassFile)
)
{
jarEntryStream.forEach(e ->
@ -917,7 +917,7 @@ public class AnnotationParser
*
* @param name the class file name
* @return whether the class file name is valid
* TODO: does multiple things, we should be able to move this FileID in future PR
* TODO: does multiple things, we should be able to eliminate this in future PR by consolidating the ZipFS and Directory scanning behaviors into a single Files.walk() stream
*/
public boolean isValidClassFileName(String name)
{
@ -926,7 +926,7 @@ public class AnnotationParser
return false;
// skip anything that is not a class file
// TODO: exists in MultiReleaseJarFile.isClassFile(Path)
// TODO: exists in FileID.isClassFile(Path) use consistently in both ZipFS and Directory cases
String lc = name.toLowerCase(Locale.ENGLISH);
if (!lc.endsWith(".class"))
{
@ -935,7 +935,7 @@ public class AnnotationParser
return false;
}
// TODO: exists in MultiReleaseJarFile.notModuleInfoClass(Path)
// TODO: exists in FileID.notModuleInfoClass(Path) use consistently in both ZipFS and Directory cases
if (lc.equals("module-info.class"))
{
if (LOG.isDebugEnabled())
@ -944,7 +944,7 @@ public class AnnotationParser
}
// skip any classfiles that are not a valid java identifier
// TODO: exists in MultiReleaseJarFile.isClassFile(Path)
// TODO: exists in FileID.isClassFile(Path) use consistently in both ZipFS and Directory cases
int c0 = 0;
int ldir = name.lastIndexOf('/', name.length() - 6);
c0 = (ldir > -1 ? ldir + 1 : c0);
@ -963,7 +963,7 @@ public class AnnotationParser
*
* @param path the class file path
* @return whether the class file path is valid
* TODO: remove, handled by MultiReleaseJarFile.skipHiddenDirs
* TODO: remove, handled by FileID.skipHiddenDirs
*/
public boolean isValidClassFilePath(String path)
{

View File

@ -294,7 +294,7 @@ public class MavenWebAppContext extends WebAppContext
_webInfJars.forEach(f ->
{
// ensure our JAR file references are `jar:file:...` URI references
URI jarFileUri = Resource.toJarFileUri(f.toURI());
URI jarFileUri = URIUtil.toJarFileUri(f.toURI());
// else use file uri as-is
_classpathUris.add(Objects.requireNonNullElseGet(jarFileUri, f::toURI));
});

View File

@ -28,6 +28,7 @@ import java.util.stream.Collectors;
import org.eclipse.jetty.ee10.quickstart.QuickStartConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.xml.XmlConfiguration;
@ -224,7 +225,7 @@ public class WebAppPropertyConverter
if (!StringUtil.isBlank(str))
{
// This is a use provided list of overlays, which could have mountable entries.
List<URI> uris = Resource.split(str);
List<URI> uris = URIUtil.split(str);
// TODO: need a better place to close/release this mount.
Resource.Mount mount = Resource.mountCollection(uris);
webApp.addBean(mount); // let jetty-core ContextHandler.doStop() release mount

View File

@ -23,6 +23,7 @@ import org.eclipse.jetty.ee10.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.ee10.webapp.MetaInfConfiguration;
import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.slf4j.Logger;
@ -95,7 +96,7 @@ public class PreconfigureQuickStartWar
if (!dir.exists())
Files.createDirectories(dir.getPath());
URI jarUri = Resource.toJarFileUri(war.getURI());
URI jarUri = URIUtil.toJarFileUri(war.getURI());
try (Resource.Mount warMount = Resource.mount(jarUri))
{
// unpack contents of war to directory

View File

@ -738,7 +738,7 @@ public class ClassMatcher extends AbstractSet<String>
{
try
{
return URIUtil.getJarSource(url.toURI());
return URIUtil.unwrapContainer(url.toURI());
}
catch (URISyntaxException ignored)
{

View File

@ -23,6 +23,7 @@ import java.util.Map;
import java.util.Objects;
import jakarta.servlet.ServletContext;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
@ -458,7 +459,7 @@ public class MetaData
List<String> orderedLibs = new ArrayList<>();
for (Resource jar: orderedWebInfJars)
{
URI uri = Resource.unwrapContainer(jar.getURI());
URI uri = URIUtil.unwrapContainer(jar.getURI());
orderedLibs.add(uri.getPath());
}
context.setAttribute(ServletContext.ORDERED_LIBS, Collections.unmodifiableList(orderedLibs));

View File

@ -36,6 +36,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.PatternMatcher;
import org.eclipse.jetty.util.StringUtil;
@ -685,7 +686,7 @@ public class MetaInfConfiguration extends AbstractConfiguration
try (Stream<Path> entries = Files.walk(dir)
.filter(Files::isRegularFile)
.filter(MetaInfConfiguration::isTldFile))
.filter(path -> FileID.isTldFile(path)))
{
Iterator<Path> iter = entries.iterator();
while (iter.hasNext())
@ -714,7 +715,7 @@ public class MetaInfConfiguration extends AbstractConfiguration
try (Stream<Path> stream = Files.walk(mount.root().getPath()))
{
Iterator<Path> it = stream
.filter(MetaInfConfiguration::isTldFile)
.filter(path -> FileID.isTldFile(path))
.iterator();
while (it.hasNext())
{
@ -725,18 +726,6 @@ public class MetaInfConfiguration extends AbstractConfiguration
return tlds;
}
private static boolean isTldFile(Path path)
{
if (!Files.isRegularFile(path))
return false;
if (path.getNameCount() < 2)
return false;
if (!path.getName(0).toString().equalsIgnoreCase("META-INF"))
return false;
String filename = path.getFileName().toString();
return filename.toLowerCase(Locale.ENGLISH).endsWith(".tld");
}
protected List<Resource> findClassDirs(WebAppContext context)
throws Exception
{
@ -884,6 +873,6 @@ public class MetaInfConfiguration extends AbstractConfiguration
private boolean isFileSupported(Resource resource)
{
return Resource.isArchive(resource.getURI());
return FileID.isArchive(resource.getURI());
}
}

View File

@ -41,6 +41,7 @@ import org.eclipse.jetty.util.ClassVisibilityChecker;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.slf4j.Logger;
@ -271,7 +272,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
if (classPath == null)
return;
List<URI> uris = Resource.split(classPath);
List<URI> uris = URIUtil.split(classPath);
_mountedExtraClassPath = Resource.mountCollection(uris);
ResourceCollection rc = (ResourceCollection)_mountedExtraClassPath.root();
@ -324,7 +325,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
{
if (LOG.isDebugEnabled())
LOG.debug("addJar - {}", jar);
URI jarUri = Resource.toJarFileUri(jar.toUri());
URI jarUri = URIUtil.toJarFileUri(jar.toUri());
addClassPath(jarUri.toASCIIString());
}
catch (Exception ex)

View File

@ -1244,7 +1244,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
*/
public void setExtraClasspath(String extraClasspath)
{
List<URI> uris = Resource.split(extraClasspath);
List<URI> uris = URIUtil.split(extraClasspath);
Resource.Mount mount = Resource.mountCollection(uris);
addBean(mount); // let doStop() cleanup mount
setExtraClasspath((ResourceCollection)mount.root());

View File

@ -44,6 +44,8 @@ import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.Resource;
import org.hamcrest.Matchers;
@ -517,10 +519,10 @@ public class WebAppContextTest
{
expectedUris = s
.filter(Files::isRegularFile)
.filter((path) -> path.getFileName().toString().endsWith(".jar"))
.filter(FileID::isJavaArchive)
.sorted(Comparator.naturalOrder())
.map(Path::toUri)
.map(Resource::toJarFileUri)
.map(URIUtil::toJarFileUri)
.collect(Collectors.toList());
}
List<URI> actualURIs = new ArrayList<>();

View File

@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.MultiReleaseJarFile;
import org.eclipse.jetty.util.StringUtil;
@ -817,8 +818,7 @@ public class AnnotationParser
if (jarResource == null)
return;
// TODO: what if the input is "FOO.JAR" or "Bar.Jar" ? - need to use FileID.isArchive() or FileID.isJar()
if (jarResource.toString().endsWith(".jar"))
if (FileID.isJavaArchive(jarResource.getPath()))
{
if (LOG.isDebugEnabled())
LOG.debug("Scanning jar {}", jarResource);
@ -827,9 +827,9 @@ public class AnnotationParser
try (MultiReleaseJarFile jarFile = new MultiReleaseJarFile(jarResource.getPath());
Stream<Path> jarEntryStream = jarFile.stream()
.filter(Files::isRegularFile)
.filter(MultiReleaseJarFile::skipModuleInfoClass)
.filter(MultiReleaseJarFile::skipMetaInfVersions)
.filter(MultiReleaseJarFile::isClassFile))
.filter(FileID::skipModuleInfoClass)
.filter(FileID::skipMetaInfVersions)
.filter(FileID::isClassFile))
{
jarEntryStream.forEach(e ->
{
@ -915,7 +915,7 @@ public class AnnotationParser
*
* @param name the class file name
* @return whether the class file name is valid
* TODO: does multiple things, we should be able to move this FileID in future PR
* TODO: does multiple things, we should be able to eliminate this in future PR by consolidating the ZipFS and Directory scanning behaviors into a single Files.walk() stream
*/
public boolean isValidClassFileName(String name)
{
@ -924,7 +924,7 @@ public class AnnotationParser
return false;
//skip anything that is not a class file
// TODO: exists in MultiReleaseJarFile.isClassFile(Path)
// TODO: exists in FileID.isClassFile(Path) use consistently in both ZipFS and Directory cases
String lc = name.toLowerCase(Locale.ENGLISH);
if (!lc.endsWith(".class"))
{
@ -933,7 +933,7 @@ public class AnnotationParser
return false;
}
// TODO: exists in MultiReleaseJarFile.notModuleInfoClass(Path)
// TODO: exists in FileID.notModuleInfoClass(Path) use consistently in both ZipFS and Directory cases
if (lc.equals("module-info.class"))
{
if (LOG.isDebugEnabled())
@ -942,7 +942,7 @@ public class AnnotationParser
}
//skip any classfiles that are not a valid java identifier
// TODO: exists in MultiReleaseJarFile.isClassFile(Path)
// TODO: exists in FileID.isClassFile(Path) use consistently in both ZipFS and Directory cases
int c0 = 0;
int ldir = name.lastIndexOf('/', name.length() - 6);
c0 = (ldir > -1 ? ldir + 1 : c0);
@ -961,7 +961,7 @@ public class AnnotationParser
*
* @param path the class file path
* @return whether the class file path is valid
* TODO: remove, handled by MultiReleaseJarFile.skipHiddenDirs
* TODO: remove, handled by FileID.skipHiddenDirs
*/
public boolean isValidClassFilePath(String path)
{

View File

@ -294,7 +294,7 @@ public class MavenWebAppContext extends WebAppContext
_webInfJars.forEach(f ->
{
// ensure our JAR file references are `jar:file:...` URI references
URI jarFileUri = Resource.toJarFileUri(f.toURI());
URI jarFileUri = URIUtil.toJarFileUri(f.toURI());
// else use file uri as-is
_classpathUris.add(Objects.requireNonNullElseGet(jarFileUri, f::toURI));
});

View File

@ -28,6 +28,7 @@ import java.util.stream.Collectors;
import org.eclipse.jetty.ee9.quickstart.QuickStartConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.xml.XmlConfiguration;
@ -224,7 +225,7 @@ public class WebAppPropertyConverter
if (!StringUtil.isBlank(str))
{
// This is a use provided list of overlays, which could have mountable entries.
List<URI> uris = Resource.split(str);
List<URI> uris = URIUtil.split(str);
// TODO: need a better place to close/release this mount.
Resource.Mount mount = Resource.mountCollection(uris);
webApp.addBean(mount); // let ee9 ContextHandler.doStop() release mount

View File

@ -738,7 +738,7 @@ public class ClassMatcher extends AbstractSet<String>
{
try
{
return URIUtil.getJarSource(url.toURI());
return URIUtil.unwrapContainer(url.toURI());
}
catch (URISyntaxException ignored)
{

View File

@ -23,6 +23,7 @@ import java.util.Map;
import java.util.Objects;
import jakarta.servlet.ServletContext;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
@ -458,7 +459,7 @@ public class MetaData
List<String> orderedLibs = new ArrayList<>();
for (Resource jar: orderedWebInfJars)
{
URI uri = Resource.unwrapContainer(jar.getURI());
URI uri = URIUtil.unwrapContainer(jar.getURI());
orderedLibs.add(uri.getPath());
}
context.setAttribute(ServletContext.ORDERED_LIBS, Collections.unmodifiableList(orderedLibs));

View File

@ -36,6 +36,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.PatternMatcher;
import org.eclipse.jetty.util.StringUtil;
@ -668,7 +669,7 @@ public class MetaInfConfiguration extends AbstractConfiguration
try (Stream<Path> entries = Files.walk(dir)
.filter(Files::isRegularFile)
.filter(MetaInfConfiguration::isTldFile))
.filter(path -> FileID.isTldFile(path)))
{
Iterator<Path> iter = entries.iterator();
while (iter.hasNext())
@ -698,7 +699,7 @@ public class MetaInfConfiguration extends AbstractConfiguration
try (Stream<Path> stream = Files.walk(mount.root().getPath()))
{
Iterator<Path> it = stream
.filter(MetaInfConfiguration::isTldFile)
.filter(path -> FileID.isTldFile(path))
.iterator();
while (it.hasNext())
{
@ -709,18 +710,6 @@ public class MetaInfConfiguration extends AbstractConfiguration
return tlds;
}
private static boolean isTldFile(Path path)
{
if (!Files.isRegularFile(path))
return false;
if (path.getNameCount() < 2)
return false;
if (!path.getName(0).toString().equalsIgnoreCase("META-INF"))
return false;
String filename = path.getFileName().toString();
return filename.toLowerCase(Locale.ENGLISH).endsWith(".tld");
}
protected List<Resource> findClassDirs(WebAppContext context)
throws Exception
{
@ -871,6 +860,6 @@ public class MetaInfConfiguration extends AbstractConfiguration
private boolean isFileSupported(Resource resource)
{
return Resource.isArchive(resource.getURI());
return FileID.isArchive(resource.getURI());
}
}

View File

@ -41,6 +41,7 @@ import org.eclipse.jetty.util.ClassVisibilityChecker;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.slf4j.Logger;
@ -270,7 +271,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
if (classPath == null)
return;
List<URI> uris = Resource.split(classPath);
List<URI> uris = URIUtil.split(classPath);
_mountedExtraClassPath = Resource.mountCollection(uris);
ResourceCollection rc = (ResourceCollection)_mountedExtraClassPath.root();
@ -324,7 +325,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
{
if (LOG.isDebugEnabled())
LOG.debug("addJar - {}", jar);
URI jarUri = Resource.toJarFileUri(jar.toUri());
URI jarUri = URIUtil.toJarFileUri(jar.toUri());
addClassPath(jarUri.toASCIIString());
}
catch (Exception ex)

View File

@ -1251,7 +1251,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
*/
public void setExtraClasspath(String extraClasspath) throws IOException
{
List<URI> uris = Resource.split(extraClasspath);
List<URI> uris = URIUtil.split(extraClasspath);
_mountedExtraClasspath = Resource.mountCollection(uris);
setExtraClasspath((ResourceCollection)_mountedExtraClasspath.root());
}

View File

@ -44,6 +44,7 @@ import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.Resource;
import org.hamcrest.Matchers;
@ -526,7 +527,7 @@ public class WebAppContextTest
.filter((path) -> path.getFileName().toString().endsWith(".jar"))
.sorted(Comparator.naturalOrder())
.map(Path::toUri)
.map(Resource::toJarFileUri)
.map(URIUtil::toJarFileUri)
.collect(Collectors.toList());
}
List<URI> actualURIs = new ArrayList<>();