Jetty 12.0.x overlay combined resources (#10760)
* Fix overlays with CombinedResource * Improvements to CombinedResource for AnnotationParser * Added `contains` and `getPathTo` methods to the Resource API, so they can be used by the AnnotationParser * Fixed numerous bugs in CombinedResource list and getAllResources * handles cross file system copy as well. * Reduced fallback to URI string manipulation. --------- Co-authored-by: gregw <gregw@webtide.com> Co-authored-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
parent
1c2d7cee07
commit
3ae43961b8
|
@ -99,7 +99,7 @@ public class ResourceListing
|
|||
{
|
||||
case "M" -> ResourceCollators.byLastModified(sortOrderAscending);
|
||||
case "S" -> ResourceCollators.bySize(sortOrderAscending);
|
||||
default -> ResourceCollators.byName(sortOrderAscending);
|
||||
default -> ResourceCollators.byFileName(sortOrderAscending);
|
||||
};
|
||||
listing.sort(sort);
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.net.URI;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Simple, yet surprisingly common utility methods for identifying various file types commonly seen and worked with in a
|
||||
|
@ -26,6 +27,8 @@ import java.util.Locale;
|
|||
*/
|
||||
public class FileID
|
||||
{
|
||||
private static final Pattern NUMBER = Pattern.compile("[0-9]+");
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -356,7 +359,7 @@ public class FileID
|
|||
}
|
||||
|
||||
/**
|
||||
* Predicate to select all class files
|
||||
* Predicate to test for class files
|
||||
*
|
||||
* @param path the path to test
|
||||
* @return true if the filename ends with {@code .class}
|
||||
|
@ -369,7 +372,7 @@ public class FileID
|
|||
|
||||
String filename = fileNamePath.toString();
|
||||
// has to end in ".class"
|
||||
if (!filename.toLowerCase(Locale.ENGLISH).endsWith(".class"))
|
||||
if (!StringUtil.asciiEndsWithIgnoreCase(filename, ".class"))
|
||||
return false;
|
||||
// is it a valid class filename?
|
||||
int start = 0;
|
||||
|
@ -395,20 +398,16 @@ public class FileID
|
|||
* 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
|
||||
* @param path the possibly relative 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)
|
||||
public static boolean isHidden(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();
|
||||
int count = path.getNameCount();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Path segment = relative.getName(i);
|
||||
Path segment = path.getName(i);
|
||||
|
||||
String segmentName = segment.toString();
|
||||
|
||||
|
@ -430,10 +429,24 @@ public class FileID
|
|||
// ignore, if filesystem gives us an error, we cannot make the call on hidden status
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
return isHidden(base.relativize(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the URI pointing to a Java Archive (JAR) File (not directory)
|
||||
*
|
||||
|
@ -482,13 +495,13 @@ public class FileID
|
|||
if (path.getNameCount() < 3)
|
||||
return false;
|
||||
|
||||
Path path0 = path.getName(0);
|
||||
Path path1 = path.getName(1);
|
||||
Path path2 = path.getName(2);
|
||||
if (!StringUtil.asciiEqualsIgnoreCase("META-INF", path.getName(0).toString()))
|
||||
return false;
|
||||
|
||||
return (path0.toString().equals("META-INF") &&
|
||||
path1.toString().equals("versions") &&
|
||||
path2.getFileName().toString().matches("[0-9]+"));
|
||||
if (!StringUtil.asciiEqualsIgnoreCase("versions", path.getName(1).toString()))
|
||||
return false;
|
||||
|
||||
return NUMBER.matcher(path.getName(2).getFileName().toString()).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -516,13 +529,28 @@ public class FileID
|
|||
* @param path the path to test
|
||||
* @return true if not a {@code module-info.class} file
|
||||
*/
|
||||
public static boolean isNotModuleInfoClass(Path path)
|
||||
public static boolean isModuleInfoClass(Path path)
|
||||
{
|
||||
Path filenameSegment = path.getFileName();
|
||||
if (filenameSegment == null)
|
||||
return true;
|
||||
return false;
|
||||
|
||||
return !filenameSegment.toString().equalsIgnoreCase("module-info.class");
|
||||
return filenameSegment.toString().equalsIgnoreCase("module-info.class");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 isNotModuleInfoClass(Path path)
|
||||
{
|
||||
return !isModuleInfoClass(path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,20 +14,31 @@
|
|||
package org.eclipse.jetty.util.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
|
||||
/**
|
||||
* Multiple resource directories presented as a single Resource.
|
||||
* Multiple {@link Resource} directories presented as a single overlayed {@link Resource} directory.
|
||||
* <p>This class differs from a {@link List}<{@link Resource}>, as a relative path can {@link #resolve(String) resolve}
|
||||
* to only a single {@link Resource} in a {@code CombinedResource}, whilst it may resolve to multiple in a
|
||||
* {@link List}<{@link Resource}>.</p>
|
||||
*/
|
||||
public class CombinedResource extends Resource
|
||||
{
|
||||
|
@ -252,24 +263,58 @@ public class CombinedResource extends Resource
|
|||
@Override
|
||||
public List<Resource> list()
|
||||
{
|
||||
List<Resource> result = new ArrayList<>();
|
||||
for (Resource r : _resources)
|
||||
Map<String, Resource> results = new TreeMap<>();
|
||||
for (Resource base : _resources)
|
||||
{
|
||||
if (r.isDirectory())
|
||||
result.addAll(r.list());
|
||||
else
|
||||
result.add(r);
|
||||
for (Resource r : base.list())
|
||||
{
|
||||
if (r.isDirectory())
|
||||
results.computeIfAbsent(r.getFileName(), this::resolve);
|
||||
else
|
||||
results.putIfAbsent(r.getFileName(), r);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return new ArrayList<>(results.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(Path destination) throws IOException
|
||||
{
|
||||
// Copy in reverse order
|
||||
for (int r = _resources.size(); r-- > 0; )
|
||||
// This method could be implemented with the simple:
|
||||
// List<Resource> entries = getResources();
|
||||
// for (int r = entries.size(); r-- > 0; )
|
||||
// entries.get(r).copyTo(destination);
|
||||
// However, that may copy large overlayed resources. The implementation below avoids that:
|
||||
|
||||
Collection<Resource> all = getAllResources();
|
||||
for (Resource r : all)
|
||||
{
|
||||
_resources.get(r).copyTo(destination);
|
||||
Path relative = getPathTo(r);
|
||||
Path pathTo = Objects.equals(relative.getFileSystem(), destination.getFileSystem())
|
||||
? destination.resolve(relative)
|
||||
: resolveDifferentFileSystem(destination, relative);
|
||||
|
||||
if (r.isDirectory())
|
||||
{
|
||||
ensureDirExists(pathTo);
|
||||
}
|
||||
else
|
||||
{
|
||||
ensureDirExists(pathTo.getParent());
|
||||
Path pathFrom = r.getPath();
|
||||
if (pathFrom != null)
|
||||
{
|
||||
Files.copy(pathFrom, pathTo, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
else
|
||||
{
|
||||
// use old school stream based copy
|
||||
try (InputStream in = r.newInputStream(); OutputStream out = Files.newOutputStream(pathTo))
|
||||
{
|
||||
IO.copy(in, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,9 +347,60 @@ public class CombinedResource extends Resource
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedIn(Resource r)
|
||||
public boolean contains(Resource other)
|
||||
{
|
||||
// TODO could look at implementing the semantic of is this collection a subset of the Resource r?
|
||||
return false;
|
||||
// Every resource from the (possibly combined) other resource ...
|
||||
loop: for (Resource o : other)
|
||||
{
|
||||
// Must be contained in at least one of this resources
|
||||
for (Resource r : _resources)
|
||||
{
|
||||
if (r.contains(o))
|
||||
continue loop;
|
||||
}
|
||||
// A resource of the other did not match any in this
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPathTo(Resource other)
|
||||
{
|
||||
Path otherPath = other.getPath();
|
||||
|
||||
// If the other resource has a single Path
|
||||
if (otherPath != null)
|
||||
{
|
||||
// return true it's relative location to the first matching resource.
|
||||
for (Resource r : _resources)
|
||||
{
|
||||
Path path = r.getPath();
|
||||
if (otherPath.startsWith(path))
|
||||
return path.relativize(otherPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// otherwise the other resource must also be some kind of combined resource.
|
||||
// So every resource in the other combined must have the same relative relationship to us
|
||||
Path relative = null;
|
||||
loop : for (Resource o : other)
|
||||
{
|
||||
for (Resource r : _resources)
|
||||
{
|
||||
if (o.getPath().startsWith(r.getPath()))
|
||||
{
|
||||
Path rel = r.getPath().relativize(o.getPath());
|
||||
if (relative == null)
|
||||
relative = rel;
|
||||
else if (!relative.equals(rel))
|
||||
return null;
|
||||
continue loop;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return relative;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ public class MemoryResource extends Resource
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedIn(Resource r)
|
||||
public boolean isContainedIn(Resource container)
|
||||
{
|
||||
// memory resource can never be contained in another memory resource
|
||||
return false;
|
||||
|
|
|
@ -47,9 +47,9 @@ public class MountedPathResource extends PathResource
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedIn(Resource r)
|
||||
public boolean isContainedIn(Resource container)
|
||||
{
|
||||
return URIUtil.unwrapContainer(r.getURI()).equals(containerUri);
|
||||
return URIUtil.unwrapContainer(container.getURI()).equals(containerUri);
|
||||
}
|
||||
|
||||
public Path getContainerPath()
|
||||
|
|
|
@ -16,14 +16,10 @@ package org.eclipse.jetty.util.resource;
|
|||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.DirectoryIteratorException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
|
@ -204,6 +200,22 @@ public class PathResource extends Resource
|
|||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Resource other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
Path thisPath = getPath();
|
||||
if (thisPath == null)
|
||||
throw new UnsupportedOperationException("Resources without a Path must implement contains");
|
||||
|
||||
Path otherPath = other.getPath();
|
||||
return otherPath != null &&
|
||||
otherPath.getFileSystem().equals(thisPath.getFileSystem()) &&
|
||||
otherPath.startsWith(thisPath);
|
||||
}
|
||||
|
||||
public Path getRealPath()
|
||||
{
|
||||
resolveAlias();
|
||||
|
@ -343,14 +355,6 @@ public class PathResource extends Resource
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedIn(Resource r)
|
||||
{
|
||||
if (r == null)
|
||||
return false;
|
||||
return r.getClass() == PathResource.class && path.startsWith(r.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Perform a check of the original Path and as-requested URI to determine
|
||||
|
@ -489,16 +493,6 @@ public class PathResource extends Resource
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(Path destination) throws IOException
|
||||
{
|
||||
// TODO reconcile this impl with super's
|
||||
if (isDirectory())
|
||||
Files.walkFileTree(this.path, new TreeCopyFileVisitor(this.path, destination));
|
||||
else
|
||||
Files.copy(this.path, destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure Path to URI is sane when it returns a directory reference.
|
||||
*
|
||||
|
@ -547,39 +541,4 @@ public class PathResource extends Resource
|
|||
{
|
||||
return this.uri.toASCIIString();
|
||||
}
|
||||
|
||||
private static class TreeCopyFileVisitor extends SimpleFileVisitor<Path>
|
||||
{
|
||||
private final String relativeTo;
|
||||
private final Path target;
|
||||
|
||||
public TreeCopyFileVisitor(Path relativeTo, Path target)
|
||||
{
|
||||
this.relativeTo = relativeTo.getRoot().relativize(relativeTo).toString();
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException
|
||||
{
|
||||
Path resolvedTarget = target.resolve(dir.getRoot().resolve(relativeTo).relativize(dir).toString());
|
||||
if (Files.notExists(resolvedTarget))
|
||||
Files.createDirectories(resolvedTarget);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException
|
||||
{
|
||||
Path resolvedTarget = target.resolve(file.getRoot().resolve(relativeTo).relativize(file).toString());
|
||||
Files.copy(file, resolvedTarget, StandardCopyOption.REPLACE_EXISTING);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFileFailed(Path file, IOException exc)
|
||||
{
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,15 @@ import java.util.ArrayList;
|
|||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
@ -41,6 +48,7 @@ import org.eclipse.jetty.util.IO;
|
|||
*/
|
||||
public abstract class Resource implements Iterable<Resource>
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Resource.class);
|
||||
private static final LinkOption[] NO_FOLLOW_LINKS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
|
||||
|
||||
public static String dump(Resource resource)
|
||||
|
@ -54,7 +62,7 @@ public abstract class Resource implements Iterable<Resource>
|
|||
/**
|
||||
* Return the Path corresponding to this resource.
|
||||
*
|
||||
* @return the path.
|
||||
* @return the path or null if there is no Path representation.
|
||||
*/
|
||||
public abstract Path getPath();
|
||||
|
||||
|
@ -62,14 +70,78 @@ public abstract class Resource implements Iterable<Resource>
|
|||
* Return true if this resource is contained in the Resource r, either because
|
||||
* r is a folder or a jar file or any form of resource capable of containing other resources.
|
||||
*
|
||||
* @param r the containing resource
|
||||
* @param container the containing resource
|
||||
* @return true if this Resource is contained, false otherwise
|
||||
* @see #contains(Resource)
|
||||
*/
|
||||
public abstract boolean isContainedIn(Resource r);
|
||||
public boolean isContainedIn(Resource container)
|
||||
{
|
||||
return container != null && container.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this resource deeply contains the other Resource. This resource must be
|
||||
* a directory or a jar file or any form of resource capable of containing other resources.
|
||||
*
|
||||
* @param other the resource
|
||||
* @return true if this Resource is deeply contains the other Resource, false otherwise
|
||||
* @see #isContainedIn(Resource)
|
||||
*/
|
||||
public boolean contains(Resource other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
URI thisURI = getURI();
|
||||
if (thisURI == null)
|
||||
throw new UnsupportedOperationException("Resources without a URI must implement contains");
|
||||
|
||||
URI otherURI = other.getURI();
|
||||
if (otherURI == null)
|
||||
return false;
|
||||
|
||||
// Different schemes? not a chance it contains the other
|
||||
if (!StringUtil.asciiEqualsIgnoreCase(thisURI.getScheme(), otherURI.getScheme()))
|
||||
return false;
|
||||
|
||||
// Different authorities? not a valid contains() check
|
||||
if (!Objects.equals(thisURI.getAuthority(), otherURI.getAuthority()))
|
||||
return false;
|
||||
|
||||
// Ensure that if `file` scheme is used, it's using a consistent convention to allow for startsWith check
|
||||
String thisURIString = URIUtil.correctFileURI(thisURI).toASCIIString();
|
||||
String otherURIString = URIUtil.correctFileURI(otherURI).toASCIIString();
|
||||
|
||||
return otherURIString.startsWith(thisURIString) &&
|
||||
(thisURIString.length() == otherURIString.length() || otherURIString.charAt(thisURIString.length()) == '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relative path from this Resource to a possibly contained resource.
|
||||
* @param other The other resource that may be contained in this resource
|
||||
* @return a relative Path representing the path from this resource to the other resource,
|
||||
* or null if not able to represent other resources as relative to this resource
|
||||
*/
|
||||
public Path getPathTo(Resource other)
|
||||
{
|
||||
Path thisPath = getPath();
|
||||
if (thisPath == null)
|
||||
throw new UnsupportedOperationException("Resources without a Path must implement getPathTo");
|
||||
|
||||
if (!contains(other))
|
||||
return null;
|
||||
|
||||
Path otherPath = other.getPath();
|
||||
if (otherPath == null)
|
||||
return null;
|
||||
|
||||
return thisPath.relativize(otherPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Return an Iterator of all Resource's referenced in this Resource.</p>
|
||||
* <p>This is meaningful if you have a Composite Resource, otherwise it will be a single entry Iterator of this resource.</p>
|
||||
* <p>This is meaningful if you have a {@link CombinedResource},
|
||||
* otherwise it will be a single entry Iterator of this resource.</p>
|
||||
*
|
||||
* @return the iterator of Resources.
|
||||
*/
|
||||
|
@ -127,14 +199,14 @@ public abstract class Resource implements Iterable<Resource>
|
|||
/**
|
||||
* URI representing the resource.
|
||||
*
|
||||
* @return a URI representing the given resource
|
||||
* @return a URI representing the given resource, or null if there is no URI representation of the resource.
|
||||
*/
|
||||
public abstract URI getURI();
|
||||
|
||||
/**
|
||||
* The full name of the resource.
|
||||
*
|
||||
* @return the full name of the resource, or null if not backed by a Path
|
||||
* @return the full name of the resource, or null if there is no name for the resource.
|
||||
*/
|
||||
public abstract String getName();
|
||||
|
||||
|
@ -143,7 +215,8 @@ public abstract class Resource implements Iterable<Resource>
|
|||
*
|
||||
* <p>This is the last segment of the path.</p>
|
||||
*
|
||||
* @return the filename of the resource, or "" if there are no path segments (eg: path of "/"), or null if resource has no path.
|
||||
* @return the filename of the resource, or "" if there are no path segments (eg: path of "/"), or null if resource
|
||||
* cannot determine a filename.
|
||||
* @see Path#getFileName()
|
||||
*/
|
||||
public abstract String getFileName();
|
||||
|
@ -222,49 +295,126 @@ public abstract class Resource implements Iterable<Resource>
|
|||
}
|
||||
|
||||
/**
|
||||
* Copy the Resource to the new destination file.
|
||||
* <p>
|
||||
* Will not replace existing destination file.
|
||||
* Copy the Resource to the new destination file or directory
|
||||
*
|
||||
* @param destination the destination file to create
|
||||
* @param destination the destination file to create or directory to use.
|
||||
* @throws IOException if unable to copy the resource
|
||||
*/
|
||||
public void copyTo(Path destination)
|
||||
throws IOException
|
||||
{
|
||||
if (Files.exists(destination))
|
||||
throw new IllegalArgumentException(destination + " exists");
|
||||
|
||||
// attempt simple file copy
|
||||
Path src = getPath();
|
||||
if (src != null)
|
||||
if (src == null)
|
||||
{
|
||||
// TODO ATOMIC_MOVE seems useless for a copy and REPLACE_EXISTING contradicts the
|
||||
// javadoc that explicitly states "Will not replace existing destination file."
|
||||
Files.copy(src, destination,
|
||||
StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
|
||||
if (!isDirectory())
|
||||
{
|
||||
// use old school stream based copy
|
||||
try (InputStream in = newInputStream(); OutputStream out = Files.newOutputStream(destination))
|
||||
{
|
||||
IO.copy(in, out);
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new UnsupportedOperationException("Directory Resources without a Path must implement copyTo");
|
||||
}
|
||||
|
||||
// Do we have to copy a single file?
|
||||
if (Files.isRegularFile(src))
|
||||
{
|
||||
// Is the destination a directory?
|
||||
if (Files.isDirectory(destination))
|
||||
{
|
||||
// to a directory, preserve the filename
|
||||
Path destPath = destination.resolve(src.getFileName().toString());
|
||||
Files.copy(src, destPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
else
|
||||
{
|
||||
// to a file, use destination as-is
|
||||
Files.copy(src, destination, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// use old school stream based copy
|
||||
try (InputStream in = newInputStream();
|
||||
OutputStream out = Files.newOutputStream(destination))
|
||||
// At this point this PathResource is a directory.
|
||||
assert isDirectory();
|
||||
|
||||
BiFunction<Path, Path, Path> resolver = src.getFileSystem().equals(destination.getFileSystem())
|
||||
? Path::resolve
|
||||
: Resource::resolveDifferentFileSystem;
|
||||
|
||||
try (Stream<Path> entriesStream = Files.walk(src))
|
||||
{
|
||||
IO.copy(in, out);
|
||||
for (Iterator<Path> pathIterator = entriesStream.iterator(); pathIterator.hasNext();)
|
||||
{
|
||||
Path path = pathIterator.next();
|
||||
if (src.equals(path))
|
||||
continue;
|
||||
|
||||
Path relative = src.relativize(path);
|
||||
Path destPath = resolver.apply(destination, relative);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("CopyTo: {} > {}", path, destPath);
|
||||
if (Files.isDirectory(path))
|
||||
{
|
||||
ensureDirExists(destPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
ensureDirExists(destPath.getParent());
|
||||
Files.copy(path, destPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Path resolveDifferentFileSystem(Path path, Path relative)
|
||||
{
|
||||
for (Path segment : relative)
|
||||
path = path.resolve(segment.toString());
|
||||
return path;
|
||||
}
|
||||
|
||||
void ensureDirExists(Path dir) throws IOException
|
||||
{
|
||||
if (Files.exists(dir))
|
||||
{
|
||||
if (!Files.isDirectory(dir))
|
||||
{
|
||||
throw new IOException("Conflict, unable to create directory where file exists: " + dir);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Files.createDirectories(dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a deep collection of contained resources.
|
||||
* @return A collection of all Resources deeply contained within this resource if it is a directory,
|
||||
* otherwise an empty collection is returned.
|
||||
*/
|
||||
public Collection<Resource> getAllResources()
|
||||
{
|
||||
try
|
||||
{
|
||||
List<Resource> children = list();
|
||||
if (children == null || children.isEmpty())
|
||||
return List.of();
|
||||
|
||||
boolean noDepth = true;
|
||||
|
||||
for (Iterator<Resource> i = children.iterator(); noDepth && i.hasNext(); )
|
||||
noDepth = !i.next().isDirectory();
|
||||
if (noDepth)
|
||||
return children;
|
||||
|
||||
ArrayList<Resource> deep = new ArrayList<>();
|
||||
for (Resource r: list())
|
||||
for (Resource r: children)
|
||||
{
|
||||
deep.add(r);
|
||||
if (r.isDirectory())
|
||||
deep.addAll(r.getAllResources());
|
||||
else
|
||||
deep.add(r);
|
||||
}
|
||||
return deep;
|
||||
}
|
||||
|
|
|
@ -17,10 +17,11 @@ import java.text.Collator;
|
|||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ResourceCollators
|
||||
{
|
||||
private static Comparator<? super Resource> BY_NAME_ASCENDING =
|
||||
private static final Comparator<? super Resource> BY_NAME_ASCENDING =
|
||||
new Comparator<>()
|
||||
{
|
||||
private final Collator collator = Collator.getInstance(Locale.ENGLISH);
|
||||
|
@ -28,11 +29,11 @@ public class ResourceCollators
|
|||
@Override
|
||||
public int compare(Resource o1, Resource o2)
|
||||
{
|
||||
return collator.compare(o1.getName(), o2.getName());
|
||||
return collator.compare(Objects.requireNonNullElse(o1.getName(), ""), Objects.requireNonNullElse(o2.getName(), ""));
|
||||
}
|
||||
};
|
||||
|
||||
private static Comparator<? super Resource> BY_FILENAME_ASCENDING =
|
||||
private static final Comparator<? super Resource> BY_FILENAME_ASCENDING =
|
||||
new Comparator<>()
|
||||
{
|
||||
private final Collator collator = Collator.getInstance(Locale.ENGLISH);
|
||||
|
@ -44,22 +45,22 @@ public class ResourceCollators
|
|||
}
|
||||
};
|
||||
|
||||
private static Comparator<? super Resource> BY_NAME_DESCENDING =
|
||||
private static final Comparator<? super Resource> BY_NAME_DESCENDING =
|
||||
Collections.reverseOrder(BY_NAME_ASCENDING);
|
||||
|
||||
private static Comparator<? super Resource> BY_FILENAME_DESCENDING =
|
||||
private static final Comparator<? super Resource> BY_FILENAME_DESCENDING =
|
||||
Collections.reverseOrder(BY_FILENAME_ASCENDING);
|
||||
|
||||
private static Comparator<? super Resource> BY_LAST_MODIFIED_ASCENDING =
|
||||
private static final Comparator<? super Resource> BY_LAST_MODIFIED_ASCENDING =
|
||||
Comparator.comparing(Resource::lastModified);
|
||||
|
||||
private static Comparator<? super Resource> BY_LAST_MODIFIED_DESCENDING =
|
||||
private static final Comparator<? super Resource> BY_LAST_MODIFIED_DESCENDING =
|
||||
Collections.reverseOrder(BY_LAST_MODIFIED_ASCENDING);
|
||||
|
||||
private static Comparator<? super Resource> BY_SIZE_ASCENDING =
|
||||
private static final Comparator<? super Resource> BY_SIZE_ASCENDING =
|
||||
Comparator.comparingLong(Resource::length);
|
||||
|
||||
private static Comparator<? super Resource> BY_SIZE_DESCENDING =
|
||||
private static final Comparator<? super Resource> BY_SIZE_DESCENDING =
|
||||
Collections.reverseOrder(BY_SIZE_ASCENDING);
|
||||
|
||||
public static Comparator<? super Resource> byLastModified(boolean sortOrderAscending)
|
||||
|
|
|
@ -155,7 +155,7 @@ public class URLResourceFactory implements ResourceFactory
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedIn(Resource r)
|
||||
public boolean isContainedIn(Resource container)
|
||||
{
|
||||
// compare starting URIs?
|
||||
return false;
|
||||
|
|
|
@ -419,7 +419,9 @@ public class FileIDTest
|
|||
"/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"
|
||||
"/META-INF/versions/10/module-info.class",
|
||||
"/meta-inf/versions/10/Foo.class",
|
||||
"/meta-inf/VERSIONS/10/Zed.class",
|
||||
})
|
||||
public void testIsMetaInfVersions(String input) throws IOException
|
||||
{
|
||||
|
@ -510,8 +512,6 @@ public class FileIDTest
|
|||
"/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
|
||||
{
|
||||
|
|
|
@ -25,7 +25,6 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
|
@ -46,15 +45,15 @@ import static org.hamcrest.Matchers.empty;
|
|||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@ExtendWith(WorkDirExtension.class)
|
||||
public class CombinedResourceTest
|
||||
{
|
||||
|
||||
public WorkDir workDir;
|
||||
|
||||
private final ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable();
|
||||
|
@ -75,7 +74,6 @@ public class CombinedResourceTest
|
|||
@Test
|
||||
public void testList() throws Exception
|
||||
{
|
||||
Path testBaseDir = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource");
|
||||
Path one = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/one");
|
||||
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
||||
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
||||
|
@ -86,33 +84,38 @@ public class CombinedResourceTest
|
|||
resourceFactory.newResource(three)
|
||||
);
|
||||
|
||||
Function<Resource, String> relativizeToTestResources = (r) -> testBaseDir.toUri().relativize(r.getURI()).toASCIIString();
|
||||
|
||||
List<Resource> listing = rc.list();
|
||||
List<String> listingFilenames = listing.stream().map(relativizeToTestResources).toList();
|
||||
List<String> relative = listing.stream()
|
||||
.map(rc::getPathTo)
|
||||
.map(Path::toString)
|
||||
.toList();
|
||||
|
||||
String[] expected = new String[] {
|
||||
"one/dir/",
|
||||
"one/1.txt",
|
||||
"two/2.txt",
|
||||
"two/dir/",
|
||||
"two/1.txt",
|
||||
"three/3.txt",
|
||||
"three/2.txt",
|
||||
"three/dir/"
|
||||
"1.txt",
|
||||
"2.txt",
|
||||
"3.txt",
|
||||
"dir"
|
||||
};
|
||||
assertThat(relative, containsInAnyOrder(expected));
|
||||
|
||||
assertThat(listingFilenames, containsInAnyOrder(expected));
|
||||
for (Resource r : listing)
|
||||
{
|
||||
if ("dir".equals(r.getFileName()))
|
||||
assertThat(r, instanceOf(CombinedResource.class));
|
||||
}
|
||||
|
||||
listingFilenames = rc.resolve("dir").list().stream().map(relativizeToTestResources).toList();
|
||||
relative = rc.resolve("dir").list().stream()
|
||||
.map(rc::getPathTo)
|
||||
.map(Path::toString)
|
||||
.toList();
|
||||
|
||||
expected = new String[] {
|
||||
"one/dir/1.txt",
|
||||
"two/dir/2.txt",
|
||||
"three/dir/3.txt"
|
||||
"dir/1.txt",
|
||||
"dir/2.txt",
|
||||
"dir/3.txt"
|
||||
};
|
||||
|
||||
assertThat(listingFilenames, containsInAnyOrder(expected));
|
||||
assertThat(relative, containsInAnyOrder(expected));
|
||||
|
||||
Resource unk = rc.resolve("unknown");
|
||||
assertNull(unk);
|
||||
|
@ -137,11 +140,10 @@ public class CombinedResourceTest
|
|||
|
||||
// This should return a ResourceCollection with 3 `/dir/` sub-directories.
|
||||
Resource r = rc.resolve("dir");
|
||||
assertTrue(r instanceof CombinedResource);
|
||||
rc = (CombinedResource)r;
|
||||
assertEquals(getContent(rc, "1.txt"), "1 - one (in dir)");
|
||||
assertEquals(getContent(rc, "2.txt"), "2 - two (in dir)");
|
||||
assertEquals(getContent(rc, "3.txt"), "3 - three (in dir)");
|
||||
assertThat(r, instanceOf(CombinedResource.class));
|
||||
assertEquals(getContent(r, "1.txt"), "1 - one (in dir)");
|
||||
assertEquals(getContent(r, "2.txt"), "2 - two (in dir)");
|
||||
assertEquals(getContent(r, "3.txt"), "3 - three (in dir)");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -169,6 +171,125 @@ public class CombinedResourceTest
|
|||
assertEquals(getContent(r, "3.txt"), "3 - three (in dir)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of CombinedResource.copyTo(Resource) where the CombinedResource is a mix
|
||||
* of FileSystem types.
|
||||
*/
|
||||
@Test
|
||||
public void testCopyToDifferentFileSystem() throws Exception
|
||||
{
|
||||
Path testDir = workDir.getEmptyPathDir();
|
||||
|
||||
// Create a JAR file with contents
|
||||
Path testJar = testDir.resolve("test.jar");
|
||||
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
|
||||
URI jarUri = URIUtil.uriJarPrefix(testJar.toUri(), "!/");
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(jarUri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
Files.writeString(root.resolve("test.txt"), "Contents of test.txt", StandardCharsets.UTF_8);
|
||||
Path deepDir = root.resolve("deep/dir/foo");
|
||||
Files.createDirectories(deepDir);
|
||||
Files.writeString(deepDir.resolve("foo.txt"), "Contents of foo.txt", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource archiveResource = resourceFactory.newResource(jarUri);
|
||||
Path one = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/one");
|
||||
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
||||
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
||||
|
||||
// A CombinedResource that has a mix of FileSystem types
|
||||
Resource rc = ResourceFactory.combine(
|
||||
archiveResource,
|
||||
resourceFactory.newResource(one),
|
||||
resourceFactory.newResource(two),
|
||||
resourceFactory.newResource(three)
|
||||
);
|
||||
|
||||
Path destDir = testDir.resolve("dest");
|
||||
Files.createDirectory(destDir);
|
||||
rc.copyTo(destDir);
|
||||
|
||||
Resource r = resourceFactory.newResource(destDir);
|
||||
assertEquals(getContent(r, "1.txt"), "1 - one");
|
||||
assertEquals(getContent(r, "2.txt"), "2 - two");
|
||||
assertEquals(getContent(r, "3.txt"), "3 - three");
|
||||
r = r.resolve("dir");
|
||||
assertEquals(getContent(r, "1.txt"), "1 - one (in dir)");
|
||||
assertEquals(getContent(r, "2.txt"), "2 - two (in dir)");
|
||||
assertEquals(getContent(r, "3.txt"), "3 - three (in dir)");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedResourceCollectionGetPathTo() throws IOException
|
||||
{
|
||||
Path testDir = workDir.getEmptyPathDir();
|
||||
|
||||
// Create a JAR file with contents
|
||||
Path testJar = testDir.resolve("test.jar");
|
||||
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
|
||||
URI jarUri = URIUtil.uriJarPrefix(testJar.toUri(), "!/");
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(jarUri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
Files.writeString(root.resolve("3.txt"), "Contents of 3.txt from JAR", StandardCharsets.UTF_8);
|
||||
Files.writeString(root.resolve("4.txt"), "Contents of 4.txt from JAR", StandardCharsets.UTF_8);
|
||||
Path dir = root.resolve("dir");
|
||||
Files.createDirectories(dir);
|
||||
Files.writeString(dir.resolve("2.txt"), "Contents of dir/2.txt from JAR", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource archiveResource = resourceFactory.newResource(jarUri);
|
||||
Path one = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/one");
|
||||
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
||||
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
||||
|
||||
// A CombinedResource that has a mix of FileSystem types
|
||||
Resource rc = ResourceFactory.combine(
|
||||
resourceFactory.newResource(one),
|
||||
resourceFactory.newResource(two),
|
||||
resourceFactory.newResource(three),
|
||||
archiveResource
|
||||
);
|
||||
|
||||
List<String> actual = new ArrayList<>();
|
||||
|
||||
for (Resource candidate : rc.getAllResources())
|
||||
{
|
||||
// Skip directories
|
||||
if (candidate.isDirectory())
|
||||
continue;
|
||||
|
||||
// Get the path relative to the base resource
|
||||
Path relative = rc.getPathTo(candidate); // should not throw an exception
|
||||
actual.add(relative.toString());
|
||||
}
|
||||
|
||||
String[] expected = {
|
||||
"1.txt",
|
||||
"2.txt",
|
||||
"3.txt",
|
||||
"4.txt",
|
||||
"dir/1.txt",
|
||||
"dir/2.txt",
|
||||
"dir/3.txt"
|
||||
};
|
||||
|
||||
assertThat(actual, contains(expected));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceCollectionInResourceCollection()
|
||||
{
|
||||
|
@ -342,6 +463,353 @@ public class CombinedResourceTest
|
|||
assertThat(getContent(rc, "testZed.txt"), is("TestZed inside lib-zed.jar"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests of {@link CombinedResource#contains(Resource)} consisting of only simple PathResources,
|
||||
* where the "other" Resource is a simple Resource (like a PathResource)
|
||||
*/
|
||||
@Test
|
||||
public void testContainsSimple()
|
||||
{
|
||||
Path one = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/one");
|
||||
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
||||
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
||||
Path four = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/four");
|
||||
|
||||
Resource composite = ResourceFactory.combine(
|
||||
List.of(
|
||||
resourceFactory.newResource(one),
|
||||
resourceFactory.newResource(two),
|
||||
resourceFactory.newResource(three)
|
||||
)
|
||||
);
|
||||
|
||||
Resource oneTxt = composite.resolve("1.txt");
|
||||
assertThat(oneTxt, notNullValue());
|
||||
assertThat(composite.contains(oneTxt), is(true));
|
||||
|
||||
Resource dir = composite.resolve("dir");
|
||||
assertThat(dir, notNullValue());
|
||||
assertThat(composite.contains(dir), is(true));
|
||||
|
||||
Resource threeTxt = composite.resolve("dir/3.txt");
|
||||
assertThat(oneTxt, notNullValue());
|
||||
assertThat(composite.contains(threeTxt), is(true));
|
||||
assertThat(dir.contains(threeTxt), is(true));
|
||||
|
||||
Resource fourth = resourceFactory.newResource(four);
|
||||
|
||||
// some negations
|
||||
assertThat(oneTxt.contains(composite), is(false));
|
||||
assertThat(threeTxt.contains(composite), is(false));
|
||||
assertThat(oneTxt.contains(dir), is(false));
|
||||
assertThat(threeTxt.contains(dir), is(false));
|
||||
assertThat(dir.contains(composite), is(false));
|
||||
|
||||
assertThat(composite.contains(fourth), is(false));
|
||||
assertThat(dir.contains(fourth), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests of {@link CombinedResource#contains(Resource)} consisting of mixed PathResources types (file system and jars),
|
||||
* testing against "other" single Resource (not a CombinedResource)
|
||||
*/
|
||||
@Test
|
||||
public void testMixedContainsSimple() throws IOException
|
||||
{
|
||||
Path testDir = workDir.getEmptyPathDir();
|
||||
|
||||
// Create a JAR file with contents
|
||||
Path testJar = testDir.resolve("test.jar");
|
||||
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
|
||||
URI jarUri = URIUtil.uriJarPrefix(testJar.toUri(), "!/");
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(jarUri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
Files.writeString(root.resolve("1.txt"), "Contents of 1.txt from TEST JAR", StandardCharsets.UTF_8);
|
||||
Path dir = root.resolve("dir");
|
||||
Files.createDirectories(dir);
|
||||
Files.writeString(dir.resolve("2.txt"), "Contents of 2.txt from TEST JAR", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
// Create a JAR that is never part of the CombinedResource.
|
||||
Path unusedJar = testDir.resolve("unused.jar");
|
||||
URI unusedJarURI = URIUtil.uriJarPrefix(unusedJar.toUri(), "!/");
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(unusedJarURI, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
Files.writeString(root.resolve("unused.txt"), "Contents of unused.txt from UNUSED JAR", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource archiveResource = resourceFactory.newResource(jarUri);
|
||||
Path one = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/one");
|
||||
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
||||
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
||||
Path four = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/four");
|
||||
|
||||
Resource composite = ResourceFactory.combine(
|
||||
List.of(
|
||||
resourceFactory.newResource(one),
|
||||
resourceFactory.newResource(two),
|
||||
resourceFactory.newResource(three),
|
||||
archiveResource
|
||||
)
|
||||
);
|
||||
|
||||
Resource oneTxt = composite.resolve("1.txt");
|
||||
assertThat(oneTxt, notNullValue());
|
||||
assertThat(composite.contains(oneTxt), is(true));
|
||||
|
||||
Resource dir = composite.resolve("dir");
|
||||
assertThat(dir, notNullValue());
|
||||
assertThat(composite.contains(dir), is(true));
|
||||
|
||||
Resource threeTxt = composite.resolve("dir/3.txt");
|
||||
assertThat(oneTxt, notNullValue());
|
||||
assertThat(composite.contains(threeTxt), is(true));
|
||||
assertThat(dir.contains(threeTxt), is(true));
|
||||
|
||||
// some negations
|
||||
Resource fourth = resourceFactory.newResource(four);
|
||||
Resource fourText = fourth.resolve("four.txt");
|
||||
|
||||
|
||||
assertThat(oneTxt.contains(composite), is(false));
|
||||
assertThat(threeTxt.contains(composite), is(false));
|
||||
assertThat(oneTxt.contains(dir), is(false));
|
||||
assertThat(threeTxt.contains(dir), is(false));
|
||||
assertThat(dir.contains(composite), is(false));
|
||||
|
||||
assertThat(composite.contains(fourth), is(false));
|
||||
assertThat(composite.contains(fourText), is(false));
|
||||
assertThat(dir.contains(fourth), is(false));
|
||||
|
||||
Resource unused = resourceFactory.newResource(unusedJarURI);
|
||||
assertThat(composite.contains(unused), is(false));
|
||||
Resource unusedText = unused.resolve("unused.txt");
|
||||
assertThat(composite.contains(unusedText), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests of {@link CombinedResource#contains(Resource)} consisting of mixed PathResources types (file system and jars),
|
||||
* testing against "other" which are CombinedResource instances of their own.
|
||||
*/
|
||||
@Test
|
||||
public void testMixedContainsOtherCombinedResource() throws IOException
|
||||
{
|
||||
Path testDir = workDir.getEmptyPathDir();
|
||||
|
||||
// Create a JAR file with contents
|
||||
Path testJar = testDir.resolve("test.jar");
|
||||
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
|
||||
URI jarUri = URIUtil.uriJarPrefix(testJar.toUri(), "!/");
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(jarUri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
Files.writeString(root.resolve("1.txt"), "Contents of 1.txt from TEST JAR", StandardCharsets.UTF_8);
|
||||
Path dir = root.resolve("dir");
|
||||
Files.createDirectories(dir);
|
||||
Files.writeString(dir.resolve("2.txt"), "Contents of dir/2.txt from TEST JAR", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
// Create a JAR that is never part of the CombinedResource.
|
||||
Path unusedJar = testDir.resolve("unused.jar");
|
||||
URI unusedJarURI = URIUtil.uriJarPrefix(unusedJar.toUri(), "!/");
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(unusedJarURI, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
Files.writeString(root.resolve("unused.txt"), "Contents of unused.txt from UNUSED JAR", StandardCharsets.UTF_8);
|
||||
Path dir = root.resolve("dir");
|
||||
Files.createDirectories(dir);
|
||||
Files.writeString(dir.resolve("un.txt"), "Contents of dir/un.txt from TEST JAR", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource archiveResource = resourceFactory.newResource(jarUri);
|
||||
Resource unused = resourceFactory.newResource(unusedJarURI);
|
||||
Resource one = resourceFactory.newResource(MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/one"));
|
||||
Resource two = resourceFactory.newResource(MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two"));
|
||||
Resource three = resourceFactory.newResource(MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three"));
|
||||
Resource four = resourceFactory.newResource(MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/four"));
|
||||
|
||||
Resource composite = ResourceFactory.combine(
|
||||
List.of(
|
||||
one,
|
||||
two,
|
||||
three,
|
||||
archiveResource
|
||||
)
|
||||
);
|
||||
|
||||
Resource other = ResourceFactory.combine(
|
||||
List.of(
|
||||
one.resolve("dir"),
|
||||
two.resolve("dir"),
|
||||
three.resolve("dir")
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(composite.contains(other), is(true));
|
||||
|
||||
other = ResourceFactory.combine(
|
||||
List.of(
|
||||
one.resolve("dir"),
|
||||
two.resolve("dir"),
|
||||
three.resolve("dir"),
|
||||
archiveResource.resolve("dir")
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(composite.contains(other), is(true));
|
||||
|
||||
// some negations
|
||||
|
||||
other = ResourceFactory.combine(
|
||||
List.of(
|
||||
one.resolve("dir"),
|
||||
four.resolve("dir") // not in composite
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(composite.contains(other), is(false));
|
||||
|
||||
|
||||
other = ResourceFactory.combine(
|
||||
List.of(
|
||||
archiveResource.resolve("dir"),
|
||||
unused.resolve("dir")
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(composite.contains(other), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsAndPathTo()
|
||||
{
|
||||
Path one = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/one");
|
||||
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
||||
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
||||
Path four = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/four");
|
||||
|
||||
Resource composite = ResourceFactory.combine(
|
||||
List.of(
|
||||
resourceFactory.newResource(one),
|
||||
resourceFactory.newResource(two),
|
||||
resourceFactory.newResource(three)
|
||||
)
|
||||
);
|
||||
|
||||
Resource compositeAlt = ResourceFactory.combine(
|
||||
List.of(
|
||||
resourceFactory.newResource(one),
|
||||
resourceFactory.newResource(four)
|
||||
)
|
||||
);
|
||||
|
||||
Resource fourth = resourceFactory.newResource(four);
|
||||
|
||||
Resource oneTxt = composite.resolve("1.txt");
|
||||
assertThat(oneTxt, notNullValue());
|
||||
assertThat(composite.contains(oneTxt), is(true));
|
||||
|
||||
Path rel = composite.getPathTo(oneTxt);
|
||||
assertThat(rel, notNullValue());
|
||||
assertThat(rel.getNameCount(), is(1));
|
||||
assertThat(rel.getName(0).toString(), is("1.txt"));
|
||||
|
||||
Resource dir = composite.resolve("dir");
|
||||
assertThat(dir, notNullValue());
|
||||
assertThat(composite.contains(dir), is(true));
|
||||
|
||||
rel = composite.getPathTo(dir);
|
||||
assertThat(rel, notNullValue());
|
||||
assertThat(rel.getNameCount(), is(1));
|
||||
assertThat(rel.getName(0).toString(), is("dir"));
|
||||
|
||||
Resource threeTxt = composite.resolve("dir/3.txt");
|
||||
assertThat(oneTxt, notNullValue());
|
||||
assertThat(composite.contains(threeTxt), is(true));
|
||||
assertThat(dir.contains(threeTxt), is(true));
|
||||
|
||||
rel = composite.getPathTo(threeTxt);
|
||||
assertThat(rel, notNullValue());
|
||||
assertThat(rel.getNameCount(), is(2));
|
||||
assertThat(rel.getName(0).toString(), is("dir"));
|
||||
assertThat(rel.getName(1).toString(), is("3.txt"));
|
||||
|
||||
// some negations
|
||||
assertThat(oneTxt.contains(composite), is(false));
|
||||
assertThat(threeTxt.contains(composite), is(false));
|
||||
assertThat(oneTxt.contains(dir), is(false));
|
||||
assertThat(threeTxt.contains(dir), is(false));
|
||||
assertThat(dir.contains(composite), is(false));
|
||||
|
||||
assertThat(composite.contains(fourth), is(false));
|
||||
assertThat(dir.contains(fourth), is(false));
|
||||
|
||||
Resource dirAlt = compositeAlt.resolve("dir");
|
||||
assertThat(compositeAlt.contains(dirAlt), is(true));
|
||||
assertThat(composite.contains(dirAlt), is(false));
|
||||
assertThat(composite.getPathTo(dirAlt), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFileName()
|
||||
{
|
||||
Path one = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/one");
|
||||
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
||||
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
||||
|
||||
Resource composite = ResourceFactory.combine(
|
||||
List.of(
|
||||
resourceFactory.newResource(one),
|
||||
resourceFactory.newResource(two),
|
||||
resourceFactory.newResource(three)
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(composite.getFileName(), nullValue());
|
||||
Resource dir = composite.resolve("dir");
|
||||
assertThat(dir.getFileName(), is("dir"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllResources()
|
||||
{
|
||||
Path one = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/one");
|
||||
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
||||
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
||||
|
||||
Resource composite = ResourceFactory.combine(
|
||||
List.of(
|
||||
resourceFactory.newResource(one),
|
||||
resourceFactory.newResource(two),
|
||||
resourceFactory.newResource(three)
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(composite.getAllResources().stream().map(composite::getPathTo).map(Path::toString).toList(), containsInAnyOrder(
|
||||
"1.txt",
|
||||
"2.txt",
|
||||
"3.txt",
|
||||
"dir",
|
||||
"dir/1.txt",
|
||||
"dir/2.txt",
|
||||
"dir/3.txt"
|
||||
));
|
||||
}
|
||||
|
||||
private void createJar(Path outputJar, String entryName, String entryContents) throws IOException
|
||||
{
|
||||
Map<String, String> env = new HashMap<>();
|
||||
|
|
|
@ -359,6 +359,9 @@ public class FileSystemResourceTest
|
|||
Resource base = ResourceFactory.root().newResource(dir);
|
||||
Resource res = base.resolve("foo");
|
||||
assertThat("is contained in", res.isContainedIn(base), is(true));
|
||||
assertThat(base.contains(res), is(true));
|
||||
assertThat(base.getPathTo(res).getNameCount(), is(1));
|
||||
assertThat(base.getPathTo(res).getName(0).toString(), is("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -110,12 +110,13 @@ public class MountedPathResourceTest
|
|||
public void testZipFileName()
|
||||
{
|
||||
Path testZip = MavenTestingUtils.getTestResourcePathFile("TestData/test.zip");
|
||||
String s = "jar:" + testZip.toUri().toASCIIString() + "!/subdir/numbers";
|
||||
URI uri = URI.create(s);
|
||||
URI uri = testZip.toUri();
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource r = resourceFactory.newResource(uri);
|
||||
Resource r = resourceFactory.newJarFileResource(uri);
|
||||
assertThat(r.getFileName(), is(""));
|
||||
|
||||
r = r.resolve("subdir/numbers");
|
||||
assertTrue(Resources.isReadableFile(r));
|
||||
assertThat(r.getFileName(), is("numbers"));
|
||||
}
|
||||
|
@ -125,12 +126,13 @@ public class MountedPathResourceTest
|
|||
public void testJarFileName()
|
||||
{
|
||||
Path testZip = MavenPaths.findTestResourceFile("jar-file-resource.jar");
|
||||
String s = "jar:" + testZip.toUri().toASCIIString() + "!/rez/deep/zzz";
|
||||
URI uri = URI.create(s);
|
||||
URI uri = testZip.toUri();
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource r = resourceFactory.newResource(uri);
|
||||
Resource r = resourceFactory.newJarFileResource(uri);
|
||||
assertThat(r.getFileName(), is(""));
|
||||
|
||||
r = r.resolve("rez/deep/zzz");
|
||||
assertTrue(Resources.isReadableFile(r));
|
||||
assertThat(r.getFileName(), is("zzz"));
|
||||
}
|
||||
|
@ -199,7 +201,7 @@ public class MountedPathResourceTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testJarFileGetAllResoures()
|
||||
public void testJarFileGetAllResources()
|
||||
throws Exception
|
||||
{
|
||||
Path testZip = MavenTestingUtils.getTestResourcePathFile("TestData/test.zip");
|
||||
|
@ -210,7 +212,14 @@ public class MountedPathResourceTest
|
|||
Resource r = resourceFactory.newResource(uri);
|
||||
Collection<Resource> deep = r.getAllResources();
|
||||
|
||||
assertEquals(4, deep.size());
|
||||
assertThat(deep.stream().map(r::getPathTo).map(Path::toString).toList(),
|
||||
containsInAnyOrder(
|
||||
"numbers",
|
||||
"subsubdir",
|
||||
"subsubdir/numbers",
|
||||
"subsubdir/alphabet",
|
||||
"alphabet"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,9 @@ import java.nio.file.FileSystems;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
|
@ -41,6 +43,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
@ -144,6 +147,95 @@ public class PathResourceTest
|
|||
assertThat("Resource.getFileName", resource.getFileName(), is("example.jar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContains(WorkDir workDir) throws IOException
|
||||
{
|
||||
Path testPath = workDir.getEmptyPathDir();
|
||||
Path fooJar = testPath.resolve("foo.jar");
|
||||
Path barJar = testPath.resolve("bar.jar");
|
||||
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
URI fooJarUri = URIUtil.uriJarPrefix(fooJar.toUri(), "!/");
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(fooJarUri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
Path dir = root.resolve("deep/dir");
|
||||
Files.createDirectories(dir);
|
||||
Files.writeString(dir.resolve("foo.txt"), "Contents of foo.txt in foo.jar", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
URI barJarUri = URIUtil.uriJarPrefix(barJar.toUri(), "!/");
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(barJarUri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
Path dir = root.resolve("deep/dir");
|
||||
Files.createDirectories(dir);
|
||||
Files.writeString(dir.resolve("bar.txt"), "Contents of bar.txt in bar.jar", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource fooResource = resourceFactory.newResource(fooJarUri);
|
||||
Resource barResource = resourceFactory.newResource(barJarUri);
|
||||
|
||||
Resource fooText = fooResource.resolve("deep/dir/foo.txt");
|
||||
assertTrue(fooResource.contains(fooText));
|
||||
|
||||
Resource barText = barResource.resolve("deep/dir/bar.txt");
|
||||
assertTrue(barResource.contains(barText));
|
||||
|
||||
assertFalse(fooResource.contains(barText));
|
||||
assertFalse(barResource.contains(fooText));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPathTo(WorkDir workDir) throws IOException
|
||||
{
|
||||
Path testPath = workDir.getEmptyPathDir();
|
||||
Path fooJar = testPath.resolve("foo.jar");
|
||||
Path barJar = testPath.resolve("bar.jar");
|
||||
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
URI fooJarUri = URIUtil.uriJarPrefix(fooJar.toUri(), "!/");
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(fooJarUri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
Path dir = root.resolve("deep/dir");
|
||||
Files.createDirectories(dir);
|
||||
Files.writeString(dir.resolve("foo.txt"), "Contents of foo.txt in foo.jar", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
URI barJarUri = URIUtil.uriJarPrefix(barJar.toUri(), "!/");
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(barJarUri, env))
|
||||
{
|
||||
Path root = zipfs.getPath("/");
|
||||
Path dir = root.resolve("deep/dir");
|
||||
Files.createDirectories(dir);
|
||||
Files.writeString(dir.resolve("bar.txt"), "Contents of bar.txt in bar.jar", StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource fooResource = resourceFactory.newResource(fooJarUri);
|
||||
Resource barResource = resourceFactory.newResource(barJarUri);
|
||||
|
||||
Resource fooText = fooResource.resolve("deep/dir/foo.txt");
|
||||
Path fooPathRel = fooResource.getPathTo(fooText);
|
||||
assertThat(fooPathRel.toString(), is("deep/dir/foo.txt"));
|
||||
|
||||
Resource barText = barResource.resolve("deep/dir/bar.txt");
|
||||
Path barPathRel = barResource.getPathTo(barText);
|
||||
assertThat(barPathRel.toString(), is("deep/dir/bar.txt"));
|
||||
|
||||
// Attempt to getPathTo cross Resource will return null.
|
||||
Path crossPathText = fooResource.getPathTo(barText);
|
||||
assertNull(crossPathText);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJarFileIsAliasFile(WorkDir workDir) throws IOException
|
||||
{
|
||||
|
@ -445,6 +537,62 @@ public class PathResourceTest
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to ensure that a symlink loop will not trip up the Resource.getAllResources() implementation.
|
||||
*/
|
||||
@Test
|
||||
public void testGetAllResourcesSymlinkLoop(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path testPath = workDir.getEmptyPathDir();
|
||||
|
||||
Path base = testPath.resolve("base");
|
||||
Path deep = base.resolve("deep");
|
||||
Path deeper = deep.resolve("deeper");
|
||||
|
||||
FS.ensureDirExists(deeper);
|
||||
|
||||
Files.writeString(deeper.resolve("test.txt"), "This is the deeper TEST TXT", StandardCharsets.UTF_8);
|
||||
Files.writeString(base.resolve("foo.txt"), "This is the FOO TXT in the Base dir", StandardCharsets.UTF_8);
|
||||
|
||||
boolean symlinkSupported;
|
||||
try
|
||||
{
|
||||
Path bar = deeper.resolve("bar");
|
||||
// Create symlink from ${base}/deep/deeper/bar/ to ${base}/deep/
|
||||
Files.createSymbolicLink(bar, deep);
|
||||
|
||||
symlinkSupported = true;
|
||||
}
|
||||
catch (UnsupportedOperationException | FileSystemException e)
|
||||
{
|
||||
symlinkSupported = false;
|
||||
}
|
||||
|
||||
assumeTrue(symlinkSupported, "Symlink not supported");
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource resource = resourceFactory.newResource(base);
|
||||
|
||||
Collection<Resource> allResources = resource.getAllResources();
|
||||
|
||||
Resource[] expected = {
|
||||
resource.resolve("deep/"),
|
||||
resource.resolve("deep/deeper/"),
|
||||
resource.resolve("deep/deeper/bar/"),
|
||||
resource.resolve("deep/deeper/test.txt"),
|
||||
resource.resolve("foo.txt")
|
||||
};
|
||||
|
||||
List<String> actual = allResources.stream()
|
||||
.map(Resource::getURI)
|
||||
.map(URI::toASCIIString)
|
||||
.toList();
|
||||
|
||||
assertThat(allResources, containsInAnyOrder(expected));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrokenSymlink(WorkDir workDir) throws Exception
|
||||
{
|
||||
|
|
|
@ -211,7 +211,7 @@ public class ResourceFactoryTest
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedIn(Resource r)
|
||||
public boolean isContainedIn(Resource container)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -385,6 +385,8 @@ public class ResourceTest
|
|||
Resource resourceDir = resourceBase.resolve("/foo");
|
||||
assertTrue(Resources.exists(resourceDir));
|
||||
assertThat(resourceDir.getURI(), is(foo.toUri()));
|
||||
assertTrue(resourceBase.contains(resourceDir));
|
||||
assertThat(resourceBase.getPathTo(resourceDir).getName(0).toString(), is("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#org.eclipse.jetty.util.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.util.Utf8Appendable.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.util.PathWatcher.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.util.resource.PathResource.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.util.thread.QueuedThreadPool.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.util.thread.ReservedThreadExecutor.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.util.thread.SerializedInvoker$NamedRunnable.LEVEL=DEBUG
|
||||
|
|
|
@ -24,7 +24,6 @@ import java.util.Map;
|
|||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.util.ExceptionUtil;
|
||||
import org.eclipse.jetty.util.FileID;
|
||||
|
@ -133,7 +132,7 @@ public class AnnotationParser
|
|||
* Convert internal names to simple names.
|
||||
*
|
||||
* @param list the list of internal names
|
||||
* @return the list of simple names
|
||||
* @return the array of simple names
|
||||
*/
|
||||
public static String[] normalize(String[] list)
|
||||
{
|
||||
|
@ -581,31 +580,38 @@ public class AnnotationParser
|
|||
*/
|
||||
protected void parseDir(Set<? extends Handler> handlers, Resource dirResource) throws Exception
|
||||
{
|
||||
Path dir = dirResource.getPath();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Scanning dir {}", dir.toUri());
|
||||
LOG.debug("Scanning dir {}", dirResource);
|
||||
|
||||
assert dirResource.isDirectory();
|
||||
|
||||
ExceptionUtil.MultiException multiException = new ExceptionUtil.MultiException();
|
||||
try (Stream<Path> classStream = Files.walk(dir))
|
||||
|
||||
for (Resource candidate : dirResource.getAllResources())
|
||||
{
|
||||
classStream
|
||||
.filter(Files::isRegularFile)
|
||||
.filter((path) -> !FileID.isHidden(dir, path))
|
||||
.filter(FileID::isNotMetaInfVersions)
|
||||
.filter(FileID::isNotModuleInfoClass)
|
||||
.filter(FileID::isClassFile)
|
||||
.forEach(classFile ->
|
||||
{
|
||||
try
|
||||
{
|
||||
parseClass(handlers, dirResource, classFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
multiException.add(new RuntimeException("Error scanning entry " + ex, ex));
|
||||
}
|
||||
});
|
||||
// Skip directories
|
||||
if (candidate.isDirectory())
|
||||
continue;
|
||||
|
||||
// Get the path relative to the base resource
|
||||
Path relative = dirResource.getPathTo(candidate);
|
||||
|
||||
// select only relative non-hidden class files that are not modules nor versions
|
||||
if (relative == null ||
|
||||
FileID.isHidden(relative) ||
|
||||
FileID.isMetaInfVersions(relative) ||
|
||||
FileID.isModuleInfoClass(relative) ||
|
||||
!FileID.isClassFile(relative))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
parseClass(handlers, dirResource, candidate.getPath());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
multiException.add(new RuntimeException("Error scanning entry " + ex, ex));
|
||||
}
|
||||
}
|
||||
|
||||
multiException.ifExceptionThrow();
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 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.its.jetty_simple_base_webapp;
|
||||
|
||||
public class Foo
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
<h1>Base Index</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -74,6 +74,8 @@
|
|||
<artifactId>jetty-simple-base-webapp</artifactId>
|
||||
<includes>
|
||||
<include>index.html</include>
|
||||
<include>base/**</include>
|
||||
<include>WEB-INF/**</include>
|
||||
</includes>
|
||||
</overlay>
|
||||
<overlay/>
|
||||
|
@ -100,8 +102,8 @@
|
|||
<systemPropertyVariables>
|
||||
<jetty.port.file>${jetty.port.file}</jetty.port.file>
|
||||
<context.path>/setbycontextxml</context.path>
|
||||
<contentCheck>Simple Base Webapp Index</contentCheck>
|
||||
<pathToCheck>/index.html</pathToCheck>
|
||||
<contentCheck>Base Index</contentCheck>
|
||||
<pathToCheck>/base/index.html</pathToCheck>
|
||||
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
|
||||
</systemPropertyVariables>
|
||||
<dependenciesToScan>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 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.its.jetty_simple_webapp;
|
||||
|
||||
public class Bar
|
||||
{
|
||||
}
|
|
@ -130,11 +130,27 @@ public class MavenResource extends Resource
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedIn(Resource r)
|
||||
public boolean isContainedIn(Resource container)
|
||||
{
|
||||
if (_resource == null)
|
||||
return false;
|
||||
return _resource.isContainedIn(r);
|
||||
return _resource.isContainedIn(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Resource other)
|
||||
{
|
||||
if (_resource == null)
|
||||
return false;
|
||||
return _resource.contains(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPathTo(Resource other)
|
||||
{
|
||||
if (_resource == null)
|
||||
return null;
|
||||
return _resource.getPathTo(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,12 +15,16 @@ package org.eclipse.jetty.ee10.maven.plugin;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.resource.MountedPathResource;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
|
||||
|
@ -123,17 +127,21 @@ public class OverlayManager
|
|||
* @return the location to which it was unpacked
|
||||
* @throws IOException if there is an IO problem
|
||||
*/
|
||||
protected Resource unpackOverlay(Overlay overlay)
|
||||
protected Resource unpackOverlay(Overlay overlay)
|
||||
throws IOException
|
||||
{
|
||||
if (overlay.getResource() == null)
|
||||
return null; //nothing to unpack
|
||||
|
||||
//Get the name of the overlayed war and unpack it to a dir of the
|
||||
//same name in the temporary directory
|
||||
String name = overlay.getResource().getFileName();
|
||||
//same name in the temporary directory.
|
||||
//We know it is a war because it came from the maven repo
|
||||
assert overlay.getResource() instanceof MountedPathResource;
|
||||
|
||||
Path p = Paths.get(URIUtil.unwrapContainer(overlay.getResource().getURI()));
|
||||
String name = p.getName(p.getNameCount() - 1).toString();
|
||||
name = name.replace('.', '_');
|
||||
|
||||
|
||||
File overlaysDir = new File(warPlugin.getProject().getBuild().getDirectory(), "jetty_overlays");
|
||||
File dir = new File(overlaysDir, name);
|
||||
|
||||
|
|
|
@ -132,9 +132,21 @@ public class SelectiveJarResource extends Resource
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedIn(Resource r)
|
||||
public boolean isContainedIn(Resource container)
|
||||
{
|
||||
return _delegate.isContainedIn(r);
|
||||
return _delegate.isContainedIn(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Resource other)
|
||||
{
|
||||
return _delegate.contains(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPathTo(Resource other)
|
||||
{
|
||||
return _delegate.getPathTo(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -401,24 +401,19 @@ public class MetaData
|
|||
*/
|
||||
private Resource getEnclosingResource(List<Resource> resources, Resource resource)
|
||||
{
|
||||
Resource enclosingResource = null;
|
||||
try
|
||||
{
|
||||
for (Resource r : resources)
|
||||
{
|
||||
if (resource.isContainedIn(r))
|
||||
{
|
||||
enclosingResource = r;
|
||||
break;
|
||||
}
|
||||
if (r.contains(resource))
|
||||
return r;
|
||||
}
|
||||
return enclosingResource;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Not contained within?", e);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addDescriptorProcessor(DescriptorProcessor p)
|
||||
|
|
|
@ -772,8 +772,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
|||
return null;
|
||||
|
||||
// Is there a WEB-INF directory anywhere in the Resource Base?
|
||||
// ResourceBase could be a ResourceCollection
|
||||
// The result could be a ResourceCollection with multiple WEB-INF directories
|
||||
// ResourceBase could be a CombinedResource
|
||||
// The result could be a CombinedResource with multiple WEB-INF directories
|
||||
// Can return from WEB-INF/lib/foo.jar!/WEB-INF
|
||||
// Can also never return from a META-INF/versions/#/WEB-INF location
|
||||
Resource webInf = getBaseResource().resolve("WEB-INF/");
|
||||
|
|
|
@ -24,7 +24,6 @@ import java.util.Map;
|
|||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.util.ExceptionUtil;
|
||||
import org.eclipse.jetty.util.FileID;
|
||||
|
@ -578,31 +577,38 @@ public class AnnotationParser
|
|||
*/
|
||||
protected void parseDir(Set<? extends Handler> handlers, Resource dirResource) throws Exception
|
||||
{
|
||||
Path dir = dirResource.getPath();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Scanning dir {}", dir.toUri());
|
||||
LOG.debug("Scanning dir {}", dirResource);
|
||||
|
||||
assert dirResource.isDirectory();
|
||||
|
||||
ExceptionUtil.MultiException multiException = new ExceptionUtil.MultiException();
|
||||
try (Stream<Path> classStream = Files.walk(dir))
|
||||
|
||||
for (Resource candidate : dirResource.getAllResources())
|
||||
{
|
||||
classStream
|
||||
.filter(Files::isRegularFile)
|
||||
.filter((path) -> !FileID.isHidden(dir, path))
|
||||
.filter(FileID::isNotMetaInfVersions)
|
||||
.filter(FileID::isNotModuleInfoClass)
|
||||
.filter(FileID::isClassFile)
|
||||
.forEach(classFile ->
|
||||
{
|
||||
try
|
||||
{
|
||||
parseClass(handlers, dirResource, classFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
multiException.add(new RuntimeException("Error scanning entry " + ex, ex));
|
||||
}
|
||||
});
|
||||
// Skip directories
|
||||
if (candidate.isDirectory())
|
||||
continue;
|
||||
|
||||
// Get the path relative to the base resource
|
||||
Path relative = dirResource.getPathTo(candidate);
|
||||
|
||||
// select only relative non-hidden class files that are not modules nor versions
|
||||
if (relative == null ||
|
||||
FileID.isHidden(relative) ||
|
||||
FileID.isMetaInfVersions(relative) ||
|
||||
FileID.isModuleInfoClass(relative) ||
|
||||
!FileID.isClassFile(relative))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
parseClass(handlers, dirResource, candidate.getPath());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
multiException.add(new RuntimeException("Error scanning entry " + ex, ex));
|
||||
}
|
||||
}
|
||||
|
||||
multiException.ifExceptionThrow();
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 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.its.jetty_simple_base_webapp;
|
||||
|
||||
public class Foo
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
<h1>Base Index</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -74,6 +74,8 @@
|
|||
<artifactId>jetty-simple-base-webapp</artifactId>
|
||||
<includes>
|
||||
<include>index.html</include>
|
||||
<include>base/**</include>
|
||||
<include>WEB-INF/**</include>
|
||||
</includes>
|
||||
</overlay>
|
||||
<overlay/>
|
||||
|
@ -100,8 +102,8 @@
|
|||
<systemPropertyVariables>
|
||||
<jetty.port.file>${jetty.port.file}</jetty.port.file>
|
||||
<context.path>/setbycontextxml</context.path>
|
||||
<contentCheck>Simple Base Webapp Index</contentCheck>
|
||||
<pathToCheck>/index.html</pathToCheck>
|
||||
<contentCheck>Base Index</contentCheck>
|
||||
<pathToCheck>/base/index.html</pathToCheck>
|
||||
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
|
||||
</systemPropertyVariables>
|
||||
<dependenciesToScan>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 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.its.jetty_simple_webapp;
|
||||
|
||||
public class Bar
|
||||
{
|
||||
}
|
|
@ -130,11 +130,27 @@ public class MavenResource extends Resource
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedIn(Resource r)
|
||||
public boolean isContainedIn(Resource container)
|
||||
{
|
||||
if (_resource == null)
|
||||
return false;
|
||||
return _resource.isContainedIn(r);
|
||||
return _resource.isContainedIn(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Resource other)
|
||||
{
|
||||
if (_resource == null)
|
||||
return false;
|
||||
return _resource.contains(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPathTo(Resource other)
|
||||
{
|
||||
if (_resource == null)
|
||||
return null;
|
||||
return _resource.getPathTo(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,12 +15,16 @@ package org.eclipse.jetty.ee9.maven.plugin;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.resource.MountedPathResource;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
|
||||
|
@ -133,9 +137,12 @@ public class OverlayManager
|
|||
|
||||
//Get the name of the overlayed war and unpack it to a dir of the
|
||||
//same name in the temporary directory
|
||||
String name = overlay.getResource().getFileName();
|
||||
//We know it is a war because it came from the maven repo
|
||||
assert overlay.getResource() instanceof MountedPathResource;
|
||||
Path p = Paths.get(URIUtil.unwrapContainer(overlay.getResource().getURI()));
|
||||
String name = p.getName(p.getNameCount() - 1).toString();
|
||||
name = name.replace('.', '_');
|
||||
|
||||
|
||||
File overlaysDir = new File(warPlugin.getProject().getBuild().getDirectory(), "jetty_overlays");
|
||||
File dir = new File(overlaysDir, name);
|
||||
|
||||
|
|
|
@ -132,9 +132,21 @@ public class SelectiveJarResource extends Resource
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedIn(Resource r)
|
||||
public boolean isContainedIn(Resource container)
|
||||
{
|
||||
return _delegate.isContainedIn(r);
|
||||
return _delegate.isContainedIn(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Resource other)
|
||||
{
|
||||
return _delegate.contains(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPathTo(Resource other)
|
||||
{
|
||||
return _delegate.getPathTo(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -401,24 +401,19 @@ public class MetaData
|
|||
*/
|
||||
private Resource getEnclosingResource(List<Resource> resources, Resource resource)
|
||||
{
|
||||
Resource enclosingResource = null;
|
||||
try
|
||||
{
|
||||
for (Resource r : resources)
|
||||
{
|
||||
if (resource.isContainedIn(r))
|
||||
{
|
||||
enclosingResource = r;
|
||||
break;
|
||||
}
|
||||
if (r.contains(resource))
|
||||
return r;
|
||||
}
|
||||
return enclosingResource;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Not contained within?", e);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addDescriptorProcessor(DescriptorProcessor p)
|
||||
|
|
Loading…
Reference in New Issue