Merge remote-tracking branch 'origin/master' into 3.x

diff --git a/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/StressTest.java b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/StressTest.java
deleted file mode 100644
index fa8414b..0000000
--- a/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/StressTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (c) 2021 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.jdk.connector.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.core.Application;
-import jakarta.ws.rs.core.Response;
-
-import org.glassfish.jersey.client.ClientConfig;
-import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
-import org.glassfish.jersey.jdk.connector.JdkConnectorProvider;
-import org.glassfish.jersey.server.ResourceConfig;
-import org.glassfish.jersey.test.JerseyTest;
-import org.glassfish.jersey.test.TestProperties;
-import org.junit.Test;
-
-public class StressTest extends JerseyTest {
-
-    private static final int PARALLELISM = 50;
-    private static final int ITERATIONS = 1000;
-
-    @Path("/test")
-    public static class TestResource {
-
-        @GET
-        public String test() {
-            return "test";
-        }
-    }
-
-    @Override
-    protected Application configure() {
-        enable(TestProperties.LOG_TRAFFIC);
-        enable(TestProperties.DUMP_ENTITY);
-        return new ResourceConfig(TestResource.class);
-    }
-
-    @Override
-    protected void configureClient(ClientConfig config) {
-        config.connectorProvider(new JdkConnectorProvider());
-    }
-
-    @Test
-    public void randomnessStatus200() throws InterruptedException, ExecutionException {
-        ExecutorService executor = Executors.newFixedThreadPool(PARALLELISM,
-                new ThreadFactoryBuilder().setNameFormat("client-%d").build());
-        for (int i = 0; i < ITERATIONS; i++) {
-            System.out.println("Iteration " + i);
-            List<Future<Response>> responses = new ArrayList<>();
-            for (int j = 0; j < 100; j++) {
-                Future<Response> future = executor.submit(() -> target("/test").request().get());
-                responses.add(future);
-            }
-            for (Future<Response> response : responses) {
-                assertEquals(200, response.get().getStatus());
-            }
-        }
-        executor.shutdown();
-        assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
-    }
-}
diff --git a/connectors/jetty-connector/pom.xml b/connectors/jetty-connector/pom.xml
index 8be410e..234e0ae 100644
--- a/connectors/jetty-connector/pom.xml
+++ b/connectors/jetty-connector/pom.xml
@@ -107,6 +107,19 @@
             </execution>
             </executions>
             </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>
     </build>
 
diff --git a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyClientProperties.java b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyClientProperties.java
index ef2c8be..b6817dc 100644
--- a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyClientProperties.java
+++ b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyClientProperties.java
@@ -86,6 +86,24 @@
         "jersey.config.jetty.client.syncListenerResponseMaxSize";
 
     /**
+     * Total timeout interval for request/response conversation, in milliseconds.
+     * Opposed to {@link org.glassfish.jersey.client.ClientProperties#READ_TIMEOUT}.
+     * <p>
+     * The value MUST be an instance convertible to {@link java.lang.Integer}. The
+     * value of zero (0) is equivalent to an interval of infinity.
+     * </p>
+     * <p>
+     * The default value is zero (infinity).
+     * </p>
+     * <p>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     * </p>
+     *
+     * @since 2.37
+     */
+    public static final String TOTAL_TIMEOUT = "jersey.config.jetty.client.totalTimeout";
+
+    /**
      * Get the value of the specified property.
      *
      * If the property is not set or the real value type is not compatible with the specified value type, returns {@code null}.
diff --git a/connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyConnector.java b/connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyConnector.java
index c2b881d..33749a2 100644
--- a/connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyConnector.java
+++ b/connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyConnector.java
@@ -323,8 +323,14 @@
         request.followRedirects(clientRequest.resolveProperty(ClientProperties.FOLLOW_REDIRECTS, true));
         final Object readTimeout = clientRequest.resolveProperty(ClientProperties.READ_TIMEOUT, -1);
         if (readTimeout != null && readTimeout instanceof Integer && (Integer) readTimeout > 0) {
-            request.timeout((Integer) readTimeout, TimeUnit.MILLISECONDS);
+            request.idleTimeout((Integer) readTimeout, TimeUnit.MILLISECONDS);
         }
+
+        final Object totalTimeout = clientRequest.resolveProperty(JettyClientProperties.TOTAL_TIMEOUT, -1);
+        if (totalTimeout != null && totalTimeout instanceof Integer && (Integer) totalTimeout > 0) {
+            request.timeout((Integer) totalTimeout, TimeUnit.MILLISECONDS);
+        }
+
         return request;
     }
 
diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/TimeoutTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/TimeoutTest.java
index 5922920..e1ea028 100644
--- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/TimeoutTest.java
+++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/TimeoutTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2022 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,30 +16,37 @@
 
 package org.glassfish.jersey.jetty.connector;
 
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.logging.Logger;
 
+import jakarta.ws.rs.DefaultValue;
 import jakarta.ws.rs.GET;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.QueryParam;
 import jakarta.ws.rs.client.Client;
 import jakarta.ws.rs.client.ClientBuilder;
 import jakarta.ws.rs.client.WebTarget;
 import jakarta.ws.rs.core.Application;
 import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.StreamingOutput;
 
+import org.glassfish.jersey.CommonProperties;
 import org.glassfish.jersey.client.ClientConfig;
 import org.glassfish.jersey.client.ClientProperties;
 import org.glassfish.jersey.logging.LoggingFeature;
 import org.glassfish.jersey.server.ResourceConfig;
 import org.glassfish.jersey.test.JerseyTest;
-
 import org.junit.Test;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
 
 /**
  * @author Martin Matula
@@ -65,6 +72,48 @@
             }
             return "GET";
         }
+
+        /**
+         * Long-running streaming request
+         *
+         * @param count number of packets send
+         * @param pauseMillis pause between each packets
+         */
+        @GET
+        @Path("stream")
+        public Response streamsWithDelay(@QueryParam("start") @DefaultValue("0") int startMillis, @QueryParam("count") int count,
+                @QueryParam("pauseMillis") int pauseMillis) {
+            StreamingOutput streamingOutput = streamSlowly(startMillis, count, pauseMillis);
+
+            return Response.ok(streamingOutput)
+                    .build();
+        }
+    }
+
+    private static StreamingOutput streamSlowly(int startMillis, int count, int pauseMillis) {
+
+        return output -> {
+            try {
+                TimeUnit.MILLISECONDS.sleep(startMillis);
+            }
+            catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+            output.write("begin\n".getBytes(StandardCharsets.UTF_8));
+            output.flush();
+            for (int i = 0; i < count; i++) {
+                try {
+                    TimeUnit.MILLISECONDS.sleep(pauseMillis);
+                }
+                catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+
+                output.write(("message " + i + "\n").getBytes(StandardCharsets.UTF_8));
+                output.flush();
+            }
+            output.write("end".getBytes(StandardCharsets.UTF_8));
+        };
     }
 
     @Override
@@ -121,4 +170,74 @@
             c.close();
         }
     }
+
+    /**
+     * Test accessing an operation that is streaming slowly
+     *
+     * @throws ProcessingException in case of a test error.
+     */
+    @Test
+    public void testSlowlyStreamedContentDoesNotReadTimeout() throws Exception {
+
+        int count = 5;
+        int pauseMillis = 50;
+
+        final Response response = target("test")
+                .property(ClientProperties.READ_TIMEOUT, 100L)
+                .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1")
+                .path("stream")
+                .queryParam("count", count)
+                .queryParam("pauseMillis", pauseMillis)
+                .request().get();
+
+        assertTrue(response.readEntity(String.class).contains("end"));
+    }
+
+    @Test
+    public void testSlowlyStreamedContentDoesTotalTimeout() throws Exception {
+
+        int count = 5;
+        int pauseMillis = 50;
+
+        try {
+        target("test")
+                .property(JettyClientProperties.TOTAL_TIMEOUT, 100L)
+                .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1")
+                .path("stream")
+                .queryParam("count", count)
+                .queryParam("pauseMillis", pauseMillis)
+                .request().get();
+
+            fail("This operation should trigger total timeout");
+        } catch (ProcessingException e) {
+            assertEquals(TimeoutException.class, e.getCause().getClass());
+        }
+    }
+
+    /**
+     * Test accessing an operation that is streaming slowly
+     *
+     * @throws ProcessingException in case of a test error.
+     */
+    @Test
+    public void testSlowToStartStreamedContentDoesReadTimeout() throws Exception {
+
+        int start = 150;
+        int count = 5;
+        int pauseMillis = 50;
+
+        try {
+            target("test")
+                    .property(ClientProperties.READ_TIMEOUT, 100L)
+                    .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1")
+                    .path("stream")
+                    .queryParam("start", start)
+                    .queryParam("count", count)
+                    .queryParam("pauseMillis", pauseMillis)
+                    .request().get();
+            fail("This operation should trigger idle timeout");
+        } catch (ProcessingException e) {
+            assertEquals(TimeoutException.class, e.getCause().getClass());
+        }
+    }
 }
diff --git a/connectors/netty-connector/pom.xml b/connectors/netty-connector/pom.xml
index 1805490..f206f96 100644
--- a/connectors/netty-connector/pom.xml
+++ b/connectors/netty-connector/pom.xml
@@ -67,6 +67,11 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
             </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/containers/jetty-http/pom.xml b/containers/jetty-http/pom.xml
index 8ffde3c..62a849c 100644
--- a/containers/jetty-http/pom.xml
+++ b/containers/jetty-http/pom.xml
@@ -80,6 +80,14 @@
                 <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>
 
diff --git a/containers/jetty-servlet/pom.xml b/containers/jetty-servlet/pom.xml
index 373d004..ef1cff9 100644
--- a/containers/jetty-servlet/pom.xml
+++ b/containers/jetty-servlet/pom.xml
@@ -66,6 +66,7 @@
                     <instructions>
                         <Import-Package>
                             jakarta.servlet.*;version="[5.0,7.0)",
+                            ${jetty.osgi.version},
                             *
                         </Import-Package>
                     </instructions>
diff --git a/docs/src/main/docbook/appendix-properties.xml b/docs/src/main/docbook/appendix-properties.xml
index 6f258ca..938e440 100644
--- a/docs/src/main/docbook/appendix-properties.xml
+++ b/docs/src/main/docbook/appendix-properties.xml
@@ -1825,6 +1825,23 @@
                             </para>
                         </entry>
                     </row>
+                    <row>
+                        <entry>&jersey.jetty.JettyClientProperties.TOTAL_TIMEOUT;</entry>
+                        <entry><literal>jersey.config.jetty.client.totalTimeout</literal></entry>
+                        <entry>
+                            <para>
+                                Total timeout interval for request/response conversation, in milliseconds.
+                                Opposed to &jersey.client.ClientProperties.READ_TIMEOUT;.
+                            </para>
+                            <para>
+                                The value MUST be an instance convertible to <literal>Integer</literal>.
+                                The value of zero <literal>0</literal> is equivalent to an interval of infinity.
+                            </para>
+                            <para>
+                                The default value is <literal>0</literal> (infinity).
+                            </para>
+                        </entry>
+                    </row>
                 </tbody>
             </tgroup>
         </table>
diff --git a/docs/src/main/docbook/jersey.ent b/docs/src/main/docbook/jersey.ent
index 2f91101..e947990 100644
--- a/docs/src/main/docbook/jersey.ent
+++ b/docs/src/main/docbook/jersey.ent
@@ -472,6 +472,7 @@
 <!ENTITY jersey.jetty.JettyClientProperties.DISABLE_COOKIES "<link xlink:href='&jersey.javadoc.uri.prefix;/jetty/connector/JettyClientProperties.html#DISABLE_COOKIES'>JettyClientProperties.DISABLE_COOKIES</link>" >
 <!ENTITY jersey.jetty.JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION "<link xlink:href='&jersey.javadoc.uri.prefix;/jetty/connector/JettyClientProperties.html#PREEMPTIVE_BASIC_AUTHENTICATION'>JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION</link>" >
 <!ENTITY jersey.jetty.JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE "<link xlink:href='&jersey.javadoc.uri.prefix;/jetty/connector/JettyClientProperties.html#SYNC_LISTENER_RESPONSE_MAX_SIZE'>JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE</link>" >
+<!ENTITY jersey.jetty.JettyClientProperties.TOTAL_TIMEOUT "<link xlink:href='&jersey.javadoc.uri.prefix;/jetty/connector/JettyClientProperties.html#TOTAL_TIMEOUT'>JettyClientProperties.TOTAL_TIMEOUT</link>" >
 <!ENTITY jersey.jetty.JettyConnectorProvider "<link xlink:href='&jersey.javadoc.uri.prefix;/jetty/connector/JettyConnectorProvider.html'>JettyConnectorProvider</link>">
 <!ENTITY jersey.jetty.JettyHttpContainer "<link xlink:href='&jersey.javadoc.uri.prefix;/jetty/JettyHttpContainer.html'>JettyHttpContainer</link>">
 <!ENTITY jersey.jetty.JettyHttpContainerFactory "<link xlink:href='&jersey.javadoc.uri.prefix;/jetty/JettyHttpContainerFactory.html'>JettyHttpContainerFactory</link>">
diff --git a/incubator/declarative-linking/pom.xml b/incubator/declarative-linking/pom.xml
index 97d9d54..df798a3 100644
--- a/incubator/declarative-linking/pom.xml
+++ b/incubator/declarative-linking/pom.xml
@@ -190,6 +190,11 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/incubator/kryo/pom.xml b/incubator/kryo/pom.xml
index 3911bf8..66d26bb 100644
--- a/incubator/kryo/pom.xml
+++ b/incubator/kryo/pom.xml
@@ -78,5 +78,21 @@
                 <directory>${project.build.directory}/legal</directory>
             </resource>
         </resources>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <configuration>
+                    <instructions>
+                        <Import-Package>
+                            com.esotericsoftware.kryo.*;version="!",
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
     </build>
 </project>
diff --git a/pom.xml b/pom.xml
index edfec7e..bdde4d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2246,6 +2246,7 @@
         <jaxb.ri.version>3.0.2</jaxb.ri.version>
         <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.9</jetty.version>
         <jetty.plugin.version>11.0.9</jetty.plugin.version>
         <jetty.servlet.api.25.version>6.1.14</jetty.servlet.api.25.version>
diff --git a/tests/integration/jersey-4697/src/test/java/org/glassfish/jersey/tests/integration/jersey4697/MonitoringEventListenerTest.java b/tests/integration/jersey-4697/src/test/java/org/glassfish/jersey/tests/integration/jersey4697/MonitoringEventListenerTest.java
index 14fa3bc..15eef57 100644
--- a/tests/integration/jersey-4697/src/test/java/org/glassfish/jersey/tests/integration/jersey4697/MonitoringEventListenerTest.java
+++ b/tests/integration/jersey-4697/src/test/java/org/glassfish/jersey/tests/integration/jersey4697/MonitoringEventListenerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2022 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
@@ -52,7 +52,7 @@
 
 public class MonitoringEventListenerTest extends JerseyTest {
 
-    private static final long TIMEOUT = 500;
+    private static final long TIMEOUT = 1000;
     private static final String MBEAN_EXCEPTION =
             "org.glassfish.jersey:type=MonitoringEventListenerTest,subType=Global,exceptions=ExceptionMapper";
 
@@ -116,6 +116,7 @@
         resourceConfig.property(ServerProperties.MONITORING_ENABLED, true);
         resourceConfig.property(ServerProperties.MONITORING_STATISTICS_ENABLED, true);
         resourceConfig.property(ServerProperties.MONITORING_STATISTICS_MBEANS_ENABLED, true);
+        // Scheduler will process 1000 events per second
         resourceConfig.property(ServerProperties.MONITORING_STATISTICS_REFRESH_INTERVAL, 1);
         resourceConfig.setApplicationName("MonitoringEventListenerTest");
         return resourceConfig;
@@ -126,17 +127,18 @@
         final Long ERRORS_BEFORE_FAIL = 10L;
         // Send some requests to process some statistics.
         request(ERRORS_BEFORE_FAIL);
-        // Give some time to the scheduler to collect data.
+        // Give some time to process events
         Thread.sleep(TIMEOUT);
-        // All events were consumed by scheduler
+        // Verify the exceptionMapperEvents is empty, because no event of this type was sent yet
         queueIsEmpty();
-        // Make the scheduler to fail. No more statistics are collected.
+        // Sending one event that will make an internal error in the scheduler.
+        // No new events will be pushed in the queues after this.
         makeFailure();
-        // Sending again requests
+        // Sending again requests. These events will not be processed, so they will not be counted for statistics.
         request(20);
+        // The expectation is that the scheduler is not going to process previous events because there was a failure before.
+        // We give some time before checking that no new errors are registered.
         Thread.sleep(TIMEOUT);
-        // No new events should be accepted because scheduler is not working.
-        queueIsEmpty();
         Long monitoredErrors = mappedErrorsFromJMX(MBEAN_EXCEPTION);
         assertEquals(ERRORS_BEFORE_FAIL, monitoredErrors);
     }