util: Add support for GraalVM Native-Image resource:-URIs and Paths

GraalVM Native-Image makes its classpath resources accessible through an
opaque resource: URL scheme.

Add support for this scheme in URIUtil and PathResource.

Automatically create the NativeImageResourceFileSystem when necessary.
Also add a workaround where converting such Native-Image Paths back to
URI would yield the wrong URI (potentially a bug in GraalVM).

https://github.com/eclipse/jetty.project/issues/9116
https://github.com/oracle/graal/issues/5720

Signed-off-by: Christian Kohlschütter <christian@kohlschutter.com>
This commit is contained in:
Christian Kohlschütter 2023-01-07 23:20:24 +01:00
parent 1dc175efac
commit b53e7e063e
4 changed files with 90 additions and 5 deletions

View File

@ -19,11 +19,14 @@ import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
@ -54,6 +57,7 @@ public final class URIUtil
.with("file:")
.with("jrt:")
.with("jar:")
.with("resource:")
.build();
// From https://www.rfc-editor.org/rfc/rfc3986
@ -211,6 +215,8 @@ public final class URIUtil
private static final boolean[] ENCODE_PATH_NEEDS_ENCODING;
private static final String URI_BAD_RESOURCE_PREFIX = "file:///resources!";
private URIUtil()
{
}
@ -1735,6 +1741,54 @@ public final class URIUtil
return query1 + '&' + query2;
}
/**
* Corrects any bad {@code resource} based URIs, such as those starting with {@code resource:file:///resources!}.
*
* @param uri
* The URI to correct.
* @return the corrected URI, or the original URI.
* @see <a href="https://github.com/oracle/graal/issues/5720">Graal issue 5720</a>
*/
public static URI correctResourceURI(URI uri)
{
if (uri == null || !"resource".equals(uri.getScheme()))
return uri;
String ssp = uri.getSchemeSpecificPart();
if (ssp.startsWith(URI_BAD_RESOURCE_PREFIX))
{
return URI.create("resource:" + ssp.substring(URI_BAD_RESOURCE_PREFIX.length()));
}
else
{
return uri;
}
}
/**
* A wrapper for {@link Paths#get(URI)} and {@link Path#of(URI)}.
*
* It automatically handles custom file systems, such as ZipFileSystem and NativeImageResourceFileSystem.
*
* @param uri
* The URI.
* @return The path.
* @throws IOException
* on error.
*/
public static Path getPath(URI uri) throws IOException
{
try
{
return Path.of(uri);
}
catch (FileSystemNotFoundException e)
{
FileSystems.newFileSystem(uri, Collections.emptyMap());
return Path.of(uri);
}
}
/**
* <p>
* Corrects any bad {@code file} based URIs (even within a {@code jar:file:} based URIs) from the bad out-of-spec

View File

@ -48,6 +48,7 @@ public class PathResource extends Resource
.caseSensitive(false)
.with("file")
.with("jrt")
.with("resource")
.build();
// The path object represented by this instance
@ -141,15 +142,16 @@ public class PathResource extends Resource
* Must be an absolute URI using the <code>file</code> scheme.
*
* @param uri the URI to build this PathResource from.
* @throws IOException if the path could not be resolved.
*/
PathResource(URI uri)
PathResource(URI uri) throws IOException
{
this(uri, false);
}
PathResource(URI uri, boolean bypassAllowedSchemeCheck)
PathResource(URI uri, boolean bypassAllowedSchemeCheck) throws IOException
{
this(Paths.get(uri), uri, bypassAllowedSchemeCheck);
this(URIUtil.getPath(URIUtil.correctResourceURI(uri)), uri, bypassAllowedSchemeCheck);
}
PathResource(Path path)
@ -166,6 +168,7 @@ public class PathResource extends Resource
*/
PathResource(Path path, URI uri, boolean bypassAllowedSchemeCheck)
{
uri = URIUtil.correctResourceURI(uri);
if (!uri.isAbsolute())
throw new IllegalArgumentException("not an absolute uri: " + uri);
if (!bypassAllowedSchemeCheck && !SUPPORTED_SCHEMES.contains(uri.getScheme()))

View File

@ -13,19 +13,46 @@
package org.eclipse.jetty.util.resource;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.eclipse.jetty.util.URIUtil;
public class PathResourceFactory implements ResourceFactory
{
@Override
public Resource newResource(URI uri)
{
Path path = Paths.get(uri.normalize());
Path path = Path.of(uri.normalize());
if (!Files.exists(path))
return null;
return new PathResource(path, uri, false);
}
@Override
public Resource newResource(Path path)
{
if (path == null)
return null;
URI uri = path.toUri();
try
{
// Validate URI conversion, and trigger FileSystem initialization, if necessary
if (URIUtil.getPath(uri) == null)
return null;
}
catch (IOException e)
{
return null;
}
if (!Files.exists(path))
return null;
return new PathResource(path, uri, false);
}
}

View File

@ -52,6 +52,7 @@ class ResourceFactoryInternals
PathResourceFactory pathResourceFactory = new PathResourceFactory();
RESOURCE_FACTORIES.put("file", pathResourceFactory);
RESOURCE_FACTORIES.put("jrt", pathResourceFactory);
RESOURCE_FACTORIES.put("resource", pathResourceFactory); // GraalVM native-image resource
}
static ResourceFactory ROOT = new CompositeResourceFactory()