merge of the actual master into 3.x branch
diff --git a/NOTICE.md b/NOTICE.md index ca03fd1..f542417 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 e70010e..71e7065 100644 --- a/bom/pom.xml +++ b/bom/pom.xml
@@ -85,6 +85,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/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..63663fb 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 @@ -45,4 +45,18 @@ 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 + && ("org.eclipse.jetty.server.Handler".equalsIgnoreCase(type.getCanonicalName()) + || JettyHttpContainer.class == type) + ) { + return type.cast(new JettyHttpContainer(application, parentContext)); + } + return null; + } + }
diff --git a/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpContainer.java b/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpContainer.java index df67cb5..1205576 100644 --- a/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpContainer.java +++ b/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpContainer.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2022 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
diff --git a/containers/jetty-http/src/main/java8/org/glassfish/jersey/jetty/JettyHttpContainer.java b/containers/jetty-http/src/main/java8/org/glassfish/jersey/jetty/JettyHttpContainer.java index b852e54..a2159c8 100644 --- a/containers/jetty-http/src/main/java8/org/glassfish/jersey/jetty/JettyHttpContainer.java +++ b/containers/jetty-http/src/main/java8/org/glassfish/jersey/jetty/JettyHttpContainer.java
@@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -35,6 +35,10 @@ throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); } + public JettyHttpContainer(Application application, final Object parentContext) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + @Override public ResourceConfig getConfiguration() { throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED());
diff --git a/containers/jetty-http2/pom.xml b/containers/jetty-http2/pom.xml new file mode 100644 index 0000000..74da64c --- /dev/null +++ b/containers/jetty-http2/pom.xml
@@ -0,0 +1,280 @@ +<?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.0.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> + + <properties> + <java8.build.outputDirectory>${project.basedir}/target</java8.build.outputDirectory> + <java8.sourceDirectory>${project.basedir}/src/main/java8</java8.sourceDirectory> + <java11.build.outputDirectory>${project.basedir}/target11</java11.build.outputDirectory> + <java11.sourceDirectory>${project.basedir}/src/main/java11</java11.sourceDirectory> + </properties> + + + <profiles> + <profile> + <id>JettyExclude</id> + <activation> + <jdk>1.8</jdk> + </activation> + <properties> + <jetty.version>${jetty9.version}</jetty.version> + </properties> + <dependencies> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-client</artifactId> + <version>${jetty.version}</version> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-util</artifactId> + <version>${jetty.version}</version> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> + <build> + <directory>${java8.build.outputDirectory}</directory> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <executions> + <execution> + <phase>generate-sources</phase> + <goals> + <goal>add-source</goal> + </goals> + <configuration> + <sources> + <source>${java8.sourceDirectory}</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <testExcludes> + <testExclude>org/glassfish/jersey/jetty/*.java</testExclude> + </testExcludes> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>Jetty11</id> + <activation> + <jdk>[11,)</jdk> + </activation> + <build> + <directory>${java11.build.outputDirectory}</directory> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <executions> + <execution> + <phase>generate-sources</phase> + <goals> + <goal>add-source</goal> + </goals> + <configuration> + <sources> + <source>${java11.sourceDirectory}</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>copyJDK11FilesToMultiReleaseJar</id> + <activation> + <file> + <!-- ${java11.build.outputDirectory} does not work here --> + <exists>target11/classes/org/glassfish/jersey/jetty/JettyHttp2ContainerFactory.class</exists> + </file> + <jdk>1.8</jdk> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <inherited>true</inherited> + <extensions>true</extensions> + <configuration> + <instructions> + <Multi-Release>true</Multi-Release> + </instructions> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <inherited>true</inherited> + <executions> + <execution> + <id>copy-jdk11-classes</id> + <phase>prepare-package</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${java8.build.outputDirectory}/classes/META-INF/versions/11</outputDirectory> + <resources> + <resource> + <directory>${java11.build.outputDirectory}/classes</directory> + </resource> + </resources> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + <execution> + <id>copy-jdk11-sources</id> + <phase>package</phase> + <configuration> + <target> + <property name="sources-jar" value="${java8.build.outputDirectory}/${project.artifactId}-${project.version}-sources.jar"/> + <echo>sources-jar: ${sources-jar}</echo> + <zip destfile="${sources-jar}" update="true"> + <zipfileset dir="${java11.sourceDirectory}" prefix="META-INF/versions/11"/> + </zip> + </target> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> + +</project>
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..5d8e6fd --- /dev/null +++ b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerProvider.java
@@ -0,0 +1,45 @@ +/* + * 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; + +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 + && ("org.eclipse.jetty.server.Handler".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/java11/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java b/containers/jetty-http2/src/main/java11/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java new file mode 100644 index 0000000..068ea4c --- /dev/null +++ b/containers/jetty-http2/src/main/java11/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/java8/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java b/containers/jetty-http2/src/main/java8/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java new file mode 100644 index 0000000..6b148ed --- /dev/null +++ b/containers/jetty-http2/src/main/java8/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java
@@ -0,0 +1,75 @@ +/* + * 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.server.Server; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import org.glassfish.jersey.internal.util.JdkVersion; +import org.glassfish.jersey.jetty.JettyHttpContainer; +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() { + + } + + public static Server createHttp2Server(final URI uri) throws ProcessingException { + validateJdk(); + return null; // does not work at JDK 1.8 + } + + public static Server createHttp2Server(final URI uri, final ResourceConfig configuration, final boolean start) + throws ProcessingException { + validateJdk(); + return null; // does not work at JDK 1.8 + } + + public static Server createHttp2Server(final URI uri, final boolean start) throws ProcessingException { + validateJdk(); + return null; // does not work at JDK 1.8 + } + + public static Server createHttp2Server(final URI uri, final ResourceConfig config, final boolean start, + final Object parentContext) { + validateJdk(); + return null; // does not work at JDK 1.8 + } + + public static Server createHttp2Server(final URI uri, + final SslContextFactory.Server sslContextFactory, + final JettyHttpContainer handler, + final boolean start) { + + validateJdk(); + return null; // does not work at JDK 1.8 + } + + private static void validateJdk() { + if (JdkVersion.getJdkVersion().getMajor() < 11) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + } +}
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 0f99633..f64b698 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 71390cd..020b11a 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 c00a41d..74b46c1 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 8d650e2..910f57a 100644 --- a/pom.xml +++ b/pom.xml
@@ -1744,6 +1744,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> @@ -2217,7 +2227,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> @@ -2281,7 +2291,7 @@ <rxjava.version>1.3.8</rxjava.version> <rxjava2.version>2.2.21</rxjava2.version> <simple.version>6.0.1</simple.version> - <slf4j.version>2.0.3</slf4j.version> + <slf4j.version>2.0.5</slf4j.version> <spring6.version>6.0.0-M3</spring6.version> <testng.version>7.6.1</testng.version> <testng6.version>6.9.13.6</testng6.version> @@ -2328,8 +2338,8 @@ <jaxrs.api.spec.version>3.0</jaxrs.api.spec.version> <jaxrs.api.impl.version>3.0.0</jaxrs.api.impl.version> <jetty.osgi.version>org.eclipse.jetty.*;version="[11,15)"</jetty.osgi.version> - <jetty.version>11.0.14</jetty.version> - <jetty9.version>9.4.49.v20220914</jetty9.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> <jetty.servlet.api.25.version>6.1.14</jetty.servlet.api.25.version> <jsonb.api.version>2.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..7dddf54 --- /dev/null +++ b/test-framework/providers/jetty-http2/pom.xml
@@ -0,0 +1,191 @@ +<?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.0.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> + + <properties> + <java8.build.outputDirectory>${project.basedir}/target</java8.build.outputDirectory> + <java8.sourceDirectory>${project.basedir}/src/main/java8</java8.sourceDirectory> + <java11.build.outputDirectory>${project.basedir}/target11</java11.build.outputDirectory> + <java11.sourceDirectory>${project.basedir}/src/main/java11</java11.sourceDirectory> + </properties> + + <profiles> + <profile> + <id>JettyExclude</id> + <activation> + <jdk>1.8</jdk> + </activation> + <build> + <directory>${java8.build.outputDirectory}</directory> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <executions> + <execution> + <phase>generate-sources</phase> + <goals> + <goal>add-source</goal> + </goals> + <configuration> + <sources> + <source>${java8.sourceDirectory}</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <testExcludes> + <testExclude>org/glassfish/jersey/test/jetty/http2/*.java</testExclude> + </testExcludes> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>Jetty11</id> + <activation> + <jdk>[11,)</jdk> + </activation> + <build> + <directory>${java11.build.outputDirectory}</directory> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <executions> + <execution> + <phase>generate-sources</phase> + <goals> + <goal>add-source</goal> + </goals> + <configuration> + <sources> + <source>${java11.sourceDirectory}</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>copyJDK11FilesToMultiReleaseJar</id> + <activation> + <file> + <!-- ${java11.build.outputDirectory} does not work here --> + <exists>target11/classes/org/glassfish/jersey/test/jetty/JettyHttp2TestContainerFactory.class</exists> + </file> + <jdk>1.8</jdk> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <inherited>true</inherited> + <extensions>true</extensions> + <configuration> + <instructions> + <Multi-Release>true</Multi-Release> + </instructions> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <inherited>true</inherited> + <executions> + <execution> + <id>copy-jdk11-classes</id> + <phase>prepare-package</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${java8.build.outputDirectory}/classes/META-INF/versions/11</outputDirectory> + <resources> + <resource> + <directory>${java11.build.outputDirectory}/classes</directory> + </resource> + </resources> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + <execution> + <id>copy-jdk11-sources</id> + <phase>package</phase> + <configuration> + <target> + <property name="sources-jar" value="${java8.build.outputDirectory}/${project.artifactId}-${project.version}-sources.jar"/> + <echo>sources-jar: ${sources-jar}</echo> + <zip destfile="${sources-jar}" update="true"> + <zipfileset dir="${java11.sourceDirectory}" prefix="META-INF/versions/11"/> + </zip> + </target> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> + +</project>
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/java11/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java b/test-framework/providers/jetty-http2/src/main/java11/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/java11/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/java8/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java b/test-framework/providers/jetty-http2/src/main/java8/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java new file mode 100644 index 0000000..44fa02a --- /dev/null +++ b/test-framework/providers/jetty-http2/src/main/java8/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java
@@ -0,0 +1,36 @@ +/* + * 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.ProcessingException; +import org.glassfish.jersey.jetty.http2.LocalizationMessages; +import org.glassfish.jersey.test.DeploymentContext; +import org.glassfish.jersey.test.spi.TestContainer; +import org.glassfish.jersey.test.spi.TestContainerFactory; + +import java.net.URI; +/** + * Factory for testing {@link JettyHttp2ContainerFactory}. + * + */ +public final class JettyHttp2TestContainerFactory implements TestContainerFactory { + + @Override + public TestContainer create(final URI baseUri, final DeploymentContext context) throws IllegalArgumentException { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } +}
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 ed62ce6..258ae06 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")); + } +}