From ee097316bb7556bf120d7d003c978b528898ccd6 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 11 Nov 2022 10:10:53 -0600 Subject: [PATCH 1/8] Issue #8886 - support extensible Resource URI schemes --- .../java/org/eclipse/jetty/util/URIUtil.java | 4 +- .../jetty/util/resource/FileSystemPool.java | 4 +- .../util/resource/MountedPathResource.java | 10 -- .../resource/MountedPathResourceFactory.java | 31 +++++++ .../jetty/util/resource/PathResource.java | 12 +-- .../util/resource/PathResourceFactory.java | 31 +++++++ .../eclipse/jetty/util/resource/Resource.java | 24 ++--- .../jetty/util/resource/ResourceFactory.java | 15 +++ .../resource/ResourceFactoryInternals.java | 15 +++ .../util/resource/FileSystemResourceTest.java | 5 +- .../util/resource/ResourceFactoryTest.java | 92 +++++++++++++++++++ 11 files changed, 204 insertions(+), 39 deletions(-) create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MountedPathResourceFactory.java create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java create mode 100644 jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java index a2084f573833..467833d4e30e 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -31,6 +31,7 @@ import java.util.stream.Stream; import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception; +import org.eclipse.jetty.util.resource.ResourceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1903,7 +1904,8 @@ public static URI toURI(String resource) Objects.requireNonNull(resource); // Only try URI for string for known schemes, otherwise assume it is a Path - return (KNOWN_SCHEMES.getBest(resource) != null) + ResourceFactory resourceFactory = ResourceFactory.getBestByScheme(resource); + return (resourceFactory != null) ? correctFileURI(URI.create(resource)) : Paths.get(resource).toUri(); } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileSystemPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileSystemPool.java index 0701a1a2336e..8eb8ea20b74d 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileSystemPool.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileSystemPool.java @@ -104,8 +104,8 @@ Mount mount(URI uri) throws IOException { if (!uri.isAbsolute()) throw new IllegalArgumentException("not an absolute uri: " + uri); - if (PathResource.ALLOWED_SCHEMES.contains(uri.getScheme())) - throw new IllegalArgumentException("not an allowed scheme: " + uri); + if (!uri.getScheme().equalsIgnoreCase("jar")) + throw new IllegalArgumentException("not an supported scheme: " + uri); FileSystem fileSystem = null; try (AutoLock ignore = poolLock.lock()) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MountedPathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MountedPathResource.java index 7d64c8aa35ad..66a1342bc813 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MountedPathResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MountedPathResource.java @@ -16,9 +16,7 @@ import java.io.IOException; import java.net.URI; import java.nio.file.FileSystem; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import org.eclipse.jetty.util.URIUtil; @@ -28,14 +26,6 @@ */ public class MountedPathResource extends PathResource { - public static MountedPathResource of(URI uri) throws IOException - { - Path path = Paths.get(uri.normalize()); - if (!Files.exists(path)) - return null; - return new MountedPathResource(path, uri); - } - private final URI containerUri; MountedPathResource(URI uri) throws IOException diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MountedPathResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MountedPathResourceFactory.java new file mode 100644 index 000000000000..c1a968a2b109 --- /dev/null +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MountedPathResourceFactory.java @@ -0,0 +1,31 @@ +// +// ======================================================================== +// 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.resource; + +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class MountedPathResourceFactory implements ResourceFactory +{ + @Override + public Resource newResource(URI uri) + { + Path path = Paths.get(uri.normalize()); + if (!Files.exists(path)) + return null; + return new MountedPathResource(path, uri); + } +} diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java index f334d2077294..997eb42b4d0b 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java @@ -44,20 +44,12 @@ public class PathResource extends Resource { private static final Logger LOG = LoggerFactory.getLogger(PathResource.class); - public static Index ALLOWED_SCHEMES = new Index.Builder() + public static Index SUPPORTED_SCHEMES = new Index.Builder() .caseSensitive(false) .with("file") .with("jrt") .build(); - public static PathResource of(URI uri) throws IOException - { - Path path = Paths.get(uri.normalize()); - if (!Files.exists(path)) - return null; - return new PathResource(path, uri, false); - } - // The path object represented by this instance private final Path path; // The as-requested URI for this path object @@ -176,7 +168,7 @@ public static boolean isSameName(Path pathA, Path pathB) { if (!uri.isAbsolute()) throw new IllegalArgumentException("not an absolute uri: " + uri); - if (!bypassAllowedSchemeCheck && !ALLOWED_SCHEMES.contains(uri.getScheme())) + if (!bypassAllowedSchemeCheck && !SUPPORTED_SCHEMES.contains(uri.getScheme())) throw new IllegalArgumentException("not an allowed scheme: " + uri); if (Files.isDirectory(path)) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java new file mode 100644 index 000000000000..4c0737b972f7 --- /dev/null +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java @@ -0,0 +1,31 @@ +// +// ======================================================================== +// 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.resource; + +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class PathResourceFactory implements ResourceFactory +{ + @Override + public Resource newResource(URI uri) + { + Path path = Paths.get(uri.normalize()); + if (!Files.exists(path)) + return null; + return new PathResource(path, uri, false); + } +} diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java index 302ef7292414..d69f5312aca2 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java @@ -22,7 +22,6 @@ import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; -import java.nio.file.Paths; import java.nio.file.ProviderNotFoundException; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; @@ -34,8 +33,6 @@ import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.URIUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** *

@@ -47,9 +44,13 @@ */ public abstract class Resource implements Iterable { - private static final Logger LOG = LoggerFactory.getLogger(Resource.class); private static final LinkOption[] NO_FOLLOW_LINKS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; - private static final LinkOption[] FOLLOW_LINKS = new LinkOption[]{}; + private static final Path CURRENT_WORKING_DIR; + + static + { + CURRENT_WORKING_DIR = Path.of(System.getProperty("user.dir")); + } public static String dump(Resource resource) { @@ -78,19 +79,18 @@ static Resource create(URI uri) uri = new URI("file", uri.toString(), null); else // otherwise resolve against the current directory - uri = Paths.get("").toAbsolutePath().toUri().resolve(uri); + uri = CURRENT_WORKING_DIR.toUri().resolve(uri); // Correct any `file:/path` to `file:///path` mistakes uri = URIUtil.correctFileURI(uri); } - // If the scheme is allowed by PathResource, we can build a non-mounted PathResource. - if (PathResource.ALLOWED_SCHEMES.contains(uri.getScheme())) - return PathResource.of(uri); - - return MountedPathResource.of(uri); + ResourceFactory resourceFactory = ResourceFactory.byScheme(uri.getScheme()); + if (resourceFactory == null) + throw new IllegalArgumentException("URI scheme not supported: " + uri); + return resourceFactory.newResource(uri); } - catch (URISyntaxException | ProviderNotFoundException | IOException ex) + catch (URISyntaxException | ProviderNotFoundException ex) { throw new IllegalArgumentException(ex); } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java index cf6f6acb2f78..4eb9c7c5030f 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java @@ -236,6 +236,21 @@ default Resource newJarFileResource(URI uri) return newResource(URIUtil.toJarFileUri(uri)); } + static void addResourceFactory(String scheme, ResourceFactory resource) + { + ResourceFactoryInternals.RESOURCE_FACTORIES.put(scheme, resource); + } + + static ResourceFactory byScheme(String scheme) + { + return ResourceFactoryInternals.RESOURCE_FACTORIES.get(scheme); + } + + static ResourceFactory getBestByScheme(String str) + { + return ResourceFactoryInternals.RESOURCE_FACTORIES.getBest(str); + } + static ResourceFactory root() { return ResourceFactoryInternals.ROOT; diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java index 07a4e66e49b1..833a65b80ab6 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java @@ -19,6 +19,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.Index; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.Dumpable; @@ -30,6 +31,20 @@ class ResourceFactoryInternals { private static final Logger LOG = LoggerFactory.getLogger(ResourceFactoryInternals.class); + static final Index.Mutable RESOURCE_FACTORIES = new Index.Builder() + .caseSensitive(false) + .mutable() + .build(); + + static + { + // The default resource factories + RESOURCE_FACTORIES.put("jar", new MountedPathResourceFactory()); + PathResourceFactory pathResourceFactory = new PathResourceFactory(); + RESOURCE_FACTORIES.put("file", pathResourceFactory); + RESOURCE_FACTORIES.put("jrt", pathResourceFactory); + } + static ResourceFactory ROOT = new ResourceFactory() { @Override diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java index 1bbf7bede2e7..fcc838956cda 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java @@ -28,7 +28,6 @@ import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.FileSystemException; -import java.nio.file.FileSystemNotFoundException; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; @@ -62,8 +61,6 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -188,7 +185,7 @@ public void testNonAbsoluteURI(WorkDir workDir) throws Exception @Test public void testNotFileURI() { - assertThrows(FileSystemNotFoundException.class, + assertThrows(IllegalArgumentException.class, () -> ResourceFactory.root().newResource(new URI("https://www.eclipse.org/jetty/"))); } diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java new file mode 100644 index 000000000000..8b1773a1ae68 --- /dev/null +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java @@ -0,0 +1,92 @@ +// +// ======================================================================== +// 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.resource; + +import java.net.URI; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class ResourceFactoryTest +{ + @Test + public void testCustomUriScheme() + { + ResourceFactory.addResourceFactory("custom", new CustomResourceFactory()); + Resource resource = ResourceFactory.root().newResource("custom://foo"); + assertThat(resource.getURI(), is(URI.create("custom://foo"))); + assertThat(resource.getName(), is("custom-impl")); + } + + public static class CustomResourceFactory implements ResourceFactory + { + @Override + public Resource newResource(URI uri) + { + return new Resource() + { + @Override + public Path getPath() + { + return null; + } + + @Override + public boolean isContainedIn(Resource r) + { + return false; + } + + @Override + public boolean isDirectory() + { + return false; + } + + @Override + public boolean isReadable() + { + return false; + } + + @Override + public URI getURI() + { + return uri; + } + + @Override + public String getName() + { + return "custom-impl"; + } + + @Override + public String getFileName() + { + return null; + } + + @Override + public Resource resolve(String subUriPath) + { + return null; + } + }; + } + } +} From 43937b19870f54d36cadeb47f6a19acb3f0764a7 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 14 Nov 2022 13:08:50 -0600 Subject: [PATCH 2/8] Issue #8886 - changes to method names + abstract url impl --- .../resource/AbstractUrlResourceFactory.java | 234 ++++++++++++++++++ .../jetty/util/resource/ResourceFactory.java | 7 +- .../util/resource/ResourceFactoryTest.java | 32 ++- .../util/resource/UrlResourceFactoryTest.java | 80 ++++++ 4 files changed, 350 insertions(+), 3 deletions(-) create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/AbstractUrlResourceFactory.java create mode 100644 jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/AbstractUrlResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/AbstractUrlResourceFactory.java new file mode 100644 index 000000000000..c8d1d9de7e24 --- /dev/null +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/AbstractUrlResourceFactory.java @@ -0,0 +1,234 @@ +// +// ======================================================================== +// 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.resource; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.Path; +import java.time.Instant; + +import org.eclipse.jetty.util.FileID; + +/** + * Abstract ResourceFactory for {@link java.net.URL} based resources. + */ +public abstract class AbstractUrlResourceFactory implements ResourceFactory +{ + private final String supportedProtocol; + private int connectTimeout; + private boolean useCaches; + + protected AbstractUrlResourceFactory(String protocol) + { + this.supportedProtocol = protocol; + this.connectTimeout = Integer.parseInt(System.getProperty(this.getClass().getName() + ".connectTimeout", "1000")); + this.useCaches = Boolean.parseBoolean(System.getProperty(this.getClass().getName() + ".useCaches", "true")); + } + + public int getConnectTimeout() + { + return connectTimeout; + } + + public void setConnectTimeout(int connectTimeout) + { + this.connectTimeout = connectTimeout; + } + + public boolean isUseCaches() + { + return useCaches; + } + + public void setUseCaches(boolean useCaches) + { + this.useCaches = useCaches; + } + + @Override + public Resource newResource(final URI uri) + { + if (!uri.getScheme().equalsIgnoreCase(supportedProtocol)) + throw new IllegalArgumentException("Scheme not support: " + uri.getScheme()); + + try + { + return new URLResource(uri, this.connectTimeout, this.useCaches); + } + catch (MalformedURLException e) + { + throw new RuntimeException("Bad URI: " + uri, e); + } + } + + private static class URLResource extends Resource + { + private final URI uri; + private final URL url; + private final int connectTimeout; + private final boolean useCaches; + + public URLResource(URI uri, int connectTimeout, boolean useCaches) throws MalformedURLException + { + this.uri = uri; + this.url = uri.toURL(); + this.connectTimeout = connectTimeout; + this.useCaches = useCaches; + } + + private URLConnection newConnection() throws IOException + { + URLConnection urlConnection = url.openConnection(); + urlConnection.setUseCaches(this.useCaches); + urlConnection.setConnectTimeout(this.connectTimeout); + return urlConnection; + } + + @Override + public Path getPath() + { + return null; + } + + @Override + public boolean isContainedIn(Resource r) + { + // compare starting URIs? + return false; + } + + @Override + public boolean isDirectory() + { + return uri.getPath().endsWith("/"); + } + + @Override + public boolean isReadable() + { + return exists(); + } + + @Override + public URI getURI() + { + return uri; + } + + @Override + public String getName() + { + return uri.getPath(); + } + + @Override + public String getFileName() + { + return FileID.getFileName(uri); + } + + @Override + public Resource resolve(String subUriPath) + { + URI newURI = uri.resolve(subUriPath); + try + { + return new URLResource(newURI, this.connectTimeout, this.useCaches); + } + catch (MalformedURLException e) + { + return null; + } + } + + @Override + public boolean exists() + { + try + { + newConnection(); + return true; + } + catch (IOException e) + { + return false; + } + } + + @Override + public InputStream newInputStream() throws IOException + { + URLConnection urlConnection = newConnection(); + return urlConnection.getInputStream(); + } + + @Override + public Instant lastModified() + { + try + { + URLConnection urlConnection = newConnection(); + return Instant.ofEpochMilli(urlConnection.getLastModified()); + } + catch (IOException e) + { + return Instant.EPOCH; + } + } + + @Override + public long length() + { + try + { + URLConnection urlConnection = newConnection(); + return urlConnection.getContentLengthLong(); + } + catch (IOException e) + { + return -1; + } + } + + @Override + public ReadableByteChannel newReadableByteChannel() + { + // not really possible with the URL interface + return null; + } + + @Override + public boolean isAlias() + { + return false; + } + + @Override + public URI getRealURI() + { + return getURI(); + } + + @Override + public String toString() + { + return String.format("URLResource@%X(%s)", this.uri.hashCode(), this.uri.toASCIIString()); + } + } +} diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java index 4eb9c7c5030f..ad5ac1070cd6 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java @@ -236,11 +236,16 @@ default Resource newJarFileResource(URI uri) return newResource(URIUtil.toJarFileUri(uri)); } - static void addResourceFactory(String scheme, ResourceFactory resource) + static void registerResourceFactory(String scheme, ResourceFactory resource) { ResourceFactoryInternals.RESOURCE_FACTORIES.put(scheme, resource); } + static ResourceFactory unregisterResourceFactory(String scheme) + { + return ResourceFactoryInternals.RESOURCE_FACTORIES.remove(scheme); + } + static ResourceFactory byScheme(String scheme) { return ResourceFactoryInternals.RESOURCE_FACTORIES.get(scheme); diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java index 8b1773a1ae68..6ed15c299915 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java @@ -19,17 +19,45 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; public class ResourceFactoryTest { @Test - public void testCustomUriScheme() + public void testCustomUriSchemeNotRegistered() { - ResourceFactory.addResourceFactory("custom", new CustomResourceFactory()); + // Try this as a normal String input first. + // We are subject to the URIUtil.toURI(String) behaviors here. + // Since the `ftp` scheme is not registered, it's not recognized as a supported URI. + // This will be treated as a relative path instead. (and the '//' will be compacted) + Resource resource = ResourceFactory.root().newResource("ftp://webtide.com/favicon.ico"); + // Should not find this, as it doesn't exist on the filesystem. + assertNull(resource); + + // Now try it as a formal URI object as input. + URI uri = URI.create("ftp://webtide.com/favicon.ico"); + // This is an unsupported URI scheme + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> ResourceFactory.root().newResource(uri)); + assertThat(iae.getMessage(), containsString("URI scheme not supported")); + } + + @Test + public void testCustomUriSchemeRegistered() + { + ResourceFactory.registerResourceFactory("custom", new CustomResourceFactory()); + // Try as a normal String input Resource resource = ResourceFactory.root().newResource("custom://foo"); assertThat(resource.getURI(), is(URI.create("custom://foo"))); assertThat(resource.getName(), is("custom-impl")); + + // Try as a formal URI object as input + URI uri = URI.create("custom://foo"); + resource = ResourceFactory.root().newResource(uri); + assertThat(resource.getURI(), is(URI.create("custom://foo"))); + assertThat(resource.getName(), is("custom-impl")); } public static class CustomResourceFactory implements ResourceFactory diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java new file mode 100644 index 000000000000..e2e65a782ca3 --- /dev/null +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java @@ -0,0 +1,80 @@ +// +// ======================================================================== +// 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.resource; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.time.Instant; + +import org.eclipse.jetty.util.IO; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class UrlResourceFactoryTest +{ + @Test + @Tag("external") + public void testHttps() throws IOException + { + ResourceFactory.registerResourceFactory("https", new HttpsResourceFactory()); + Resource resource = ResourceFactory.root().newResource(URI.create("https://webtide.com/")); + assertThat(resource, notNullValue()); + assertTrue(resource.exists()); + + try (InputStream in = resource.newInputStream()) + { + String result = IO.toString(in, StandardCharsets.UTF_8); + assertThat(result, containsString("webtide.com")); + } + + assertThat(resource.lastModified().toEpochMilli(), not(Instant.EPOCH)); + assertThat(resource.length(), not(-1)); + assertTrue(resource.isDirectory()); + assertThat(resource.getFileName(), is("")); + + Resource blogs = resource.resolve("blogs/"); + assertThat(blogs, notNullValue()); + assertTrue(blogs.exists()); + assertThat(blogs.lastModified().toEpochMilli(), not(Instant.EPOCH)); + assertThat(blogs.length(), not(-1)); + assertTrue(blogs.isDirectory()); + assertThat(blogs.getFileName(), is("")); + + Resource favicon = resource.resolve("favicon.ico"); + assertThat(favicon, notNullValue()); + assertTrue(favicon.exists()); + assertThat(favicon.lastModified().toEpochMilli(), not(Instant.EPOCH)); + assertThat(favicon.length(), not(-1)); + assertFalse(favicon.isDirectory()); + assertThat(favicon.getFileName(), is("favicon.ico")); + } + + public static class HttpsResourceFactory extends AbstractUrlResourceFactory + { + public HttpsResourceFactory() + { + super("https"); + } + } +} From 092d24cde5bfca02a2f0377fd7ee276c9ac19809 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 14 Nov 2022 15:12:59 -0600 Subject: [PATCH 3/8] Flagging flaky test --- .../test/java/org/eclipse/jetty/http2/tests/TrailersTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java index abeefaf6aaa6..1a58563361ab 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java @@ -44,6 +44,7 @@ import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.StringUtil; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import static java.nio.charset.StandardCharsets.UTF_8; @@ -253,6 +254,7 @@ public void onHeaders(Stream stream, HeadersFrame frame) } @Test + @Tag("flaky") // see #8896 public void testTrailersSentByServerShouldNotSendEmptyDataFrame() throws Exception { String trailerName = "X-Trailer"; From eae1d4c63b966f0cb977710e1254500a3ba3edbc Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 21 Nov 2022 10:47:24 -0600 Subject: [PATCH 4/8] Revert flaky --- .../test/java/org/eclipse/jetty/http2/tests/TrailersTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java index 0f9c86a0d329..dde7789dbb5d 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java @@ -44,7 +44,6 @@ import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.StringUtil; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import static java.nio.charset.StandardCharsets.UTF_8; @@ -254,7 +253,6 @@ public void onHeaders(Stream stream, HeadersFrame frame) } @Test - @Tag("flaky") // see #8896 public void testTrailersSentByServerShouldNotSendEmptyDataFrame() throws Exception { String trailerName = "X-Trailer"; From c981b3b5f5eb8ba36fbb8af9e65fac444e46eae4 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 21 Nov 2022 11:35:09 -0600 Subject: [PATCH 5/8] Introduce CompositeResourceFactory + CompositeResourceFactory is doing what Resource.create(URI) did before. + CompositeResourceFactory is tracking mounts, and allowing the ability to report onMounted (useful for RF.ROOT) + ResourceFactory.ROOT, ResourceFactory.Closable, and ResourceFactory.LifeCycle all use this new CompositeResourceFactory --- .../jetty/util/resource/FileSystemPool.java | 2 +- .../eclipse/jetty/util/resource/Resource.java | 45 ----- .../jetty/util/resource/ResourceFactory.java | 2 +- .../resource/ResourceFactoryInternals.java | 169 ++++++++++++------ 4 files changed, 114 insertions(+), 104 deletions(-) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileSystemPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileSystemPool.java index 8eb8ea20b74d..ef828a782338 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileSystemPool.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileSystemPool.java @@ -128,7 +128,7 @@ Mount mount(URI uri) throws IOException } // use root FS URI so that pool key/release/sweep is sane URI rootURI = fileSystem.getPath("/").toUri(); - Mount mount = new Mount(rootURI, Resource.create(uri)); + Mount mount = new Mount(rootURI, new MountedPathResource(uri)); retain(rootURI, fileSystem, mount); return mount; } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java index d69f5312aca2..89bbd8f2dfa7 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java @@ -17,12 +17,10 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URI; -import java.net.URISyntaxException; import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; -import java.nio.file.ProviderNotFoundException; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.time.Instant; @@ -32,7 +30,6 @@ import java.util.List; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.URIUtil; /** *

@@ -45,12 +42,6 @@ public abstract class Resource implements Iterable { private static final LinkOption[] NO_FOLLOW_LINKS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; - private static final Path CURRENT_WORKING_DIR; - - static - { - CURRENT_WORKING_DIR = Path.of(System.getProperty("user.dir")); - } public static String dump(Resource resource) { @@ -60,42 +51,6 @@ public static String dump(Resource resource) .formatted(resource.toString(), resource.exists(), resource.isDirectory(), resource.lastModified()); } - /** - * Construct a resource from a uri. - * - * @param uri A URI. - * @return A Resource object. - */ - static Resource create(URI uri) - { - try - { - // If the URI is not absolute - if (!uri.isAbsolute()) - { - // If it is an absolute path, - if (uri.toString().startsWith("/")) - // just add the scheme - uri = new URI("file", uri.toString(), null); - else - // otherwise resolve against the current directory - uri = CURRENT_WORKING_DIR.toUri().resolve(uri); - - // Correct any `file:/path` to `file:///path` mistakes - uri = URIUtil.correctFileURI(uri); - } - - ResourceFactory resourceFactory = ResourceFactory.byScheme(uri.getScheme()); - if (resourceFactory == null) - throw new IllegalArgumentException("URI scheme not supported: " + uri); - return resourceFactory.newResource(uri); - } - catch (URISyntaxException | ProviderNotFoundException ex) - { - throw new IllegalArgumentException(ex); - } - } - /** * Return the Path corresponding to this resource. * diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java index ad5ac1070cd6..dfea8b0b45b2 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java @@ -148,7 +148,7 @@ default Resource newClassPathResource(String resource) try { URI uri = url.toURI(); - return Resource.create(uri); + return newResource(uri); } catch (URISyntaxException e) { diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java index 833a65b80ab6..5f3406e24cd1 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java @@ -15,6 +15,9 @@ import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.ProviderNotFoundException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -30,7 +33,11 @@ class ResourceFactoryInternals { private static final Logger LOG = LoggerFactory.getLogger(ResourceFactoryInternals.class); + private static final Path CURRENT_WORKING_DIR; + /** + * The Index (Map) of URI schemes to ResourceFactory implementations that is used by {@link CompositeResourceFactory} + */ static final Index.Mutable RESOURCE_FACTORIES = new Index.Builder() .caseSensitive(false) .mutable() @@ -38,6 +45,8 @@ class ResourceFactoryInternals static { + CURRENT_WORKING_DIR = Path.of(System.getProperty("user.dir")); + // The default resource factories RESOURCE_FACTORIES.put("jar", new MountedPathResourceFactory()); PathResourceFactory pathResourceFactory = new PathResourceFactory(); @@ -45,102 +54,148 @@ class ResourceFactoryInternals RESOURCE_FACTORIES.put("jrt", pathResourceFactory); } - static ResourceFactory ROOT = new ResourceFactory() + static ResourceFactory ROOT = new CompositeResourceFactory() { @Override - public Resource newResource(URI uri) - { - FileSystemPool.Mount mount = mountIfNeeded(uri); - if (mount != null) - { - if (LOG.isDebugEnabled()) - LOG.warn("Leaked {} for {}", mount, uri, new Throwable()); - else - LOG.warn("Leaked {} for {}", mount, uri); - } - return Resource.create(uri); - } - - @Override - public Resource newResource(String resource) + protected void onMounted(FileSystemPool.Mount mount, URI uri) { - return newResource(URIUtil.toURI(resource)); + // Since this ROOT ResourceFactory and has no lifecycle that can clean up + // the mount, we shall report this mount as a leak + if (LOG.isDebugEnabled()) + LOG.warn("Leaked {} for {}", mount, uri, new Throwable()); + else + LOG.warn("Leaked {} for {}", mount, uri); } }; - /** - *

Mount a URI if it is needed.

- * - * @param uri The URI to mount that may require a FileSystem (e.g. "jar:file://tmp/some.jar!/directory/file.txt") - * @return A reference counted {@link FileSystemPool.Mount} for that file system or null. Callers should call - * {@link FileSystemPool.Mount#close()} once they no longer require any resources from a mounted resource. - * @throws IllegalArgumentException If the uri could not be mounted. - */ - static FileSystemPool.Mount mountIfNeeded(URI uri) - { - if (uri == null) - return null; - String scheme = uri.getScheme(); - if (scheme == null || !scheme.equalsIgnoreCase("jar")) - return null; - try - { - return FileSystemPool.INSTANCE.mount(uri); - } - catch (IOException ioe) - { - throw new IllegalArgumentException(ioe); - } - } - static class Closeable implements ResourceFactory.Closeable { - private final List _mounts = new CopyOnWriteArrayList<>(); + private final CompositeResourceFactory _compositeResourceFactory = new CompositeResourceFactory(); @Override public Resource newResource(URI uri) { - FileSystemPool.Mount mount = mountIfNeeded(uri); - if (mount != null) - _mounts.add(mount); - return Resource.create(uri); + return _compositeResourceFactory.newResource(uri); } @Override public void close() { - for (FileSystemPool.Mount mount : _mounts) + for (FileSystemPool.Mount mount : _compositeResourceFactory.getMounts()) IO.close(mount); - _mounts.clear(); + _compositeResourceFactory.clearMounts(); } } static class LifeCycle extends AbstractLifeCycle implements ResourceFactory.LifeCycle { - private final List _mounts = new CopyOnWriteArrayList<>(); + private final CompositeResourceFactory _compositeResourceFactory = new CompositeResourceFactory(); @Override public Resource newResource(URI uri) { - FileSystemPool.Mount mount = mountIfNeeded(uri); - if (mount != null) - _mounts.add(mount); - return Resource.create(uri); + return _compositeResourceFactory.newResource(uri); } @Override protected void doStop() throws Exception { - for (FileSystemPool.Mount mount : _mounts) + for (FileSystemPool.Mount mount : _compositeResourceFactory.getMounts()) IO.close(mount); - _mounts.clear(); + _compositeResourceFactory.clearMounts(); super.doStop(); } @Override public void dump(Appendable out, String indent) throws IOException { - Dumpable.dumpObjects(out, indent, this, new DumpableCollection("mounts", _mounts)); + Dumpable.dumpObjects(out, indent, this, new DumpableCollection("mounts", _compositeResourceFactory.getMounts())); + } + } + + static class CompositeResourceFactory implements ResourceFactory + { + private final List _mounts = new CopyOnWriteArrayList<>(); + + @Override + public Resource newResource(URI uri) + { + if (uri == null) + return null; + + try + { + // If the URI is not absolute + if (!uri.isAbsolute()) + { + // If it is an absolute path, + if (uri.toString().startsWith("/")) + // just add the scheme + uri = new URI("file", uri.toString(), null); + else + // otherwise resolve against the current directory + uri = CURRENT_WORKING_DIR.toUri().resolve(uri); + + // Correct any `file:/path` to `file:///path` mistakes + uri = URIUtil.correctFileURI(uri); + } + + ResourceFactory resourceFactory = ResourceFactory.byScheme(uri.getScheme()); + if (resourceFactory == null) + throw new IllegalArgumentException("URI scheme not supported: " + uri); + if (resourceFactory instanceof MountedPathResourceFactory) + { + FileSystemPool.Mount mount = mountIfNeeded(uri); + if (mount != null) + { + _mounts.add(mount); + onMounted(mount, uri); + } + } + return resourceFactory.newResource(uri); + } + catch (URISyntaxException | ProviderNotFoundException ex) + { + throw new IllegalArgumentException("Unable to create resource from: " + uri, ex); + } + } + + /** + *

Mount a URI if it is needed.

+ * + * @param uri The URI to mount that may require a FileSystem (e.g. "jar:file://tmp/some.jar!/directory/file.txt") + * @return A reference counted {@link FileSystemPool.Mount} for that file system or null. Callers should call + * {@link FileSystemPool.Mount#close()} once they no longer require any resources from a mounted resource. + * @throws IllegalArgumentException If the uri could not be mounted. + */ + private FileSystemPool.Mount mountIfNeeded(URI uri) + { + String scheme = uri.getScheme(); + if (!"jar".equalsIgnoreCase(scheme)) + return null; + try + { + return FileSystemPool.INSTANCE.mount(uri); + } + catch (IOException ioe) + { + throw new IllegalArgumentException("Unable to mount: " + uri, ioe); + } + } + + protected void onMounted(FileSystemPool.Mount mount, URI uri) + { + // override to specify behavior + } + + public List getMounts() + { + return _mounts; + } + + public void clearMounts() + { + _mounts.clear(); } } } From c00def58cb0e6e3d97b5f7c54ff88e81b2e8534a Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 21 Nov 2022 11:42:40 -0600 Subject: [PATCH 6/8] Rename AbstractUrlResourceFactory to UrlResourceFactory + Is package private + Add ResourceFactory.registerResourceFactory(String) --- .../jetty/util/resource/ResourceFactory.java | 5 +++++ ...ceFactory.java => UrlResourceFactory.java} | 8 +++---- .../util/resource/ResourceFactoryTest.java | 22 +++++++++++++++++++ .../util/resource/UrlResourceFactoryTest.java | 2 +- 4 files changed, 32 insertions(+), 5 deletions(-) rename jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/{AbstractUrlResourceFactory.java => UrlResourceFactory.java} (96%) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java index dfea8b0b45b2..309e09331f89 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java @@ -241,6 +241,11 @@ static void registerResourceFactory(String scheme, ResourceFactory resource) ResourceFactoryInternals.RESOURCE_FACTORIES.put(scheme, resource); } + static void registerUrlResourceFactory(String scheme) + { + ResourceFactoryInternals.RESOURCE_FACTORIES.put(scheme, new UrlResourceFactory(scheme)); + } + static ResourceFactory unregisterResourceFactory(String scheme) { return ResourceFactoryInternals.RESOURCE_FACTORIES.remove(scheme); diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/AbstractUrlResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/UrlResourceFactory.java similarity index 96% rename from jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/AbstractUrlResourceFactory.java rename to jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/UrlResourceFactory.java index c8d1d9de7e24..3f42711613e7 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/AbstractUrlResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/UrlResourceFactory.java @@ -26,15 +26,15 @@ import org.eclipse.jetty.util.FileID; /** - * Abstract ResourceFactory for {@link java.net.URL} based resources. + * {@link ResourceFactory} for {@link java.net.URL} based resources. */ -public abstract class AbstractUrlResourceFactory implements ResourceFactory +class UrlResourceFactory implements ResourceFactory { private final String supportedProtocol; private int connectTimeout; private boolean useCaches; - protected AbstractUrlResourceFactory(String protocol) + protected UrlResourceFactory(String protocol) { this.supportedProtocol = protocol; this.connectTimeout = Integer.parseInt(System.getProperty(this.getClass().getName() + ".connectTimeout", "1000")); @@ -134,7 +134,7 @@ public URI getURI() @Override public String getName() { - return uri.getPath(); + return uri.toASCIIString(); } @Override diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java index 6ed15c299915..385bfd7c46cf 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java @@ -20,6 +20,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -60,6 +61,27 @@ public void testCustomUriSchemeRegistered() assertThat(resource.getName(), is("custom-impl")); } + @Test + public void testRegisterHttpsUrlFactory() + { + ResourceFactory.registerUrlResourceFactory("https"); + // Try as a normal String input + Resource resource = ResourceFactory.root().newResource("https://webtide.com/"); + assertThat(resource.getURI(), is(URI.create("https://webtide.com/"))); + assertThat(resource.getName(), is("https://webtide.com/")); + + // Try as a formal URI object as input + URI uri = URI.create("https://webtide.com/"); + resource = ResourceFactory.root().newResource(uri); + assertThat(resource.getURI(), is(URI.create("https://webtide.com/"))); + assertThat(resource.getName(), is("https://webtide.com/")); + + // Try a sub-resource + Resource subResource = resource.resolve("favicon.ico"); + assertThat(subResource.getFileName(), is("favicon.ico")); + assertThat(subResource.length(), greaterThan(0L)); + } + public static class CustomResourceFactory implements ResourceFactory { @Override diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java index e2e65a782ca3..ee458f0e222a 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java @@ -70,7 +70,7 @@ public void testHttps() throws IOException assertThat(favicon.getFileName(), is("favicon.ico")); } - public static class HttpsResourceFactory extends AbstractUrlResourceFactory + public static class HttpsResourceFactory extends UrlResourceFactory { public HttpsResourceFactory() { From 0a95ea2f9c74597e373b996ca636e32842fe00d2 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 21 Nov 2022 11:52:31 -0600 Subject: [PATCH 7/8] Adding UrlResourceFactory.toString --- .../org/eclipse/jetty/util/resource/UrlResourceFactory.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/UrlResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/UrlResourceFactory.java index 3f42711613e7..924a4286fe4c 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/UrlResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/UrlResourceFactory.java @@ -77,6 +77,12 @@ public Resource newResource(final URI uri) } } + @Override + public String toString() + { + return String.format("%s@%x[%s]", this.getClass().getName(), this.hashCode(), this.supportedProtocol); + } + private static class URLResource extends Resource { private final URI uri; From 46ccfa1c9eaca2fc02ab616f5133be92654d388c Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 22 Nov 2022 17:15:03 -0600 Subject: [PATCH 8/8] Fixing URLResourceFactory --- .../jetty/util/resource/ResourceFactory.java | 5 ----- ...ceFactory.java => URLResourceFactory.java} | 21 ++++--------------- .../util/resource/ResourceFactoryTest.java | 2 +- .../util/resource/UrlResourceFactoryTest.java | 10 +-------- 4 files changed, 6 insertions(+), 32 deletions(-) rename jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/{UrlResourceFactory.java => URLResourceFactory.java} (86%) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java index 309e09331f89..dfea8b0b45b2 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java @@ -241,11 +241,6 @@ static void registerResourceFactory(String scheme, ResourceFactory resource) ResourceFactoryInternals.RESOURCE_FACTORIES.put(scheme, resource); } - static void registerUrlResourceFactory(String scheme) - { - ResourceFactoryInternals.RESOURCE_FACTORIES.put(scheme, new UrlResourceFactory(scheme)); - } - static ResourceFactory unregisterResourceFactory(String scheme) { return ResourceFactoryInternals.RESOURCE_FACTORIES.remove(scheme); diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/UrlResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResourceFactory.java similarity index 86% rename from jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/UrlResourceFactory.java rename to jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResourceFactory.java index 924a4286fe4c..8d98d34dae54 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/UrlResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResourceFactory.java @@ -28,17 +28,13 @@ /** * {@link ResourceFactory} for {@link java.net.URL} based resources. */ -class UrlResourceFactory implements ResourceFactory +public class URLResourceFactory implements ResourceFactory { - private final String supportedProtocol; - private int connectTimeout; - private boolean useCaches; + private int connectTimeout = 1000; + private boolean useCaches = true; - protected UrlResourceFactory(String protocol) + public URLResourceFactory() { - this.supportedProtocol = protocol; - this.connectTimeout = Integer.parseInt(System.getProperty(this.getClass().getName() + ".connectTimeout", "1000")); - this.useCaches = Boolean.parseBoolean(System.getProperty(this.getClass().getName() + ".useCaches", "true")); } public int getConnectTimeout() @@ -64,9 +60,6 @@ public void setUseCaches(boolean useCaches) @Override public Resource newResource(final URI uri) { - if (!uri.getScheme().equalsIgnoreCase(supportedProtocol)) - throw new IllegalArgumentException("Scheme not support: " + uri.getScheme()); - try { return new URLResource(uri, this.connectTimeout, this.useCaches); @@ -77,12 +70,6 @@ public Resource newResource(final URI uri) } } - @Override - public String toString() - { - return String.format("%s@%x[%s]", this.getClass().getName(), this.hashCode(), this.supportedProtocol); - } - private static class URLResource extends Resource { private final URI uri; diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java index 385bfd7c46cf..aa8dc944deba 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceFactoryTest.java @@ -64,7 +64,7 @@ public void testCustomUriSchemeRegistered() @Test public void testRegisterHttpsUrlFactory() { - ResourceFactory.registerUrlResourceFactory("https"); + ResourceFactory.registerResourceFactory("https", new URLResourceFactory()); // Try as a normal String input Resource resource = ResourceFactory.root().newResource("https://webtide.com/"); assertThat(resource.getURI(), is(URI.create("https://webtide.com/"))); diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java index ee458f0e222a..6d7c31217628 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java @@ -37,7 +37,7 @@ public class UrlResourceFactoryTest @Tag("external") public void testHttps() throws IOException { - ResourceFactory.registerResourceFactory("https", new HttpsResourceFactory()); + ResourceFactory.registerResourceFactory("https", new URLResourceFactory()); Resource resource = ResourceFactory.root().newResource(URI.create("https://webtide.com/")); assertThat(resource, notNullValue()); assertTrue(resource.exists()); @@ -69,12 +69,4 @@ public void testHttps() throws IOException assertFalse(favicon.isDirectory()); assertThat(favicon.getFileName(), is("favicon.ico")); } - - public static class HttpsResourceFactory extends UrlResourceFactory - { - public HttpsResourceFactory() - { - super("https"); - } - } }