Merge pull request #4396 from jansupol/gf.osgi.ver.up

Preparation for GF 6
diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java
index 5531498..9ab9745 100644
--- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java
+++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2020 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
@@ -20,17 +20,14 @@
 import java.io.InputStream;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.LinkedBlockingDeque;
 
 import javax.ws.rs.core.Response;
 
 import org.glassfish.jersey.client.ClientRequest;
 import org.glassfish.jersey.client.ClientResponse;
-import org.glassfish.jersey.client.spi.AsyncConnectorCallback;
 import org.glassfish.jersey.netty.connector.internal.NettyInputStream;
 
 import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.SimpleChannelInboundHandler;
 import io.netty.handler.codec.http.HttpContent;
@@ -39,8 +36,6 @@
 import io.netty.handler.codec.http.HttpResponse;
 import io.netty.handler.codec.http.HttpUtil;
 import io.netty.handler.codec.http.LastHttpContent;
-import io.netty.util.concurrent.Future;
-import io.netty.util.concurrent.GenericFutureListener;
 
 /**
  * Jersey implementation of Netty channel handler.
@@ -49,19 +44,38 @@
  */
 class JerseyClientHandler extends SimpleChannelInboundHandler<HttpObject> {
 
-    private final NettyConnector connector;
-    private final LinkedBlockingDeque<ByteBuf> isList = new LinkedBlockingDeque<>();
-
-    private final AsyncConnectorCallback asyncConnectorCallback;
     private final ClientRequest jerseyRequest;
-    private final CompletableFuture future;
+    private final CompletableFuture<ClientResponse> responseAvailable;
+    private final CompletableFuture<?> responseDone;
 
-    JerseyClientHandler(NettyConnector nettyConnector, ClientRequest request,
-                        AsyncConnectorCallback callback, CompletableFuture future) {
-        this.connector = nettyConnector;
-        this.asyncConnectorCallback = callback;
+    private NettyInputStream nis;
+    private ClientResponse jerseyResponse;
+
+    JerseyClientHandler(ClientRequest request,
+                        CompletableFuture<ClientResponse> responseAvailable,
+                        CompletableFuture<?> responseDone) {
         this.jerseyRequest = request;
-        this.future = future;
+        this.responseAvailable = responseAvailable;
+        this.responseDone = responseDone;
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+       notifyResponse();
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) {
+       // assert: no-op, if channel is closed after LastHttpContent has been consumed
+       responseDone.completeExceptionally(new IOException("Stream closed"));
+    }
+
+    protected void notifyResponse() {
+       if (jerseyResponse != null) {
+          ClientResponse cr = jerseyResponse;
+          jerseyResponse = null;
+          responseAvailable.complete(cr);
+       }
     }
 
     @Override
@@ -69,7 +83,7 @@
         if (msg instanceof HttpResponse) {
             final HttpResponse response = (HttpResponse) msg;
 
-            final ClientResponse jerseyResponse = new ClientResponse(new Response.StatusType() {
+            jerseyResponse = new ClientResponse(new Response.StatusType() {
                 @Override
                 public int getStatusCode() {
                     return response.status().code();
@@ -89,19 +103,15 @@
             for (Map.Entry<String, String> entry : response.headers().entries()) {
                 jerseyResponse.getHeaders().add(entry.getKey(), entry.getValue());
             }
-            isList.clear(); // clearing the content - possible leftover from previous request processing.
+
             // request entity handling.
             if ((response.headers().contains(HttpHeaderNames.CONTENT_LENGTH) && HttpUtil.getContentLength(response) > 0)
                     || HttpUtil.isTransferEncodingChunked(response)) {
 
-                ctx.channel().closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
-                    @Override
-                    public void operationComplete(Future<? super Void> future) throws Exception {
-                        isList.add(Unpooled.EMPTY_BUFFER);
-                    }
-                });
+                nis = new NettyInputStream();
+                responseDone.whenComplete((_r, th) -> nis.complete(th));
 
-                jerseyResponse.setEntityStream(new NettyInputStream(isList));
+                jerseyResponse.setEntityStream(nis);
             } else {
                 jerseyResponse.setEntityStream(new InputStream() {
                     @Override
@@ -110,44 +120,29 @@
                     }
                 });
             }
-
-            if (asyncConnectorCallback != null) {
-                connector.executorService.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        asyncConnectorCallback.response(jerseyResponse);
-                        future.complete(jerseyResponse);
-                    }
-                });
-            }
-
         }
         if (msg instanceof HttpContent) {
+
             HttpContent httpContent = (HttpContent) msg;
 
             ByteBuf content = httpContent.content();
+
             if (content.isReadable()) {
                 content.retain();
-                isList.add(content);
+                nis.publish(content);
             }
 
             if (msg instanceof LastHttpContent) {
-                isList.add(Unpooled.EMPTY_BUFFER);
+                responseDone.complete(null);
+                notifyResponse();
             }
         }
     }
 
+
+
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, final Throwable cause) {
-        if (asyncConnectorCallback != null) {
-            connector.executorService.execute(new Runnable() {
-                @Override
-                public void run() {
-                    asyncConnectorCallback.failure(cause);
-                }
-            });
-        }
-        future.completeExceptionally(cause);
-        ctx.close();
+        responseDone.completeExceptionally(cause);
     }
 }
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 0cd1f75..373d3c8 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2020 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
@@ -20,6 +20,8 @@
 import java.io.OutputStream;
 import java.net.InetSocketAddress;
 import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
@@ -28,7 +30,6 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
 
 import javax.ws.rs.ProcessingException;
 import javax.ws.rs.client.Client;
@@ -76,6 +77,7 @@
     final ExecutorService executorService;
     final EventLoopGroup group;
     final Client client;
+    final HashMap<String, ArrayList<Channel>> connections = new HashMap<>();
 
     NettyConnector(Client client) {
 
@@ -83,72 +85,72 @@
 
         if (threadPoolSize != null && threadPoolSize instanceof Integer && (Integer) threadPoolSize > 0) {
             executorService = Executors.newFixedThreadPool((Integer) threadPoolSize);
+            this.group = new NioEventLoopGroup((Integer) threadPoolSize);
         } else {
             executorService = Executors.newCachedThreadPool();
+            this.group = new NioEventLoopGroup();
         }
 
-        this.group = new NioEventLoopGroup();
         this.client = client;
     }
 
     @Override
     public ClientResponse apply(ClientRequest jerseyRequest) {
-
-        final AtomicReference<ClientResponse> syncResponse = new AtomicReference<>(null);
-        final AtomicReference<Throwable> syncException = new AtomicReference<>(null);
-
         try {
-            Future<?> resultFuture = apply(jerseyRequest, new AsyncConnectorCallback() {
-                @Override
-                public void response(ClientResponse response) {
-                    syncResponse.set(response);
-                }
-
-                @Override
-                public void failure(Throwable failure) {
-                    syncException.set(failure);
-                }
-            });
+            CompletableFuture<ClientResponse> resultFuture = execute(jerseyRequest);
 
             Integer timeout = ClientProperties.getValue(jerseyRequest.getConfiguration().getProperties(),
                                                         ClientProperties.READ_TIMEOUT, 0);
 
-            if (timeout != null && timeout > 0) {
-                resultFuture.get(timeout, TimeUnit.MILLISECONDS);
-            } else {
-                resultFuture.get();
-            }
+            return (timeout != null && timeout > 0) ? resultFuture.get(timeout, TimeUnit.MILLISECONDS)
+                                                    : resultFuture.get();
         } catch (ExecutionException ex) {
             Throwable e = ex.getCause() == null ? ex : ex.getCause();
             throw new ProcessingException(e.getMessage(), e);
         } catch (Exception ex) {
             throw new ProcessingException(ex.getMessage(), ex);
         }
-
-        Throwable throwable = syncException.get();
-        if (throwable == null) {
-            return syncResponse.get();
-        } else {
-            throw new RuntimeException(throwable);
-        }
     }
 
     @Override
     public Future<?> apply(final ClientRequest jerseyRequest, final AsyncConnectorCallback jerseyCallback) {
+        return execute(jerseyRequest).whenCompleteAsync((r, th) -> {
+                  if (th == null) jerseyCallback.response(r);
+                  else jerseyCallback.failure(th);
+               }, executorService);
+    }
 
-        final CompletableFuture<Object> settableFuture = new CompletableFuture<>();
+    protected CompletableFuture<ClientResponse> execute(final ClientRequest jerseyRequest) {
+        final CompletableFuture<ClientResponse> responseAvailable = new CompletableFuture<>();
+        final CompletableFuture<?> responseDone = new CompletableFuture<>();
 
         final URI requestUri = jerseyRequest.getUri();
         String host = requestUri.getHost();
         int port = requestUri.getPort() != -1 ? requestUri.getPort() : "https".equals(requestUri.getScheme()) ? 443 : 80;
 
         try {
-            Bootstrap b = new Bootstrap();
-            b.group(group)
-             .channel(NioSocketChannel.class)
-             .handler(new ChannelInitializer<SocketChannel>() {
-                 @Override
-                 protected void initChannel(SocketChannel ch) throws Exception {
+            String key = requestUri.getScheme() + "://" + host + ":" + port;
+            ArrayList<Channel> conns;
+            synchronized (connections) {
+               conns = connections.get(key);
+               if (conns == null) {
+                  conns = new ArrayList<>(0);
+                  connections.put(key, conns);
+               }
+            }
+
+            Channel chan;
+            synchronized (conns) {
+               chan = conns.size() == 0 ? null : conns.remove(conns.size() - 1);
+            }
+
+            if (chan == null) {
+               Bootstrap b = new Bootstrap();
+               b.group(group)
+                .channel(NioSocketChannel.class)
+                .handler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    protected void initChannel(SocketChannel ch) throws Exception {
                      ChannelPipeline p = ch.pipeline();
 
                      // Enable HTTPS if necessary.
@@ -177,43 +179,59 @@
                      p.addLast(new HttpClientCodec());
                      p.addLast(new ChunkedWriteHandler());
                      p.addLast(new HttpContentDecompressor());
-                     p.addLast(new JerseyClientHandler(NettyConnector.this, jerseyRequest, jerseyCallback, settableFuture));
-                 }
-             });
+                    }
+                });
 
-            // connect timeout
-            Integer connectTimeout = ClientProperties.getValue(jerseyRequest.getConfiguration().getProperties(),
-                                                               ClientProperties.CONNECT_TIMEOUT, 0);
-            if (connectTimeout > 0) {
-                b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout);
+               // connect timeout
+               Integer connectTimeout = ClientProperties.getValue(jerseyRequest.getConfiguration().getProperties(),
+                                                                  ClientProperties.CONNECT_TIMEOUT, 0);
+               if (connectTimeout > 0) {
+                   b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout);
+               }
+
+               // Make the connection attempt.
+               chan = b.connect(host, port).sync().channel();
             }
 
-            // Make the connection attempt.
-            final Channel ch = b.connect(host, port).sync().channel();
+            // assert: clientHandler will always notify responseDone: either normally, or exceptionally
+            // assert: clientHandler may notify responseAvailable, if sufficient parts of response are detected to construct
+            //         a valid ClientResponse
+            // assert: responseAvailable completion may be racing against responseDone completion
+            // assert: it is ok to abort the entire response, if responseDone is completed exceptionally - in particular, nothing
+            //         will leak
+            final Channel ch = chan;
+            JerseyClientHandler clientHandler = new JerseyClientHandler(jerseyRequest, responseAvailable, responseDone);
+            ch.pipeline().addLast(clientHandler);
 
-            // guard against prematurely closed channel
-            final GenericFutureListener<io.netty.util.concurrent.Future<? super Void>> closeListener =
-                    new GenericFutureListener<io.netty.util.concurrent.Future<? super Void>>() {
-                        @Override
-                        public void operationComplete(io.netty.util.concurrent.Future<? super Void> future) throws Exception {
-                            if (!settableFuture.isDone()) {
-                                settableFuture.completeExceptionally(new IOException("Channel closed."));
-                            }
-                        }
-                    };
+            responseDone.whenComplete((_r, th) -> {
+               ch.pipeline().remove(clientHandler);
 
-            ch.closeFuture().addListener(closeListener);
+               if (th == null) {
+                  synchronized (connections) {
+                     ArrayList<Channel> conns1 = connections.get(key);
+                     synchronized (conns1) {
+                        conns1.add(ch);
+                     }
+                  }
+               } else {
+                  ch.close();
+                  // if responseAvailable has been completed, no-op: jersey will encounter IOException while reading response body
+                  // if responseAvailable has not been completed, abort
+                  responseAvailable.completeExceptionally(th);
+               }
+            });
 
             HttpRequest nettyRequest;
+            String pathWithQuery = buildPathWithQueryParameters(requestUri);
 
             if (jerseyRequest.hasEntity()) {
                 nettyRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_1,
                                                       HttpMethod.valueOf(jerseyRequest.getMethod()),
-                                                      requestUri.getRawPath());
+                                                      pathWithQuery);
             } else {
                 nettyRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
                                                           HttpMethod.valueOf(jerseyRequest.getMethod()),
-                                                          requestUri.getRawPath());
+                                                          pathWithQuery);
             }
 
             // headers
@@ -225,14 +243,23 @@
             nettyRequest.headers().add(HttpHeaderNames.HOST, jerseyRequest.getUri().getHost());
 
             if (jerseyRequest.hasEntity()) {
+                // guard against prematurely closed channel
+                final GenericFutureListener<io.netty.util.concurrent.Future<? super Void>> closeListener =
+                    new GenericFutureListener<io.netty.util.concurrent.Future<? super Void>>() {
+                        @Override
+                        public void operationComplete(io.netty.util.concurrent.Future<? super Void> future) throws Exception {
+                            if (!responseDone.isDone()) {
+                                responseDone.completeExceptionally(new IOException("Channel closed."));
+                            }
+                        }
+                    };
+                ch.closeFuture().addListener(closeListener);
                 if (jerseyRequest.getLengthLong() == -1) {
                     HttpUtil.setTransferEncodingChunked(nettyRequest, true);
                 } else {
                     nettyRequest.headers().add(HttpHeaderNames.CONTENT_LENGTH, jerseyRequest.getLengthLong());
                 }
-            }
 
-            if (jerseyRequest.hasEntity()) {
                 // Send the HTTP request.
                 ch.writeAndFlush(nettyRequest);
 
@@ -259,27 +286,30 @@
                         try {
                             jerseyRequest.writeEntity();
                         } catch (IOException e) {
-                            jerseyCallback.failure(e);
-                            settableFuture.completeExceptionally(e);
+                            responseDone.completeExceptionally(e);
                         }
                     }
                 });
 
                 ch.flush();
             } else {
-                // close listener is not needed any more.
-                ch.closeFuture().removeListener(closeListener);
-
                 // Send the HTTP request.
                 ch.writeAndFlush(nettyRequest);
             }
 
         } catch (InterruptedException e) {
-            settableFuture.completeExceptionally(e);
-            return settableFuture;
+            responseDone.completeExceptionally(e);
         }
 
-        return settableFuture;
+        return responseAvailable;
+    }
+
+    private String buildPathWithQueryParameters(URI requestUri) {
+        if (requestUri.getRawQuery() != null) {
+            return String.format("%s?%s", requestUri.getRawPath(), requestUri.getRawQuery());
+        } else {
+            return requestUri.getRawPath();
+        }
     }
 
     @Override
diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyInputStream.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyInputStream.java
index 741121f..3da7019 100644
--- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyInputStream.java
+++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyInputStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2020 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,107 +16,150 @@
 
 package org.glassfish.jersey.netty.connector.internal;
 
-import java.io.IOException;
 import java.io.InputStream;
-import java.util.concurrent.LinkedBlockingDeque;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
 
 import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
 
 /**
  * Input stream which servers as Request entity input.
  * <p>
- * Consumes a list of pending {@link ByteBuf}s and processes them on request by Jersey
+ * Converts Netty NIO buffers to an input streams and stores them in the queue,
+ * waiting for Jersey to process it.
+ *
+ * @author Pavel Bucek
  */
 public class NettyInputStream extends InputStream {
 
-    private final LinkedBlockingDeque<ByteBuf> isList;
+    private volatile boolean end = false;
+    private Throwable cause;
 
-    public NettyInputStream(LinkedBlockingDeque<ByteBuf> isList) {
-        this.isList = isList;
+    private final ArrayDeque<ByteBuf> isList;
+    private ByteBuf current;
+    private ByteBuffer buffer;
+
+    private byte[] ONE_BYTE;
+    private boolean reading;
+
+    public NettyInputStream() {
+        this.isList = new ArrayDeque<>();
     }
 
     @Override
     public int read(byte[] b, int off, int len) throws IOException {
-
-        ByteBuf take;
-        try {
-            take = isList.take();
-            boolean isReadable = take.isReadable();
-            int read = -1;
-            if (checkEndOfInputOrError(take)) {
-                take.release();
+       if (current == null) {
+          buffer = awaitNext();
+          if (buffer == null) {
+             // assert: end is true
+             if (cause == null) {
                 return -1;
-            }
+             }
 
-            if (isReadable) {
-                int readableBytes = take.readableBytes();
-                read = Math.min(readableBytes, len);
-                take.readBytes(b, off, read);
-                if (read < len) {
-                    take.release();
-                } else {
-                    isList.addFirst(take);
-                }
-            } else {
-                read = 0;
-                take.release(); //We don't need `0`
-            }
+             throw new IOException(cause);
+          }
+       }
 
-            return read;
-        } catch (InterruptedException e) {
-            throw new IOException("Interrupted.", e);
-        }
+       int rem = buffer.remaining();
+       if (rem < len) {
+          len = rem;
+       }
+       buffer.get(b, off, len);
+       if (rem == len) {
+          releaseByteBuf();
+       }
+
+       return len;
     }
 
     @Override
     public int read() throws IOException {
+       if (ONE_BYTE == null) {
+          ONE_BYTE = new byte[1];
+       }
+       int r = read(ONE_BYTE, 0, 1);
+       if (r < 0) {
+          return r;
+       }
 
-        ByteBuf take;
-        try {
-            take = isList.take();
-            boolean isReadable = take.isReadable();
-            if (checkEndOfInputOrError(take)) {
-                take.release();
-                return -1;
-            }
-
-            if (isReadable) {
-                return take.readInt();
-            } else {
-                take.release(); //We don't need `0`
-            }
-
-            return 0;
-        } catch (InterruptedException e) {
-            throw new IOException("Interrupted.", e);
-        }
+       return ONE_BYTE[0] & 0xff;
     }
 
     @Override
-    public void close() throws IOException {
-        if (isList != null) {
-            while (!isList.isEmpty()) {
-                try {
-                    isList.take().release();
-                } catch (InterruptedException e) {
-                    throw new IOException("Interrupted. Potential ByteBuf Leak.", e);
-                }
-            }
+    public void close() {
+
+        releaseByteBuf();
+
+        cleanup(true);
+    }
+
+    private void releaseByteBuf() {
+        if (current != null) {
+            current.release();
         }
-        super.close();
+
+        current = null;
+        buffer = null;
+    }
+
+    protected synchronized ByteBuffer awaitNext() {
+       while (isList.isEmpty()) {
+          if (end) {
+             return null;
+          }
+
+          try {
+             reading = true;
+             wait();
+             reading = false;
+          } catch (InterruptedException ie) {
+             // waiting uninterruptibly
+          }
+       }
+
+       current = isList.poll();
+       return current.nioBuffer().asReadOnlyBuffer();
+    }
+
+    public void complete(Throwable cause) {
+       this.cause = cause;
+       cleanup(cause != null);
+    }
+
+    protected synchronized void cleanup(boolean drain) {
+       if (drain) {
+          while (!isList.isEmpty()) {
+             isList.poll().release();
+          }
+       }
+
+       end = true;
+
+       if (reading) {
+          notifyAll();
+       }
     }
 
     @Override
     public int available() throws IOException {
-        ByteBuf peek = isList.peek();
-        if (peek != null && peek.isReadable()) {
-            return peek.readableBytes();
-        }
-        return 0;
+        return buffer == null ? 0 : buffer.remaining();
     }
 
-    private boolean checkEndOfInputOrError(ByteBuf take) throws IOException {
-        return take == Unpooled.EMPTY_BUFFER;
+    public synchronized void publish(ByteBuf content) {
+       if (end || content.nioBuffer().remaining() == 0) {
+          content.release();
+          return;
+       }
+
+       isList.add(content);
+       if (reading) {
+          notifyAll();
+       }
+    }
+
+    public void clear() {
+        end = false;
+        isList.clear();
     }
 }
diff --git a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/HelloWorldTest.java b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/HelloWorldTest.java
index 6716c9b..20caa53 100644
--- a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/HelloWorldTest.java
+++ b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/HelloWorldTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2020 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
@@ -77,6 +77,7 @@
     public void testConnection() {
         Response response = target().path(ROOT_PATH).request("text/plain").get();
         assertEquals(200, response.getStatus());
+        response.close();
     }
 
     @Test
diff --git a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/HttpHeadersTest.java b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/HttpHeadersTest.java
index 10cfa14..f5438e5 100644
--- a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/HttpHeadersTest.java
+++ b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/HttpHeadersTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2020 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
@@ -64,5 +64,6 @@
 
         assertEquals(200, response.getStatus());
         assertTrue(response.hasEntity());
+        response.close();
     }
 }
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentProvider.java b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentProvider.java
index 2602c3f..669e1d2 100644
--- a/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentProvider.java
+++ b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentProvider.java
@@ -16,6 +16,8 @@
  */
 package org.glassfish.jersey.gf.ejb.internal;
 
+import java.io.Externalizable;
+import java.io.Serializable;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
@@ -42,6 +44,7 @@
 import javax.annotation.Priority;
 import javax.ejb.Local;
 import javax.ejb.Remote;
+import javax.ejb.Stateless;
 import javax.inject.Singleton;
 import javax.naming.InitialContext;
 import javax.naming.NamingException;
@@ -92,23 +95,43 @@
 
         final InitialContext ctx;
         final Class<T> clazz;
+        final String beanName;
         final EjbComponentProvider ejbProvider;
 
         @SuppressWarnings("unchecked")
         @Override
         public T get() {
             try {
-                return (T) lookup(ctx, clazz, clazz.getSimpleName(), ejbProvider);
+                return (T) lookup(ctx, clazz, beanName, ejbProvider);
             } catch (NamingException ex) {
                 Logger.getLogger(ApplicationHandler.class.getName()).log(Level.SEVERE, null, ex);
                 return null;
             }
         }
 
+        private static <T> String getBeanName(final Class<T> clazz) {
+            final Stateless stateless = clazz.getAnnotation(Stateless.class);
+            if (stateless != null) {
+                if (stateless.name().isEmpty()) {
+                    return clazz.getSimpleName();
+                }
+                return stateless.name();
+            }
+            final javax.ejb.Singleton singleton = clazz.getAnnotation(javax.ejb.Singleton.class);
+            if (singleton != null) {
+                if (singleton.name().isEmpty()) {
+                    return clazz.getSimpleName();
+                }
+                return singleton.name();
+            }
+            return clazz.getSimpleName();
+        }
+
         public EjbFactory(Class<T> rawType, InitialContext ctx, EjbComponentProvider ejbProvider) {
             this.clazz = rawType;
             this.ctx = ctx;
             this.ejbProvider = ejbProvider;
+            this.beanName = getBeanName(rawType);
         }
     }
 
@@ -346,9 +369,23 @@
                 allLocalOrRemoteIfaces.add(i);
             }
         }
+        if (allLocalOrRemoteIfaces.isEmpty()) {
+            for (Class<?> i : resourceClass.getInterfaces()) {
+                if (isAcceptableLocalInterface(i)) {
+                    allLocalOrRemoteIfaces.add(i);
+                }
+            }
+        }
         return allLocalOrRemoteIfaces;
     }
 
+    private static boolean isAcceptableLocalInterface(final Class<?> iface) {
+        if ("javax.ejb".equals(iface.getPackage().getName())) {
+            return false;
+        }
+        return !Serializable.class.equals(iface) && !Externalizable.class.equals(iface);
+    }
+
     private static InitialContext getInitialContext() {
         try {
             // Deployment on Google App Engine will
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java
index e64476d..6eeac17 100644
--- a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2020 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
@@ -25,11 +25,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.LinkedBlockingDeque;
 
 import javax.ws.rs.core.SecurityContext;
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelDuplexHandler;
 import io.netty.channel.ChannelHandler;
 import io.netty.channel.ChannelHandlerContext;
@@ -55,7 +52,7 @@
 class JerseyHttp2ServerHandler extends ChannelDuplexHandler {
 
     private final URI baseUri;
-    private final LinkedBlockingDeque<ByteBuf> isList = new LinkedBlockingDeque<>();
+    private final NettyInputStream nettyInputStream = new NettyInputStream();
     private final NettyHttpContainer container;
     private final ResourceConfig resourceConfig;
 
@@ -92,9 +89,9 @@
      * Process incoming data.
      */
     private void onDataRead(ChannelHandlerContext ctx, Http2DataFrame data) throws Exception {
-        isList.add(data.content());
+        nettyInputStream.publish(data.content());
         if (data.isEndStream()) {
-            isList.add(Unpooled.EMPTY_BUFFER);
+            nettyInputStream.complete(null);
         }
     }
 
@@ -163,11 +160,11 @@
             ctx.channel().closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
                 @Override
                 public void operationComplete(Future<? super Void> future) throws Exception {
-                    isList.add(Unpooled.EMPTY_BUFFER);
+                    nettyInputStream.complete(future.cause());
                 }
             });
 
-            requestContext.setEntityStream(new NettyInputStream(isList));
+            requestContext.setEntityStream(nettyInputStream);
         } else {
             requestContext.setEntityStream(new InputStream() {
                 @Override
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java
index 712cb1f..0f2a7ae 100644
--- a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2020 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
@@ -20,13 +20,11 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.concurrent.LinkedBlockingDeque;
 
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.MediaType;
 
 import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.handler.codec.http.DefaultFullHttpResponse;
@@ -53,7 +51,7 @@
 class JerseyServerHandler extends ChannelInboundHandlerAdapter {
 
     private final URI baseUri;
-    private final LinkedBlockingDeque<ByteBuf> isList = new LinkedBlockingDeque<>();
+    private final NettyInputStream nettyInputStream = new NettyInputStream();
     private final NettyHttpContainer container;
     private final ResourceConfig resourceConfig;
 
@@ -82,7 +80,7 @@
                 ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE));
             }
 
-            isList.clear(); // clearing the content - possible leftover from previous request processing.
+            nettyInputStream.clear(); // clearing the content - possible leftover from previous request processing.
             final ContainerRequest requestContext = createContainerRequest(ctx, req);
 
             requestContext.setWriter(new NettyResponseWriter(ctx, req, container));
@@ -105,7 +103,7 @@
                 //Otherwise, it's safe to discard during next processing
                 if ((!isJson && contentLength != -1) || HttpUtil.isTransferEncodingChunked(req)
                         || (isJson && contentLength >= 2)) {
-                    requestContext.setEntityStream(new NettyInputStream(isList));
+                    requestContext.setEntityStream(nettyInputStream);
                 }
             }
 
@@ -128,11 +126,11 @@
 
           ByteBuf content = httpContent.content();
           if (content.isReadable()) {
-              isList.add(content);
+              nettyInputStream.publish(content);
           }
 
           if (msg instanceof LastHttpContent) {
-              isList.add(Unpooled.EMPTY_BUFFER);
+              nettyInputStream.complete(null);
           }
       }
     }
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/CookiesParser.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/CookiesParser.java
index 5ac1b42..a0257ca 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/CookiesParser.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/CookiesParser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2020 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
@@ -70,10 +70,7 @@
                 value = value.substring(1, value.length() - 1);
             }
             if (!name.startsWith("$")) {
-                if (cookie != null) {
-                    cookies.put(cookie.name, cookie.getImmutableCookie());
-                }
-
+                checkSimilarCookieName(cookies, cookie);
                 cookie = new MutableCookie(name, value);
                 cookie.version = version;
             } else if (name.startsWith("$Version")) {
@@ -84,12 +81,28 @@
                 cookie.domain = value;
             }
         }
-        if (cookie != null) {
-            cookies.put(cookie.name, cookie.getImmutableCookie());
-        }
+        checkSimilarCookieName(cookies, cookie);
         return cookies;
     }
 
+    /**
+     * Check if a cookie with identical name had been parsed.
+     * If yes, the one with the longest string will be kept
+     * @param cookies : Map of cookies
+     * @param cookie : Cookie to be checked
+     */
+    private static void checkSimilarCookieName(Map<String, Cookie> cookies, MutableCookie cookie) {
+        if (cookie != null) {
+            if (cookies.containsKey(cookie.name)){
+                if (cookie.value.length() > cookies.get(cookie.name).getValue().length()){
+                    cookies.put(cookie.name, cookie.getImmutableCookie());
+                }
+            } else {
+                cookies.put(cookie.name, cookie.getImmutableCookie());
+            }
+        }
+    }
+
     public static Cookie parseCookie(String header) {
         Map<String, Cookie> cookies = parseCookies(header);
         return cookies.entrySet().iterator().next().getValue();
@@ -148,8 +161,6 @@
                     cookie.secure = true;
                 } else if (param.startsWith("version")) {
                     cookie.version = Integer.parseInt(value);
-                } else if (param.startsWith("domain")) {
-                    cookie.domain = value;
                 } else if (param.startsWith("httponly")) {
                     cookie.httpOnly = true;
                 }  else if (param.startsWith("expires")) {
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/HeaderUtils.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/HeaderUtils.java
index 38097d0..c8636ce 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/HeaderUtils.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/HeaderUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2020 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
@@ -17,6 +17,7 @@
 package org.glassfish.jersey.message.internal;
 
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -29,6 +30,7 @@
 import javax.ws.rs.core.AbstractMultivaluedMap;
 import javax.ws.rs.core.Configuration;
 import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.NewCookie;
 import javax.ws.rs.ext.RuntimeDelegate;
 import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
 
@@ -299,6 +301,33 @@
     }
 
     /**
+     * Compare two NewCookies having the same name. See documentation RFC.
+     *
+     * @param first    NewCookie to be compared.
+     * @param second NewCookie to be compared.
+     * @return the preferred NewCookie according to rules :
+     *              - the latest maxAge.
+     *              - if equal, compare the expiry date
+     *              - if equal, compare name length
+     */
+    public static NewCookie getPreferredCookie(NewCookie first, NewCookie second) {
+
+        if (first == null) {
+            return second;
+        } else if (second == null) {
+            return first;
+        }
+
+        if (first.getMaxAge() != second.getMaxAge()){
+            return Comparator.comparing(NewCookie::getMaxAge).compare(first, second) > 0 ? first : second;
+        } else if (first.getExpiry() != null && second.getExpiry() != null && !first.getExpiry().equals(second.getExpiry())) {
+            return Comparator.comparing(NewCookie::getExpiry).compare(first, second) > 0 ? first : second;
+        } else {
+            return first.getPath().length() > second.getPath().length() ? first : second;
+        }
+    }
+
+    /**
      * Convert a message header value, represented as a general object, to it's
      * string representation. If the supplied header value is {@code null},
      * this method returns {@code null}.
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java
index 4e469b1..00e453e 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020 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
@@ -587,7 +587,12 @@
         for (String cookie : cookies) {
             if (cookie != null) {
                 NewCookie newCookie = HttpHeaderReader.readNewCookie(cookie);
-                result.put(newCookie.getName(), newCookie);
+                String cookieName = newCookie.getName();
+                if (result.containsKey(cookieName)) {
+                    result.put(cookieName, HeaderUtils.getPreferredCookie(result.get(cookieName), newCookie));
+                } else {
+                    result.put(cookieName, newCookie);
+                }
             }
         }
         return result;
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java
index b9c63dd..d50c929 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020 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
@@ -456,7 +456,12 @@
         for (String cookie : HeaderUtils.asStringList(cookies, configuration)) {
             if (cookie != null) {
                 NewCookie newCookie = HttpHeaderReader.readNewCookie(cookie);
-                result.put(newCookie.getName(), newCookie);
+                String cookieName = newCookie.getName();
+                if (result.containsKey(cookieName)) {
+                    result.put(cookieName, HeaderUtils.getPreferredCookie(result.get(cookieName), newCookie));
+                } else {
+                    result.put(cookieName, newCookie);
+                }
             }
         }
         return result;
diff --git a/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java b/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java
index ca51259..d70faea 100644
--- a/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java
+++ b/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020 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
@@ -28,6 +28,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.logging.Level;
@@ -618,15 +619,19 @@
      * @param injectionManager injection manager in which the binders and features should be configured.
      */
     public void configureMetaProviders(InjectionManager injectionManager, ManagedObjectsFinalizer finalizer) {
+        final Set<Object> configuredExternals = Collections.newSetFromMap(new IdentityHashMap<>());
+
         // First, configure existing binders
-        Set<Binder> configuredBinders = configureBinders(injectionManager, Collections.emptySet());
+        final Set<Binder> configuredBinders = configureBinders(injectionManager, Collections.emptySet());
 
         // Check whether meta providers have been initialized for a config this config has been loaded from.
         if (!disableMetaProviderConfiguration) {
+            // Next, register external meta objects
+            configureExternalObjects(injectionManager, configuredExternals);
             // Configure all features
             configureFeatures(injectionManager, new HashSet<>(), resetRegistrations(), finalizer);
-            // Next, register external meta objects
-            configureExternalObjects(injectionManager);
+            // Next, register external meta objects registered by features
+            configureExternalObjects(injectionManager, configuredExternals);
             // At last, configure any new binders added by features
             configureBinders(injectionManager, configuredBinders);
         }
@@ -653,11 +658,17 @@
                 .collect(Collectors.toList());
     }
 
-    private void configureExternalObjects(InjectionManager injectionManager) {
+    private void configureExternalObjects(InjectionManager injectionManager, Set<Object> externalObjects) {
+        Consumer<Object> registerOnce = o -> {
+            if (!externalObjects.contains(o)) {
+                injectionManager.register(o);
+                externalObjects.add(o);
+            }
+        };
         componentBag.getInstances(model -> ComponentBag.EXTERNAL_ONLY.test(model, injectionManager))
-                .forEach(injectionManager::register);
+                .forEach(registerOnce);
         componentBag.getClasses(model -> ComponentBag.EXTERNAL_ONLY.test(model, injectionManager))
-                .forEach(injectionManager::register);
+                .forEach(registerOnce);
     }
 
     private void configureFeatures(InjectionManager injectionManager,
diff --git a/examples/helloworld-netty/src/main/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldResource.java b/examples/helloworld-netty/src/main/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldResource.java
index 8f2c4e1..b95dff9 100644
--- a/examples/helloworld-netty/src/main/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldResource.java
+++ b/examples/helloworld-netty/src/main/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldResource.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0, which is available at
@@ -10,9 +10,13 @@
 
 package org.glassfish.jersey.examples.helloworld.netty;
 
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
+import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 
 /**
  *
@@ -28,4 +32,21 @@
         return CLICHED_MESSAGE;
     }
 
+    @GET
+    @Path("query1")
+    @Produces("text/plain")
+    public String getQueryParameter(@DefaultValue("error1") @QueryParam(value = "test1") String test1,
+            @DefaultValue("error2") @QueryParam(value = "test2") String test2) {
+        return test1 + test2;
+    }
+
+    @POST
+    @Path("query2")
+    @Consumes("text/plain")
+    @Produces("text/plain")
+    public String postQueryParameter(@DefaultValue("error1") @QueryParam(value = "test1") String test1,
+            @DefaultValue("error2") @QueryParam(value = "test2") String test2, String entity) {
+        return entity + test1 + test2;
+    }
+
 }
diff --git a/examples/helloworld-netty/src/test/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldTest.java b/examples/helloworld-netty/src/test/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldTest.java
index cdb82b5..23e8956 100644
--- a/examples/helloworld-netty/src/test/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldTest.java
+++ b/examples/helloworld-netty/src/test/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0, which is available at
@@ -18,6 +18,7 @@
 
 import javax.ws.rs.client.Client;
 import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
 import javax.ws.rs.client.InvocationCallback;
 import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.core.MediaType;
@@ -224,4 +225,22 @@
         assertEquals(1, CustomLoggingFilter.preFilterCalled);
         assertEquals(1, CustomLoggingFilter.postFilterCalled);
     }
+
+    @Test
+    @RunSeparately
+    public void testQueryParameterGet() {
+        String result = target().path(App.ROOT_PATH + "/query1").queryParam("test1", "expected1")
+                .queryParam("test2", "expected2").request().get(String.class);
+        assertEquals("expected1expected2", result);
+    }
+
+    @Test
+    @RunSeparately
+    public void testQueryParameterPost() {
+        String result = target().path(App.ROOT_PATH + "/query2").queryParam("test1", "expected1")
+                .queryParam("test2", "expected2").request("text/plain").post(Entity.entity("entity", "text/plain"))
+                .readEntity(String.class);
+        assertEquals("entityexpected1expected2", result);
+    }
+
 }
diff --git a/media/moxy/pom.xml b/media/moxy/pom.xml
index 0b7a244..1b02cc5 100644
--- a/media/moxy/pom.xml
+++ b/media/moxy/pom.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
 
-    Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
+    Copyright (c) 2012, 2020 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
@@ -130,7 +130,7 @@
         <repository>
             <id>eclipselink.repository</id>
             <name>Eclipse Maven Repository</name>
-            <url>http://download.eclipse.org/rt/eclipselink/maven.repo</url>
+            <url>https://download.eclipse.org/rt/eclipselink/maven.repo</url>
             <layout>default</layout>
         </repository>
     </repositories>
diff --git a/pom.xml b/pom.xml
index 0744470..77c8cbe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1112,7 +1112,7 @@
                 <repository>
                     <id>eclipselink.repository</id>
                     <name>Eclipse Maven Repository</name>
-                    <url>http://www.eclipse.org/downloads/download.php?r=1&amp;nf=1&amp;file=/rt/eclipselink/maven.repo</url>
+                    <url>https://www.eclipse.org/downloads/download.php?r=1&amp;nf=1&amp;file=/rt/eclipselink/maven.repo</url>
                     <layout>default</layout>
                 </repository>
             </repositories>
@@ -2141,7 +2141,7 @@
         <mockito.version>1.10.19</mockito.version>
         <moxy.version>2.7.4</moxy.version>
         <mustache.version>0.8.17</mustache.version>
-        <netty.version>4.1.31.Final</netty.version>
+        <netty.version>4.1.43.Final</netty.version>
         <nexus-staging.mvn.plugin.version>1.6.7</nexus-staging.mvn.plugin.version>
         <opentracing.version>0.30.0</opentracing.version>
         <osgi.version>6.0.0</osgi.version>
diff --git a/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/HeaderUtilsTest.java b/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/HeaderUtilsTest.java
index 7932181..589a671 100644
--- a/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/HeaderUtilsTest.java
+++ b/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/HeaderUtilsTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2020 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
@@ -18,12 +18,15 @@
 
 import java.net.URI;
 import java.util.Arrays;
+import java.util.Calendar;
 import java.util.Collections;
+import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
 
 import javax.ws.rs.core.AbstractMultivaluedMap;
 import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.NewCookie;
 import javax.ws.rs.ext.RuntimeDelegate;
 
 import org.glassfish.jersey.message.internal.HeaderUtils;
@@ -180,4 +183,48 @@
         final String result = HeaderUtils.asHeaderString(values, null);
         assertEquals("value,[null]," + uri.toASCIIString(), result);
     }
+
+    @Test
+    public void testgetPreferredCookie(){
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(2000, Calendar.JANUARY, 1);
+        Date earlyDate = calendar.getTime();
+        calendar.set(2000, Calendar.JANUARY, 2);
+        Date laterDate = calendar.getTime();
+
+        NewCookie earlyCookie = new NewCookie("fred", "valuestring", "pathstring", "domainstring",
+                0, "commentstring", 0, earlyDate, false, false);
+        NewCookie laterCookie = new NewCookie("fred", "valuestring", "pathstring", "domainstring",
+                0, "commentstring", 0, laterDate, false, false);
+
+        assertEquals(laterCookie, HeaderUtils.getPreferredCookie(earlyCookie, laterCookie));
+
+        NewCookie one = new NewCookie("fred", "valuestring", "pathstring", "domainstring",
+                0, "commentstring", 100, null, false, false);
+        NewCookie second = new NewCookie("fred", "valuestring", "pathstring", "domainstring",
+                0, "commentstring", 10, null, false, false);
+
+        assertEquals(one, HeaderUtils.getPreferredCookie(one, second));
+
+        NewCookie longPathNewCookie = new NewCookie("fred", "valuestring", "longestpathstring",
+                "domainstring", 0, "commentstring", 0, null,
+                false, false);
+        NewCookie shortPathNewCookie = new NewCookie("fred", "valuestring", "shortestpath",
+                "domainstring", 0, "commentstring", 0, null,
+                false, false);
+
+        assertEquals(longPathNewCookie, HeaderUtils.getPreferredCookie(longPathNewCookie, shortPathNewCookie));
+
+        NewCookie identicalNewCookie = new NewCookie("fred", "valuestring", "pathstring",
+                "domainstring", 0, "commentstring", 0, null,
+                false, false);
+        NewCookie identicalNewCookie1 = new NewCookie("fred", "valuestring", "pathstring",
+                "domainstring", 0, "commentstring", 0, null,
+                false, false);
+
+        assertEquals(identicalNewCookie, HeaderUtils.getPreferredCookie(identicalNewCookie, identicalNewCookie1));
+
+    }
+
 }
diff --git a/tests/e2e-inject/hk2/src/test/java/org/glassfish/jersey/tests/e2e/inject/hk2/HK2AbstractBinderInFeaturesTest.java b/tests/e2e-inject/hk2/src/test/java/org/glassfish/jersey/tests/e2e/inject/hk2/HK2AbstractBinderInFeaturesTest.java
new file mode 100644
index 0000000..7a348e2
--- /dev/null
+++ b/tests/e2e-inject/hk2/src/test/java/org/glassfish/jersey/tests/e2e/inject/hk2/HK2AbstractBinderInFeaturesTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2020 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.tests.e2e.inject.hk2;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Test that HK2 binder allows for injection into a feature
+ */
+public class HK2AbstractBinderInFeaturesTest extends JerseyTest {
+
+    private static final AtomicInteger binderCounter = new AtomicInteger();
+    private static final AtomicInteger feature1Counter = new AtomicInteger();
+    private static final AtomicInteger feature2Counter = new AtomicInteger();
+    private static final String VALUE = "CONFIGURED_VALUE";
+
+    public static class InjectableHK2Binder extends org.glassfish.hk2.utilities.binding.AbstractBinder {
+        @Override
+        protected void configure() {
+            binderCounter.incrementAndGet();
+            bindAsContract(ConfigurableInjectable.class).to(Injectable.class).in(Singleton.class);
+        }
+    }
+
+    public static class JerseyInjectableHK2Binder extends AbstractBinder {
+        @Override
+        protected void configure() {
+            bindAsContract(ConfigurableInjectable.class).to(ExtendedInjectable.class).in(Singleton.class);
+        }
+    }
+
+    public static final class InjectableHK2BindingFeature implements Feature {
+        private final Injectable service;
+        private final ExtendedInjectable extendedService;
+
+        @Inject
+        public InjectableHK2BindingFeature(Injectable service, ExtendedInjectable extendedService) {
+            feature1Counter.incrementAndGet();
+            this.service = service;
+            this.extendedService = extendedService;
+        }
+
+        @Override
+        public boolean configure(FeatureContext context) {
+            if (service != null) {
+                ((ConfigurableInjectable) service).set(VALUE);
+            }
+            if (extendedService != null) {
+                feature2Counter.incrementAndGet();
+            }
+            return true;
+        }
+    }
+
+    public static class ConfigurableInjectable implements ExtendedInjectable {
+        private String value;
+        public void set(String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return value;
+        }
+    }
+
+    public static interface ExtendedInjectable extends Injectable {
+    };
+
+    @Override
+    protected Application configure() {
+        return new ResourceConfig(InjectableHK2BindingFeature.class, AbstractBinderTestResource.class,
+                InjectableTestFilter.class, InjectableHK2Binder.class).register(new JerseyInjectableHK2Binder());
+    }
+
+    @Test
+    public void testInjectableInjection() {
+        String response = target().request().get(String.class);
+        assertThat(response, is(VALUE));
+        assertThat(1, is(binderCounter.get()));
+        assertThat(1, is(feature1Counter.get()));
+        assertThat(1, is(feature2Counter.get()));
+    }
+}
diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/CookieImplTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/CookieImplTest.java
index 64e1435..db4d91c 100644
--- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/CookieImplTest.java
+++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/CookieImplTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2020 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
@@ -149,6 +149,32 @@
     }
 
     @Test
+    public void testMultipleCookiesWithSameName(){
+
+        String cookieHeader = "kobe=longeststring; kobe=shortstring";
+        Map<String, Cookie> cookies = HttpHeaderReader.readCookies(cookieHeader);
+        assertEquals(cookies.size(), 1);
+        Cookie c = cookies.get("kobe");
+        assertEquals(c.getVersion(), 0);
+        assertEquals("kobe", c.getName());
+        assertEquals("longeststring", c.getValue());
+
+        cookieHeader = "bryant=longeststring; bryant=shortstring; fred=shortstring ;fred=longeststring;$Path=/path";
+        cookies = HttpHeaderReader.readCookies(cookieHeader);
+        assertEquals(cookies.size(), 2);
+        c = cookies.get("bryant");
+        assertEquals(c.getVersion(), 0);
+        assertEquals("bryant", c.getName());
+        assertEquals("longeststring", c.getValue());
+        c = cookies.get("fred");
+        assertEquals(c.getVersion(), 0);
+        assertEquals("fred", c.getName());
+        assertEquals("longeststring", c.getValue());
+        assertEquals("/path", c.getPath());
+
+    }
+
+    @Test
     public void testNewCookieToString() {
         NewCookie cookie = new NewCookie("fred", "flintstone");
         String expResult = "fred=flintstone;Version=1";