diff --git a/Jenkinsfile b/Jenkinsfile index c3ac98e1f00..9cb469dc7da 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -28,21 +28,10 @@ pipeline { } } - stage("Build / Test - JDK22") { - agent { node { label 'linux' } } - steps { - timeout( time: 210, unit: 'MINUTES' ) { - checkout scm - mavenBuild( "jdk22", "clean install -Dspotbugs.skip=true -Djacoco.skip=true", "maven3") - recordIssues id: "jdk22", name: "Static Analysis jdk22", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle(), javaDoc()] - } - } - } - stage("Build / Test - JDK23") { agent { node { label 'linux' } } steps { - timeout( time: 180, unit: 'MINUTES' ) { + timeout( time: 210, unit: 'MINUTES' ) { checkout scm mavenBuild( "jdk23", "clean install -Dspotbugs.skip=true -Djacoco.skip=true", "maven3") recordIssues id: "jdk23", name: "Static Analysis jdk23", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle(), javaDoc()] diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java index 1782d1de9f1..dd478f1ab26 100644 --- a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java +++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java @@ -47,6 +47,7 @@ import org.eclipse.jetty.client.InputStreamRequestContent; import org.eclipse.jetty.client.InputStreamResponseListener; import org.eclipse.jetty.client.OutputStreamRequestContent; import org.eclipse.jetty.client.PathRequestContent; +import org.eclipse.jetty.client.PathResponseListener; import org.eclipse.jetty.client.ProxyConfiguration; import org.eclipse.jetty.client.Request; import org.eclipse.jetty.client.Response; @@ -494,6 +495,30 @@ public class HTTPClientDocs // end::inputStreamResponseListener[] } + public void pathResponseListener() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + + // tag::pathResponseListener[] + Path savePath = Path.of("/path/to/save/file.bin"); + + // Typical usage as a response listener. + PathResponseListener listener = new PathResponseListener(savePath, true); + httpClient.newRequest("http://domain.com/path") + .send(listener); + // Wait for the response content to be saved. + var result = listener.get(5, TimeUnit.SECONDS); + + // Alternative usage with CompletableFuture. + var completable = PathResponseListener.write(httpClient.newRequest("http://domain.com/path"), savePath, true); + completable.whenComplete((pathResponse, failure) -> + { + // Your logic here. + }); + // end::pathResponseListener[] + } + public void forwardContent() throws Exception { HttpClient httpClient = new HttpClient(); diff --git a/documentation/jetty/modules/programming-guide/pages/client/http.adoc b/documentation/jetty/modules/programming-guide/pages/client/http.adoc index e09b2ef1ea6..5ee9cba567e 100644 --- a/documentation/jetty/modules/programming-guide/pages/client/http.adoc +++ b/documentation/jetty/modules/programming-guide/pages/client/http.adoc @@ -468,6 +468,13 @@ If you want to avoid buffering, you can wait for the response and then stream th include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tags=inputStreamResponseListener] ---- +If you want to save the response content to a file, you can use the `PathResponseListener` utility class: + +[,java,indent=0] +---- +include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tags=pathResponseListener] +---- + Finally, let's look at the advanced usage of the response content handling. The response content is provided by the `HttpClient` implementation to application listeners following the read/demand model of `org.eclipse.jetty.io.Content.Source`. diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/PathResponseListener.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/PathResponseListener.java new file mode 100644 index 00000000000..a908861b462 --- /dev/null +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/PathResponseListener.java @@ -0,0 +1,140 @@ +// +// ======================================================================== +// 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.client; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.jetty.client.Response.Listener; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.util.IO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

Implementation of {@link Response.ContentListener} that + * saves the response content to a file {@link Path}, like + * {@code curl -o file.bin} does.

+ *

Typical usage is:

+ *
{@code
+ * // Typical usage.
+ * httpClient.newRequest(host, port)
+ *     .send(new PathResponseListener(Path.of("/tmp/file.bin")), overwriteExistingFile);
+ *
+ * // Alternative usage.
+ * var request = httpClient.newRequest(host, port);
+ * CompletableFuture completable = PathResponseListener.write(request, Path.of("/tmp/file.bin"), overwriteExistingFile);
+ * }
+ */ +public class PathResponseListener extends CompletableFuture implements Listener +{ + private static final Logger LOG = LoggerFactory.getLogger(InputStreamResponseListener.class); + + private final Path path; + private final FileChannel fileChannel; + + public PathResponseListener(Path path, boolean overwrite) throws IOException + { + this.path = path; + + if (Files.exists(path) && !overwrite) + throw new FileAlreadyExistsException(path.toString(), null, "File cannot be overwritten"); + + fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + } + + @Override + public void onHeaders(Response response) + { + if (response.getStatus() != HttpStatus.OK_200) + response.abort(new HttpResponseException(String.format("Cannot save response content for HTTP status code %d", response.getStatus()), response)); + else if (LOG.isDebugEnabled()) + LOG.debug("saving response content to {}", path); + } + + @Override + public void onContent(Response response, ByteBuffer content) + { + try + { + var bytesWritten = fileChannel.write(content); + if (LOG.isDebugEnabled()) + LOG.debug("{} bytes written to {}", bytesWritten, path); + } + catch (Throwable x) + { + response.abort(x); + } + } + + @Override + public void onSuccess(Response response) + { + if (LOG.isDebugEnabled()) + LOG.debug("saved response content to {}", path); + } + + @Override + public void onFailure(Response response, Throwable failure) + { + if (LOG.isDebugEnabled()) + LOG.debug("failed to save response content to {}", path); + } + + @Override + public void onComplete(Result result) + { + IO.close(fileChannel); + if (result.isSucceeded()) + complete(new PathResponse(result.getResponse(), path)); + else + completeExceptionally(result.getFailure()); + } + + /** + *

Writes the response content to the given file {@link Path}.

+ * + * @param request the HTTP request + * @param path the path to write the response content to + * @param overwrite whether to overwrite an existing file + * @return a {@link CompletableFuture} that is completed when the exchange completes + */ + public static CompletableFuture write(Request request, Path path, boolean overwrite) + { + PathResponseListener listener = null; + try + { + listener = new PathResponseListener(path, overwrite); + request.send(listener); + return listener; + } + catch (Throwable x) + { + CompletableFuture completable = Objects.requireNonNullElse(listener, new CompletableFuture<>()); + completable.completeExceptionally(x); + return completable; + } + } + + public record PathResponse(Response response, Path path) + { + } +} diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/PathResponseListenerTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/PathResponseListenerTest.java new file mode 100644 index 00000000000..d5a4a7b333c --- /dev/null +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/PathResponseListenerTest.java @@ -0,0 +1,133 @@ +// +// ======================================================================== +// 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.client; + +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.ResourceHandler; +import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.resource.ResourceFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class PathResponseListenerTest +{ + private Server server; + private ServerConnector connector; + private Path resourceDir; + + @BeforeEach + public void startServer() throws Exception + { + QueuedThreadPool serverThreads = new QueuedThreadPool(); + serverThreads.setName("server"); + server = new Server(serverThreads); + ResourceHandler resourceHandler = new ResourceHandler(); + resourceDir = MavenTestingUtils.getTargetTestingPath(PathResponseListenerTest.class.getSimpleName()); + FS.ensureEmpty(resourceDir); + resourceHandler.setBaseResource(ResourceFactory.of(server).newResource(resourceDir)); + resourceHandler.setDirAllowed(false); + connector = new ServerConnector(server); + server.addConnector(connector); + server.setHandler(resourceHandler); + server.start(); + } + + @AfterEach + public void stopServer() throws Exception + { + FS.ensureEmpty(resourceDir); + LifeCycle.stop(server); + } + + private Path createServerFile(int length) throws IOException + { + Path path = Files.createTempFile(resourceDir, "file-", ".bin"); + try (SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.WRITE)) + { + ByteBuffer buffer = ByteBuffer.allocate(length); + ThreadLocalRandom.current().nextBytes(buffer.array()); + channel.write(buffer); + } + return path; + } + + @ParameterizedTest + @ValueSource(ints = {0, 1024, 1048576}) + public void testFileDownload(int length) throws Exception + { + try (HttpClient client = new HttpClient()) + { + client.start(); + + Path serverPath = createServerFile(length); + URI uri = URI.create("http://localhost:" + connector.getLocalPort() + "/" + serverPath.getFileName()); + + Path clientPath = Files.createTempFile(resourceDir, "saved-", ".bin"); + PathResponseListener listener = new PathResponseListener(clientPath, true); + client.newRequest(uri).send(listener); + var pathResponse = listener.get(5, TimeUnit.SECONDS); + + assertTrue(Files.exists(pathResponse.path())); + assertArrayEquals(Files.readAllBytes(serverPath), Files.readAllBytes(clientPath)); + + // Do it again with overwrite=false. + Files.delete(clientPath); + listener = new PathResponseListener(clientPath, false); + client.newRequest(uri).send(listener); + pathResponse = listener.get(5, TimeUnit.SECONDS); + + assertTrue(Files.exists(pathResponse.path())); + assertArrayEquals(Files.readAllBytes(serverPath), Files.readAllBytes(clientPath)); + } + } + + @ParameterizedTest + @ValueSource(ints = {0, 1024, 1048576}) + public void testFileDownloadWithCompletable(int length) throws Exception + { + try (HttpClient client = new HttpClient()) + { + client.start(); + + Path serverPath = createServerFile(length); + URI uri = URI.create("http://localhost:" + connector.getLocalPort() + "/" + serverPath.getFileName()); + + Path clientPath = Files.createTempFile(resourceDir, "saved-", ".bin"); + var pathResponse = PathResponseListener.write(client.newRequest(uri), clientPath, true) + .get(5, TimeUnit.SECONDS); + + assertTrue(Files.exists(pathResponse.path())); + assertArrayEquals(Files.readAllBytes(serverPath), Files.readAllBytes(clientPath)); + } + } +} diff --git a/jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/SessionHandler.java b/jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/SessionHandler.java index 3e71cc1f05f..361063fbb98 100644 --- a/jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/SessionHandler.java +++ b/jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/SessionHandler.java @@ -66,7 +66,7 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si return false; SessionRequest sessionRequest = new SessionRequest(request); - addSessionStreamWrapper(request); + addSessionStreamWrapper(sessionRequest); return sessionRequest.process(next, response, callback); } diff --git a/jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/SessionHandlerTest.java b/jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/SessionHandlerTest.java index 9092c64630c..ffd60100860 100644 --- a/jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/SessionHandlerTest.java +++ b/jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/SessionHandlerTest.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.session; +import java.io.File; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -26,7 +27,9 @@ import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Session; import org.eclipse.jetty.session.AbstractSessionManager.RequestedSession; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.IO; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -739,4 +742,70 @@ public class SessionHandlerTest } } + @Test + public void testFlushOnResponseCommit() throws Exception + { + // Setup temporary datastore with write-through null cache + + File datastoreDir = MavenTestingUtils.getTargetTestingDir("datastore"); + IO.delete(datastoreDir); + + FileSessionDataStore datastore = new FileSessionDataStore(); + datastore.setStoreDir(datastoreDir); + + NullSessionCache cache = new NullSessionCache(_sessionHandler); + cache.setSessionDataStore(datastore); + cache.setFlushOnResponseCommit(true); + + _sessionHandler.setSessionCache(cache); + + _server.start(); + + LocalConnector.LocalEndPoint endPoint = _connector.connect(); + endPoint.addInput(""" + GET /create HTTP/1.1 + Host: localhost + + """); + + HttpTester.Response response = HttpTester.parseResponse(endPoint.getResponse()); + assertThat(response.getStatus(), equalTo(200)); + String setCookie = response.get(HttpHeader.SET_COOKIE); + String id = setCookie.substring(setCookie.indexOf("SESSION_ID=") + 11, setCookie.indexOf("; Path=/")); + String content = response.getContent(); + assertThat(content, startsWith("Session=")); + + endPoint.addInput(""" + GET /set/attribute/value HTTP/1.1 + Host: localhost + Cookie: SESSION_ID=%s + + """.formatted(id)); + + response = HttpTester.parseResponse(endPoint.getResponse()); + assertThat(response.getStatus(), equalTo(200)); + assertThat(response.get(HttpHeader.SET_COOKIE), nullValue()); + content = response.getContent(); + assertThat(content, containsString("Session=" + id.substring(0, id.indexOf(".node0")))); + assertThat(content, containsString("attribute = value")); + + // Session should persist through restart + _server.stop(); + _server.start(); + + endPoint.addInput(""" + GET /set/attribute/value HTTP/1.1 + Host: localhost + Cookie: SESSION_ID=%s + + """.formatted(id)); + + response = HttpTester.parseResponse(endPoint.getResponse()); + assertThat(response.getStatus(), equalTo(200)); + assertThat(response.get(HttpHeader.SET_COOKIE), nullValue()); + content = response.getContent(); + assertThat(content, containsString("Session=" + id.substring(0, id.indexOf(".node0")))); + assertThat(content, containsString("attribute = value")); + } + } 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 835434e0659..96cce9218d4 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 @@ -447,21 +447,40 @@ public interface ResourceFactory } /** - * Split a string of references, that may be split with '{@code ,}', or '{@code ;}', or '{@code |}' into URIs. + * Split a string of references, that may be split with '{@code ,}', or '{@code ;}', or '{@code |}' into a List of {@link Resource}. *

- * Each part of the input string could be path references (unix or windows style), or string URI references. + * Each part of the input string could be path references (unix or windows style), string URI references, or even glob references (eg: {@code /path/to/libs/*}). *

*

* If the result of processing the input segment is a java archive, then its resulting URI will be a mountable URI as {@code jar:file:...!/} *

* * @param str the input string of references + * @return list of resources */ default List split(String str) + { + return split(str, ",;|"); + } + + /** + * Split a string of references by provided delims into a List of {@link Resource}. + *

+ * Each part of the input string could be path references (unix or windows style), string URI references, or even glob references (eg: {@code /path/to/libs/*}). + * Note: that if you use the {@code :} character in your delims, then URI references will be impossible. + *

+ *

+ * If the result of processing the input segment is a java archive, then its resulting URI will be a mountable URI as {@code jar:file:...!/} + *

+ * + * @param str the input string of references + * @return list of resources + */ + default List split(String str, String delims) { List list = new ArrayList<>(); - StringTokenizer tokenizer = new StringTokenizer(str, ",;|"); + StringTokenizer tokenizer = new StringTokenizer(str, delims); while (tokenizer.hasMoreTokens()) { String reference = tokenizer.nextToken(); @@ -475,7 +494,6 @@ public interface ResourceFactory { List expanded = dir.list(); expanded.sort(ResourceCollators.byName(true)); - // TODO it is unclear why non archive files are not expanded into the list expanded.stream().filter(r -> FileID.isLibArchive(r.getName())).forEach(list::add); } } @@ -487,11 +505,12 @@ public interface ResourceFactory } catch (Exception e) { - LOG.warn("Invalid Resource Reference: " + reference); + LOG.warn("Invalid Resource Reference: {}", reference); throw e; } } + // Perform Archive file mounting (if needed) for (ListIterator i = list.listIterator(); i.hasNext(); ) { Resource resource = i.next(); 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 75febbcc883..1b9e9e7cb1a 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 @@ -405,7 +405,7 @@ public class ResourceFactoryTest } @Test - public void testSplitOnPipeWithGlob() throws IOException + public void testSplitOnPathSeparatorWithGlob() throws IOException { try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable()) { @@ -418,9 +418,54 @@ public class ResourceFactoryTest FS.ensureDirExists(bar); Files.copy(MavenPaths.findTestResourceFile("jar-file-resource.jar"), bar.resolve("lib-foo.jar")); Files.copy(MavenPaths.findTestResourceFile("jar-file-resource.jar"), bar.resolve("lib-zed.zip")); + Path exampleJar = base.resolve("example.jar"); + Files.copy(MavenPaths.findTestResourceFile("example.jar"), exampleJar); + + // This represents a classpath with a glob + String config = String.join(File.pathSeparator, List.of( + dir.toString(), foo.toString(), bar + File.separator + "*", exampleJar.toString() + )); + + // Split using commas + List uris = resourceFactory.split(config, File.pathSeparator).stream().map(Resource::getURI).toList(); + + URI[] expected = new URI[]{ + dir.toUri(), + foo.toUri(), + // Should see the two archives as `jar:file:` URI entries + URIUtil.toJarFileUri(bar.resolve("lib-foo.jar").toUri()), + URIUtil.toJarFileUri(bar.resolve("lib-zed.zip").toUri()), + URIUtil.toJarFileUri(exampleJar.toUri()) + }; + + assertThat(uris, contains(expected)); + } + } + + @ParameterizedTest + @ValueSource(strings = {";", "|", ","}) + public void testSplitOnDelimWithGlob(String delimChar) throws IOException + { + try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable()) + { + // TIP: don't allow raw delim to show up in base dir, otherwise the string split later will be wrong. + Path base = MavenPaths.targetTestDir("testSplitOnPipeWithGlob_%02x".formatted((byte)delimChar.charAt(0))); + FS.ensureEmpty(base); + Path dir = base.resolve("dir"); + FS.ensureDirExists(dir); + Path foo = dir.resolve("foo"); + FS.ensureDirExists(foo); + Path bar = dir.resolve("bar"); + FS.ensureDirExists(bar); + Files.copy(MavenPaths.findTestResourceFile("jar-file-resource.jar"), bar.resolve("lib-foo.jar")); + Files.copy(MavenPaths.findTestResourceFile("jar-file-resource.jar"), bar.resolve("lib-zed.zip")); + Path exampleJar = base.resolve("example.jar"); + Files.copy(MavenPaths.findTestResourceFile("example.jar"), exampleJar); // This represents the user-space raw configuration with a glob - String config = String.format("%s;%s;%s%s*", dir, foo, bar, File.separator); + String config = String.join(delimChar, List.of( + dir.toString(), foo.toString(), bar + File.separator + "*", exampleJar.toString() + )); // Split using commas List uris = resourceFactory.split(config).stream().map(Resource::getURI).toList(); @@ -430,7 +475,8 @@ public class ResourceFactoryTest foo.toUri(), // Should see the two archives as `jar:file:` URI entries URIUtil.toJarFileUri(bar.resolve("lib-foo.jar").toUri()), - URIUtil.toJarFileUri(bar.resolve("lib-zed.zip").toUri()) + URIUtil.toJarFileUri(bar.resolve("lib-zed.zip").toUri()), + URIUtil.toJarFileUri(exampleJar.toUri()) }; assertThat(uris, contains(expected)); diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-sessions/jetty-ee10-test-sessions-common/src/test/java/org/eclipse/jetty/ee10/session/AsyncTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-sessions/jetty-ee10-test-sessions-common/src/test/java/org/eclipse/jetty/ee10/session/AsyncTest.java index d6d60c97ff9..a89cfae4e99 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-sessions/jetty-ee10-test-sessions-common/src/test/java/org/eclipse/jetty/ee10/session/AsyncTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-sessions/jetty-ee10-test-sessions-common/src/test/java/org/eclipse/jetty/ee10/session/AsyncTest.java @@ -60,6 +60,7 @@ public class AsyncTest DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); @@ -106,6 +107,7 @@ public class AsyncTest DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); @@ -156,6 +158,7 @@ public class AsyncTest DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); @@ -202,9 +205,9 @@ public class AsyncTest @Test public void testSessionCreatedBeforeDispatch() throws Exception { - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); @@ -257,6 +260,7 @@ public class AsyncTest DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/MetaInfConfiguration.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/MetaInfConfiguration.java index cc14378bcad..fcb66de954d 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/MetaInfConfiguration.java +++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/MetaInfConfiguration.java @@ -166,10 +166,10 @@ public class MetaInfConfiguration extends AbstractConfiguration String classPath = System.getProperty("java.class.path"); if (classPath != null) { - Stream.of(classPath.split(File.pathSeparator)) - .map(resourceFactory::newResource) + resourceFactory.split(classPath, File.pathSeparator) + .stream() .filter(Objects::nonNull) - .filter(r -> uriPatternPredicate.test(r.getURI())) + .filter(r -> uriPatternPredicate.test(URIUtil.unwrapContainer(r.getURI()))) .forEach(addContainerResource); } diff --git a/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/MetaInfConfigurationTest.java b/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/MetaInfConfigurationTest.java index 3d54cf02593..df9d2b5aa50 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/MetaInfConfigurationTest.java +++ b/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/MetaInfConfigurationTest.java @@ -557,6 +557,7 @@ public class MetaInfConfigurationTest .stream() .sorted(ResourceCollators.byName(true)) .map(Resource::getURI) + .map(URIUtil::unwrapContainer) .map(URI::toASCIIString) .toList(); // we "correct" the bad file URLs that come from the ClassLoader diff --git a/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-sessions/jetty-ee11-test-sessions-common/src/test/java/org/eclipse/jetty/ee11/session/AsyncTest.java b/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-sessions/jetty-ee11-test-sessions-common/src/test/java/org/eclipse/jetty/ee11/session/AsyncTest.java index c3bb3dd5951..120ab75788a 100644 --- a/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-sessions/jetty-ee11-test-sessions-common/src/test/java/org/eclipse/jetty/ee11/session/AsyncTest.java +++ b/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-sessions/jetty-ee11-test-sessions-common/src/test/java/org/eclipse/jetty/ee11/session/AsyncTest.java @@ -60,6 +60,7 @@ public class AsyncTest DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); @@ -106,6 +107,7 @@ public class AsyncTest DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); @@ -156,6 +158,7 @@ public class AsyncTest DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); @@ -202,9 +205,9 @@ public class AsyncTest @Test public void testSessionCreatedBeforeDispatch() throws Exception { - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); @@ -257,6 +260,7 @@ public class AsyncTest DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); diff --git a/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/MetaInfConfiguration.java b/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/MetaInfConfiguration.java index 911b6485dc9..57e5d76cfc4 100644 --- a/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/MetaInfConfiguration.java +++ b/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/MetaInfConfiguration.java @@ -166,10 +166,10 @@ public class MetaInfConfiguration extends AbstractConfiguration String classPath = System.getProperty("java.class.path"); if (classPath != null) { - Stream.of(classPath.split(File.pathSeparator)) - .map(resourceFactory::newResource) + resourceFactory.split(classPath, File.pathSeparator) + .stream() .filter(Objects::nonNull) - .filter(r -> uriPatternPredicate.test(r.getURI())) + .filter(r -> uriPatternPredicate.test(URIUtil.unwrapContainer(r.getURI()))) .forEach(addContainerResource); } diff --git a/jetty-ee11/jetty-ee11-webapp/src/test/java/org/eclipse/jetty/ee11/webapp/MetaInfConfigurationTest.java b/jetty-ee11/jetty-ee11-webapp/src/test/java/org/eclipse/jetty/ee11/webapp/MetaInfConfigurationTest.java index eb0bf01f864..eb18799b89b 100644 --- a/jetty-ee11/jetty-ee11-webapp/src/test/java/org/eclipse/jetty/ee11/webapp/MetaInfConfigurationTest.java +++ b/jetty-ee11/jetty-ee11-webapp/src/test/java/org/eclipse/jetty/ee11/webapp/MetaInfConfigurationTest.java @@ -557,6 +557,7 @@ public class MetaInfConfigurationTest .stream() .sorted(ResourceCollators.byName(true)) .map(Resource::getURI) + .map(URIUtil::unwrapContainer) .map(URI::toASCIIString) .toList(); // we "correct" the bad file URLs that come from the ClassLoader diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-common/src/test/java/org/eclipse/jetty/ee9/session/AsyncTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-common/src/test/java/org/eclipse/jetty/ee9/session/AsyncTest.java index 0923120a8b1..b00e45316fb 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-common/src/test/java/org/eclipse/jetty/ee9/session/AsyncTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-common/src/test/java/org/eclipse/jetty/ee9/session/AsyncTest.java @@ -60,6 +60,7 @@ public class AsyncTest DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); @@ -102,9 +103,9 @@ public class AsyncTest public void testSessionWithAsyncComplete() throws Exception { // Test async write, which creates a session and completes outside of a dispatch - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); @@ -153,6 +154,7 @@ public class AsyncTest DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); @@ -198,9 +200,9 @@ public class AsyncTest @Test public void testSessionCreatedBeforeDispatch() throws Exception { - DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); @@ -252,6 +254,7 @@ public class AsyncTest DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT); + cacheFactory.setFlushOnResponseCommit(true); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); SessionTestSupport server = new SessionTestSupport(0, -1, -1, cacheFactory, storeFactory); diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/MetaInfConfiguration.java b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/MetaInfConfiguration.java index 8cea3d13a8d..3c4799de97f 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/MetaInfConfiguration.java +++ b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/MetaInfConfiguration.java @@ -169,9 +169,10 @@ public class MetaInfConfiguration extends AbstractConfiguration String classPath = System.getProperty("java.class.path"); if (classPath != null) { - Stream.of(classPath.split(File.pathSeparator)) - .map(resourceFactory::newResource) - .filter(r -> uriPatternPredicate.test(r.getURI())) + resourceFactory.split(classPath, File.pathSeparator) + .stream() + .filter(Objects::nonNull) + .filter(r -> uriPatternPredicate.test(URIUtil.unwrapContainer(r.getURI()))) .forEach(addContainerResource); } diff --git a/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/MetaInfConfigurationTest.java b/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/MetaInfConfigurationTest.java index df1f8ca66be..34a51ff1883 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/MetaInfConfigurationTest.java +++ b/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/MetaInfConfigurationTest.java @@ -20,14 +20,11 @@ import java.util.List; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.util.resource.FileSystemPool; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.resource.Resource; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -153,13 +150,14 @@ public class MetaInfConfigurationTest assertEquals(2, containerResources.size()); for (Resource r : containerResources) { - String s = r.toString(); + String s = URIUtil.unwrapContainer(r.getURI()).toASCIIString(); assertTrue(s.endsWith("foo-bar-janb.jar") || s.contains("servlet-api")); } } finally { config.postConfigure(context); + LifeCycle.stop(context.getResourceFactory()); } } }