Merge remote-tracking branch 'origin/3.x' into 'origin/3.1'
Signed-off-by: Maxim Nesen <maxim.nesen@oracle.com>
diff --git a/NOTICE.md b/NOTICE.md
index 9c76708..48af210 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -95,7 +95,7 @@
* Project: http://www.kineticjs.com, https://github.com/ericdrowell/KineticJS
* Copyright: Eric Rowell
-org.objectweb.asm Version 9.4
+org.objectweb.asm Version 9.5
* License: Modified BSD (https://asm.ow2.io/license.html)
* Copyright (c) 2000-2011 INRIA, France Telecom. All rights reserved.
diff --git a/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/pom.xml b/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/pom.xml
index a781334..1cd8f78 100644
--- a/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/pom.xml
+++ b/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/pom.xml
@@ -112,7 +112,7 @@
<properties>
<jersey.version>${project.version}</jersey.version>
- <jetty.version>11.0.14</jetty.version>
+ <jetty.version>11.0.15</jetty.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<surefire.mvn.plugin.version>3.0.0-M7</surefire.mvn.plugin.version>
</properties>
diff --git a/bom/pom.xml b/bom/pom.xml
index feb6cba..9dd6ad4 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -90,6 +90,11 @@
</dependency>
<dependency>
<groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-jetty-http2-connector</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
<artifactId>jersey-jdk-connector</artifactId>
<version>${project.version}</version>
</dependency>
diff --git a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java
index 3ede8a9..825a231 100644
--- a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java
+++ b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java
@@ -46,6 +46,7 @@
import javax.net.ssl.SSLContext;
+import jakarta.ws.rs.ext.RuntimeDelegate;
import org.eclipse.jetty.client.HttpClientTransport;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
@@ -254,7 +255,6 @@
@Override
public ClientResponse apply(final ClientRequest jerseyRequest) throws ProcessingException {
- applyUserAgentHeader(jerseyRequest.getHeaders());
final Request jettyRequest = translateRequest(jerseyRequest);
final Map<String, String> clientHeadersSnapshot = writeOutBoundHeaders(jerseyRequest.getHeaders(), jettyRequest);
final ContentProvider entity = getBytesProvider(jerseyRequest);
@@ -338,34 +338,17 @@
return request;
}
- /**
- * Re-write User-agent header set by Jetty; Jersey already sets this in its request (incl. Jetty version)
- * it shall be propagated to Jetty client before HttpRequest instance is created.
- * HttpRequest takes User Agent header from client then.
- *
- * @param headers - map of Jersey headers
- */
- private void applyUserAgentHeader(final MultivaluedMap<String, Object> headers) {
- if (headers.containsKey(HttpHeaders.USER_AGENT)) {
- final Map<String, String> stringHeaders =
- HeaderUtils.asStringHeadersSingleValue(headers, configuration);
- client.setUserAgentField(
- new HttpField(HttpHeader.USER_AGENT,
- HttpHeader.USER_AGENT.name(),
- stringHeaders.get(HttpHeaders.USER_AGENT))
- );
- }
- }
-
private Map<String, String> writeOutBoundHeaders(final MultivaluedMap<String, Object> headers, final Request request) {
final Map<String, String> stringHeaders = HeaderUtils.asStringHeadersSingleValue(headers, configuration);
- if (request instanceof HttpRequest) {
- final HttpRequest httpRequest = (HttpRequest) request;
- for (final Map.Entry<String, String> e : stringHeaders.entrySet()) {
- httpRequest.addHeader(new HttpField(e.getKey(), e.getValue()));
- }
- }
+ // remove User-agent header set by Jetty; Jersey already sets this in its request (incl. Jetty version)
+ request.headers(httpFields -> httpFields.remove(HttpHeader.USER_AGENT));
+ if (request instanceof HttpRequest) {
+ final HttpRequest httpRequest = (HttpRequest) request;
+ for (final Map.Entry<String, String> e : stringHeaders.entrySet()) {
+ httpRequest.addHeader(new HttpField(e.getKey(), e.getValue()));
+ }
+ }
return stringHeaders;
}
@@ -422,7 +405,6 @@
@Override
public Future<?> apply(final ClientRequest jerseyRequest, final AsyncConnectorCallback callback) {
- applyUserAgentHeader(jerseyRequest.getHeaders());
final Request jettyRequest = translateRequest(jerseyRequest);
final Map<String, String> clientHeadersSnapshot = writeOutBoundHeaders(jerseyRequest.getHeaders(), jettyRequest);
final ContentProvider entity = getStreamProvider(jerseyRequest);
diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java
index 5a776b6..4b4f16f 100644
--- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java
+++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java
@@ -87,7 +87,7 @@
import org.glassfish.jersey.client.spi.AsyncConnectorCallback;
import org.glassfish.jersey.client.spi.Connector;
import org.glassfish.jersey.message.internal.OutboundMessageContext;
-import org.glassfish.jersey.netty.connector.internal.JerseyChunkedInput;
+import org.glassfish.jersey.netty.connector.internal.NettyEntityWriter;
/**
* Netty connector implementation.
@@ -394,27 +394,34 @@
}
};
ch.closeFuture().addListener(closeListener);
- if (jerseyRequest.getLengthLong() == -1) {
- HttpUtil.setTransferEncodingChunked(nettyRequest, true);
- } else {
- nettyRequest.headers().add(HttpHeaderNames.CONTENT_LENGTH, jerseyRequest.getLengthLong());
+
+ final NettyEntityWriter entityWriter = NettyEntityWriter.getInstance(jerseyRequest, ch);
+ switch (entityWriter.getType()) {
+ case CHUNKED:
+ HttpUtil.setTransferEncodingChunked(nettyRequest, true);
+ break;
+ case PRESET:
+ nettyRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH, jerseyRequest.getLengthLong());
+ break;
+// case DELAYED:
+// // Set later after the entity is "written"
+// break;
}
// Send the HTTP request.
- ch.writeAndFlush(nettyRequest);
+ entityWriter.writeAndFlush(nettyRequest);
- final JerseyChunkedInput jerseyChunkedInput = new JerseyChunkedInput(ch);
jerseyRequest.setStreamProvider(new OutboundMessageContext.StreamProvider() {
@Override
public OutputStream getOutputStream(int contentLength) throws IOException {
- return jerseyChunkedInput;
+ return entityWriter.getOutputStream();
}
});
if (HttpUtil.isTransferEncodingChunked(nettyRequest)) {
- ch.write(new HttpChunkedInput(jerseyChunkedInput));
+ entityWriter.write(new HttpChunkedInput(entityWriter.getChunkedInput()));
} else {
- ch.write(jerseyChunkedInput);
+ entityWriter.write(entityWriter.getChunkedInput());
}
executorService.execute(new Runnable() {
@@ -425,19 +432,28 @@
try {
jerseyRequest.writeEntity();
+
+ if (entityWriter.getType() == NettyEntityWriter.Type.DELAYED) {
+ replaceHeaders(jerseyRequest, nettyRequest.headers()); // WriterInterceptor changes
+ nettyRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH, entityWriter.getLength());
+ entityWriter.flush();
+ }
+
} catch (IOException e) {
responseDone.completeExceptionally(e);
}
}
});
- ch.flush();
+ if (entityWriter.getType() != NettyEntityWriter.Type.DELAYED) {
+ entityWriter.flush();
+ }
} else {
// Send the HTTP request.
ch.writeAndFlush(nettyRequest);
}
- } catch (InterruptedException e) {
+ } catch (IOException | InterruptedException e) {
responseDone.completeExceptionally(e);
}
}
@@ -511,4 +527,11 @@
}
return headers;
}
+
+ private static HttpHeaders replaceHeaders(ClientRequest jerseyRequest, HttpHeaders headers) {
+ for (final Map.Entry<String, List<String>> e : jerseyRequest.getStringHeaders().entrySet()) {
+ headers.set(e.getKey(), e.getValue());
+ }
+ return headers;
+ }
}
diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyEntityWriter.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyEntityWriter.java
new file mode 100644
index 0000000..a9e7040
--- /dev/null
+++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyEntityWriter.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.connector.internal;
+
+import io.netty.channel.Channel;
+import io.netty.handler.stream.ChunkedInput;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.client.ClientRequest;
+import org.glassfish.jersey.client.RequestEntityProcessing;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * The Entity Writer is used to write entity in Netty. One implementation is delayed,
+ * so that the complete message length can be set to Content-Length header.
+ */
+public interface NettyEntityWriter {
+
+ /**
+ * Type of the entity writer. {@code CHUNKED} is used for chunked data. {@code PRESET} is for buffered data, but the
+ * content length was pre-set by the customer. {@code DELAYED} is for buffered data where the content-length is unknown.
+ * The headers must not be written before the entity is provided by MessageBodyWriter to know the exact length.
+ */
+ enum Type {
+ CHUNKED,
+ PRESET,
+ DELAYED
+ }
+
+ /**
+ * Writes the Object to the channel
+ * @param object object to be written
+ */
+ void write(Object object);
+
+ /**
+ * Writes the Object to the channel and flush.
+ * @param object object to be written
+ */
+ void writeAndFlush(Object object);
+
+ /**
+ * Flushes the writen objects. Can throw IOException.
+ * @throws IOException
+ */
+ void flush() throws IOException;
+
+ /**
+ * Get the netty Chunked Input to be written.
+ * @return The Chunked input instance
+ */
+ ChunkedInput getChunkedInput();
+
+ /**
+ * Get the {@link OutputStream} used to write an entity
+ * @return the OutputStream to write an entity
+ */
+ OutputStream getOutputStream();
+
+ /**
+ * Get the length of the entity written to the {@link OutputStream}
+ * @return
+ */
+ long getLength();
+
+ /**
+ * Return Type of
+ * @return
+ */
+ Type getType();
+
+ static NettyEntityWriter getInstance(ClientRequest clientRequest, Channel channel) {
+ final long lengthLong = clientRequest.getLengthLong();
+ final RequestEntityProcessing entityProcessing = clientRequest.resolveProperty(
+ ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.class);
+
+ if ((entityProcessing == null && lengthLong == -1) || entityProcessing == RequestEntityProcessing.CHUNKED) {
+ return new DirectEntityWriter(channel, Type.CHUNKED);
+ } else if (lengthLong != -1) {
+ return new DirectEntityWriter(channel, Type.PRESET);
+ } else {
+ return new DelayedEntityWriter(channel, Type.DELAYED);
+ }
+ }
+
+ class DirectEntityWriter implements NettyEntityWriter {
+ private final Channel channel;
+ private final JerseyChunkedInput stream;
+ private final Type type;
+
+ public DirectEntityWriter(Channel channel, Type type) {
+ this.channel = channel;
+ stream = new JerseyChunkedInput(channel);
+ this.type = type;
+ }
+
+ @Override
+ public void write(Object object) {
+ channel.write(object);
+ }
+
+ @Override
+ public void writeAndFlush(Object object) {
+ channel.writeAndFlush(object);
+ }
+
+ @Override
+ public void flush() {
+ channel.flush();
+ }
+
+ @Override
+ public ChunkedInput getChunkedInput() {
+ return stream;
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return stream;
+ }
+
+ @Override
+ public long getLength() {
+ return stream.progress();
+ }
+
+ @Override
+ public Type getType() {
+ return type;
+ }
+ }
+
+ class DelayedEntityWriter implements NettyEntityWriter {
+ private final List<Runnable> delayedOps;
+ private final DirectEntityWriter writer;
+ private final DelayedOutputStream outputStream;
+
+ private boolean flushed = false;
+ private boolean closed = false;
+
+ public DelayedEntityWriter(Channel channel, Type type) {
+ this.writer = new DirectEntityWriter(channel, type);
+ this.delayedOps = new LinkedList<>();
+ this.outputStream = new DelayedOutputStream();
+ }
+
+
+ @Override
+ public void write(Object object) {
+ if (!flushed) {
+ delayedOps.add(() -> writer.write(object));
+ } else {
+ writer.write(object);
+ }
+ }
+
+ @Override
+ public void writeAndFlush(Object object) {
+ if (!flushed) {
+ delayedOps.add(() -> writer.writeAndFlush(object));
+ } else {
+ writer.writeAndFlush(object);
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ _flush();
+ if (!closed) {
+ closed = true;
+ writer.getOutputStream().close(); // Jersey automatically closes DelayedOutputStream not this one!
+ }
+ writer.flush();
+ }
+
+ private void _flush() throws IOException {
+ if (!flushed) {
+ flushed = true;
+ for (Runnable runnable : delayedOps) {
+ runnable.run();
+ }
+
+ if (outputStream.b != null) {
+ writer.getOutputStream().write(outputStream.b, outputStream.off, outputStream.len);
+ }
+ }
+ }
+
+ @Override
+ public ChunkedInput getChunkedInput() {
+ return writer.getChunkedInput();
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return outputStream;
+ }
+
+
+ @Override
+ public long getLength() {
+ return outputStream.len - outputStream.off;
+ }
+
+ @Override
+ public Type getType() {
+ return writer.getType();
+ }
+
+ private class DelayedOutputStream extends OutputStream {
+ private byte[] b;
+ private int off;
+ private int len;
+
+ @Override
+ public void write(int b) throws IOException {
+ write(new byte[]{(byte) (b & 0xFF)}, 0, 1);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (!flushed && this.b == null) {
+ this.b = b;
+ this.off = off;
+ this.len = len;
+ } else {
+ DelayedEntityWriter.this._flush();
+ writer.getOutputStream().write(b, off, len);
+ }
+ }
+ }
+ }
+}
diff --git a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/BufferedTest.java b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/BufferedTest.java
new file mode 100644
index 0000000..a01c04d
--- /dev/null
+++ b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/BufferedTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.client.RequestEntityProcessing;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.WriterInterceptor;
+import jakarta.ws.rs.ext.WriterInterceptorContext;
+import java.io.IOException;
+
+public class BufferedTest extends JerseyTest {
+
+ private static String HEADER_1 = "First";
+ private static String HEADER_2 = "Second";
+ private static String HEADER_3 = "Third";
+ private static String ENTITY = "entity";
+
+
+ @Path("/buffered")
+ public static class BufferedTestResource {
+ @POST
+ public String post(@Context HttpHeaders headers, String entity) {
+ System.out.println("Remote");
+ String ret = headers.getHeaderString(HEADER_1)
+ + headers.getHeaderString(HEADER_2)
+ + headers.getHeaderString(HEADER_3)
+ + entity;
+ System.out.println(ret);
+ return ret;
+ }
+ }
+
+ @Override
+ protected Application configure() {
+ return new ResourceConfig(BufferedTestResource.class);
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.connectorProvider(new NettyConnectorProvider())
+ .property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED);
+ }
+
+ @Test
+ public void test() {
+ try (Response r = target("buffered")
+ .register(new ClientRequestFilter() {
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ requestContext.setEntity(ENTITY);
+ requestContext.getHeaders().add(HEADER_2, HEADER_2);
+ }
+ })
+ .register(new WriterInterceptor() {
+ @Override
+ public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
+ context.getHeaders().add(HEADER_3, HEADER_3);
+ context.proceed();
+ }
+ })
+ .request()
+ .header(HEADER_1, HEADER_1)
+ .post(Entity.entity("ENTITY", MediaType.TEXT_PLAIN_TYPE))) {
+ String response = r.readEntity(String.class);
+ Assertions.assertEquals(HEADER_1 + HEADER_2 + HEADER_3 + ENTITY, response);
+ }
+ }
+}
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java
index f4faac6..4b80825 100644
--- a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -31,18 +31,27 @@
*/
public final class JettyHttpContainerProvider implements ContainerProvider {
+ public static final String HANDLER_NAME = "org.eclipse.jetty.server.Handler";
@Override
public <T> T createContainer(final Class<T> type, final Application application) throws ProcessingException {
if (JdkVersion.getJdkVersion().getMajor() < 11) {
throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED());
}
- if (type != null
- && ("org.eclipse.jetty.server.Handler".equalsIgnoreCase(type.getCanonicalName())
- || JettyHttpContainer.class == type)
- ) {
+ if (type != null && (HANDLER_NAME.equalsIgnoreCase(type.getCanonicalName()) || JettyHttpContainer.class == type)) {
return type.cast(new JettyHttpContainer(application));
}
return null;
}
+ public <T> T createContainer(final Class<T> type, final Application application,
+ Object parentContext) throws ProcessingException {
+ if (JdkVersion.getJdkVersion().getMajor() < 11) {
+ throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED());
+ }
+ if (type != null && (HANDLER_NAME.equalsIgnoreCase(type.getCanonicalName()) || JettyHttpContainer.class == type)) {
+ return type.cast(new JettyHttpContainer(application, parentContext));
+ }
+ return null;
+ }
+
}
diff --git a/containers/jetty-http2/pom.xml b/containers/jetty-http2/pom.xml
new file mode 100644
index 0000000..ce2e199
--- /dev/null
+++ b/containers/jetty-http2/pom.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v. 2.0, which is available at
+ http://www.eclipse.org/legal/epl-2.0.
+
+ This Source Code may also be made available under the following Secondary
+ Licenses when the conditions for such availability set forth in the
+ Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ version 2 with the GNU Classpath Exception, which is available at
+ https://www.gnu.org/software/classpath/license.html.
+
+ SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>project</artifactId>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <version>3.1.99-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-container-jetty-http2</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-container-jetty-http2</name>
+
+ <description>Jetty Http2 Container</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-jetty-http</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.inject</groupId>
+ <artifactId>jakarta.inject-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.http2</groupId>
+ <artifactId>http2-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-alpn-conscrypt-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.sun.istack</groupId>
+ <artifactId>istack-commons-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ ${jetty.osgi.version},
+ *
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </build>
+
+</project>
diff --git a/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java
new file mode 100644
index 0000000..068ea4c
--- /dev/null
+++ b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2;
+
+import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
+import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.glassfish.jersey.jetty.JettyHttpContainer;
+import org.glassfish.jersey.jetty.JettyHttpContainerFactory;
+import org.glassfish.jersey.jetty.JettyHttpContainerProvider;
+import org.glassfish.jersey.jetty.http2.LocalizationMessages;
+import org.glassfish.jersey.server.ContainerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import jakarta.ws.rs.ProcessingException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class JettyHttp2ContainerFactory {
+
+ private JettyHttp2ContainerFactory() {
+
+ }
+
+ /**
+ * Creates HTTP/2 enabled {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}.
+ *
+ * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path
+ * segment will be used as context path, the rest will be ignored.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ */
+ public static Server createHttp2Server(final URI uri) throws ProcessingException {
+ return createHttp2Server(uri, null, null, true);
+ }
+
+ /**
+ * Create HTTP/2 enabled {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+ * in turn manages all root resource and provider classes declared by the
+ * resource configuration.
+ * <p/>
+ * This implementation defers to the
+ * {@link org.glassfish.jersey.server.ContainerFactory#createContainer(Class, jakarta.ws.rs.core.Application)} method
+ * for creating an Container that manages the root resources.
+ *
+ * @param uri URI on which the Jersey web application will be deployed. Only first path segment will be
+ * used as context path, the rest will be ignored.
+ * @param configuration web application configuration.
+ * @param start if set to false, server will not get started, which allows to configure the underlying
+ * transport layer, see above for details.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ */
+ public static Server createHttp2Server(final URI uri, final ResourceConfig configuration, final boolean start)
+ throws ProcessingException {
+ return createHttp2Server(uri, null,
+ ContainerFactory.createContainer(JettyHttpContainer.class, configuration), start);
+ }
+
+ /**
+ * Creates HTTP/2 enabled {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}.
+ *
+ * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path
+ * segment will be used as context path, the rest will be ignored.
+ * @param start if set to false, server will not get started, which allows to configure the underlying transport
+ * layer, see above for details.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ *
+ * @since 2.40
+ */
+
+ public static Server createHttp2Server(final URI uri, final boolean start) throws ProcessingException {
+ return createHttp2Server(uri, null, null, start);
+ }
+
+ /**
+ * Create HTTP/2 enabled {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+ * in turn manages all root resource and provider classes declared by the
+ * resource configuration.
+ *
+ * @param uri the URI to create the http server. The URI scheme must be
+ * equal to "https". The URI user information and host
+ * are ignored If the URI port is not present then port 143 will be
+ * used. The URI path, query and fragment components are ignored.
+ * @param config the resource configuration.
+ * @param parentContext DI provider specific context with application's registered bindings.
+ * @param start if set to false, server will not get started, this allows end users to set
+ * additional properties on the underlying listener.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ * @see JettyHttpContainer
+ *
+ * @since 2.40
+ */
+ public static Server createHttp2Server(final URI uri, final ResourceConfig config, final boolean start,
+ final Object parentContext) {
+ return createHttp2Server(uri, null,
+ new JettyHttpContainerProvider().createContainer(JettyHttpContainer.class,
+ config, parentContext), start);
+ }
+
+ /**
+ * Create HTTP/2 enabled {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+ * in turn manages all root resource and provider classes found by searching the
+ * classes referenced in the java classpath.
+ *
+ * @param uri the URI to create the http server. The URI scheme must be
+ * equal to {@code https}. The URI user information and host
+ * are ignored. If the URI port is not present then port
+ * {@value org.glassfish.jersey.server.spi.Container#DEFAULT_HTTPS_PORT} will be
+ * used. The URI path, query and fragment components are ignored.
+ * @param sslContextFactory this is the SSL context factory used to configure SSL connector
+ * @param handler the container that handles all HTTP requests
+ * @param start if set to false, server will not get started, this allows end users to set
+ * additional properties on the underlying listener.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ * @see JettyHttpContainer
+ *
+ * @since 2.40
+ */
+ public static Server createHttp2Server(final URI uri,
+ final SslContextFactory.Server sslContextFactory,
+ final JettyHttpContainer handler,
+ final boolean start) {
+
+ /**
+ * Creating basic Jetty HTTP/1.1 container (but always not started)
+ */
+ final Server server = JettyHttpContainerFactory.createServer(uri, sslContextFactory, handler, false);
+ /**
+ * Obtain configured HTTP connection factory
+ */
+ final ServerConnector httpServerConnector = (ServerConnector) server.getConnectors()[0];
+ final HttpConnectionFactory httpConnectionFactory = httpServerConnector.getConnectionFactory(HttpConnectionFactory.class);
+
+ /**
+ * Obtain prepared config
+ */
+ final HttpConfiguration config = httpConnectionFactory.getHttpConfiguration();
+
+ /**
+ * Add required H2/H2C connection factories using pre-configured config from the HTTP/1.1 server
+ */
+ final List<ConnectionFactory> factories = getConnectionFactories(config, sslContextFactory);
+
+ /**
+ * adding connection factories for H2/H2C protocol
+ */
+ for (final ConnectionFactory factory : factories) {
+ httpServerConnector.addConnectionFactory(factory);
+ }
+ server.setConnectors(new Connector[]{httpServerConnector});
+
+ /**
+ * Starting the server if required
+ */
+ if (start) {
+ try {
+ // Start the server.
+ server.start();
+ } catch (final Exception e) {
+ throw new ProcessingException(LocalizationMessages.ERROR_WHEN_CREATING_SERVER(), e);
+ }
+ }
+ return server;
+ }
+
+ private static List<ConnectionFactory> getConnectionFactories(final HttpConfiguration config,
+ final SslContextFactory.Server sslContextFactory) {
+ final List<ConnectionFactory> factories = new ArrayList<>();
+ if (sslContextFactory != null) {
+ factories.add(new HTTP2ServerConnectionFactory(config));
+ final ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
+ alpn.setDefaultProtocol("h2");
+ factories.add(new SslConnectionFactory(sslContextFactory, alpn.getProtocol()));
+ factories.add(alpn);
+ } else {
+ factories.add(new HTTP2CServerConnectionFactory(config));
+ }
+
+ return factories;
+ }
+}
diff --git a/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerProvider.java b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerProvider.java
new file mode 100644
index 0000000..01c61fc
--- /dev/null
+++ b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2;
+
+import org.glassfish.jersey.internal.util.JdkVersion;
+import org.glassfish.jersey.jetty.JettyHttpContainer;
+import org.glassfish.jersey.jetty.JettyHttpContainerProvider;
+import org.glassfish.jersey.jetty.internal.LocalizationMessages;
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Application;
+
+import static org.glassfish.jersey.jetty.JettyHttpContainerProvider.HANDLER_NAME;
+
+public final class JettyHttp2ContainerProvider implements ContainerProvider {
+
+ @Override
+ public <T> T createContainer(final Class<T> type, final Application application) throws ProcessingException {
+ if (JdkVersion.getJdkVersion().getMajor() < 11) {
+ throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED());
+ }
+ if (type != null && (HANDLER_NAME.equalsIgnoreCase(type.getCanonicalName()) || JettyHttpContainer.class == type)) {
+ return type.cast(new JettyHttpContainerProvider().createContainer(JettyHttpContainer.class, application));
+ }
+ return null;
+ }
+}
+
diff --git a/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/package-info.java b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/package-info.java
new file mode 100644
index 0000000..a402b27
--- /dev/null
+++ b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey Jetty HTTP2 container classes.
+ */
+package org.glassfish.jersey.jetty.http2;
diff --git a/containers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000..4d2a88d
--- /dev/null
+++ b/containers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.jetty.http2.JettyHttp2ContainerProvider
\ No newline at end of file
diff --git a/containers/jetty-http2/src/main/resources/org/glassfish/jersey/jetty/http2/localization.properties b/containers/jetty-http2/src/main/resources/org/glassfish/jersey/jetty/http2/localization.properties
new file mode 100644
index 0000000..9807184
--- /dev/null
+++ b/containers/jetty-http2/src/main/resources/org/glassfish/jersey/jetty/http2/localization.properties
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+# {0} - status code; {1} - status reason message
+error.when.creating.server=Exception thrown when trying to create jetty server.
+not.supported=Jetty container is not supported on JDK version less than 11.
\ No newline at end of file
diff --git a/containers/pom.xml b/containers/pom.xml
index cc799ff..a778a77 100644
--- a/containers/pom.xml
+++ b/containers/pom.xml
@@ -41,6 +41,7 @@
<module>jersey-servlet-core</module>
<module>jersey-servlet</module>
<module>jetty-http</module>
+ <module>jetty-http2</module>
<module>jetty-servlet</module>
<module>netty-http</module>
<module>simple-http</module>
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/GuardianStringKeyMultivaluedMap.java b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/GuardianStringKeyMultivaluedMap.java
index 7e9e9f5..358a329 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/GuardianStringKeyMultivaluedMap.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/GuardianStringKeyMultivaluedMap.java
@@ -16,13 +16,17 @@
package org.glassfish.jersey.internal.util.collection;
+import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import java.util.Collection;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Consumer;
/**
* The {@link MultivaluedMap} wrapper that is able to set guards observing changes of values represented by a key.
@@ -35,6 +39,10 @@
private final MultivaluedMap<String, V> inner;
private final Map<String, Boolean> guards = new HashMap<>();
+ private static boolean isMutable(Object mutable) {
+ return !String.class.isInstance(mutable) && !MediaType.class.isInstance(mutable);
+ }
+
public GuardianStringKeyMultivaluedMap(MultivaluedMap<String, V> inner) {
this.inner = inner;
}
@@ -53,7 +61,11 @@
@Override
public V getFirst(String key) {
- return inner.getFirst(key);
+ V first = inner.getFirst(key);
+ if (isMutable(key)) {
+ observe(key);
+ }
+ return first;
}
@Override
@@ -101,7 +113,15 @@
@Override
public List<V> get(Object key) {
- return inner.get(key);
+ final List<V> innerList = inner.get(key);
+ if (innerList != null) {
+ for (Map.Entry<String, Boolean> guard : guards.entrySet()) {
+ if (guard.getKey().equals(key)) {
+ return new GuardianList(innerList, guard);
+ }
+ }
+ }
+ return innerList;
}
@Override
@@ -139,11 +159,13 @@
@Override
public Collection<List<V>> values() {
+ observeAll();
return inner.values();
}
@Override
public Set<Entry<String, List<V>>> entrySet() {
+ observeAll();
return inner.entrySet();
}
@@ -208,4 +230,225 @@
public int hashCode() {
return Objects.hash(inner, guards);
}
+
+ private static class MutableGuardian<V> {
+ protected final Map.Entry<String, Boolean> guard;
+
+ private MutableGuardian(Entry<String, Boolean> guard) {
+ this.guard = guard;
+ }
+
+ protected V guardMutable(V mutable) {
+ if (isMutable(mutable)) {
+ guard.setValue(true);
+ }
+ return mutable;
+ }
+ }
+
+ private static class GuardianList<V> extends MutableGuardian<V> implements List<V> {
+ private final List<V> guarded;
+
+ public GuardianList(List<V> guarded, Map.Entry<String, Boolean> guard) {
+ super(guard);
+ this.guarded = guarded;
+ }
+
+ @Override
+ public int size() {
+ return guarded.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return guarded.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return guarded.contains(o);
+ }
+
+ @Override
+ public Iterator<V> iterator() {
+ return new GuardianIterator<>(guarded.iterator(), guard);
+ }
+
+ @Override
+ public Object[] toArray() {
+ guard.setValue(true);
+ return guarded.toArray();
+ }
+
+ @Override
+ public <T> T[] toArray(T[] a) {
+ guard.setValue(true);
+ return guarded.toArray(a);
+ }
+
+ @Override
+ public boolean add(V e) {
+ guard.setValue(true);
+ return guarded.add(e);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ guard.setValue(true);
+ return guarded.remove(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return guarded.containsAll(c);
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends V> c) {
+ guard.setValue(true);
+ return guarded.addAll(c);
+ }
+
+ @Override
+ public boolean addAll(int index, Collection<? extends V> c) {
+ guard.setValue(true);
+ return guarded.addAll(index, c);
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ guard.setValue(true);
+ return guarded.removeAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c) {
+ guard.setValue(true);
+ return guarded.retainAll(c);
+ }
+
+ @Override
+ public void clear() {
+ guard.setValue(true);
+ guarded.clear();
+ }
+
+ @Override
+ public V get(int index) {
+ return guardMutable(guarded.get(index));
+ }
+
+ @Override
+ public V set(int index, V element) {
+ guard.setValue(true);
+ return guarded.set(index, element);
+ }
+
+ @Override
+ public void add(int index, V element) {
+ guard.setValue(true);
+ guarded.add(index, element);
+ }
+
+ @Override
+ public V remove(int index) {
+ guard.setValue(true);
+ return guarded.remove(index);
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return guarded.indexOf(o);
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return guarded.lastIndexOf(o);
+ }
+
+ @Override
+ public ListIterator<V> listIterator() {
+ return new GuardianListIterator<>(guarded.listIterator(), guard);
+ }
+
+ @Override
+ public ListIterator<V> listIterator(int index) {
+ return new GuardianListIterator<>(guarded.listIterator(index), guard);
+ }
+
+ @Override
+ public List<V> subList(int fromIndex, int toIndex) {
+ final List<V> sublist = guarded.subList(fromIndex, toIndex);
+ return sublist != null ? new GuardianList<>(sublist, guard) : sublist;
+ }
+ }
+
+ private static class GuardianIterator<V> extends MutableGuardian<V> implements Iterator<V> {
+ protected final Iterator<V> guarded;
+
+ public GuardianIterator(Iterator<V> guarded, Map.Entry<String, Boolean> guard) {
+ super(guard);
+ this.guarded = guarded;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return guarded.hasNext();
+ }
+
+ @Override
+ public V next() {
+ return guardMutable(guarded.next());
+ }
+
+ @Override
+ public void remove() {
+ guard.setValue(true);
+ guarded.remove();
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super V> action) {
+ guarded.forEachRemaining(action);
+ }
+ }
+
+ private static class GuardianListIterator<V> extends GuardianIterator<V> implements ListIterator<V> {
+
+ public GuardianListIterator(Iterator<V> guarded, Entry<String, Boolean> guard) {
+ super(guarded, guard);
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ return ((ListIterator<V>) guarded).hasPrevious();
+ }
+
+ @Override
+ public V previous() {
+ return guardMutable(((ListIterator<V>) guarded).previous());
+ }
+
+ @Override
+ public int nextIndex() {
+ return ((ListIterator<V>) guarded).nextIndex();
+ }
+
+ @Override
+ public int previousIndex() {
+ return ((ListIterator<V>) guarded).previousIndex();
+ }
+
+ @Override
+ public void set(V v) {
+ ((ListIterator<V>) guarded).set(v);
+ guard.setValue(true);
+ }
+
+ @Override
+ public void add(V v) {
+ ((ListIterator<V>) guarded).add(v);
+ guard.setValue(true);
+ }
+ }
}
diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassReader.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassReader.java
index d6e90a7..21969f0 100644
--- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassReader.java
+++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassReader.java
@@ -194,7 +194,7 @@
this.b = classFileBuffer;
// Check the class' major_version. This field is after the magic and minor_version fields, which
// use 4 and 2 bytes respectively.
- if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V20) {
+ if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V21) {
throw new IllegalArgumentException(
"Unsupported class file major version " + readShort(classFileOffset + 6));
}
@@ -2050,6 +2050,7 @@
currentOffset = bytecodeStartOffset;
while (currentOffset < bytecodeEndOffset) {
final int currentBytecodeOffset = currentOffset - bytecodeStartOffset;
+ readBytecodeInstructionOffset(currentBytecodeOffset);
// Visit the label and the line number(s) for this bytecode offset, if any.
Label currentLabel = labels[currentBytecodeOffset];
@@ -2666,6 +2667,20 @@
}
/**
+ * Handles the bytecode offset of the next instruction to be visited in {@link
+ * #accept(ClassVisitor,int)}. This method is called just before the instruction and before its
+ * associated label and stack map frame, if any. The default implementation of this method does
+ * nothing. Subclasses can override this method to store the argument in a mutable field, for
+ * instance, so that {@link MethodVisitor} instances can get the bytecode offset of each visited
+ * instruction (if so, the usual concurrency issues related to mutable data should be addressed).
+ *
+ * @param bytecodeOffset the bytecode offset of the next instruction to be visited.
+ */
+ protected void readBytecodeInstructionOffset(final int bytecodeOffset) {
+ // Do nothing by default.
+ }
+
+ /**
* Returns the label corresponding to the given bytecode offset. The default implementation of
* this method creates a label for the given offset if it has not been already created.
*
diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Frame.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Frame.java
index 41d9ef4..be4364a 100644
--- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Frame.java
+++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Frame.java
@@ -367,11 +367,12 @@
typeValue = REFERENCE_KIND | symbolTable.addType(internalName);
break;
default:
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException(
+ "Invalid descriptor fragment: " + buffer.substring(elementDescriptorOffset));
}
return ((elementDescriptorOffset - offset) << DIM_SHIFT) | typeValue;
default:
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Invalid descriptor: " + buffer.substring(offset));
}
}
diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Label.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Label.java
index a01326c..2933a99 100644
--- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Label.java
+++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Label.java
@@ -81,6 +81,9 @@
/** A flag indicating that the basic block corresponding to a label is the end of a subroutine. */
static final int FLAG_SUBROUTINE_END = 64;
+ /** A flag indicating that this label has at least one associated line number. */
+ static final int FLAG_LINE_NUMBER = 128;
+
/**
* The number of elements to add to the {@link #otherLineNumbers} array when it needs to be
* resized to store a new source line number.
@@ -145,9 +148,9 @@
short flags;
/**
- * The source line number corresponding to this label, or 0. If there are several source line
- * numbers corresponding to this label, the first one is stored in this field, and the remaining
- * ones are stored in {@link #otherLineNumbers}.
+ * The source line number corresponding to this label, if {@link #FLAG_LINE_NUMBER} is set. If
+ * there are several source line numbers corresponding to this label, the first one is stored in
+ * this field, and the remaining ones are stored in {@link #otherLineNumbers}.
*/
private short lineNumber;
@@ -332,7 +335,8 @@
* @param lineNumber a source line number (which should be strictly positive).
*/
final void addLineNumber(final int lineNumber) {
- if (this.lineNumber == 0) {
+ if ((flags & FLAG_LINE_NUMBER) == 0) {
+ flags |= FLAG_LINE_NUMBER;
this.lineNumber = (short) lineNumber;
} else {
if (otherLineNumbers == null) {
@@ -356,7 +360,7 @@
*/
final void accept(final MethodVisitor methodVisitor, final boolean visitLineNumbers) {
methodVisitor.visitLabel(this);
- if (visitLineNumbers && lineNumber != 0) {
+ if (visitLineNumbers && (flags & FLAG_LINE_NUMBER) != 0) {
methodVisitor.visitLineNumber(lineNumber & 0xFFFF, this);
if (otherLineNumbers != null) {
for (int i = 1; i <= otherLineNumbers[0]; ++i) {
diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodVisitor.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodVisitor.java
index 269b034..c8a482b 100644
--- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodVisitor.java
+++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodVisitor.java
@@ -30,7 +30,7 @@
/**
* A visitor to visit a Java method. The methods of this class must be called in the following
* order: ( {@code visitParameter} )* [ {@code visitAnnotationDefault} ] ( {@code visitAnnotation} |
- * {@code visitAnnotableParameterCount} | {@code visitParameterAnnotation} {@code
+ * {@code visitAnnotableParameterCount} | {@code visitParameterAnnotation} | {@code
* visitTypeAnnotation} | {@code visitAttribute} )* [ {@code visitCode} ( {@code visitFrame} |
* {@code visit<i>X</i>Insn} | {@code visitLabel} | {@code visitInsnAnnotation} | {@code
* visitTryCatchBlock} | {@code visitTryCatchAnnotation} | {@code visitLocalVariable} | {@code
diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Opcodes.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Opcodes.java
index 63963b0..b577623 100644
--- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Opcodes.java
+++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Opcodes.java
@@ -286,6 +286,7 @@
int V18 = 0 << 16 | 62;
int V19 = 0 << 16 | 63;
int V20 = 0 << 16 | 64;
+ int V21 = 0 << 16 | 65;
/**
* Version flag indicating that the class is using 'preview' features.
diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/RecordComponentWriter.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/RecordComponentWriter.java
index 90053bd..b222c31 100644
--- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/RecordComponentWriter.java
+++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/RecordComponentWriter.java
@@ -37,7 +37,7 @@
/** The name_index field of the Record attribute. */
private final int nameIndex;
- /** The descriptor_index field of the the Record attribute. */
+ /** The descriptor_index field of the Record attribute. */
private final int descriptorIndex;
/**
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/AnnotationAcceptingListener.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/AnnotationAcceptingListener.java
index e85d57a..7505b0c 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/AnnotationAcceptingListener.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/AnnotationAcceptingListener.java
@@ -309,7 +309,7 @@
private static class ClassReaderWrapper {
private static final Logger LOGGER = Logger.getLogger(ClassReader.class.getName());
- private static final int WARN_VERSION = Opcodes.V20;
+ private static final int WARN_VERSION = Opcodes.V21;
private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096;
private final byte[] b;
diff --git a/core-server/src/main/resources/META-INF/NOTICE.markdown b/core-server/src/main/resources/META-INF/NOTICE.markdown
index f06adeb..00b8fa0 100644
--- a/core-server/src/main/resources/META-INF/NOTICE.markdown
+++ b/core-server/src/main/resources/META-INF/NOTICE.markdown
@@ -36,7 +36,7 @@
* Copyright (c) 2015-2018 Oracle and/or its affiliates. All rights reserved.
* Copyright 2010-2013 Coda Hale and Yammer, Inc.
-org.objectweb.asm Version 9.4
+org.objectweb.asm Version 9.5
* License: Modified BSD (https://asm.ow2.io/license.html)
* Copyright: (c) 2000-2011 INRIA, France Telecom. All rights reserved.
diff --git a/examples/NOTICE.md b/examples/NOTICE.md
index e8d46fd..f1ddd2d 100644
--- a/examples/NOTICE.md
+++ b/examples/NOTICE.md
@@ -96,7 +96,7 @@
* Project: http://www.kineticjs.com, https://github.com/ericdrowell/KineticJS
* Copyright: Eric Rowell
-org.objectweb.asm Version 9.4
+org.objectweb.asm Version 9.5
* License: Modified BSD (https://asm.ow2.io/license.html)
* Copyright (c) 2000-2011 INRIA, France Telecom. All rights reserved.
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java
index c15658e..f070d61 100644
--- a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,10 +16,13 @@
package org.glassfish.jersey.ext.cdi1x.internal;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.logging.Logger;
import jakarta.enterprise.context.spi.Contextual;
import jakarta.enterprise.context.spi.CreationalContext;
@@ -44,6 +47,8 @@
*/
public abstract class AbstractCdiBeanSupplier<T> implements DisposableSupplier<T> {
+ private static final Logger LOGGER = Logger.getLogger(AbstractCdiBeanSupplier.class.getName());
+
final Class<T> clazz;
final InstanceManager<T> referenceProvider;
final Annotation[] qualifiers;
@@ -115,7 +120,16 @@
try {
return target.produce(ctx);
} catch (Exception e) {
- return im.create(clazz);
+ LOGGER.fine(LocalizationMessages.CDI_FAILED_LOADING(clazz, e.getMessage()));
+ try {
+ return im.create(clazz);
+ } catch (RuntimeException re) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ LOGGER.warning(LocalizationMessages.CDI_FAILED_LOADING(clazz, sw.toString()));
+ throw re;
+ }
}
}
diff --git a/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties b/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties
index ee1f8e7..fcd79f3 100644
--- a/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties
+++ b/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2018 Payara Foundation and/or its affiliates. All rights reserved.
#
# This program and the accompanying materials are made available under the
@@ -21,6 +21,7 @@
cdi.class.bound.with.cdi=Class, {0}, has been bound by Jersey CDI component provider.
cdi.hk2.bean.registered=CDI beans backed by HK2 have been registered for the following types: {0}
cdi.lookup.failed=Error when lookup instance of class, {0}, in CDI.
+cdi.failed.loading=CDI failed instantiating the class {0} with {1}, falling back to HK2.
cdi.multiple.locators.into.simple.app=Trying to register multiple service locators into single service locator application.
cdi.provider.initialized=Jersey CDI component provider initialized.
cdi.request.scoped.components.recognized=The following CDI types were recognized as request scoped components in Jersey: {0}.
diff --git a/ext/microprofile/mp-rest-client/pom.xml b/ext/microprofile/mp-rest-client/pom.xml
index b01d133..0bf5d0d 100644
--- a/ext/microprofile/mp-rest-client/pom.xml
+++ b/ext/microprofile/mp-rest-client/pom.xml
@@ -92,6 +92,11 @@
<artifactId>jersey-media-sse</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
index 014d603..059aa84 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -31,7 +31,7 @@
* Invokes all interceptors bound to the target.
*
* This approach needs to be used due to CDI does not handle properly interceptor invocation
- * on proxy instances.
+ * on proxy instances. This class is thread safe.
*
* @author David Kral
*/
@@ -42,8 +42,8 @@
private final Map<String, Object> contextData;
private final List<InvocationInterceptor> interceptors;
private final WebTarget classLevelWebTarget;
- private Object[] args;
- private int currentPosition;
+ private volatile Object[] args;
+ private final int currentPosition;
/**
* Creates new instance of InterceptorInvocationContext.
@@ -57,14 +57,23 @@
MethodModel methodModel,
Method method,
Object[] args) {
- this.contextData = new HashMap<>();
this.currentPosition = 0;
+ this.contextData = new HashMap<>();
this.methodModel = methodModel;
this.method = method;
this.args = args;
this.classLevelWebTarget = classLevelWebTarget;
this.interceptors = methodModel.getInvocationInterceptors();
+ }
+ InterceptorInvocationContext(InterceptorInvocationContext other, int currentPosition) {
+ this.currentPosition = currentPosition;
+ this.contextData = other.contextData;
+ this.methodModel = other.methodModel;
+ this.method = other.method;
+ this.args = other.args;
+ this.classLevelWebTarget = other.classLevelWebTarget;
+ this.interceptors = other.interceptors;
}
@Override
@@ -102,10 +111,21 @@
return contextData;
}
+ /**
+ * This method shall create the next invocation context using {@code position + 1}
+ * and store it in the {@code contextData} map. This is currently used by Helidon's
+ * fault tolerance implementation to get around a problem with CDI's default invocation
+ * context {@code WeldInvocationContextImpl} not correctly supporting async calls.
+ *
+ * @return value returned by intercepted method.
+ */
@Override
public Object proceed() {
+ InvocationContext nextContext = new InterceptorInvocationContext(this, currentPosition + 1);
+ contextData.put(getClass().getName(), nextContext); // accessible to FT interceptor
+
if (currentPosition < interceptors.size()) {
- return interceptors.get(currentPosition++).intercept(this);
+ return interceptors.get(currentPosition).intercept(nextContext);
} else {
return methodModel.invokeMethod(classLevelWebTarget, method, args);
}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java
index 6a00a44..d3241df 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -43,7 +43,7 @@
@Override
public WebTarget handleParameter(WebTarget requestPart, Class<? extends Annotation> annotationClass, Object instance) {
Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
- return requestPart.resolveTemplate(pathParamName, resolvedValue);
+ return requestPart.resolveTemplate(pathParamName, resolvedValue, false);
}
@Override
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContextTest.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContextTest.java
new file mode 100644
index 0000000..9ac7451
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContextTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import jakarta.ws.rs.client.WebTarget;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.glassfish.jersey.microprofile.restclient.InterceptorInvocationContext.InvocationInterceptor;
+
+class InterceptorInvocationContextTest {
+
+ @Test
+ void testContextDataAfterProceed() throws NoSuchMethodException {
+ WebTarget webTarget = Mockito.mock(WebTarget.class);
+ MethodModel methodModel = Mockito.mock(MethodModel.class);
+ InvocationInterceptor invocationInterceptor = Mockito.mock(InvocationInterceptor.class);
+ Mockito.when(methodModel.getInvocationInterceptors())
+ .thenReturn(Collections.singletonList(invocationInterceptor));
+
+ Method method = getClass().getMethod("method");
+ InterceptorInvocationContext c = new InterceptorInvocationContext(
+ webTarget, methodModel, method, new Object[]{});
+
+ Assertions.assertEquals(0, c.getContextData().size());
+ c.proceed();
+ Assertions.assertEquals(1, c.getContextData().size());
+ Assertions.assertTrue(c.getContextData().containsKey(InterceptorInvocationContext.class.getName()));
+ }
+
+ public static Object method() {
+ return null;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 7f852ea..62ef050 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1749,6 +1749,16 @@
<artifactId>jetty-webapp</artifactId>
<version>${jetty.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.http2</groupId>
+ <artifactId>http2-server</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-alpn-conscrypt-server</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
<dependency>
<groupId>org.simpleframework</groupId>
@@ -2228,7 +2238,7 @@
<arquillian.weld.version>3.0.1.Final</arquillian.weld.version>
<!-- asm is now source integrated - keeping this property to see the version -->
<!-- see core-server/src/main/java/jersey/repackaged/asm/.. -->
- <asm.version>9.4</asm.version>
+ <asm.version>9.5</asm.version>
<bnd.plugin.version>2.3.6</bnd.plugin.version>
<bouncycastle.version>1.68</bouncycastle.version>
@@ -2340,7 +2350,7 @@
<jaxrs.api.spec.version>3.1</jaxrs.api.spec.version>
<jaxrs.api.impl.version>3.1.0</jaxrs.api.impl.version>
<jetty.osgi.version>org.eclipse.jetty.*;version="[11,15)"</jetty.osgi.version>
- <jetty.version>11.0.14</jetty.version>
+ <jetty.version>11.0.15</jetty.version>
<jetty9.version>9.4.51.v20230217</jetty9.version>
<jetty.plugin.version>11.0.14</jetty.plugin.version>
<jsonb.api.version>3.0.0</jsonb.api.version>
diff --git a/test-framework/providers/jetty-http2/pom.xml b/test-framework/providers/jetty-http2/pom.xml
new file mode 100644
index 0000000..46a156c
--- /dev/null
+++ b/test-framework/providers/jetty-http2/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v. 2.0, which is available at
+ http://www.eclipse.org/legal/epl-2.0.
+
+ This Source Code may also be made available under the following Secondary
+ Licenses when the conditions for such availability set forth in the
+ Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ version 2 with the GNU Classpath Exception, which is available at
+ https://www.gnu.org/software/classpath/license.html.
+
+ SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>project</artifactId>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <version>3.1.99-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>jersey-test-framework-provider-jetty-http2</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-test-framework-provider-jetty-http2</name>
+
+ <description>Jersey Test Framework - Jetty HTTP2 container</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-jetty-http2</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java b/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java
new file mode 100644
index 0000000..e63f057
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.test.jetty.http2;
+
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import jakarta.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jetty.http2.JettyHttp2ContainerFactory;
+import org.glassfish.jersey.test.DeploymentContext;
+import org.glassfish.jersey.test.spi.TestContainer;
+import org.glassfish.jersey.test.spi.TestContainerException;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+import org.glassfish.jersey.test.spi.TestHelper;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+
+/**
+ * Factory for testing {@link JettyHttp2ContainerFactory}.
+ *
+ */
+public final class JettyHttp2TestContainerFactory implements TestContainerFactory {
+
+ private static class JettyHttp2TestContainer implements TestContainer {
+
+ private static final Logger LOGGER = Logger.getLogger(JettyHttp2TestContainer.class.getName());
+
+ private URI baseUri;
+ private final Server server;
+
+ private JettyHttp2TestContainer(final URI baseUri, final DeploymentContext context) {
+ final URI base = UriBuilder.fromUri(baseUri).path(context.getContextPath()).build();
+
+ if (!"/".equals(base.getRawPath())) {
+ throw new TestContainerException(String.format(
+ "Cannot deploy on %s. Jetty HTTP2 container only supports deployment on root path.",
+ base.getRawPath()));
+ }
+
+ this.baseUri = base;
+
+ if (LOGGER.isLoggable(Level.INFO)) {
+ LOGGER.info("Creating JettyHttp2TestContainer configured at the base URI "
+ + TestHelper.zeroPortToAvailablePort(baseUri));
+ }
+
+ this.server = JettyHttp2ContainerFactory.createHttp2Server(this.baseUri, context.getResourceConfig(), false);
+ }
+
+ @Override
+ public ClientConfig getClientConfig() {
+ return null;
+ }
+
+ @Override
+ public URI getBaseUri() {
+ return baseUri;
+ }
+
+ @Override
+ public void start() {
+ if (server.isStarted()) {
+ LOGGER.log(Level.WARNING, "Ignoring start request - JettyHttp2TestContainer is already started.");
+ } else {
+ LOGGER.log(Level.FINE, "Starting JettyHttp2TestContainer...");
+ try {
+ server.start();
+
+ if (baseUri.getPort() == 0) {
+ int port = 0;
+ for (final Connector connector : server.getConnectors()) {
+ if (connector instanceof ServerConnector) {
+ port = ((ServerConnector) connector).getLocalPort();
+ break;
+ }
+ }
+
+ baseUri = UriBuilder.fromUri(baseUri).port(port).build();
+
+ LOGGER.log(Level.INFO, "Started JettyHttp2TestContainer at the base URI " + baseUri);
+ }
+ } catch (Exception e) {
+ throw new TestContainerException(e);
+ }
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (server.isStarted()) {
+ LOGGER.log(Level.FINE, "Stopping JettyHttp2TestContainer...");
+ try {
+ this.server.stop();
+ } catch (Exception ex) {
+ LOGGER.log(Level.WARNING, "Error Stopping JettyHttp2TestContainer...", ex);
+ }
+ } else {
+ LOGGER.log(Level.WARNING, "Ignoring stop request - JettyHttp2TestContainer is already stopped.");
+ }
+ }
+ }
+
+ @Override
+ public TestContainer create(final URI baseUri, final DeploymentContext context) throws IllegalArgumentException {
+ return new JettyHttp2TestContainer(baseUri, context);
+ }
+}
diff --git a/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/package-info.java b/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/package-info.java
new file mode 100644
index 0000000..cd4980f
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey test framework for Jetty HTTP/2 Container.
+ */
+package org.glassfish.jersey.test.jetty.http2;
diff --git a/test-framework/providers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory b/test-framework/providers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory
new file mode 100644
index 0000000..42b7847
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory
@@ -0,0 +1 @@
+org.glassfish.jersey.test.jetty.http2.JettyHttp2TestContainerFactory
\ No newline at end of file
diff --git a/test-framework/providers/jetty-http2/src/main/resources/org/glassfish/jersey/test/jetty/http2/localization.properties b/test-framework/providers/jetty-http2/src/main/resources/org/glassfish/jersey/test/jetty/http2/localization.properties
new file mode 100644
index 0000000..2886c72
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/main/resources/org/glassfish/jersey/test/jetty/http2/localization.properties
@@ -0,0 +1,18 @@
+#
+# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+# {0} - status code; {1} - status reason message
+not.supported=Jetty container is not supported on JDK version less than 11.
diff --git a/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/AvailablePortJettyTest.java b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/AvailablePortJettyTest.java
new file mode 100644
index 0000000..1964156
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/AvailablePortJettyTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.test.jetty.http2;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.DeploymentContext;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.glassfish.jersey.test.jetty.http2.JettyHttp2TestContainerFactory;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+
+import org.junit.jupiter.api.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Tests finding an available port for container.
+ *
+ */
+public class AvailablePortJettyTest extends JerseyTest {
+
+ @Override
+ protected TestContainerFactory getTestContainerFactory() {
+ return new JettyHttp2TestContainerFactory();
+ }
+
+ @Path("AvailablePortJettyTest")
+ public static class TestResource {
+ @GET
+ public String get() {
+ return "GET";
+ }
+ }
+
+ @Override
+ protected DeploymentContext configureDeployment() {
+ forceSet(TestProperties.CONTAINER_PORT, "0");
+
+ return DeploymentContext.builder(new ResourceConfig(TestResource.class)).build();
+ }
+
+ @Test
+ public void testGet() {
+ assertThat(target().getUri().getPort(), not(0));
+ assertThat(getBaseUri().getPort(), not(0));
+
+ assertThat(target("AvailablePortJettyTest").request().get(String.class), equalTo("GET"));
+ }
+}
diff --git a/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/JettyContainerTest.java b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/JettyContainerTest.java
new file mode 100644
index 0000000..f96c10c
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/JettyContainerTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.test.jetty.http2;
+
+import java.net.URI;
+import java.util.List;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Response;
+
+import org.glassfish.jersey.inject.hk2.DelayedHk2InjectionManager;
+import org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.jetty.http2.JettyHttp2ContainerFactory;
+import org.glassfish.jersey.jetty.JettyHttpContainer;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.glassfish.hk2.api.ServiceLocator;
+
+import org.jvnet.hk2.internal.ServiceLocatorImpl;
+
+import org.eclipse.jetty.server.Server;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test class for {@link JettyHttpContainer}.
+ *
+ */
+public class JettyContainerTest extends JerseyTest {
+
+ /**
+ * Creates new instance.
+ */
+ public JettyContainerTest() {
+ super(new JettyHttp2TestContainerFactory());
+ }
+
+ @Override
+ protected ResourceConfig configure() {
+ return new ResourceConfig(Resource.class);
+ }
+
+ /**
+ * Test resource class.
+ */
+ @Path("one")
+ public static class Resource {
+
+ /**
+ * Test resource method.
+ *
+ * @return Test simple string response.
+ */
+ @GET
+ public String getSomething() {
+ return "get";
+ }
+ }
+
+ @Test
+ /**
+ * Test {@link Server Jetty Server} container.
+ */
+ public void testJettyContainerTarget() {
+ final Response response = target().path("one").request().get();
+
+ assertEquals(200, response.getStatus(), "Response status unexpected.");
+ assertEquals("get", response.readEntity(String.class), "Response entity unexpected.");
+ }
+
+ /**
+ * Test that defined ServiceLocator becomes a parent of the newly created service locator.
+ */
+ @Test
+ public void testParentServiceLocator() {
+ final ServiceLocator locator = new ServiceLocatorImpl("MyServiceLocator", null);
+ final Server server = JettyHttp2ContainerFactory.createHttp2Server(URI.create("http://localhost:9876"),
+ new ResourceConfig(Resource.class), false, locator);
+ final JettyHttpContainer container = (JettyHttpContainer) server.getHandler();
+ final InjectionManager injectionManager = container.getApplicationHandler().getInjectionManager();
+
+ ServiceLocator serviceLocator;
+ if (injectionManager instanceof ImmediateHk2InjectionManager) {
+ serviceLocator = ((ImmediateHk2InjectionManager) injectionManager).getServiceLocator();
+ } else if (injectionManager instanceof DelayedHk2InjectionManager) {
+ serviceLocator = ((DelayedHk2InjectionManager) injectionManager).getServiceLocator();
+ } else {
+ throw new RuntimeException("Invalid Hk2 InjectionManager");
+ }
+ assertTrue(serviceLocator.getParent() == locator,
+ "Application injection manager was expected to have defined parent locator");
+ }
+ @Test
+ public void testHttp2Container() {
+ final ServiceLocator locator = new ServiceLocatorImpl("MyServiceLocator", null);
+ final Server server = JettyHttp2ContainerFactory.createHttp2Server(URI.create("http://localhost:9876"),
+ new ResourceConfig(Resource.class), true, locator);
+ final List<String> protocols = server.getConnectors()[0].getProtocols();
+ assertTrue(protocols.contains("h2") || protocols.contains("h2c"));
+ }
+}
diff --git a/test-framework/providers/pom.xml b/test-framework/providers/pom.xml
index f271da3..3098913 100644
--- a/test-framework/providers/pom.xml
+++ b/test-framework/providers/pom.xml
@@ -40,6 +40,7 @@
<module>inmemory</module>
<module>jdk-http</module>
<module>jetty</module>
+ <module>jetty-http2</module>
<module>netty</module>
<module>simple</module>
</modules>
diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/BufferingTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/BufferingTest.java
index d71e996..5762179 100644
--- a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/BufferingTest.java
+++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/BufferingTest.java
@@ -38,6 +38,7 @@
import org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider;
import org.glassfish.jersey.jdk.connector.JdkConnectorProvider;
import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.netty.connector.NettyConnectorProvider;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
@@ -78,6 +79,7 @@
Arguments.of(new TestArguments(() -> new ApacheConnectorProvider(), RequestEntityProcessing.CHUNKED)),
Arguments.of(new TestArguments(() -> new Apache5ConnectorProvider(), RequestEntityProcessing.CHUNKED)),
Arguments.of(new TestArguments(() -> new GrizzlyConnectorProvider(), RequestEntityProcessing.CHUNKED)),
+ Arguments.of(new TestArguments(() -> new NettyConnectorProvider(), RequestEntityProcessing.CHUNKED)),
Arguments.of(new TestArguments(() -> new HttpUrlConnectorProvider(), RequestEntityProcessing.BUFFERED)),
Arguments.of(new TestArguments(() -> new JdkConnectorProvider(), RequestEntityProcessing.BUFFERED))
);
diff --git a/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/OutboundMessageContextTest.java b/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/OutboundMessageContextTest.java
index 29a3cb3..bd6e3c5 100644
--- a/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/OutboundMessageContextTest.java
+++ b/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/OutboundMessageContextTest.java
@@ -20,6 +20,7 @@
import java.net.URISyntaxException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -237,6 +238,33 @@
}
@Test
+ public void testChangedContentTypeOnList() {
+ OutboundMessageContext ctx = new OutboundMessageContext((Configuration) null);
+ ctx.setMediaType(MediaType.APPLICATION_XML_TYPE);
+ Assertions.assertEquals(MediaType.APPLICATION_XML_TYPE, ctx.getMediaType());
+ ctx.getHeaders().get(HttpHeaders.CONTENT_TYPE).set(0, MediaType.APPLICATION_JSON);
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, ctx.getMediaType());
+ }
+
+ @Test
+ public void testChangedContentTypeOnValues() {
+ OutboundMessageContext ctx = new OutboundMessageContext((Configuration) null);
+ ctx.setMediaType(MediaType.APPLICATION_XML_TYPE);
+ Assertions.assertEquals(MediaType.APPLICATION_XML_TYPE, ctx.getMediaType());
+ ctx.getHeaders().values().clear();
+ Assertions.assertEquals(null, ctx.getMediaType());
+ }
+
+ @Test
+ public void testChangedContentTypeOnEntrySet() {
+ OutboundMessageContext ctx = new OutboundMessageContext((Configuration) null);
+ ctx.setMediaType(MediaType.APPLICATION_XML_TYPE);
+ Assertions.assertEquals(MediaType.APPLICATION_XML_TYPE, ctx.getMediaType());
+ ctx.getHeaders().entrySet().clear();
+ Assertions.assertEquals(null, ctx.getMediaType());
+ }
+
+ @Test
public void testCopyConstructor() {
OutboundMessageContext ctx = new OutboundMessageContext((Configuration) null);
OutboundMessageContext newCtx = new OutboundMessageContext(ctx);
diff --git a/tests/integration/microprofile/rest-client/src/test/java/org/glassfish/jersey/restclient/PathParamTest.java b/tests/integration/microprofile/rest-client/src/test/java/org/glassfish/jersey/restclient/PathParamTest.java
new file mode 100644
index 0000000..3975cd4
--- /dev/null
+++ b/tests/integration/microprofile/rest-client/src/test/java/org/glassfish/jersey/restclient/PathParamTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.restclient;
+
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import jakarta.json.Json;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.MediaType;
+import java.net.URI;
+import java.util.Collections;
+
+public class PathParamTest extends JerseyTest {
+ @Path("/greet")
+ public static class GreetResource {
+ private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/everything/everywhere")
+ public JsonObject getDefaultMessage() {
+ return createResponse("World");
+ }
+
+ private JsonObject createResponse(String who) {
+ String msg = String.format("%s %s!", "Hello", who);
+
+ return JSON.createObjectBuilder()
+ .add("message", msg)
+ .build();
+ }
+ }
+
+ @Path("/greet")
+ public static interface GreetResourceClient {
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{folderpath}")
+ JsonObject getDefaultMessage(@PathParam("folderpath") String folderPath);
+ }
+
+ @Override
+ protected Application configure() {
+ return new ResourceConfig(GreetResource.class);
+ }
+
+ @Test
+ public void testRestClientRequest() {
+ GreetResourceClient client = RestClientBuilder.newBuilder()
+ .baseUri(URI.create("http://localhost:9998")).build(GreetResourceClient.class);
+ JsonObject jsonObject = client.getDefaultMessage("everything/everywhere");
+ Assertions.assertEquals("Hello World!", jsonObject.getString("message"));
+ }
+
+ @Test
+ public void testStandardRequest() {
+ JsonObject jsonObject = target()
+ .path("greet").path("everything").path("everywhere")
+ .request()
+ .get(JsonObject.class);
+ Assertions.assertEquals("Hello World!", jsonObject.getString("message"));
+ }
+}
diff --git a/tests/pom.xml b/tests/pom.xml
index 901478f..6b35eba 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -121,6 +121,7 @@
</activation>
<modules>
<module>release-test</module>
+ <module>version-agnostic</module>
</modules>
</profile>
</profiles>
diff --git a/tests/version-agnostic/pom.xml b/tests/version-agnostic/pom.xml
new file mode 100644
index 0000000..b711a14
--- /dev/null
+++ b/tests/version-agnostic/pom.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v. 2.0, which is available at
+ http://www.eclipse.org/legal/epl-2.0.
+
+ This Source Code may also be made available under the following Secondary
+ Licenses when the conditions for such availability set forth in the
+ Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ version 2 with the GNU Classpath Exception, which is available at
+ https://www.gnu.org/software/classpath/license.html.
+
+ SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.eclipse.ee4j</groupId>
+ <artifactId>project</artifactId>
+ <version>1.0.8</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+
+ <groupId>org.glassfish.jersey.tests</groupId>
+ <artifactId>version-agnostic</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-agnostic-test</name>
+
+ <description>Jersey post-release validation tests</description>
+
+ <properties>
+ <maven.compiler.source>8</maven.compiler.source>
+ <maven.compiler.target>8</maven.compiler.target>
+ <hk2.version>3.0.3</hk2.version>
+ <jersey.version>3.0.99-SNAPSHOT</jersey.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>3.0.0-M7</version>
+ <configuration>
+ <forkCount>1</forkCount>
+ <reuseForks>false</reuseForks>
+ <enableAssertions>false</enableAssertions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.glassfish.copyright</groupId>
+ <artifactId>glassfish-copyright-maven-plugin</artifactId>
+ <version>2.4</version>
+ <configuration>
+ <excludeFile>etc/config/copyright-exclude</excludeFile>
+ <!--svn|mercurial|git - defaults to svn-->
+ <scm>git</scm>
+ <!-- turn on/off debugging -->
+ <debug>false</debug>
+ <!-- skip files not under SCM-->
+ <scmOnly>true</scmOnly>
+ <!-- turn off warnings -->
+ <warn>false</warn>
+ <!-- for use with repair -->
+ <update>false</update>
+ <!-- check that year is correct -->
+ <ignoreYear>false</ignoreYear>
+ <templateFile>etc/config/copyright.txt</templateFile>
+ <bsdTemplateFile>etc/config/edl-copyright.txt</bsdTemplateFile>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey</groupId>
+ <artifactId>jersey-bom</artifactId>
+ <version>${jersey.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-jetty-connector</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <version>${jersey.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2</groupId>
+ <artifactId>hk2-api</artifactId>
+ <version>${hk2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2</groupId>
+ <artifactId>hk2-utils</artifactId>
+ <version>${hk2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2</groupId>
+ <artifactId>hk2-locator</artifactId>
+ <version>${hk2.version}</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/tests/version-agnostic/src/test/java/org/glassfish/jersey/test/agnostic/JettyHeaderTest.java b/tests/version-agnostic/src/test/java/org/glassfish/jersey/test/agnostic/JettyHeaderTest.java
new file mode 100644
index 0000000..01187a2
--- /dev/null
+++ b/tests/version-agnostic/src/test/java/org/glassfish/jersey/test/agnostic/JettyHeaderTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.test.agnostic;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.Response;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jetty.connector.JettyConnectorProvider;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.logging.Level;
+
+public class JettyHeaderTest extends JerseyTest {
+ @Path("/")
+ public static class JettyHeaderTestResource {
+ @Context
+ HttpHeaders httpHeaders;
+
+ @GET
+ public String get() {
+ return httpHeaders.getHeaderString(HttpHeaders.USER_AGENT);
+ }
+ }
+
+ @Override
+ protected Application configure() {
+ return new ResourceConfig(JettyHeaderTestResource.class);
+ //.register(LoggingFeature.builder().level(Level.INFO).verbosity(LoggingFeature.Verbosity.PAYLOAD_ANY).build());
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.connectorProvider(new JettyConnectorProvider());
+ }
+
+ @Test
+ public void helloWorldTest() {
+ try (Response r = target().request().get()) {
+ Assertions.assertEquals(200, r.getStatus());
+
+ int index = -1;
+ String userAgent = r.readEntity(String.class);
+ index = userAgent.indexOf("Jersey");
+ Assertions.assertTrue(index != -1);
+
+ index = userAgent.indexOf("Jersey", index + 1);
+ Assertions.assertEquals(-1, index);
+ }
+ }
+}