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());
}
}
}