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&nf=1&file=/rt/eclipselink/maven.repo</url>
+ <url>https://www.eclipse.org/downloads/download.php?r=1&nf=1&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";