Merge pull request #4589 from senivam/3.x_merged
merge of master (2.32) into 3.x
diff --git a/bom/pom.xml b/bom/pom.xml
index f95783f..007dead 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -65,6 +65,11 @@
</dependency>
<dependency>
<groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-helidon-connector</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
<artifactId>jersey-grizzly-connector</artifactId>
<version>${project.version}</version>
</dependency>
diff --git a/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java b/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java
index a2a8f9d..f45ae41 100644
--- a/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java
+++ b/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java
@@ -600,6 +600,10 @@
return null;
}
+ if (HttpEntity.class.isInstance(entity)) {
+ return wrapHttpEntity(clientRequest, (HttpEntity) entity);
+ }
+
final AbstractHttpEntity httpEntity = new AbstractHttpEntity() {
@Override
public boolean isRepeatable() {
@@ -639,6 +643,70 @@
}
};
+ return bufferEntity(httpEntity, bufferingEnabled);
+ }
+
+ private HttpEntity wrapHttpEntity(final ClientRequest clientRequest, final HttpEntity originalEntity) {
+ final boolean bufferingEnabled = BufferedHttpEntity.class.isInstance(originalEntity);
+
+ try {
+ clientRequest.setEntity(originalEntity.getContent());
+ } catch (IOException e) {
+ throw new ProcessingException(LocalizationMessages.ERROR_READING_HTTPENTITY_STREAM(e.getMessage()), e);
+ }
+
+ final AbstractHttpEntity httpEntity = new AbstractHttpEntity() {
+ @Override
+ public boolean isRepeatable() {
+ return originalEntity.isRepeatable();
+ }
+
+ @Override
+ public long getContentLength() {
+ return originalEntity.getContentLength();
+ }
+
+ @Override
+ public Header getContentType() {
+ return originalEntity.getContentType();
+ }
+
+ @Override
+ public Header getContentEncoding() {
+ return originalEntity.getContentEncoding();
+ }
+
+ @Override
+ public InputStream getContent() throws IOException, IllegalStateException {
+ return originalEntity.getContent();
+ }
+
+ @Override
+ public void writeTo(final OutputStream outputStream) throws IOException {
+ clientRequest.setStreamProvider(new OutboundMessageContext.StreamProvider() {
+ @Override
+ public OutputStream getOutputStream(final int contentLength) throws IOException {
+ return outputStream;
+ }
+ });
+ clientRequest.writeEntity();
+ }
+
+ @Override
+ public boolean isStreaming() {
+ return originalEntity.isStreaming();
+ }
+
+ @Override
+ public boolean isChunked() {
+ return originalEntity.isChunked();
+ }
+ };
+
+ return bufferEntity(httpEntity, bufferingEnabled);
+ }
+
+ private static HttpEntity bufferEntity(HttpEntity httpEntity, boolean bufferingEnabled) {
if (bufferingEnabled) {
try {
return new BufferedHttpEntity(httpEntity);
diff --git a/connectors/apache-connector/src/main/resources/org/glassfish/jersey/apache/connector/localization.properties b/connectors/apache-connector/src/main/resources/org/glassfish/jersey/apache/connector/localization.properties
index 9977d76..f0cc55b 100644
--- a/connectors/apache-connector/src/main/resources/org/glassfish/jersey/apache/connector/localization.properties
+++ b/connectors/apache-connector/src/main/resources/org/glassfish/jersey/apache/connector/localization.properties
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2013, 2018 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
@@ -15,6 +15,7 @@
#
error.buffering.entity=Error buffering the entity.
+error.reading.httpentity.stream=Error reading InputStream from HttpEntity: "{0}"
failed.to.stop.client=Failed to stop the client.
# {0} - property name, e.g. jersey.config.client.httpclient.connectionManager; {1}, {2} - full class name
ignoring.value.of.property=Ignoring value of property "{0}" ("{1}") - not instance of "{2}".
diff --git a/connectors/apache-connector/src/test/java/org/glassfish/jersey/apache/connector/HttpEntityTest.java b/connectors/apache-connector/src/test/java/org/glassfish/jersey/apache/connector/HttpEntityTest.java
new file mode 100644
index 0000000..5e33f6e
--- /dev/null
+++ b/connectors/apache-connector/src/test/java/org/glassfish/jersey/apache/connector/HttpEntityTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.apache.connector;
+
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.InputStreamEntity;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Assert;
+import org.junit.Test;
+
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import java.io.ByteArrayInputStream;
+import java.util.logging.Logger;
+
+public class HttpEntityTest extends JerseyTest {
+
+ private static final Logger LOGGER = Logger.getLogger(HttpEntityTest.class.getName());
+ private static final String ECHO_MESSAGE = "ECHO MESSAGE";
+
+ @Path("/")
+ public static class Resource {
+ @POST
+ public String echo(String message) {
+ return message;
+ }
+ }
+
+ @Override
+ protected Application configure() {
+ return new ResourceConfig(Resource.class)
+ .register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+ config.connectorProvider(new ApacheConnectorProvider());
+ }
+
+ @Test
+ public void testInputStreamEntity() {
+ ByteArrayInputStream bais = new ByteArrayInputStream(ECHO_MESSAGE.getBytes());
+ InputStreamEntity entity = new InputStreamEntity(bais);
+
+ try (Response response = target().request().post(Entity.entity(entity, MediaType.APPLICATION_OCTET_STREAM))) {
+ Assert.assertEquals(200, response.getStatus());
+ Assert.assertEquals(ECHO_MESSAGE, response.readEntity(String.class));
+ }
+ }
+
+ @Test
+ public void testByteArrayEntity() {
+ ByteArrayEntity entity = new ByteArrayEntity(ECHO_MESSAGE.getBytes());
+
+ try (Response response = target().request().post(Entity.entity(entity, MediaType.APPLICATION_OCTET_STREAM))) {
+ Assert.assertEquals(200, response.getStatus());
+ Assert.assertEquals(ECHO_MESSAGE, response.readEntity(String.class));
+ }
+ }
+}
diff --git a/connectors/helidon-connector/pom.xml b/connectors/helidon-connector/pom.xml
index 195ad6c..64aee57 100644
--- a/connectors/helidon-connector/pom.xml
+++ b/connectors/helidon-connector/pom.xml
@@ -32,10 +32,10 @@
<dependencies>
<dependency>
- <groupId>io.helidon.webclient</groupId>
- <artifactId>helidon-webclient</artifactId>
- <!-- Dear user, please use more stable version -->
- <version>2.0.0-M3</version>
+ <groupId>io.helidon.jersey</groupId>
+ <artifactId>helidon-jersey-connector</artifactId>
+ <!-- Use 2.0.3 when available -->
+ <version>2.0.2</version>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonProperties.java b/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonClientProperties.java
similarity index 85%
rename from connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonProperties.java
rename to connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonClientProperties.java
index 62d0dbd..7e7c10d 100644
--- a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonProperties.java
+++ b/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonClientProperties.java
@@ -21,13 +21,14 @@
import io.helidon.webclient.WebClient;
/**
- * Configuration options specific to the Client API that utilizes {@link HelidonConnector}
+ * Configuration options specific to the Client API that utilizes {@link HelidonConnectorProvider}
+ * @since 2.31
*/
@PropertiesClass
-public final class HelidonProperties {
+public final class HelidonClientProperties {
/**
* A Helidon {@link Config} instance that is passed to {@link WebClient.Builder#config(Config)} if available
*/
- public static final String CONFIG = "jersey.config.helidon.client.config";
+ public static final String CONFIG = io.helidon.jersey.connector.HelidonProperties.CONFIG;
}
diff --git a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonConnector.java b/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonConnector.java
deleted file mode 100644
index a06b3f7..0000000
--- a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonConnector.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * 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.helidon.connector;
-
-import io.helidon.common.Version;
-import io.helidon.webclient.WebClient;
-import io.helidon.webclient.WebClientRequestBuilder;
-import io.helidon.webclient.WebClientResponse;
-import org.glassfish.jersey.client.ClientAsyncExecutorLiteral;
-import org.glassfish.jersey.client.ClientConfig;
-import org.glassfish.jersey.client.ClientProperties;
-import org.glassfish.jersey.client.ClientRequest;
-import org.glassfish.jersey.client.ClientResponse;
-import org.glassfish.jersey.client.JerseyClient;
-import org.glassfish.jersey.client.spi.AsyncConnectorCallback;
-import org.glassfish.jersey.client.spi.Connector;
-import org.glassfish.jersey.internal.util.PropertiesHelper;
-import org.glassfish.jersey.spi.ExecutorServiceProvider;
-
-import jakarta.ws.rs.ProcessingException;
-import jakarta.ws.rs.client.Client;
-import jakarta.ws.rs.core.Configuration;
-import jakarta.ws.rs.core.Response;
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.AccessController;
-import java.time.temporal.ChronoUnit;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.BiConsumer;
-import java.util.logging.Logger;
-
-/**
- * A {@link Connector} that utilizes the Helidon HTTP Client to send and receive
- * HTTP request and responses.
- */
-class HelidonConnector implements Connector {
-
- private static final String helidonVersion = "Helidon/" + Version.VERSION + " (java " + AccessController
- .doPrivileged(PropertiesHelper.getSystemProperty("java.runtime.version")) + ")";
- static final Logger LOGGER = Logger.getLogger(HelidonConnector.class.getName());
-
- private final WebClient webClient;
-
- private final ExecutorServiceKeeper executorServiceKeeper;
- private final HelidonEntity.HelidonEntityType entityType;
-
- private static final InputStream NO_CONTENT_INPUT_STREAM = new InputStream() {
- @Override
- public int read() throws IOException {
- return -1;
- }
- };
- // internal implementation entity type, can be removed in the future
- // settable for testing purposes
- private static final String INTERNAL_ENTITY_TYPE = "jersey.config.helidon.client.entity.type";
-
- HelidonConnector(final Client client, final Configuration config) {
- executorServiceKeeper = new ExecutorServiceKeeper(client);
- entityType = getEntityType(config);
-
- final WebClient.Builder webClientBuilder = WebClient.builder();
-
- webClientBuilder.addReader(HelidonStructures.createInputStreamBodyReader());
- HelidonEntity.helidonWriter(entityType).ifPresent(webClientBuilder::addWriter);
-
- HelidonStructures.createProxy(config).ifPresent(webClientBuilder::proxy);
-
- HelidonStructures.helidonConfig(config).ifPresent(webClientBuilder::config);
-
- webClientBuilder.connectTimeout(ClientProperties.getValue(config.getProperties(),
- ClientProperties.CONNECT_TIMEOUT, 10000), ChronoUnit.MILLIS);
-
- HelidonStructures.createSSL(client.getSslContext()).ifPresent(webClientBuilder::ssl);
-
- webClient = webClientBuilder.build();
- }
-
- @Override
- public ClientResponse apply(ClientRequest request) {
- try {
- return applyInternal(request).toCompletableFuture().get();
- } catch (InterruptedException | ExecutionException e) {
- throw new ProcessingException(e);
- }
- }
-
- @Override
- public Future<?> apply(ClientRequest request, AsyncConnectorCallback callback) {
- final BiConsumer<? super ClientResponse, ? super Throwable> action = (r, th) -> {
- if (th == null) callback.response(r);
- else callback.failure(th);
- };
- return applyInternal(request)
- .whenCompleteAsync(action, executorServiceKeeper.getExecutorService(request))
- .toCompletableFuture();
- }
-
- @Override
- public String getName() {
- return helidonVersion;
- }
-
- @Override
- public void close() {
-
- }
-
- private CompletionStage<ClientResponse> applyInternal(ClientRequest request) {
- final WebClientRequestBuilder webClientRequestBuilder = webClient.method(request.getMethod());
- webClientRequestBuilder.uri(request.getUri());
-
- webClientRequestBuilder.headers(HelidonStructures.createHeaders(request.getRequestHeaders()));
-
- for (String propertyName : request.getConfiguration().getPropertyNames()) {
- Object property = request.getConfiguration().getProperty(propertyName);
- if (!propertyName.startsWith("jersey") && String.class.isInstance(property)) {
- webClientRequestBuilder.property(propertyName, (String) property);
- }
- }
-
- for (String propertyName : request.getPropertyNames()) {
- Object property = request.resolveProperty(propertyName, null);
- if (!propertyName.startsWith("jersey") && String.class.isInstance(property)) {
- webClientRequestBuilder.property(propertyName, (String) property);
- }
- }
-
- // 2.0.0-M3
- // HelidonStructures.createProxy(request).ifPresent(webClientRequestBuilder::proxy);
-
- webClientRequestBuilder.followRedirects(request.resolveProperty(ClientProperties.FOLLOW_REDIRECTS, true));
- webClientRequestBuilder.readTimeout(request.resolveProperty(ClientProperties.READ_TIMEOUT, 10000), ChronoUnit.MILLIS);
-
- CompletionStage<WebClientResponse> responseStage = null;
-
- if (request.hasEntity()) {
- responseStage = HelidonEntity.submit(
- entityType, request, webClientRequestBuilder, executorServiceKeeper.getExecutorService(request)
- );
- } else {
- responseStage = webClientRequestBuilder.submit();
- }
-
- return responseStage.thenCompose((a) -> convertResponse(request, a));
- }
-
- private CompletionStage<ClientResponse> convertResponse(final ClientRequest requestContext,
- final WebClientResponse webClientResponse) {
-
- final ClientResponse responseContext = new ClientResponse(new Response.StatusType() {
- @Override
- public int getStatusCode() {
- return webClientResponse.status().code();
- }
-
- @Override
- public Response.Status.Family getFamily() {
- return Response.Status.Family.familyOf(getStatusCode());
- }
-
- @Override
- public String getReasonPhrase() {
- return webClientResponse.status().reasonPhrase();
- }
- }, requestContext);
-
- for (Map.Entry<String, List<String>> entry : webClientResponse.headers().toMap().entrySet()) {
- for (String value : entry.getValue()) {
- responseContext.getHeaders().add(entry.getKey(), value);
- }
- }
-
- responseContext.setResolvedRequestUri(webClientResponse.lastEndpointURI());
-
- final CompletionStage<InputStream> stream = HelidonStructures.hasEntity(webClientResponse)
- ? webClientResponse.content().as(InputStream.class)
- : CompletableFuture.supplyAsync(() -> NO_CONTENT_INPUT_STREAM);
-
- return stream.thenApply((a) -> {
- responseContext.setEntityStream(new FilterInputStream(a) {
- private final AtomicBoolean closed = new AtomicBoolean(false);
-
- @Override
- public void close() throws IOException {
- // Avoid idempotent close in the underlying input stream
- if (!closed.compareAndSet(false, true)) {
- super.close();
- }
- }
- });
- return responseContext;
- });
- }
-
- private static HelidonEntity.HelidonEntityType getEntityType(final Configuration config) {
- final String helidonType = ClientProperties.getValue(config.getProperties(),
- INTERNAL_ENTITY_TYPE, HelidonEntity.HelidonEntityType.READABLE_BYTE_CHANNEL.name());
- final HelidonEntity.HelidonEntityType entityType = HelidonEntity.HelidonEntityType.valueOf(helidonType);
-
-// if (entityType != HelidonEntity.HelidonEntityType.READABLE_BYTE_CHANNEL) {
-// // log warning for internal feature - no localization.properties
-// LOGGER.warning(INTERNAL_ENTITY_TYPE + " is " + entityType.name());
-// }
-
- return entityType;
- }
-
- private static class ExecutorServiceKeeper {
- private Optional<ExecutorService> executorService;
-
- private ExecutorServiceKeeper(Client client) {
- final ClientConfig config = ((JerseyClient) client).getConfiguration();
- executorService = Optional.ofNullable(config.getExecutorService());
- }
-
- private ExecutorService getExecutorService(ClientRequest request) {
- if (!executorService.isPresent()) {
- // cache for multiple requests
- executorService = Optional.ofNullable(request.getInjectionManager()
- .getInstance(ExecutorServiceProvider.class, ClientAsyncExecutorLiteral.INSTANCE).getExecutorService());
- }
-
- return executorService.get();
- }
- }
-}
diff --git a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java b/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java
index 06dfe07..1da44f9 100644
--- a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java
+++ b/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java
@@ -18,7 +18,6 @@
import org.glassfish.jersey.Beta;
import org.glassfish.jersey.client.spi.Connector;
-import org.glassfish.jersey.client.spi.ConnectorProvider;
import org.glassfish.jersey.internal.util.JdkVersion;
import jakarta.ws.rs.ProcessingException;
@@ -38,7 +37,7 @@
* <li>{@link org.glassfish.jersey.client.ClientProperties#PROXY_USERNAME}</li>
* <li>{@link org.glassfish.jersey.client.ClientProperties#PROXY_PASSWORD}</li>
* <li>{@link org.glassfish.jersey.client.ClientProperties#READ_TIMEOUT}</li>
- * <li>{@link HelidonProperties#CONFIG}</li>
+ * <li>{@link HelidonClientProperties#CONFIG}</li>
* </ul>
* <p>
* If a {@link org.glassfish.jersey.client.ClientResponse} is obtained and an
@@ -66,12 +65,12 @@
* @since 2.31
*/
@Beta
-public class HelidonConnectorProvider implements ConnectorProvider {
+public class HelidonConnectorProvider extends io.helidon.jersey.connector.HelidonConnectorProvider {
@Override
public Connector getConnector(Client client, Configuration runtimeConfig) {
if (JdkVersion.getJdkVersion().getMajor() < 11) {
throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED());
}
- return new HelidonConnector(client, runtimeConfig);
+ return super.getConnector(client, runtimeConfig);
}
}
diff --git a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonEntity.java b/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonEntity.java
deleted file mode 100644
index f8ada9b..0000000
--- a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonEntity.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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.helidon.connector;
-
-import io.helidon.common.GenericType;
-import io.helidon.common.http.DataChunk;
-import io.helidon.common.http.MediaType;
-import io.helidon.common.reactive.Multi;
-import io.helidon.common.reactive.OutputStreamPublisher;
-import io.helidon.common.reactive.Single;
-import io.helidon.media.common.ByteChannelBodyWriter;
-import io.helidon.media.common.ContentWriters;
-import io.helidon.media.common.MessageBodyContext;
-import io.helidon.media.common.MessageBodyWriter;
-import io.helidon.media.common.MessageBodyWriterContext;
-import io.helidon.webclient.WebClientRequestBuilder;
-import io.helidon.webclient.WebClientResponse;
-import org.glassfish.jersey.client.ClientProperties;
-import org.glassfish.jersey.client.ClientRequest;
-
-import jakarta.ws.rs.ProcessingException;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.Optional;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Flow;
-import java.util.function.Function;
-
-/**
- * A utility class that converts outbound client entity to a class understandable by Helidon.
- * Based on the {@link HelidonEntityType} an entity writer is provided to be registered by Helidon client
- * and an Entity is provided to be submitted by the Helidon Client.
- */
-class HelidonEntity {
- /**
- * HelidonEnity type chosen by HelidonEntityType
- */
- enum HelidonEntityType {
- /**
- * Simplest structure. Loads all data to the memory.
- */
- BYTE_ARRAY_OUTPUT_STREAM,
- /**
- * Readable ByteChannel that is capable of sending data in chunks.
- * Capable of caching of bytes before the data are consumed by Helidon.
- */
- READABLE_BYTE_CHANNEL,
- /**
- * Helidon most native entity. Could be slower than {@link #READABLE_BYTE_CHANNEL}.
- */
- OUTPUT_STREAM_PUBLISHER
- }
-
- /**
- * Get optional entity writer to be registered by the Helidon Client. For some default providers,
- * nothing is needed to be registered.
- * @param type the type of the entity class that works best for the Http Client request use case.
- * @return possible writer to be registerd by the Helidon Client.
- */
- static Optional<MessageBodyWriter<?>> helidonWriter(HelidonEntityType type) {
- switch (type) {
- case BYTE_ARRAY_OUTPUT_STREAM:
- return Optional.of(new OutputStreamBodyWriter());
- case OUTPUT_STREAM_PUBLISHER:
- //Helidon default
- return Optional.empty();
- case READABLE_BYTE_CHANNEL:
- return Optional.of(ByteChannelBodyWriter.create());
- }
- return Optional.empty();
- }
-
- /**
- * Convert Jersey {@code OutputStream} to an entity based on the client request use case and submits to the provided
- * {@code WebClientRequestBuilder}.
- * @param type the type of the Helidon entity.
- * @param requestContext Jersey {@link ClientRequest} providing the entity {@code OutputStream}.
- * @param requestBuilder Helidon {@code WebClientRequestBuilder} which is used to submit the entity
- * @param executorService {@link ExecutorService} that fills the entity instance for Helidon with data from Jersey
- * {@code OutputStream}.
- * @return Helidon Client response completion stage.
- */
- static CompletionStage<WebClientResponse> submit(HelidonEntityType type,
- ClientRequest requestContext,
- WebClientRequestBuilder requestBuilder,
- ExecutorService executorService) {
- CompletionStage<WebClientResponse> stage = null;
- if (type != null) {
- final int bufferSize = requestContext.resolveProperty(
- ClientProperties.OUTBOUND_CONTENT_LENGTH_BUFFER, 8192);
- switch (type) {
- case BYTE_ARRAY_OUTPUT_STREAM:
- final ByteArrayOutputStream baos = new ByteArrayOutputStream(bufferSize);
- requestContext.setStreamProvider(contentLength -> baos);
- ((ProcessingRunnable) () -> requestContext.writeEntity()).run();
- stage = requestBuilder.submit(baos);
- break;
- case READABLE_BYTE_CHANNEL:
- final OutputStreamChannel channel = new OutputStreamChannel(bufferSize);
- requestContext.setStreamProvider(contentLength -> channel);
- executorService.execute((ProcessingRunnable) () -> requestContext.writeEntity());
- stage = requestBuilder.submit(channel);
- break;
- case OUTPUT_STREAM_PUBLISHER:
- final OutputStreamPublisher publisher = new OutputStreamPublisher();
- requestContext.setStreamProvider(contentLength -> publisher);
- executorService.execute((ProcessingRunnable) () -> {
- requestContext.writeEntity();
- publisher.close();
- });
- stage = requestBuilder.submit(Multi.from(publisher).map(DataChunk::create));
- break;
- }
- }
- return stage;
- }
-
- @FunctionalInterface
- private interface ProcessingRunnable extends Runnable {
- void runOrThrow() throws IOException;
-
- @Override
- default void run() {
- try {
- runOrThrow();
- } catch (IOException e) {
- throw new ProcessingException(LocalizationMessages.ERROR_WRITING_ENTITY(e.getMessage()), e);
- }
- }
- }
-
- @SuppressWarnings("unchecked")
- private static class OutputStreamBodyWriter implements MessageBodyWriter {
- private OutputStreamBodyWriter() {
- }
-
- @Override
- public Flow.Publisher<DataChunk> write(Single content, GenericType type, MessageBodyWriterContext context) {
- context.contentType(MediaType.APPLICATION_OCTET_STREAM);
- return content.flatMap(new ByteArrayOutputStreamToChunks());
- }
-
- @Override
- public boolean accept(GenericType type, MessageBodyContext context) {
- return ByteArrayOutputStream.class.isAssignableFrom(type.rawType());
- }
-
- private static class ByteArrayOutputStreamToChunks implements Function<ByteArrayOutputStream, Flow.Publisher<DataChunk>> {
- @Override
- public Flow.Publisher<DataChunk> apply(ByteArrayOutputStream byteArrayOutputStream) {
- return ContentWriters.writeBytes(byteArrayOutputStream.toByteArray(), false);
- }
- }
- }
-}
diff --git a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonStructures.java b/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonStructures.java
deleted file mode 100644
index 3b7a9c4..0000000
--- a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonStructures.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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.helidon.connector;
-
-import io.helidon.common.http.Headers;
-import io.helidon.common.http.Http;
-import io.helidon.common.http.ReadOnlyParameters;
-import io.helidon.config.Config;
-import io.helidon.config.ConfigSources;
-import io.helidon.media.common.InputStreamBodyReader;
-import io.helidon.media.common.MessageBodyReader;
-import io.helidon.webclient.Proxy;
-import io.helidon.webclient.Ssl;
-import io.helidon.webclient.WebClientResponse;
-import io.netty.handler.codec.http.HttpHeaderValues;
-import org.glassfish.jersey.client.ClientProperties;
-import org.glassfish.jersey.client.ClientRequest;
-
-import javax.net.ssl.SSLContext;
-import jakarta.ws.rs.ProcessingException;
-import jakarta.ws.rs.core.Configuration;
-import java.io.InputStream;
-import java.net.URI;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Optional;
-
-/**
- * Helidon specific classes and implementations.
- */
-class HelidonStructures {
-
- static Headers createHeaders(Map<String, List<String>> data) {
- return new ReadOnlyHeaders(data);
- }
-
- static MessageBodyReader<InputStream> createInputStreamBodyReader() {
- return InputStreamBodyReader.create();
- }
-
- static Optional<Config> helidonConfig(Configuration configuration) {
- final Object helidonConfig = configuration.getProperty(HelidonProperties.CONFIG);
- if (helidonConfig != null) {
- if (!Config.class.isInstance(helidonConfig)) {
- HelidonConnector.LOGGER.warning(LocalizationMessages.NOT_HELIDON_CONFIG(helidonConfig.getClass().getName()));
- return Optional.empty();
- } else {
- return Optional.of((Config) helidonConfig);
- }
- }
- return Optional.empty();
- }
-
- static Optional<Proxy> createProxy(Configuration config) {
- return ProxyBuilder.createProxy(config);
- }
-
- static Optional<Proxy> createProxy(ClientRequest request) {
- return ProxyBuilder.createProxy(request);
- }
-
- static Optional<Ssl> createSSL(SSLContext context) {
- return context == null ? Optional.empty() : Optional.of(Ssl.builder().sslContext(context).build());
- }
-
- static boolean hasEntity(WebClientResponse webClientResponse) {
- final ReadOnlyParameters headers = webClientResponse.content().readerContext().headers();
- final Optional<String> contentLenth = headers.first(Http.Header.CONTENT_LENGTH);
- final Optional<String> encoding = headers.first(Http.Header.TRANSFER_ENCODING);
-
- return ((contentLenth.isPresent() && !contentLenth.get().equals("0"))
- || (encoding.isPresent() && encoding.get().equals(HttpHeaderValues.CHUNKED.toString())));
- }
-
- private static class ReadOnlyHeaders extends ReadOnlyParameters implements Headers {
- public ReadOnlyHeaders(Map<String, List<String>> data) {
- super(data);
- }
- }
-
- private static class ProxyBuilder {
- private static Optional<Proxy> createProxy(Configuration config) {
- final Object proxyUri = config.getProperty(ClientProperties.PROXY_URI);
- final String userName
- = ClientProperties.getValue(config.getProperties(), ClientProperties.PROXY_USERNAME, String.class);
- final String password
- = ClientProperties.getValue(config.getProperties(), ClientProperties.PROXY_PASSWORD, String.class);
- return createProxy(proxyUri, userName, password);
- }
-
- private static Optional<Proxy> createProxy(ClientRequest clientRequest) {
- final Object proxyUri = clientRequest.resolveProperty(ClientProperties.PROXY_URI, Object.class);
- final String userName = clientRequest.resolveProperty(ClientProperties.PROXY_USERNAME, String.class);
- final String password = clientRequest.resolveProperty(ClientProperties.PROXY_PASSWORD, String.class);
- return createProxy(proxyUri, userName, password);
- }
-
- private static Optional<Proxy> createProxy(Object proxyUri, String userName, String password) {
- if (proxyUri != null) {
- final URI u = getProxyUri(proxyUri);
- final Proxy.Builder builder = Proxy.builder();
- Map<String, String> proxyMap;
- if (u.getScheme().toUpperCase(Locale.ROOT).equals("DIRECT")) {
- proxyMap = Map.of("type", "NONE");
- //builder.type(Proxy.ProxyType.NONE);
- } else {
- builder.host(u.getHost()).port(u.getPort());
- switch (u.getScheme().toUpperCase(Locale.ROOT)) {
- case "HTTP":
- proxyMap = Map.of("type", "HTTP");
- //builder.type(Proxy.ProxyType.HTTP);
- break;
- case "SOCKS":
- proxyMap = Map.of("type", "SOCKS_4");
- //builder.type(Proxy.ProxyType.SOCKS_4);
- break;
- case "SOCKS5":
- proxyMap = Map.of("type", "SOCKS_5");
- //builder.type(Proxy.ProxyType.SOCKS_5);
- break;
- default:
- HelidonConnector.LOGGER.warning(LocalizationMessages.UNSUPPORTED_PROXY_SCHEMA(u.getScheme()));
- return Optional.empty();
- }
- builder.config(Config.create(ConfigSources.create(proxyMap)));
- }
- if (userName != null) {
- builder.username(userName);
-
- if (password != null) {
- builder.password(password.toCharArray());
- }
- }
- return Optional.of(builder.build());
- } else {
- return Optional.empty();
- }
- }
-
- private static URI getProxyUri(final Object proxy) {
- if (proxy instanceof URI) {
- return (URI) proxy;
- } else if (proxy instanceof String) {
- return URI.create((String) proxy);
- } else {
- throw new ProcessingException(LocalizationMessages.WRONG_PROXY_URI_TYPE(ClientProperties.PROXY_URI));
- }
- }
- }
-}
diff --git a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/OutputStreamChannel.java b/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/OutputStreamChannel.java
deleted file mode 100644
index 152ac7f..0000000
--- a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/OutputStreamChannel.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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.helidon.connector;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.ClosedByInterruptException;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.ReadableByteChannel;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
-
-class OutputStreamChannel extends OutputStream implements ReadableByteChannel {
-
- private ReentrantLock lock = new ReentrantLock();
- private static final ByteBuffer VOID = ByteBuffer.allocate(0);
- private static final int CAPACITY = Integer.getInteger("jersey.helidon.connector.osc.capacity", 8);
- private static final int WRITE_TIMEOUT = Integer.getInteger("jersey.helidon.connector.osc.read.timeout", 10000);
- private static final int READ_TIMEOUT = Integer.getInteger("jersey.helidon.connector.osc.write.timeout", 10000);
- private final int bufferSize;
-
- OutputStreamChannel(int bufferSize) {
- this.bufferSize = bufferSize;
- }
-
- private final LinkedBlockingDeque<ByteBuffer> queue = new LinkedBlockingDeque<>(CAPACITY);
-
- private volatile boolean open = true;
- private ByteBuffer remainingByteBuffer;
-
- @Override
- public int read(ByteBuffer dst) throws IOException {
- if (!open) {
- throw new ClosedChannelException();
- }
-
- int sum = 0;
-
- do {
- ByteBuffer top;
- try {
- top = poll(READ_TIMEOUT, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- open = false;
- throw new ClosedByInterruptException();
- }
-
- if (top == null) {
- return sum;
- }
-
- if (top == VOID) {
- if (sum == 0) {
- open = false;
- return -1;
- } else {
- queue.addFirst(top);
- return sum;
- }
- }
-
- final int topSize = top.remaining();
- final int dstAvailable = dst.remaining();
- final int minSize = Math.min(topSize, dstAvailable);
-
- if (top.hasArray()) {
- dst.put(top.array(), top.arrayOffset() + top.position(), minSize);
- top.position(top.position() + minSize);
- } else {
- while (dst.hasRemaining() && top.hasRemaining()) {
- dst.put(top.get());
- }
- }
-
- sum += minSize;
-
- if (top.hasRemaining()) {
- remainingByteBuffer = top;
- }
- } while (dst.hasRemaining());
-
- return sum;
- }
-
- private ByteBuffer poll(long timeout, TimeUnit unit) throws InterruptedException {
- if (remainingByteBuffer != null) {
- final ByteBuffer remaining = remainingByteBuffer;
- remainingByteBuffer = null;
- return remaining;
- } else {
- // do not modify head
- lock.lock();
- final ByteBuffer peek = queue.poll(timeout, unit);
- // can modify head
- lock.unlock();
- return peek;
- }
- }
-
- @Override
- public void write(int b) throws IOException {
- write(new byte[]{(byte) b}, 0, 1);
- }
-
- @Override
- public void write(byte[] b) throws IOException {
- super.write(b, 0, b.length);
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- checkClosed();
-
- if (lock.tryLock()) {
- if (len < bufferSize && queue.size() > 0) {
- final ByteBuffer buffer = queue.getLast();
- if (buffer != null && (buffer.capacity() - buffer.limit()) > len) {
- //set for write
- buffer.position(buffer.limit());
- buffer.limit(buffer.capacity());
- buffer.put(b, off, len);
- //set for read
- buffer.flip();
- lock.unlock();
- return;
- }
- }
- lock.unlock();
- }
-
- final int maxLen = Math.max(len, bufferSize);
- final byte[] bytes = new byte[maxLen];
- System.arraycopy(b, off, bytes, 0, len);
-
- final ByteBuffer buffer = ByteBuffer.wrap(bytes);
- buffer.limit(len);
- buffer.position(0);
-
- write(buffer);
- }
-
- private void write(ByteBuffer buffer) throws IOException {
- try {
- boolean queued = queue.offer(buffer, WRITE_TIMEOUT, TimeUnit.MILLISECONDS);
- if (!queued) {
- throw new IOException("Buffer overflow.");
- }
-
- } catch (InterruptedException e) {
- throw new IOException(e);
- }
- }
-
- @Override
- public void close() throws IOException {
- boolean offer = false;
-
- try {
- offer = queue.offer(VOID, WRITE_TIMEOUT, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- // ignore.
- }
-
- if (!offer) {
- lock.lock();
- queue.removeLast();
- queue.add(VOID);
- lock.unlock();
- }
- }
-
-
- @Override
- public boolean isOpen() {
- return open;
- }
-
- private void checkClosed() throws IOException {
- if (!open) {
- throw new IOException("Stream already closed.");
- }
- }
-}
diff --git a/connectors/helidon-connector/src/main/resources/org/glassfish/jersey/helidon/connector/localization.properties b/connectors/helidon-connector/src/main/resources/org/glassfish/jersey/helidon/connector/localization.properties
index a451b76..7b06288 100644
--- a/connectors/helidon-connector/src/main/resources/org/glassfish/jersey/helidon/connector/localization.properties
+++ b/connectors/helidon-connector/src/main/resources/org/glassfish/jersey/helidon/connector/localization.properties
@@ -14,8 +14,4 @@
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
#
-error.writing.entity=Error writing entity: {0}.
-not.helidon.config=Given instance {0} is not Helidon config. Provided HelidonProperties.CONFIG is ignored.
-not.supported=Helidon connector is not supported on JDK version < 11.
-unsupported.proxy.schema=Proxy schema "{0}" not supported.
-wrong.proxy.uri.type=The proxy URI ("{0}") property MUST be an instance of String or URI.
\ No newline at end of file
+not.supported=Helidon connector is not supported on JDK version less than 11.
\ No newline at end of file
diff --git a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/sse/SseTest.java b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/sse/SseTest.java
index 74cf433..6608c10 100644
--- a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/sse/SseTest.java
+++ b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/sse/SseTest.java
@@ -112,8 +112,10 @@
final StringBuilder sb = new StringBuilder();
final CountDownLatch latch = new CountDownLatch(10);
try (SseEventSource source = SseEventSource.target(target().path("simple")).build()) {
- source.register((event) -> sb.append(event.readData()));
- source.register((event) -> latch.countDown());
+ source.register((event) -> {
+ sb.append(event.readData());
+ latch.countDown();
+ });
source.open();
latch.await(WAIT_TIME, TimeUnit.MILLISECONDS);
@@ -160,9 +162,11 @@
private void register() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
- source.register((event) -> message.append(event.readData()));
- source.register((event) -> latch.countDown());
- source.register((event) -> messageLatch.countDown());
+ source.register((event) -> {
+ message.append(event.readData());
+ latch.countDown();
+ messageLatch.countDown();
+ });
source.open();
latch.await(WAIT_TIME, TimeUnit.MILLISECONDS);
diff --git a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/SslFilter.java b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/SslFilter.java
index 15bd43a..85f4541 100644
--- a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/SslFilter.java
+++ b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/SslFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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
@@ -50,6 +50,7 @@
/* Some operations on SSL engine require a buffer as a parameter even if they don't need any data.
This buffer is for that purpose. */
private static final ByteBuffer emptyBuffer = ByteBuffer.allocate(0);
+ private static final String TLSV13 = "TLSv1.3";
// buffer for passing data to the upper filter
private final ByteBuffer applicationInputBuffer;
@@ -61,6 +62,7 @@
private final WriteQueue writeQueue = new WriteQueue();
private volatile State state = State.NOT_STARTED;
+ private volatile boolean tlsv13 = false;
/*
* Pending write operation stored when writing data was not possible. It will be resumed when write operation is
* available again. Only one write operation can be in progress at a time. Trying to store more than one pending
@@ -169,14 +171,14 @@
}
case CLOSED: {
- state = State.CLOSED;
+ setState(State.CLOSED);
break;
}
case OK: {
// check if we started re-handshaking
- if (result.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
- state = State.REHANDSHAKING;
+ if (isHandshaking(result.getHandshakeStatus())) {
+ setState(State.REHANDSHAKING);
}
((Buffer) networkOutputBuffer).flip();
@@ -367,10 +369,10 @@
}
// we started re-handshaking
- if (result.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING
+ if (!tlsv13 && isHandshaking(result.getHandshakeStatus())
// make sure we don't confuse re-handshake with closing handshake
&& !sslEngine.isOutboundDone()) {
- state = State.REHANDSHAKING;
+ setState(State.REHANDSHAKING);
return doHandshakeStep(networkData);
}
@@ -392,7 +394,8 @@
boolean handshakeFinished = false;
synchronized (this) {
- if (SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING.equals(sslEngine.getHandshakeStatus())) {
+ SSLEngineResult.HandshakeStatus hs = sslEngine.getHandshakeStatus();
+ if (!isHandshaking(hs)) {
// we stopped handshaking while waiting for the lock
return true;
}
@@ -403,18 +406,16 @@
LazyBuffer outputBuffer = new LazyBuffer();
boolean stepFinished = false;
while (!stepFinished) {
- SSLEngineResult.HandshakeStatus hs = sslEngine.getHandshakeStatus();
+ hs = sslEngine.getHandshakeStatus();
switch (hs) {
case NOT_HANDSHAKING: {
/* This should never happen. If we are here and not handshaking, it means a bug
in the state machine of this class, because we stopped handshaking and did not exit this while loop.
The could be caused either by overlooking FINISHED state or incorrectly treating an error. */
-
- throw new IllegalStateException("Trying to handshake, but SSL engine not in HANDSHAKING state."
- + "SSL filter state: \n" + getDebugState());
+ throw new IllegalStateException(
+ LocalizationMessages.HTTP_CONNECTION_INVALID_HANDSHAKE_STATUS(getDebugState()));
}
-
case FINISHED: {
/* According to SSLEngine javadoc FINISHED status can be returned only in SSLEngineResult,
but just to make sure we don't end up in an infinite loop when presented with an SSLEngine
@@ -449,7 +450,7 @@
case CLOSED: {
stepFinished = true;
- state = State.CLOSED;
+ setState(State.CLOSED);
break;
}
}
@@ -490,7 +491,7 @@
case CLOSED: {
stepFinished = true;
- state = State.CLOSED;
+ setState(State.CLOSED);
break;
}
}
@@ -532,6 +533,7 @@
if (handshakeFinished) {
handleHandshakeFinished();
+ tlsv13 = TLSV13.equals(sslEngine.getSession().getProtocol());
// indicate that there still might be usable data in the input buffer
return true;
}
@@ -550,10 +552,10 @@
}
if (state == State.HANDSHAKING) {
- state = State.DATA;
+ setState(State.DATA);
upstreamFilter.onSslHandshakeCompleted();
} else if (state == State.REHANDSHAKING) {
- state = State.DATA;
+ setState(State.DATA);
if (pendingApplicationWrite != null) {
Runnable write = pendingApplicationWrite;
// set pending write to null to cover the extremely improbable case that we start re-handshaking again
@@ -571,7 +573,7 @@
@Override
void startSsl() {
try {
- state = State.HANDSHAKING;
+ setState(State.HANDSHAKING);
sslEngine.beginHandshake();
doHandshakeStep(emptyBuffer);
} catch (SSLException e) {
@@ -707,4 +709,14 @@
}
}
}
+
+ private void setState(State state) {
+ this.state = state;
+ }
+
+ private boolean isHandshaking(SSLEngineResult.HandshakeStatus hs) {
+ return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING != hs
+ // TLSv1.3 introduces this, and it is considered as not handshaking
+ && SSLEngineResult.HandshakeStatus.FINISHED != hs;
+ }
}
diff --git a/connectors/jdk-connector/src/main/resources/org/glassfish/jersey/jdk/connector/internal/localization.properties b/connectors/jdk-connector/src/main/resources/org/glassfish/jersey/jdk/connector/internal/localization.properties
index e8f0342..0d78e71 100644
--- a/connectors/jdk-connector/src/main/resources/org/glassfish/jersey/jdk/connector/internal/localization.properties
+++ b/connectors/jdk-connector/src/main/resources/org/glassfish/jersey/jdk/connector/internal/localization.properties
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2017, 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
@@ -71,3 +71,4 @@
http.connection.establishing.illegal.state="Cannot try to establish connection if the connection is in other than CREATED state\
. Current state: {0}.
http.connection.not.idle="Http request cannot be sent over a connection that is in other state than IDLE. Current state: {0}"
+http.connection.invalid.handshake.status="Trying to handshake, but SSL engine not in HANDSHAKING state. SSL filter state: {0}"
diff --git a/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS11Test.java b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS11Test.java
new file mode 100644
index 0000000..3d1ef6c
--- /dev/null
+++ b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS11Test.java
@@ -0,0 +1,26 @@
+/*
+ * 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.jdk.connector.internal;
+
+public class SslFilterTLS11Test extends SslFilterTest {
+
+ public SslFilterTLS11Test() {
+ System.setProperty("jdk.tls.server.protocols", "TLSv1.1");
+ System.setProperty("jdk.tls.client.protocols", "TLSv1.1");
+ }
+
+}
diff --git a/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS12Test.java b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS12Test.java
new file mode 100644
index 0000000..dcb8260
--- /dev/null
+++ b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS12Test.java
@@ -0,0 +1,26 @@
+/*
+ * 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.jdk.connector.internal;
+
+public class SslFilterTLS12Test extends SslFilterTest {
+
+ public SslFilterTLS12Test() {
+ System.setProperty("jdk.tls.server.protocols", "TLSv1.2");
+ System.setProperty("jdk.tls.client.protocols", "TLSv1.2");
+ }
+
+}
diff --git a/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS13Test.java b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS13Test.java
new file mode 100644
index 0000000..757b834
--- /dev/null
+++ b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS13Test.java
@@ -0,0 +1,26 @@
+/*
+ * 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.jdk.connector.internal;
+
+public class SslFilterTLS13Test extends SslFilterTest {
+
+ public SslFilterTLS13Test() {
+ System.setProperty("jdk.tls.server.protocols", "TLSv1.3");
+ System.setProperty("jdk.tls.client.protocols", "TLSv1.3");
+ }
+
+}
diff --git a/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS1Test.java b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS1Test.java
new file mode 100644
index 0000000..a3a9237
--- /dev/null
+++ b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS1Test.java
@@ -0,0 +1,26 @@
+/*
+ * 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.jdk.connector.internal;
+
+public class SslFilterTLS1Test extends SslFilterTest {
+
+ public SslFilterTLS1Test() {
+ System.setProperty("jdk.tls.server.protocols", "TLSv1");
+ System.setProperty("jdk.tls.client.protocols", "TLSv1");
+ }
+
+}
diff --git a/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTest.java b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTest.java
index c1e7fbe..0a9dc13 100644
--- a/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTest.java
+++ b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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
@@ -39,9 +39,8 @@
import javax.net.ssl.SSLSocket;
import org.glassfish.jersey.SslConfigurator;
-
-import org.junit.Before;
import org.junit.Test;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -49,15 +48,14 @@
/**
* @author Petr Janouch
*/
-public class SslFilterTest {
+public abstract class SslFilterTest {
private static final int PORT = 8321;
- @Before
- public void beforeTest() {
- System.setProperty("javax.net.ssl.keyStore", this.getClass().getResource("/keystore_server").getPath());
+ static {
+ System.setProperty("javax.net.ssl.keyStore", SslFilterTest.class.getResource("/keystore_server").getPath());
System.setProperty("javax.net.ssl.keyStorePassword", "asdfgh");
- System.setProperty("javax.net.ssl.trustStore", this.getClass().getResource("/truststore_server").getPath());
+ System.setProperty("javax.net.ssl.trustStore", SslFilterTest.class.getResource("/truststore_server").getPath());
System.setProperty("javax.net.ssl.trustStorePassword", "asdfgh");
}
diff --git a/connectors/jdk-connector/src/test/resources/client.cert b/connectors/jdk-connector/src/test/resources/client.cert
deleted file mode 100644
index d43d88b..0000000
--- a/connectors/jdk-connector/src/test/resources/client.cert
+++ /dev/null
@@ -1,17 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDJTCCAuOgAwIBAgIET5ZyYjALBgcqhkjOOAQDBQAwdjELMAkGA1UEBhMCQ1oxFzAVBgNVBAgT
-DkN6ZWNoIFJlcHVibGljMQ8wDQYDVQQHEwZQcmFndWUxGzAZBgNVBAoTEk9yYWNsZSBDb3Jwb3Jh
-dGlvbjEPMA0GA1UECxMGSmVyc2V5MQ8wDQYDVQQDEwZDbGllbnQwHhcNMTIwNDI0MDkyOTA2WhcN
-MTIwNzIzMDkyOTA2WjB2MQswCQYDVQQGEwJDWjEXMBUGA1UECBMOQ3plY2ggUmVwdWJsaWMxDzAN
-BgNVBAcTBlByYWd1ZTEbMBkGA1UEChMST3JhY2xlIENvcnBvcmF0aW9uMQ8wDQYDVQQLEwZKZXJz
-ZXkxDzANBgNVBAMTBkNsaWVudDCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu
-7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSf
-n+gEexAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgB
-xwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlXTAs9
-B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6
-ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQBTDv+z0kqA4GEAAKBgBmHNACDk1aw
-vUZjsRecMSBlkkCSqr/cCrYOsNwpfleQKsM6rdOofujANUVeoUFhX8e8K45FknxEqAugmhGQ9NRn
-uMenrvV+XupC0V2uGH0OciXeAzHbfeItBCbmJcvMdPW/q+I2vFchv6+ajEiNHogBrCc3qwSMhyVQ
-ug2fXHmJMAsGByqGSM44BAMFAAMvADAsAhQYznYmH0hrcLni4EqX3Ovac+pNJgIUehnEaW1V5djn
-dhYBAYUkSycETl4=
------END CERTIFICATE-----
diff --git a/connectors/jdk-connector/src/test/resources/clientkey.cert b/connectors/jdk-connector/src/test/resources/clientkey.cert
new file mode 100644
index 0000000..305dc68
--- /dev/null
+++ b/connectors/jdk-connector/src/test/resources/clientkey.cert
@@ -0,0 +1,27 @@
+Alias name: clientkey
+Creation date: Jun 22, 2020
+Entry type: PrivateKeyEntry
+Certificate chain length: 1
+Certificate[1]:
+-----BEGIN CERTIFICATE-----
+MIIDjTCCAnWgAwIBAgIEdX5IhDANBgkqhkiG9w0BAQsFADB2MQswCQYDVQQGEwJD
+WjEXMBUGA1UECBMOQ3plY2ggUmVwdWJsaWMxDzANBgNVBAcTBlByYWd1ZTEbMBkG
+A1UEChMST3JhY2xlIENvcnBvcmF0aW9uMQ8wDQYDVQQLEwZKZXJzZXkxDzANBgNV
+BAMTBkNsaWVudDAgFw0yMDA2MjIwNzI5MzZaGA8yMTIwMDUyOTA3MjkzNlowdjEL
+MAkGA1UEBhMCQ1oxFzAVBgNVBAgTDkN6ZWNoIFJlcHVibGljMQ8wDQYDVQQHEwZQ
+cmFndWUxGzAZBgNVBAoTEk9yYWNsZSBDb3Jwb3JhdGlvbjEPMA0GA1UECxMGSmVy
+c2V5MQ8wDQYDVQQDEwZDbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQClVUtMXL/+WK5Ma6mV+BeGZlSlGqT9teUUDwPkipZMA9wdXTC3iSIp098s
+NCiPJBPXs+0KSrrPVgcHlMONcAzg3FHQrbP1aIH0oFOHR1bF/KNG1/XzV8HLMHqM
+ynnDJ69sYOUy6BO6lYQM1GaPf0fdqkdii2/clKiKzzT/Ohr6549q3vdzHw4hKmoE
+SKrdmv4OkOFGKzAH2dzNciHGnpiQj03DhjAN/8KobdUbsxoklnYOdDkg2UErI0zC
+qPRg2DVVOxhViMOmPBheXVvdDiIAdLlXRiPTraetr1KxDHV6KXsQhKmADZhIj8jY
+NjIyMo52bRTToaJQL5DKF6Boyx/tAgMBAAGjITAfMB0GA1UdDgQWBBSjbPqA6Tkv
+6oS99eZLNQk11M+DODANBgkqhkiG9w0BAQsFAAOCAQEACNAqN0lz1cUlIocfhSk8
+JlAHsRbWoCxxJZNJE0WWZ6lfH5xdlW0/87yfSvtLwDOY/8OYAhbzjG5O8mQv/I32
+b2Acs42Sh+otVyLjbYicv+1yMuvmBnhEImeMHSGFjczYf9zQ+2PlerxdNwVKdpUL
+V3Dt68wTlWNRYm9X6uVpNRG7fy7/goeJhAXuVER9Gtl/LQ6GJyZjVxYemckoXWDY
+DCqVlzsZpnyoI2lrYXlcT3liPlRLJjqnWvmmF9GyqiIVeng9VTfStsEQ6LUfwKQO
+dtvwYhbVtL++fuX/99WYCDRL8mSFu8REruQln3TJf79wOQ14O0H5jWjr4QoeOIf9
+aw==
+-----END CERTIFICATE-----
diff --git a/connectors/jdk-connector/src/test/resources/keystore_client b/connectors/jdk-connector/src/test/resources/keystore_client
index d016fd2..fbca50a 100644
--- a/connectors/jdk-connector/src/test/resources/keystore_client
+++ b/connectors/jdk-connector/src/test/resources/keystore_client
Binary files differ
diff --git a/connectors/jdk-connector/src/test/resources/keystore_server b/connectors/jdk-connector/src/test/resources/keystore_server
index a7c93fc..88039a2 100644
--- a/connectors/jdk-connector/src/test/resources/keystore_server
+++ b/connectors/jdk-connector/src/test/resources/keystore_server
Binary files differ
diff --git a/connectors/jdk-connector/src/test/resources/server.cert b/connectors/jdk-connector/src/test/resources/server.cert
deleted file mode 100644
index 820841c..0000000
--- a/connectors/jdk-connector/src/test/resources/server.cert
+++ /dev/null
@@ -1,17 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDKzCCAumgAwIBAgIET5ZyzjALBgcqhkjOOAQDBQAweTELMAkGA1UEBhMCQ1oxFzAVBgNVBAgT
-DkN6ZWNoIFJlcHVibGljMQ8wDQYDVQQHEwZQcmFndWUxGzAZBgNVBAoTEk9yYWNsZSBDb3Jwb3Jh
-dGlvbjEPMA0GA1UECxMGSmVyc2V5MRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTIwNDI0MDkzMDU0
-WhcNMTIwNzIzMDkzMDU0WjB5MQswCQYDVQQGEwJDWjEXMBUGA1UECBMOQ3plY2ggUmVwdWJsaWMx
-DzANBgNVBAcTBlByYWd1ZTEbMBkGA1UEChMST3JhY2xlIENvcnBvcmF0aW9uMQ8wDQYDVQQLEwZK
-ZXJzZXkxEjAQBgNVBAMTCWxvY2FsaG9zdDCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUS
-KVLfSpwu7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3
-a5lQpaSfn+gEexAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/Ii
-Axmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrq
-gvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCjrh4rs6Z1
-kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQBTDv+z0kqA4GEAAKBgGIs
-VTo7dODp6iOyHFL+mkOVlOloZcymyWlHUZXzKqrvAi5jISptZZM+AoJcUUlUWEO9uwVTvX0MCk+4
-viwlPwt+XhaPM0kqfFcx1IS07BAx7z9cXREfYQpoFSsFW7pUs6cdvu0rjj8Ip6BnHALxQDgaBk40
-zXM39kB9LdGBt4uDMAsGByqGSM44BAMFAAMvADAsAhQE4QoQP4xPibjnozo8x5ORJqBuCAIUTkLQ
-2udZ2DeknwPYXp/zMkYXLN4=
------END CERTIFICATE-----
diff --git a/connectors/jdk-connector/src/test/resources/serverkey.cert b/connectors/jdk-connector/src/test/resources/serverkey.cert
new file mode 100644
index 0000000..fe337e4
--- /dev/null
+++ b/connectors/jdk-connector/src/test/resources/serverkey.cert
@@ -0,0 +1,27 @@
+Alias name: serverkey
+Creation date: Jun 22, 2020
+Entry type: PrivateKeyEntry
+Certificate chain length: 1
+Certificate[1]:
+-----BEGIN CERTIFICATE-----
+MIIDkzCCAnugAwIBAgIEPcyqRDANBgkqhkiG9w0BAQsFADB5MQswCQYDVQQGEwJD
+WjEXMBUGA1UECBMOQ3plY2ggUmVwdWJsaWMxDzANBgNVBAcTBlByYWd1ZTEbMBkG
+A1UEChMST3JhY2xlIENvcnBvcmF0aW9uMQ8wDQYDVQQLEwZKZXJzZXkxEjAQBgNV
+BAMTCWxvY2FsaG9zdDAgFw0yMDA2MjIwNzI5MzVaGA8yMTIwMDUyOTA3MjkzNVow
+eTELMAkGA1UEBhMCQ1oxFzAVBgNVBAgTDkN6ZWNoIFJlcHVibGljMQ8wDQYDVQQH
+EwZQcmFndWUxGzAZBgNVBAoTEk9yYWNsZSBDb3Jwb3JhdGlvbjEPMA0GA1UECxMG
+SmVyc2V5MRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQCSeyzbVQcWrPEWRamOT7qD1unq3RXVNIWW4Ty80aWkVZfGknaM
+eLN4u5E1KaOpzZiz0bRMJHmODleqClK3x5agVc7fZHS5+7UpIK2YFjR40gVwtSm8
+QEqgM2Oupx64xre7ueuStc+r3w+8ogI15/NY2RmlEKbsDvb5U336vIqwkSnMQ2sc
+yVe0eqhVfjKh157GA6ORh3YeYCh2rpprBDUwuAXaRQUFENmSB7Scfa9WsVBsngwY
+OUkaksosSVs9HU7WDkGLa2tVcNakaroT9MRFIttVuT3o5LjCImdBF0jqqyinipIk
+EZaQJWmR8RgS/nB3SqDIrcASMTfKnQOWh9XXAgMBAAGjITAfMB0GA1UdDgQWBBRi
+o3s6iusqJmz27RQuZ4X/PJWC5jANBgkqhkiG9w0BAQsFAAOCAQEAetRLpMZwt/Fp
+6JLiywxEm0cpWZY3CtZpRXUYK6oiBxSoY4ZI/802cWFJnakYYRuRnTAj9yCEYbww
+fbDMYVgS2x/q+CJsvPqg7aqPfR/2tqqm24pgpTE8QrH8FCGIsMHymc3sYV+pfkyI
+mXMeztbDj3h+i654qgBI+stcnhTXhi7Hf2nHLGg2280XMC2+NVNhn3hGcbALr1mr
+ka/s+LJw8jch9UkQT4fvL6jMn3v9LJL7vpIDiOtCYtralrLG7qLBYn0uXTA6IT8G
+j22jxaJPtTZz+mXnCLqe+xQCOjp14uO0vbVf2yIOUwY5psaB2JkEJp8VlXjI0J+Z
+c3pd96UiJw==
+-----END CERTIFICATE-----
diff --git a/connectors/jdk-connector/src/test/resources/truststore_client b/connectors/jdk-connector/src/test/resources/truststore_client
index 74784fb..f553315 100644
--- a/connectors/jdk-connector/src/test/resources/truststore_client
+++ b/connectors/jdk-connector/src/test/resources/truststore_client
Binary files differ
diff --git a/connectors/jdk-connector/src/test/resources/truststore_server b/connectors/jdk-connector/src/test/resources/truststore_server
index 9b26ce4..67ecd72 100644
--- a/connectors/jdk-connector/src/test/resources/truststore_server
+++ b/connectors/jdk-connector/src/test/resources/truststore_server
Binary files differ
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 c91f592..176fcb3 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
@@ -21,6 +21,7 @@
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeoutException;
import jakarta.ws.rs.core.Response;
@@ -37,6 +38,7 @@
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.LastHttpContent;
+import io.netty.handler.timeout.IdleStateEvent;
/**
* Jersey implementation of Netty channel handler.
@@ -52,6 +54,8 @@
private NettyInputStream nis;
private ClientResponse jerseyResponse;
+ private boolean readTimedOut;
+
JerseyClientHandler(ClientRequest request,
CompletableFuture<ClientResponse> responseAvailable,
CompletableFuture<?> responseDone) {
@@ -68,7 +72,12 @@
@Override
public void channelInactive(ChannelHandlerContext ctx) {
// assert: no-op, if channel is closed after LastHttpContent has been consumed
- responseDone.completeExceptionally(new IOException("Stream closed"));
+
+ if (readTimedOut) {
+ responseDone.completeExceptionally(new TimeoutException("Stream closed: read timeout"));
+ } else {
+ responseDone.completeExceptionally(new IOException("Stream closed"));
+ }
}
protected void notifyResponse() {
@@ -146,4 +155,14 @@
public void exceptionCaught(ChannelHandlerContext ctx, final Throwable cause) {
responseDone.completeExceptionally(cause);
}
+
+ @Override
+ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+ if (evt instanceof IdleStateEvent) {
+ readTimedOut = true;
+ ctx.close();
+ } else {
+ super.userEventTriggered(ctx, evt);
+ }
+ }
}
diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyClientProperties.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyClientProperties.java
new file mode 100644
index 0000000..3ee79d7
--- /dev/null
+++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyClientProperties.java
@@ -0,0 +1,48 @@
+/*
+ * 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.netty.connector;
+
+import org.glassfish.jersey.internal.util.PropertiesClass;
+
+/**
+ * Configuration options specific to the Client API that utilizes {@link NettyConnectorProvider}.
+ *
+ * @since 2.32
+ */
+@PropertiesClass
+public class NettyClientProperties {
+
+ /**
+ * <p>
+ * This property determines the maximum number of idle connections that will be simultaneously kept alive
+ * in total, rather than per destination. The default is 60.
+ * </p>
+ */
+ public static final String MAX_CONNECTIONS_TOTAL = "jersey.config.client.maxTotalConnections";
+
+ /**
+ * <p>
+ * This property determines the maximum number of idle connections that will be simultaneously kept alive, per destination.
+ * The default is 5.
+ * </p>
+ * <p>
+ * This property is a Jersey alternative to System property {@code}http.maxConnections{@code}. The Jersey property takes
+ * precedence over the system property.
+ * </p>
+ */
+ public static final String MAX_CONNECTIONS = "jersey.config.client.maxConnections";
+}
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 1745e28..1982960 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
@@ -25,7 +25,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
+import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -37,6 +37,8 @@
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
+import io.netty.channel.ChannelDuplexHandler;
+import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
@@ -58,6 +60,9 @@
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.JdkSslContext;
import io.netty.handler.stream.ChunkedWriteHandler;
+import io.netty.handler.timeout.IdleState;
+import io.netty.handler.timeout.IdleStateEvent;
+import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.GenericFutureListener;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.ClientRequest;
@@ -79,9 +84,30 @@
final Client client;
final HashMap<String, ArrayList<Channel>> connections = new HashMap<>();
+ // If HTTP keepalive is enabled the value of "http.maxConnections" determines the maximum number
+ // of idle connections that will be simultaneously kept alive, per destination.
+ private static final String HTTP_KEEPALIVE_STRING = System.getProperty("http.keepAlive");
+ // http.keepalive (default: true)
+ private static final Boolean HTTP_KEEPALIVE =
+ HTTP_KEEPALIVE_STRING == null ? Boolean.TRUE : Boolean.parseBoolean(HTTP_KEEPALIVE_STRING);
+
+ // http.maxConnections (default: 5)
+ private static final int DEFAULT_MAX_POOL_SIZE = 5;
+ private static final int MAX_POOL_SIZE = Integer.getInteger("http.maxConnections", DEFAULT_MAX_POOL_SIZE);
+ private static final int MAX_POOL_IDLE = 60;
+
+ private final Integer maxPoolSize; // either from system property, or from Jersey config, or default
+ private final Integer maxPoolIdle; // either from Jersey config, or default
+
+ private static final String INACTIVE_POOLED_CONNECTION_HANDLER = "inactive_pooled_connection_handler";
+ private static final String PRUNE_INACTIVE_POOL = "prune_inactive_pool";
+ private static final String READ_TIMEOUT_HANDLER = "read_timeout_handler";
+ private static final String REQUEST_HANDLER = "request_handler";
+
NettyConnector(Client client) {
- final Object threadPoolSize = client.getConfiguration().getProperties().get(ClientProperties.ASYNC_THREADPOOL_SIZE);
+ final Map<String, Object> properties = client.getConfiguration().getProperties();
+ final Object threadPoolSize = properties.get(ClientProperties.ASYNC_THREADPOOL_SIZE);
if (threadPoolSize != null && threadPoolSize instanceof Integer && (Integer) threadPoolSize > 0) {
executorService = Executors.newFixedThreadPool((Integer) threadPoolSize);
@@ -92,20 +118,31 @@
}
this.client = client;
+
+ final Object maxPoolIdleProperty = properties.get(NettyClientProperties.MAX_CONNECTIONS_TOTAL);
+ final Object maxPoolSizeProperty = properties.get(NettyClientProperties.MAX_CONNECTIONS);
+
+ maxPoolIdle = maxPoolIdleProperty != null ? (Integer) maxPoolIdleProperty : MAX_POOL_IDLE;
+ maxPoolSize = maxPoolSizeProperty != null
+ ? (Integer) maxPoolSizeProperty
+ : (HTTP_KEEPALIVE ? MAX_POOL_SIZE : DEFAULT_MAX_POOL_SIZE);
+
+ if (maxPoolIdle == null || maxPoolIdle < 0) {
+ throw new ProcessingException(LocalizationMessages.WRONG_MAX_POOL_IDLE(maxPoolIdle));
+ }
+
+ if (maxPoolSize == null || maxPoolSize < 0) {
+ throw new ProcessingException(LocalizationMessages.WRONG_MAX_POOL_SIZE(maxPoolIdle));
+ }
}
@Override
public ClientResponse apply(ClientRequest jerseyRequest) {
try {
- CompletableFuture<ClientResponse> resultFuture = execute(jerseyRequest);
-
- Integer timeout = jerseyRequest.resolveProperty(ClientProperties.READ_TIMEOUT, 0);
-
- 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);
+ return execute(jerseyRequest).join();
+ } catch (CompletionException cex) {
+ final Throwable t = cex.getCause() == null ? cex : cex.getCause();
+ throw new ProcessingException(t.getMessage(), t);
} catch (Exception ex) {
throw new ProcessingException(ex.getMessage(), ex);
}
@@ -120,6 +157,11 @@
}
protected CompletableFuture<ClientResponse> execute(final ClientRequest jerseyRequest) {
+ Integer timeout = jerseyRequest.resolveProperty(ClientProperties.READ_TIMEOUT, 0);
+ if (timeout == null || timeout < 0) {
+ throw new ProcessingException(LocalizationMessages.WRONG_READ_TIMEOUT(timeout));
+ }
+
final CompletableFuture<ClientResponse> responseAvailable = new CompletableFuture<>();
final CompletableFuture<?> responseDone = new CompletableFuture<>();
@@ -128,6 +170,7 @@
int port = requestUri.getPort() != -1 ? requestUri.getPort() : "https".equals(requestUri.getScheme()) ? 443 : 80;
try {
+
String key = requestUri.getScheme() + "://" + host + ":" + port;
ArrayList<Channel> conns;
synchronized (connections) {
@@ -138,9 +181,16 @@
}
}
- Channel chan;
+ Channel chan = null;
synchronized (conns) {
- chan = conns.size() == 0 ? null : conns.remove(conns.size() - 1);
+ while (chan == null && !conns.isEmpty()) {
+ chan = conns.remove(conns.size() - 1);
+ chan.pipeline().remove(INACTIVE_POOLED_CONNECTION_HANDLER);
+ chan.pipeline().remove(PRUNE_INACTIVE_POOL);
+ if (!chan.isOpen()) {
+ chan = null;
+ }
+ }
}
if (chan == null) {
@@ -199,16 +249,30 @@
// will leak
final Channel ch = chan;
JerseyClientHandler clientHandler = new JerseyClientHandler(jerseyRequest, responseAvailable, responseDone);
- ch.pipeline().addLast(clientHandler);
+ // read timeout makes sense really as an inactivity timeout
+ ch.pipeline().addLast(READ_TIMEOUT_HANDLER,
+ new IdleStateHandler(0, 0, timeout, TimeUnit.MILLISECONDS));
+ ch.pipeline().addLast(REQUEST_HANDLER, clientHandler);
responseDone.whenComplete((_r, th) -> {
+ ch.pipeline().remove(READ_TIMEOUT_HANDLER);
ch.pipeline().remove(clientHandler);
if (th == null) {
+ ch.pipeline().addLast(INACTIVE_POOLED_CONNECTION_HANDLER, new IdleStateHandler(0, 0, maxPoolIdle));
+ ch.pipeline().addLast(PRUNE_INACTIVE_POOL, new PruneIdlePool(connections, key));
synchronized (connections) {
ArrayList<Channel> conns1 = connections.get(key);
- synchronized (conns1) {
+ if (conns1 == null) {
+ conns1 = new ArrayList<>(1);
conns1.add(ch);
+ connections.put(key, conns1);
+ } else {
+ synchronized (conns1) {
+ if (conns1.size() < maxPoolSize) {
+ conns1.add(ch);
+ } // else do not add the Channel to the idle pool
+ }
}
}
} else {
@@ -331,4 +395,35 @@
throw new ProcessingException(LocalizationMessages.WRONG_PROXY_URI_TYPE(ClientProperties.PROXY_URI));
}
}
+
+ protected static class PruneIdlePool extends ChannelDuplexHandler {
+ HashMap<String, ArrayList<Channel>> connections;
+ String key;
+
+ public PruneIdlePool(HashMap<String, ArrayList<Channel>> connections, String key) {
+ this.connections = connections;
+ this.key = key;
+ }
+
+ @Override
+ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+ if (evt instanceof IdleStateEvent) {
+ IdleStateEvent e = (IdleStateEvent) evt;
+ if (e.state() == IdleState.ALL_IDLE) {
+ ctx.close();
+ synchronized (connections) {
+ ArrayList<Channel> chans = connections.get(key);
+ synchronized (chans) {
+ chans.remove(ctx.channel());
+ if (chans.isEmpty()) {
+ connections.remove(key);
+ }
+ }
+ }
+ }
+ } else {
+ super.userEventTriggered(ctx, evt);
+ }
+ }
+ }
}
diff --git a/connectors/netty-connector/src/main/resources/org/glassfish/jersey/netty/connector/localization.properties b/connectors/netty-connector/src/main/resources/org/glassfish/jersey/netty/connector/localization.properties
index 2403307..8f9bf8f 100644
--- a/connectors/netty-connector/src/main/resources/org/glassfish/jersey/netty/connector/localization.properties
+++ b/connectors/netty-connector/src/main/resources/org/glassfish/jersey/netty/connector/localization.properties
@@ -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
@@ -15,3 +15,7 @@
#
wrong.proxy.uri.type=The proxy URI ("{0}") property MUST be an instance of String or URI.
+wrong.read.timeout=Unexpected ("{0}") READ_TIMEOUT.
+wrong.max.pool.size=Unexpected ("{0}") maximum number of connections per destination.
+wrong.max.pool.idle=Unexpected ("{0}") maximum number of connections total.
+
diff --git a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/TimeoutTest.java b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/TimeoutTest.java
index e2e86a8..0b0813a 100644
--- a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/TimeoutTest.java
+++ b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/TimeoutTest.java
@@ -16,6 +16,7 @@
package org.glassfish.jersey.netty.connector;
+import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeoutException;
import jakarta.ws.rs.GET;
@@ -80,6 +81,7 @@
target("test/timeout").property(ClientProperties.READ_TIMEOUT, 1_000).request().get();
fail("Timeout expected.");
} catch (ProcessingException e) {
+ assertEquals(e.getMessage(), "Stream closed: read timeout");
assertThat("Unexpected processing exception cause",
e.getCause(), instanceOf(TimeoutException.class));
}
@@ -91,8 +93,41 @@
target("test/timeout").request().property(ClientProperties.READ_TIMEOUT, 1_000).get();
fail("Timeout expected.");
} catch (ProcessingException e) {
+ assertEquals(e.getMessage(), "Stream closed: read timeout");
assertThat("Unexpected processing exception cause",
- e.getCause(), instanceOf(TimeoutException.class));
+ e.getCause(), instanceOf(TimeoutException.class));
+ }
+ }
+
+ @Test
+ public void testRxSlow() {
+ try {
+ target("test/timeout").property(ClientProperties.READ_TIMEOUT, 1_000).request()
+ .rx().get().toCompletableFuture().join();
+ fail("Timeout expected.");
+ } catch (CompletionException cex) {
+ assertThat("Unexpected async cause",
+ cex.getCause(), instanceOf(ProcessingException.class));
+ ProcessingException e = (ProcessingException) cex.getCause();
+ assertThat("Unexpected processing exception cause",
+ e.getCause(), instanceOf(TimeoutException.class));
+ assertEquals(e.getCause().getMessage(), "Stream closed: read timeout");
+ }
+ }
+
+ @Test
+ public void testRxTimeoutInRequest() {
+ try {
+ target("test/timeout").request().property(ClientProperties.READ_TIMEOUT, 1_000)
+ .rx().get().toCompletableFuture().join();
+ fail("Timeout expected.");
+ } catch (CompletionException cex) {
+ assertThat("Unexpected async cause",
+ cex.getCause(), instanceOf(ProcessingException.class));
+ ProcessingException e = (ProcessingException) cex.getCause();
+ assertThat("Unexpected processing exception cause",
+ e.getCause(), instanceOf(TimeoutException.class));
+ assertEquals(e.getCause().getMessage(), "Stream closed: read timeout");
}
}
}
diff --git a/connectors/pom.xml b/connectors/pom.xml
index a640dd9..15c1fd6 100644
--- a/connectors/pom.xml
+++ b/connectors/pom.xml
@@ -79,9 +79,10 @@
<profiles>
<profile>
<id>HelidonConnector</id>
- <activation>
+ <!-- TODO: activate after Helidon is jakartified -->
+ <!--<activation>
<jdk>11</jdk>
- </activation>
+ </activation>-->
<modules>
<module>helidon-connector</module>
</modules>
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java
index 8ec5c2e..1476275 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java
@@ -405,6 +405,31 @@
*/
public static final String REQUEST_ENTITY_PROCESSING = "jersey.config.client.request.entity.processing";
+ /**
+ * Allows for HTTP Expect:100-Continue being handled by the HttpUrlConnector (default Jersey
+ * connector).
+ *
+ * @since 2.32
+ */
+ public static final String EXPECT_100_CONTINUE = "jersey.config.client.request.expect.100.continue.processing";
+
+ /**
+ * Property for threshold size for content length after which Expect:100-Continue header would be applied
+ * before the main request.
+ *
+ * @since 2.32
+ */
+ public static final String
+ EXPECT_100_CONTINUE_THRESHOLD_SIZE = "jersey.config.client.request.expect.100.continue.threshold.size";
+
+ /**
+ * Default threshold size (64kb) after which which Expect:100-Continue header would be applied before
+ * the main request.
+ *
+ * @since 2.32
+ */
+ public static final Long DEFAULT_EXPECT_100_CONTINUE_THRESHOLD_SIZE = 65536L;
+
private ClientProperties() {
// prevents instantiation
}
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java
index 8f32598..90dda16 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java
@@ -167,7 +167,7 @@
private <T> T resolveProperty(final String name, Object defaultValue, final Class<T> type) {
// Check runtime configuration first
- Object result = clientConfig.getProperty(name);
+ Object result = getConfiguration().getProperty(name);
if (result != null) {
defaultValue = result;
}
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/http/Expect100ContinueFeature.java b/core-client/src/main/java/org/glassfish/jersey/client/http/Expect100ContinueFeature.java
new file mode 100644
index 0000000..43c450c
--- /dev/null
+++ b/core-client/src/main/java/org/glassfish/jersey/client/http/Expect100ContinueFeature.java
@@ -0,0 +1,70 @@
+/*
+ * 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.client.http;
+
+import org.glassfish.jersey.client.ClientProperties;
+
+import jakarta.ws.rs.core.Feature;
+import jakarta.ws.rs.core.FeatureContext;
+
+public class Expect100ContinueFeature implements Feature {
+
+ private long thresholdSize;
+
+ public Expect100ContinueFeature() {
+ this(ClientProperties.DEFAULT_EXPECT_100_CONTINUE_THRESHOLD_SIZE);
+ }
+
+ private Expect100ContinueFeature(long thresholdSize) {
+ this.thresholdSize = thresholdSize;
+ }
+
+ /**
+ * Creates Expect100ContinueFeature with custom (not default) threshold size for content length.
+ *
+ * @param thresholdSize size of threshold
+ * @return Expect100Continue Feature
+ */
+ public static Expect100ContinueFeature withCustomThreshold(long thresholdSize) {
+ return new Expect100ContinueFeature(thresholdSize);
+ }
+
+ /**
+ * Creates Expect100Continue Feature with default threshold size
+ *
+ * @return Expect100Continue Feature
+ */
+ public static Expect100ContinueFeature basic() {
+ return new Expect100ContinueFeature();
+ }
+
+ @Override
+ public boolean configure(FeatureContext configurableContext) {
+ if (configurableContext.getConfiguration().getProperty(
+ ClientProperties.EXPECT_100_CONTINUE) == null) {
+ configurableContext.property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE);
+ } else {
+ return false; //Expect:100-Continue handling is already done via property config
+ }
+ if (configurableContext.getConfiguration().getProperty(
+ ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE) == null) {
+ configurableContext.property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, thresholdSize);
+ }
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java
index bf3baef..93dff58 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java
@@ -334,8 +334,9 @@
RequestEntityProcessing entityProcessing = request.resolveProperty(
ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.class);
+ final long length = request.getLengthLong();
+
if (entityProcessing == null || entityProcessing != RequestEntityProcessing.BUFFERED) {
- final long length = request.getLengthLong();
if (fixLengthStreaming && length > 0) {
uc.setFixedLengthStreamingMode(length);
} else if (entityProcessing == RequestEntityProcessing.CHUNKED) {
@@ -351,6 +352,8 @@
}
}
+ processExpect100Continue(request, uc, length, entityProcessing);
+
request.setStreamProvider(contentLength -> {
setOutboundHeaders(request.getStringHeaders(), uc);
return uc.getOutputStream();
@@ -527,6 +530,26 @@
}
}
+ private static void processExpect100Continue(ClientRequest request, HttpURLConnection uc,
+ long length, RequestEntityProcessing entityProcessing) {
+ final Boolean expectContinueActivated = request.resolveProperty(
+ ClientProperties.EXPECT_100_CONTINUE, Boolean.class);
+ final Long expectContinueSizeThreshold = request.resolveProperty(
+ ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE,
+ ClientProperties.DEFAULT_EXPECT_100_CONTINUE_THRESHOLD_SIZE);
+
+ final boolean allowStreaming = length > expectContinueSizeThreshold
+ || entityProcessing == RequestEntityProcessing.CHUNKED;
+
+ if (!Boolean.TRUE.equals(expectContinueActivated)
+ || !("POST".equals(uc.getRequestMethod()) || "PUT".equals(uc.getRequestMethod()))
+ || !allowStreaming
+ ) {
+ return;
+ }
+ uc.setRequestProperty("Expect", "100-Continue");
+ }
+
@Override
public String getName() {
return "HttpUrlConnection " + AccessController.doPrivileged(PropertiesHelper.getSystemProperty("java.version"));
diff --git a/core-client/src/test/java/org/glassfish/jersey/client/ClientResponseTest.java b/core-client/src/test/java/org/glassfish/jersey/client/ClientResponseTest.java
new file mode 100644
index 0000000..54726cd
--- /dev/null
+++ b/core-client/src/test/java/org/glassfish/jersey/client/ClientResponseTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.client;
+
+import org.glassfish.jersey.message.internal.InboundMessageContext;
+import org.junit.Assert;
+import org.junit.Test;
+
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ReaderInterceptor;
+import java.io.InputStream;
+
+public class ClientResponseTest {
+
+ @Test
+ public void testHasEntityWhenNoEntity() {
+ final InboundMessageContext inboundMessageContext = new InboundMessageContext(new ClientConfig()) {
+ @Override
+ protected Iterable<ReaderInterceptor> getReaderInterceptors() {
+ return null;
+ }
+ };
+
+ Assert.assertFalse(inboundMessageContext.hasEntity());
+
+ inboundMessageContext.bufferEntity();
+ Assert.assertFalse(inboundMessageContext.hasEntity());
+ }
+
+ @Test
+ public void testHasEntity() {
+ final ClientRequestFilter abortFilter = requestContext -> requestContext.abortWith(Response.ok("hello").build());
+ try (Response r = ClientBuilder.newClient().register(abortFilter).target("http://localhost:8080").request().get()) {
+ Assert.assertTrue(r.hasEntity());
+
+ r.bufferEntity();
+ Assert.assertTrue(r.hasEntity());
+
+ final String s = r.readEntity(String.class);
+ Assert.assertTrue(r.hasEntity());
+
+ final InputStream bufferedEntityStream = r.readEntity(InputStream.class);
+ Assert.assertNotNull(bufferedEntityStream);
+ Assert.assertTrue(r.hasEntity());
+ }
+ }
+}
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/config/SystemPropertiesConfigurationModel.java b/core-common/src/main/java/org/glassfish/jersey/internal/config/SystemPropertiesConfigurationModel.java
index 8ea74b3..fa505fa 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/config/SystemPropertiesConfigurationModel.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/config/SystemPropertiesConfigurationModel.java
@@ -49,8 +49,10 @@
"org.glassfish.jersey.servlet.ServletProperties",
"org.glassfish.jersey.message.MessageProperties",
"org.glassfish.jersey.apache.connector.ApacheClientProperties",
+ "org.glassfish.jersey.helidon.connector.HelidonClientProperties",
"org.glassfish.jersey.jdk.connector.JdkConnectorProperties",
"org.glassfish.jersey.jetty.connector.JettyClientProperties",
+ "org.glassfish.jersey.netty.connector.NettyClientProperties",
"org.glassfish.jersey.media.multipart.MultiPartProperties",
"org.glassfish.jersey.server.oauth1.OAuth1ServerProperties");
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 0f874ae..59ec988 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
@@ -780,7 +780,7 @@
entityContent.ensureNotClosed();
try {
- return !entityContent.isEmpty();
+ return entityContent.isBuffered() || !entityContent.isEmpty();
} catch (IllegalStateException ex) {
// input stream has been closed.
return false;
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 c9cd557..328b899 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
@@ -109,19 +109,34 @@
private final Class<? extends Feature> featureClass;
private final Feature feature;
private final RuntimeType runtimeType;
+ private final int priority;
- private FeatureRegistration(final Class<? extends Feature> featureClass) {
+ private FeatureRegistration(final Class<? extends Feature> featureClass, int priority) {
this.featureClass = featureClass;
this.feature = null;
final ConstrainedTo runtimeTypeConstraint = featureClass.getAnnotation(ConstrainedTo.class);
this.runtimeType = runtimeTypeConstraint == null ? null : runtimeTypeConstraint.value();
+ this.priority = priority(featureClass, priority);
}
- private FeatureRegistration(final Feature feature) {
+ private FeatureRegistration(final Feature feature, int priority) {
this.featureClass = feature.getClass();
this.feature = feature;
final ConstrainedTo runtimeTypeConstraint = featureClass.getAnnotation(ConstrainedTo.class);
this.runtimeType = runtimeTypeConstraint == null ? null : runtimeTypeConstraint.value();
+ this.priority = priority(featureClass, priority);
+ }
+
+ private static int priority(Class<? extends Feature> featureClass, int priority) {
+ if (priority != ContractProvider.NO_PRIORITY) {
+ return priority;
+ }
+ final Priority priorityAnnotation = featureClass.getAnnotation(Priority.class);
+ if (priorityAnnotation != null) {
+ return priorityAnnotation.value();
+ } else {
+ return Priorities.USER;
+ }
}
/**
@@ -400,7 +415,7 @@
public CommonConfig register(final Class<?> componentClass) {
checkComponentClassNotNull(componentClass);
if (componentBag.register(componentClass, getModelEnhancer(componentClass))) {
- processFeatureRegistration(null, componentClass);
+ processFeatureRegistration(null, componentClass, ContractProvider.NO_PRIORITY);
}
return this;
@@ -410,7 +425,7 @@
public CommonConfig register(final Class<?> componentClass, final int bindingPriority) {
checkComponentClassNotNull(componentClass);
if (componentBag.register(componentClass, bindingPriority, getModelEnhancer(componentClass))) {
- processFeatureRegistration(null, componentClass);
+ processFeatureRegistration(null, componentClass, bindingPriority);
}
return this;
@@ -424,7 +439,7 @@
return this;
}
if (componentBag.register(componentClass, asNewIdentitySet(contracts), getModelEnhancer(componentClass))) {
- processFeatureRegistration(null, componentClass);
+ processFeatureRegistration(null, componentClass, ContractProvider.NO_PRIORITY);
}
return this;
@@ -434,7 +449,7 @@
public CommonConfig register(final Class<?> componentClass, final Map<Class<?>, Integer> contracts) {
checkComponentClassNotNull(componentClass);
if (componentBag.register(componentClass, contracts, getModelEnhancer(componentClass))) {
- processFeatureRegistration(null, componentClass);
+ processFeatureRegistration(null, componentClass, ContractProvider.NO_PRIORITY);
}
return this;
@@ -446,7 +461,7 @@
final Class<?> componentClass = component.getClass();
if (componentBag.register(component, getModelEnhancer(componentClass))) {
- processFeatureRegistration(component, componentClass);
+ processFeatureRegistration(component, componentClass, ContractProvider.NO_PRIORITY);
}
return this;
@@ -457,7 +472,7 @@
checkProviderNotNull(component);
final Class<?> componentClass = component.getClass();
if (componentBag.register(component, bindingPriority, getModelEnhancer(componentClass))) {
- processFeatureRegistration(component, componentClass);
+ processFeatureRegistration(component, componentClass, bindingPriority);
}
return this;
@@ -472,7 +487,7 @@
return this;
}
if (componentBag.register(component, asNewIdentitySet(contracts), getModelEnhancer(componentClass))) {
- processFeatureRegistration(component, componentClass);
+ processFeatureRegistration(component, componentClass, ContractProvider.NO_PRIORITY);
}
return this;
@@ -483,19 +498,19 @@
checkProviderNotNull(component);
final Class<?> componentClass = component.getClass();
if (componentBag.register(component, contracts, getModelEnhancer(componentClass))) {
- processFeatureRegistration(component, componentClass);
+ processFeatureRegistration(component, componentClass, ContractProvider.NO_PRIORITY);
}
return this;
}
- private void processFeatureRegistration(final Object component, final Class<?> componentClass) {
+ private void processFeatureRegistration(final Object component, final Class<?> componentClass, int priority) {
final ContractProvider model = componentBag.getModel(componentClass);
if (model.getContracts().contains(Feature.class)) {
@SuppressWarnings("unchecked")
final FeatureRegistration registration = (component != null)
- ? new FeatureRegistration((Feature) component)
- : new FeatureRegistration((Class<? extends Feature>) componentClass);
+ ? new FeatureRegistration((Feature) component, priority)
+ : new FeatureRegistration((Class<? extends Feature>) componentClass, priority);
newFeatureRegistrations.add(registration);
}
}
@@ -524,7 +539,7 @@
this.enabledFeatureClasses.clear();
componentBag.clear();
- resetRegistrations();
+ resetFeatureRegistrations();
for (final Class<?> clazz : config.getClasses()) {
if (Feature.class.isAssignableFrom(clazz) && config.isEnabled((Class<? extends Feature>) clazz)) {
@@ -629,7 +644,7 @@
// Next, register external meta objects
configureExternalObjects(injectionManager, configuredExternals);
// Configure all features
- configureFeatures(injectionManager, new HashSet<>(), resetRegistrations(), finalizer);
+ configureFeatures(injectionManager, new HashSet<>(), resetFeatureRegistrations(), finalizer);
// Next, register external meta objects registered by features
configureExternalObjects(injectionManager, configuredExternals);
// At last, configure any new binders added by features
@@ -718,16 +733,17 @@
if (providerModel != null) {
ProviderBinder.bindProvider(feature, providerModel, injectionManager);
}
- configureFeatures(injectionManager, processed, resetRegistrations(), managedObjectsFinalizer);
+ configureFeatures(injectionManager, processed, resetFeatureRegistrations(), managedObjectsFinalizer);
enabledFeatureClasses.add(registration.getFeatureClass());
enabledFeatures.add(feature);
}
}
}
- private List<FeatureRegistration> resetRegistrations() {
+ private List<FeatureRegistration> resetFeatureRegistrations() {
final List<FeatureRegistration> result = new ArrayList<>(newFeatureRegistrations);
newFeatureRegistrations.clear();
+ Collections.sort(result, (o1, o2) -> o1.priority < o2.priority ? -1 : 1);
return result;
}
diff --git a/core-common/src/main/java11/org/glassfish/jersey/internal/jsr166/JerseyFlowSubscriber.java b/core-common/src/main/java11/org/glassfish/jersey/internal/jsr166/JerseyFlowSubscriber.java
new file mode 100644
index 0000000..f774e4f
--- /dev/null
+++ b/core-common/src/main/java11/org/glassfish/jersey/internal/jsr166/JerseyFlowSubscriber.java
@@ -0,0 +1,34 @@
+/*
+ * 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.internal.jsr166;
+
+public interface JerseyFlowSubscriber<T> extends Flow.Subscriber<T>, java.util.concurrent.Flow.Subscriber<T> {
+ @Override
+ default void onSubscribe(java.util.concurrent.Flow.Subscription subscription) {
+ this.onSubscribe(new Flow.Subscription() {
+ @Override
+ public void request(final long n) {
+ subscription.request(n);
+ }
+
+ @Override
+ public void cancel() {
+ subscription.cancel();
+ }
+ });
+ }
+}
diff --git a/core-common/src/main/java8/org/glassfish/jersey/internal/jsr166/JerseyFlowSubscriber.java b/core-common/src/main/java8/org/glassfish/jersey/internal/jsr166/JerseyFlowSubscriber.java
new file mode 100644
index 0000000..dd25372
--- /dev/null
+++ b/core-common/src/main/java8/org/glassfish/jersey/internal/jsr166/JerseyFlowSubscriber.java
@@ -0,0 +1,20 @@
+/*
+ * 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.internal.jsr166;
+
+public interface JerseyFlowSubscriber<T> extends Flow.Subscriber<T> {
+}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ChunkedOutput.java b/core-server/src/main/java/org/glassfish/jersey/server/ChunkedOutput.java
index e252660..faf37de 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/ChunkedOutput.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ChunkedOutput.java
@@ -53,8 +53,11 @@
private final BlockingDeque<T> queue = new LinkedBlockingDeque<>();
private final byte[] chunkDelimiter;
private final AtomicBoolean resumed = new AtomicBoolean(false);
+ private final Object lock = new Object();
+ // the following flushing and touchingEntityStream variables are used in a synchronized block exclusively
private boolean flushing = false;
+ private boolean touchingEntityStream = false;
private volatile boolean closed = false;
@@ -198,7 +201,7 @@
boolean shouldClose;
T t;
- synchronized (ChunkedOutput.this) {
+ synchronized (lock) {
if (flushing) {
// if another thread is already flushing the queue, we don't have to do anything
return null;
@@ -220,6 +223,10 @@
while (t != null) {
try {
+ synchronized (lock) {
+ touchingEntityStream = true;
+ }
+
final OutputStream origStream = responseContext.getEntityStream();
final OutputStream writtenStream = requestContext.getWorkers().writeTo(
t,
@@ -256,10 +263,15 @@
connectionCallback.onDisconnect(asyncContext);
}
throw mpe;
+ } finally {
+ synchronized (lock) {
+ touchingEntityStream = false;
+ }
}
+
t = queue.poll();
if (t == null) {
- synchronized (ChunkedOutput.this) {
+ synchronized (lock) {
// queue seems empty
// check again in the synchronized block before clearing the flushing flag
// first remember the closed flag (this has to be before polling the queue,
@@ -287,10 +299,15 @@
closed = true;
// remember the exception (it will get rethrown from finally clause, once it does it's work)
ex = e;
+ onClose(e);
} finally {
if (closed) {
try {
- responseContext.close();
+ synchronized (lock) {
+ if (!touchingEntityStream) {
+ responseContext.close();
+ } // else the next thread will close responseContext
+ }
} catch (final Exception e) {
// if no exception remembered before, remember this one
// otherwise the previously remembered exception (from catch clause) takes precedence
@@ -333,6 +350,14 @@
return closed;
}
+ /**
+ * Executed only in case of close being triggered by client.
+ * @param e Exception causing the close
+ */
+ protected void onClose(Exception e){
+
+ }
+
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
@Override
public boolean equals(final Object obj) {
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java
index 4d8e39c..1fa7a85 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java
@@ -28,7 +28,9 @@
import java.util.List;
import java.util.Map;
import java.util.Queue;
+import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -489,14 +491,12 @@
private Response mapException(final Throwable originalThrowable) throws Throwable {
LOGGER.log(Level.FINER, LocalizationMessages.EXCEPTION_MAPPING_START(), originalThrowable);
- Throwable throwable = originalThrowable;
- boolean inMappable = false;
- boolean mappingNotFound = false;
+ final ThrowableWrap wrap = new ThrowableWrap(originalThrowable);
+ wrap.tryMappableException();
do {
- if (throwable instanceof MappableException) {
- inMappable = true;
- } else if (inMappable || throwable instanceof WebApplicationException) {
+ final Throwable throwable = wrap.getCurrent();
+ if (wrap.isInMappable() || throwable instanceof WebApplicationException) {
// in case ServerProperties.PROCESSING_RESPONSE_ERRORS_ENABLED is true, allow
// wrapped MessageBodyProviderNotFoundException to propagate
if (runtime.processResponseErrors && throwable instanceof InternalServerErrorException
@@ -568,8 +568,6 @@
return waeResponse;
}
-
- mappingNotFound = true;
}
// internal mapping
if (throwable instanceof HeaderValueException) {
@@ -578,18 +576,17 @@
}
}
- if (!inMappable || mappingNotFound) {
+ if (!wrap.isInMappable() || !wrap.isWrapped()) {
// user failures (thrown from Resource methods or provider methods)
// spec: Unchecked exceptions and errors that have not been mapped MUST be re-thrown and allowed to
// propagate to the underlying container.
// not logged on this level.
- throw throwable;
+ throw wrap.getWrappedOrCurrent();
}
- throwable = throwable.getCause();
- } while (throwable != null);
+ } while (wrap.unwrap() != null);
// jersey failures (not thrown from Resource methods or provider methods) -> rethrow
throw originalThrowable;
}
@@ -1181,4 +1178,91 @@
});
}
}
+
+ /**
+ * The structure that holds original {@link Throwable}, top most wrapped {@link Throwable} for the cases where the
+ * exception is to be tried to be mapped but is wrapped in a known wrapping {@link Throwable}, and the current unwrapped
+ * {@link Throwable}. For instance, the original is {@link MappableException}, the wrapped is {@link CompletionException},
+ * and the current is {@code IllegalStateException}.
+ */
+ private static class ThrowableWrap {
+ private final Throwable original;
+ private Throwable wrapped = null;
+ private Throwable current;
+ private boolean inMappable = false;
+
+ private ThrowableWrap(Throwable original) {
+ this.original = original;
+ this.current = original;
+ }
+
+ /**
+ * Gets the original {@link Throwable} to be mapped to an {@link ExceptionMapper}.
+ * @return the original Throwable.
+ */
+ private Throwable getOriginal() {
+ return original;
+ }
+
+ /**
+ * Some exceptions can be unwrapped. If an {@link ExceptionMapper} is not found for them, the original wrapping
+ * {@link Throwable} is to be returned. If the exception was not wrapped, return current.
+ * @return the wrapped or current {@link Throwable}.
+ */
+ private Throwable getWrappedOrCurrent() {
+ return wrapped != null ? wrapped : current;
+ }
+
+ /**
+ * Get current unwrapped {@link Throwable}.
+ * @return current {@link Throwable}.
+ */
+ private Throwable getCurrent() {
+ return current;
+ }
+
+ /**
+ * Check whether the current is a known wrapping exception.
+ * @return true if the current is a known wrapping exception.
+ */
+ private boolean isWrapped() {
+ final boolean isConcurrentWrap =
+ CompletionException.class.isInstance(current) || ExecutionException.class.isInstance(current);
+
+ return isConcurrentWrap;
+ }
+
+ /**
+ * Store the top most wrap exception and return the cause.
+ * @return the cause of the current {@link Throwable}.
+ */
+ private Throwable unwrap() {
+ if (wrapped == null) {
+ wrapped = current;
+ }
+ current = current.getCause();
+ return current;
+ }
+
+ /**
+ * Set flag that the original {@link Throwable} is {@link MappableException} and unwrap the nested {@link Throwable}.
+ * @return true if the original {@link Throwable} is {@link MappableException}.
+ */
+ private boolean tryMappableException() {
+ if (MappableException.class.isInstance(original)) {
+ inMappable = true;
+ current = original.getCause();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return the flag that original {@link Throwable} is {@link MappableException}.
+ * @return true if the original {@link Throwable} is {@link MappableException}.
+ */
+ private boolean isInMappable() {
+ return inMappable;
+ }
+ }
}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/monitoring/jmx/ExecutionStatisticsDynamicBean.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/monitoring/jmx/ExecutionStatisticsDynamicBean.java
index 78561de..8d79b45 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/internal/monitoring/jmx/ExecutionStatisticsDynamicBean.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/monitoring/jmx/ExecutionStatisticsDynamicBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2018 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
@@ -173,8 +173,17 @@
@Override
public AttributeList getAttributes(String[] attributes) {
- // TODO: implement
- return null;
+ final AttributeList x = new AttributeList();
+ if (attributes == null) {
+ return x;
+ }
+ for (final String k : attributes) {
+ final Value<?> value = attributeValues.get(k);
+ if (value != null) {
+ x.add(new Attribute(k, value.get()));
+ }
+ }
+ return x;
}
@Override
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/model/IntrospectionModeller.java b/core-server/src/main/java/org/glassfish/jersey/server/model/IntrospectionModeller.java
index 9663db0..be89131 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/model/IntrospectionModeller.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/model/IntrospectionModeller.java
@@ -49,6 +49,7 @@
import org.glassfish.jersey.server.ManagedAsync;
import org.glassfish.jersey.server.internal.LocalizationMessages;
import org.glassfish.jersey.server.model.internal.ModelHelper;
+import org.glassfish.jersey.server.model.internal.SseTypeResolver;
/**
* Utility class for constructing resource model from JAX-RS annotated POJO.
@@ -298,7 +299,7 @@
}
for (Class<?> paramType : am.getParameterTypes()) {
- if (SseEventSink.class.equals(paramType)) {
+ if (SseTypeResolver.isSseSinkParam(paramType)) {
resourceMethodBuilder.sse();
}
}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodValidator.java b/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodValidator.java
index 74f0a26..ac0c1da 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodValidator.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodValidator.java
@@ -42,6 +42,7 @@
import org.glassfish.jersey.internal.Errors;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.internal.LocalizationMessages;
+import org.glassfish.jersey.server.model.internal.SseTypeResolver;
import org.glassfish.jersey.server.spi.internal.ParameterValueHelper;
import org.glassfish.jersey.server.spi.internal.ValueParamProvider;
@@ -85,7 +86,7 @@
if ("GET".equals(method.getHttpMethod())) {
final long eventSinkCount = invocable.getParameters()
.stream()
- .filter(parameter -> SseEventSink.class.equals(parameter.getRawType()))
+ .filter(parameter -> SseTypeResolver.isSseSinkParam(parameter.getRawType()))
.count();
final boolean isSse = eventSinkCount > 0;
@@ -213,7 +214,8 @@
}
private boolean isSseInjected(final Invocable invocable) {
- return invocable.getParameters().stream().anyMatch(parameter -> SseEventSink.class.equals(parameter.getRawType()));
+ return invocable.getParameters().stream()
+ .anyMatch(parameter -> SseTypeResolver.isSseSinkParam(parameter.getRawType()));
}
private static final Set<Class> PARAM_ANNOTATION_SET = createParamAnnotationSet();
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/model/internal/JavaResourceMethodDispatcherProvider.java b/core-server/src/main/java/org/glassfish/jersey/server/model/internal/JavaResourceMethodDispatcherProvider.java
index 3da10ba..49b83ef 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/model/internal/JavaResourceMethodDispatcherProvider.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/model/internal/JavaResourceMethodDispatcherProvider.java
@@ -76,7 +76,7 @@
// return type is void
int i = 0;
for (final Parameter parameter : resourceMethod.getParameters()) {
- if (SseEventSink.class.equals(parameter.getRawType())) {
+ if (SseTypeResolver.isSseSinkParam(parameter.getRawType())) {
resourceMethodDispatcher =
new SseEventSinkInvoker(resourceMethod, invocationHandler, valueProviders, validator, i);
break;
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/model/internal/SseTypeResolver.java b/core-server/src/main/java/org/glassfish/jersey/server/model/internal/SseTypeResolver.java
new file mode 100644
index 0000000..f8b0ea0
--- /dev/null
+++ b/core-server/src/main/java/org/glassfish/jersey/server/model/internal/SseTypeResolver.java
@@ -0,0 +1,49 @@
+/*
+ * 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.server.model.internal;
+
+import java.security.AccessController;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+public final class SseTypeResolver {
+
+ private static final Set<Class<?>> SUPPORTED_SSE_SINK_TYPES;
+
+ private SseTypeResolver() {
+ }
+
+ static {
+ Set<Class<?>> set = new HashSet<>(8);
+
+ set.add(org.glassfish.jersey.internal.jsr166.Flow.Subscriber.class);
+ set.add(jakarta.ws.rs.sse.SseEventSink.class);
+ Class<?> clazz = AccessController
+ .doPrivileged(ReflectionHelper.classForNamePA("java.util.concurrent.Flow$Subscriber", null));
+
+ if (clazz != null) {
+ set.add(clazz);
+ }
+ SUPPORTED_SSE_SINK_TYPES = Collections.unmodifiableSet(set);
+ }
+
+ public static boolean isSseSinkParam(Class<?> type) {
+ return SUPPORTED_SSE_SINK_TYPES.contains(type);
+ }
+}
diff --git a/docs/src/main/docbook/jersey.ent b/docs/src/main/docbook/jersey.ent
index 4c20d9c..61a6d6c 100644
--- a/docs/src/main/docbook/jersey.ent
+++ b/docs/src/main/docbook/jersey.ent
@@ -53,12 +53,11 @@
<!ENTITY hk2.spring-bridge.link "<link xlink:href='https://javaee.github.io/hk2/spring-bridge/'>The Spring/HK2 Bridge</link>">
<!ENTITY jaxb.release.uri "https://eclipse-ee4j.github.io/jaxb-ri">
<!ENTITY jaxb.javadoc.uri "&jaxb.release.uri;/docs/api/jakarta.xml.bind">
-<!ENTITY jaxrs.release.uri "https://github.com/jax-rs">
-<!ENTITY jaxrs.javadoc.uri "https://jax-rs.github.io/apidocs/&jax-rs.version;/javax/ws/rs">
-<!ENTITY jaxrs21.javadoc.uri "https://jax-rs.github.io/apidocs/&jax-rs.version;/javax/ws/rs">
+<!ENTITY jaxrs.release.uri "https://github.com/eclipse-ee4j/jaxrs-api">
+<!ENTITY jaxrs.javadoc.uri "https://eclipse-ee4j.github.io/jaxrs-api/apidocs/&jax-rs.version;/javax/ws/rs">
+<!ENTITY jaxrs21.javadoc.uri "https://eclipse-ee4j.github.io/jaxrs-api/apidocs/&jax-rs.version;/javax/ws/rs">
<!ENTITY jsonb.javadoc.uri "https://javaee.github.io/javaee-spec/javadocs/javax/json/bind">
-
<!ENTITY jersey.ext.bean-validation.deps.link "<link xlink:href='&jersey.project-info.uri.prefix;/jersey-bean-validation/dependencies.html'>jersey-bean-validation</link>" >
<!ENTITY jersey.ext.declarative-linking.deps.link "<link xlink:href='&jersey.project-info.uri.prefix;/jersey-declarative-linking/dependencies.html'>jersey-declarative-linking</link>" >
<!ENTITY jersey.ext.entity-filtering.deps.link "<link xlink:href='&jersey.project-info.uri.prefix;/jersey-entity-filtering/dependencies.html'>jersey-entity-filtering</link>" >
diff --git a/docs/src/main/docbook/migration.xml b/docs/src/main/docbook/migration.xml
index c639909..2dad581 100644
--- a/docs/src/main/docbook/migration.xml
+++ b/docs/src/main/docbook/migration.xml
@@ -28,9 +28,9 @@
xml:id="migration">
<title>Migration Guide</title>
- <section xml:id="mig-2.27">
- <title>Migrating from Jersey 2.23 to 2.27</title>
- <section xml:id="mig-2.27-breaking-changes">
+ <section xml:id="mig-2.26">
+ <title>Migrating from Jersey 2.25 to &version;.</title>
+ <section xml:id="mig-2.26-breaking-changes">
<title>Breaking Changes</title>
<para>
<itemizedlist>
@@ -49,7 +49,7 @@
</para>
<para>
Spring version used in the extension module was upgraded to 4.2.1.RELEASE. The reason for that is lack
- of Java 8 support with Spring 3.x versions.
+ of Java 8 support with Spring 3.x versions. Optionally, an extension module for Spring 5 can be used.
</para>
<para>
Jersey proprietary reactive client API has been dropped and replaced by JAX-RS 2.1 Reactive Client API.
@@ -60,7 +60,7 @@
</itemizedlist>
</para>
</section>
- <section xml:id="mig-2.27-injection-manager">
+ <section xml:id="mig-2.26-injection-manager">
<title>Breaking Changes - Injection Manager</title>
<para>Following breaking changes are caused by removing and replacing HK2 (<literal>ServiceLocator</literal>) by
&jersey.common.internal.inject.InjectionManager;. Jersey injection API is considered as an internal API (except
@@ -118,6 +118,21 @@
to Jersey with an adjusted package.
</para>
</listitem>
+ <listitem>
+ If the internal Jersey injection API is to be backed by HK2, since 2.26, the following additional HK2
+ support module is required:
+ <programlisting language="xml" linenumbering="unnumbered"><dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <version>&version;</version>
+</dependency></programlisting>
+ </listitem>
+ <listitem>
+ The direct support for <literal>HK2 Binder</literal> being able to be registered as in pre 2.26 Jersey has
+ been returned back in Jersey 2.29. Note, however, this is for backward compatibility purposes and
+ not a preferred way of registering injectables; the <literal>HK2 Binder</literal> support may be removed
+ in the future versions of Jersey.
+ </listitem>
</itemizedlist>
</para>
</section>
@@ -138,7 +153,7 @@
</section>
<section>
- <title>Migrating from Jersey 2.22.1 to 2.23</title>
+ <title>Migrating from Jersey 2.22.1 to 2.25</title>
<section xml:id="mig-2.23-highlights">
<title>Release 2.23 Highlights</title>
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationBinder.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationBinder.java
index dc4de93..487786d 100644
--- a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationBinder.java
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationBinder.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2019 Payara Foundation 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,7 +18,6 @@
package org.glassfish.jersey.server.validation.internal;
import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;
@@ -258,9 +258,8 @@
private ValidatorContext getDefaultValidatorContext(final ValidateOnExecutionHandler handler) {
final ValidatorContext context = factory.usingContext();
- // if CDI is available use composite factiry
- if (AccessController.doPrivileged(
- ReflectionHelper.classForNamePA("jakarta.enterprise.inject.spi.BeanManager")) != null) {
+ // if CDI is available use composite factory
+ if (isCDIAvailable()) {
// Composite Configuration - due to PAYARA-2491
// https://github.com/payara/Payara/issues/2245
context.constraintValidatorFactory(resourceContext.getResource(
@@ -276,6 +275,15 @@
return context;
}
+ private boolean isCDIAvailable() {
+ // Both CDI & Jersey CDI modules must be available
+ return AccessController.doPrivileged(
+ ReflectionHelper.classForNamePA("jakarta.enterprise.inject.spi.BeanManager")) != null
+ &&
+ AccessController.doPrivileged(
+ ReflectionHelper.classForNamePA("org.glassfish.jersey.ext.cdi1x.internal.CdiUtil")) != null;
+ }
+
/**
* Create traversable resolver able to process {@link jakarta.validation.executable.ValidateOnExecution} annotation on
* beans.
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java
index f8e0d5c..ad820e0 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java
@@ -24,10 +24,13 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
import jakarta.enterprise.context.spi.CreationalContext;
import jakarta.enterprise.inject.spi.BeanManager;
+import jakarta.enterprise.inject.spi.CDI;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
@@ -56,6 +59,8 @@
*/
class InterfaceModel {
+ private static final Logger LOGGER = Logger.getLogger(InterfaceModel.class.getName());
+
private final InjectionManager injectionManager;
private final Class<?> restClientClass;
private final String[] produces;
@@ -336,9 +341,15 @@
}
Builder clientHeadersFactory(RegisterClientHeaders registerClientHeaders) {
- clientHeadersFactory = registerClientHeaders != null
- ? ReflectionUtil.createInstance(registerClientHeaders.value())
- : null;
+ if (registerClientHeaders != null) {
+ Class<? extends ClientHeadersFactory> value = registerClientHeaders.value();
+ try {
+ clientHeadersFactory = CDI.current().select(value).get();
+ } catch (Exception ex) {
+ LOGGER.log(Level.FINEST, ex, () -> "This class is not a CDI bean. " + value);
+ clientHeadersFactory = ReflectionUtil.createInstance(value);
+ }
+ }
return this;
}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
index 3d048e7..45e4667 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
@@ -140,15 +140,14 @@
@Override
@SuppressWarnings("unchecked")
public <T> T build(Class<T> interfaceClass) throws IllegalStateException, RestClientDefinitionException {
+ for (RestClientListener restClientListener : ServiceFinder.find(RestClientListener.class)) {
+ restClientListener.onNewClient(interfaceClass, this);
+ }
if (uri == null) {
throw new IllegalStateException("Base uri/url cannot be null!");
}
- for (RestClientListener restClientListener : ServiceFinder.find(RestClientListener.class)) {
- restClientListener.onNewClient(interfaceClass, this);
- }
-
//Provider registration part
processProviders(interfaceClass);
InjectionManagerExposer injectionManagerExposer = new InjectionManagerExposer();
diff --git a/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/JerseyEventSink.java b/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/JerseyEventSink.java
index a665eb7..1848144 100644
--- a/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/JerseyEventSink.java
+++ b/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/JerseyEventSink.java
@@ -18,19 +18,22 @@
import java.io.Flushable;
import java.io.IOException;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import jakarta.ws.rs.sse.OutboundSseEvent;
import jakarta.ws.rs.sse.SseEventSink;
-
+import jakarta.ws.rs.core.MediaType;
import jakarta.inject.Provider;
import org.glassfish.jersey.internal.jsr166.Flow;
+import org.glassfish.jersey.internal.jsr166.JerseyFlowSubscriber;
import org.glassfish.jersey.media.sse.LocalizationMessages;
+import org.glassfish.jersey.media.sse.OutboundEvent;
import org.glassfish.jersey.server.AsyncContext;
import org.glassfish.jersey.server.ChunkedOutput;
@@ -39,14 +42,16 @@
* <p>
* The reference should be obtained via injection into the resource method.
*
- * @author Adam Lindenthal]
+ * @author Adam Lindenthal
*/
class JerseyEventSink extends ChunkedOutput<OutboundSseEvent>
- implements SseEventSink, Flushable, Flow.Subscriber<OutboundSseEvent> {
+ implements SseEventSink, Flushable, JerseyFlowSubscriber<Object> {
private static final Logger LOGGER = Logger.getLogger(JerseyEventSink.class.getName());
- private static final byte[] SSE_EVENT_DELIMITER = "\n".getBytes(Charset.forName("UTF-8"));
+ private static final byte[] SSE_EVENT_DELIMITER = "\n".getBytes(StandardCharsets.UTF_8);
private Flow.Subscription subscription = null;
+ private final AtomicBoolean subscribed = new AtomicBoolean(false);
+ private volatile MediaType implicitMediaType = null;
JerseyEventSink(Provider<AsyncContext> asyncContextProvider) {
super(SSE_EVENT_DELIMITER, asyncContextProvider);
@@ -54,43 +59,77 @@
@Override
public void onSubscribe(final Flow.Subscription subscription) {
- checkClosed();
if (subscription == null) {
throw new NullPointerException(LocalizationMessages.PARAM_NULL("subscription"));
}
+ if (subscribed.getAndSet(true)) {
+ subscription.cancel();
+ return;
+ }
+
this.subscription = subscription;
- subscription.request(Long.MAX_VALUE);
+ if (isClosed()) {
+ subscription.cancel();
+ } else {
+ subscription.request(Long.MAX_VALUE);
+ }
}
@Override
- public void onNext(final OutboundSseEvent item) {
- checkClosed();
+ public void onNext(final Object item) {
if (item == null) {
throw new NullPointerException(LocalizationMessages.PARAM_NULL("outboundSseEvent"));
}
try {
- write(item);
- } catch (final IOException e) {
- onError(e);
+ checkClosed();
+ MediaType implicitType = resolveMediaType(item);
+ if (MediaType.SERVER_SENT_EVENTS_TYPE.equals(implicitType)) {
+ // already wrapped
+ write((OutboundSseEvent) item);
+ } else {
+ // implicit wrapping
+ // TODO: Jersey annotation for explicit media type
+ write(new OutboundEvent.Builder()
+ .mediaType(implicitType)
+ .data(item)
+ .build());
+ }
+ } catch (final Throwable e) {
+ // spec allows only NPE to be thrown from onNext
+ LOGGER.log(Level.SEVERE, LocalizationMessages.EVENT_SINK_NEXT_FAILED(), e);
+ cancelSubscription();
}
}
@Override
public void onError(final Throwable throwable) {
- checkClosed();
if (throwable == null) {
throw new NullPointerException(LocalizationMessages.PARAM_NULL("throwable"));
}
- subscription.cancel();
+ try {
+ LOGGER.log(Level.SEVERE, LocalizationMessages.EVENT_SOURCE_DEFAULT_ONERROR(), throwable);
+ super.close();
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, LocalizationMessages.EVENT_SINK_CLOSE_FAILED(), e);
+ }
+ }
+
+ public void onComplete() {
+ try {
+ super.close();
+ } catch (Throwable e) {
+ LOGGER.log(Level.SEVERE, LocalizationMessages.EVENT_SINK_CLOSE_FAILED(), e);
+ }
}
@Override
public void close() {
try {
+ cancelSubscription();
super.close();
} catch (IOException e) {
- LOGGER.log(Level.INFO, LocalizationMessages.EVENT_SINK_CLOSE_FAILED(), e);
+ LOGGER.log(Level.SEVERE, LocalizationMessages.EVENT_SINK_CLOSE_FAILED(), e);
}
}
@@ -101,7 +140,9 @@
this.write(event);
return CompletableFuture.completedFuture(null);
} catch (IOException e) {
- return CompletableFuture.completedFuture(e);
+ CompletableFuture<Void> future = new CompletableFuture<>();
+ future.completeExceptionally(e);
+ return future;
}
}
@@ -118,15 +159,38 @@
super.flushQueue();
}
- public void onComplete() {
- checkClosed();
- subscription.cancel();
- close();
+ @Override
+ protected void onClose(Exception e) {
+ cancelSubscription();
+ }
+
+ private void cancelSubscription() {
+ if (subscription != null) {
+ subscription.cancel();
+ }
}
private void checkClosed() {
if (isClosed()) {
+ cancelSubscription();
throw new IllegalStateException(LocalizationMessages.EVENT_SOURCE_ALREADY_CLOSED());
}
}
+
+ private MediaType resolveMediaType(Object item) {
+ // resolve lazily as all stream items are presumed to be of a same type
+ if (implicitMediaType == null) {
+ Class<?> clazz = item.getClass();
+ if (String.class.equals(clazz)
+ || Number.class.isAssignableFrom(clazz)
+ || Character.class.equals(clazz)
+ || Boolean.class.equals(clazz)) {
+ implicitMediaType = MediaType.TEXT_PLAIN_TYPE;
+ return implicitMediaType;
+ }
+ // unknown unwrapped objects are treated as json media type
+ implicitMediaType = MediaType.APPLICATION_JSON_TYPE;
+ }
+ return implicitMediaType;
+ }
}
diff --git a/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/SseEventSinkValueParamProvider.java b/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/SseEventSinkValueParamProvider.java
index 0884a36..07be277 100644
--- a/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/SseEventSinkValueParamProvider.java
+++ b/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/SseEventSinkValueParamProvider.java
@@ -29,6 +29,7 @@
import org.glassfish.jersey.server.internal.inject.AbstractValueParamProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
import org.glassfish.jersey.server.model.Parameter;
+import org.glassfish.jersey.server.model.internal.SseTypeResolver;
import org.glassfish.jersey.server.spi.internal.ValueParamProvider;
/**
@@ -59,7 +60,8 @@
}
final Class<?> rawParameterType = parameter.getRawType();
- if (rawParameterType == SseEventSink.class && parameter.isAnnotationPresent(Context.class)) {
+ if (SseTypeResolver.isSseSinkParam(rawParameterType)
+ && parameter.isAnnotationPresent(Context.class)) {
return new SseEventSinkValueSupplier(asyncContextSupplier);
}
return null;
diff --git a/media/sse/src/main/resources/org/glassfish/jersey/media/sse/localization.properties b/media/sse/src/main/resources/org/glassfish/jersey/media/sse/localization.properties
index d8ee42b..e000584 100644
--- a/media/sse/src/main/resources/org/glassfish/jersey/media/sse/localization.properties
+++ b/media/sse/src/main/resources/org/glassfish/jersey/media/sse/localization.properties
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2012, 2018 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
@@ -34,5 +34,6 @@
param.null="{0}" parameter is null.
params.null=One or more of parameters is null.
event.sink.close.failed=Closing EventSink failed. Could not close chunked output.
+event.sink.next.failed=Processing onNext signal failed.
unsupported.webtarget.type=Argument {0} is not a valid JerseyWebTarget instance. SseEventSource does not support other \
WebTarget implementations.
diff --git a/media/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyEventSinkTest.java b/media/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyEventSinkTest.java
index 465227a..6b7da0d 100644
--- a/media/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyEventSinkTest.java
+++ b/media/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyEventSinkTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 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,6 +16,7 @@
package org.glassfish.jersey.media.sse.internal;
+import org.glassfish.jersey.media.sse.OutboundEvent;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -28,43 +29,6 @@
@Rule
public ExpectedException thrown = ExpectedException.none();
-
- @Test
- public void onSubscribe() throws Exception {
- JerseyEventSink eventSink = new JerseyEventSink(null);
-
- eventSink.close();
- thrown.expect(IllegalStateException.class);
- eventSink.onSubscribe(null);
- }
-
- @Test
- public void onNext() throws Exception {
- JerseyEventSink eventSink = new JerseyEventSink(null);
-
- eventSink.close();
- thrown.expect(IllegalStateException.class);
- eventSink.onNext(null);
- }
-
- @Test
- public void onError() throws Exception {
- JerseyEventSink eventSink = new JerseyEventSink(null);
-
- eventSink.close();
- thrown.expect(IllegalStateException.class);
- eventSink.onError(null);
- }
-
- @Test
- public void onComplete() throws Exception {
- JerseyEventSink eventSink = new JerseyEventSink(null);
-
- eventSink.close();
- thrown.expect(IllegalStateException.class);
- eventSink.onComplete();
- }
-
@Test
public void test() throws Exception {
JerseyEventSink eventSink = new JerseyEventSink(null);
diff --git a/tests/e2e-client/pom.xml b/tests/e2e-client/pom.xml
index bb608d6..6d5140c 100644
--- a/tests/e2e-client/pom.xml
+++ b/tests/e2e-client/pom.xml
@@ -42,6 +42,9 @@
<reuseForks>false</reuseForks>
<enableAssertions>false</enableAssertions>
<skipTests>${skip.e2e}</skipTests>
+ <systemPropertyVariables>
+ <sun.net.http.allowRestrictedHeaders>true</sun.net.http.allowRestrictedHeaders>
+ </systemPropertyVariables>
<excludes>
<exclude>org/glassfish/jersey/tests/e2e/client/AbortResponseClientTest.java</exclude>
</excludes>
diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/httpurlconnector/Expect100ContinueTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/httpurlconnector/Expect100ContinueTest.java
new file mode 100644
index 0000000..176e4d3
--- /dev/null
+++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/httpurlconnector/Expect100ContinueTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.client.httpurlconnector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.client.HttpUrlConnectorProvider;
+import org.glassfish.jersey.client.RequestEntityProcessing;
+import org.glassfish.jersey.client.http.Expect100ContinueFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.Response;
+
+import static org.junit.Assert.assertEquals;
+
+public class Expect100ContinueTest extends JerseyTest {
+
+ private static final String RESOURCE_PATH = "expect";
+ private static final String ENTITY_STRING = "1234567890123456789012345678901234567890123456789012"
+ + "3456789012345678901234567890";
+
+
+ @Path(RESOURCE_PATH)
+ public static class Expect100ContinueResource {
+
+ /**
+ * Disclamer - in tests we do not process 100-Continue response properly, so we operate 204 (no content)
+ * and 200 (ok) response codes in order to distinguish between proper and not proper Expect:100-Continue
+ * request handling.
+ *
+ * @param expect - Header value for Expect
+ *
+ * @return 200 (no expectations), 204 (Expect:100-Continue header processed)
+ */
+ @POST
+ public Response publishResource(@HeaderParam("Expect") String expect) {
+ if ("100-Continue".equalsIgnoreCase(expect)) {
+ return Response.noContent().build();
+ }
+ return Response.ok().build();
+ }
+
+ }
+
+ @Override
+ protected Application configure() {
+ return new ResourceConfig(Expect100ContinueTest.Expect100ContinueResource.class);
+ }
+
+ @Test
+ public void testExpect100Continue() {
+ final Response response = target(RESOURCE_PATH).request().post(Entity.text(ENTITY_STRING));
+ assertEquals("Expected 200", 200, response.getStatus()); //no Expect header sent - responce OK
+ }
+
+ @Test
+ public void testExpect100ContinueChunked() {
+ final Response response = target(RESOURCE_PATH).register(Expect100ContinueFeature.basic())
+ .property(ClientProperties.REQUEST_ENTITY_PROCESSING,
+ RequestEntityProcessing.CHUNKED).request().post(Entity.text(ENTITY_STRING));
+ assertEquals("Expected 204", 204, response.getStatus()); //Expect header sent - No Content response
+ }
+
+ @Test
+ public void testExpect100ContinueBuffered() {
+ final Response response = target(RESOURCE_PATH).register(Expect100ContinueFeature.basic())
+ .property(ClientProperties.REQUEST_ENTITY_PROCESSING,
+ RequestEntityProcessing.BUFFERED).request().header(HttpHeaders.CONTENT_LENGTH, 67000L)
+ .post(Entity.text(ENTITY_STRING));
+ assertEquals("Expected 204", 204, response.getStatus()); //Expect header sent - No Content response
+ }
+
+ @Test
+ public void testExpect100ContinueCustomLength() {
+ final Response response = target(RESOURCE_PATH).register(Expect100ContinueFeature.withCustomThreshold(100L))
+ .request().header(HttpHeaders.CONTENT_LENGTH, 101L)
+ .post(Entity.text(ENTITY_STRING));
+ assertEquals("Expected 204", 204, response.getStatus()); //Expect header sent - No Content response
+ }
+
+ @Test
+ public void testExpect100ContinueCustomLengthWrong() {
+ final Response response = target(RESOURCE_PATH).register(Expect100ContinueFeature.withCustomThreshold(100L))
+ .request().header(HttpHeaders.CONTENT_LENGTH, 99L)
+ .post(Entity.text(ENTITY_STRING));
+ assertEquals("Expected 200", 200, response.getStatus()); //Expect header NOT sent - low request size
+ }
+
+ @Test
+ public void testExpect100ContinueCustomLengthProperty() {
+ final Response response = target(RESOURCE_PATH)
+ .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 555L)
+ .register(Expect100ContinueFeature.basic())
+ .request().header(HttpHeaders.CONTENT_LENGTH, 666L)
+ .post(Entity.text(ENTITY_STRING));
+ assertEquals("Expected 204", 204, response.getStatus()); //Expect header sent - No Content response
+ }
+
+ @Test
+ public void testExpect100ContinueRegisterViaCustomProperty() {
+ final Response response = target(RESOURCE_PATH)
+ .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 43L)
+ .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE)
+ .request().header(HttpHeaders.CONTENT_LENGTH, 44L)
+ .post(Entity.text(ENTITY_STRING));
+ assertEquals("Expected 204", 204, response.getStatus()); //Expect header sent - No Content response
+ }
+}
diff --git a/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/model/internal/CommonConfigTest.java b/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/model/internal/CommonConfigTest.java
index e8bf36e..77edddf 100644
--- a/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/model/internal/CommonConfigTest.java
+++ b/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/model/internal/CommonConfigTest.java
@@ -57,6 +57,7 @@
import org.junit.Ignore;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -997,4 +998,72 @@
assertThat("Feature class not injected", config.getProperty("class-injected").toString(), is("true"));
}
+ // ===========================================================================================================================
+
+ @Test
+ public void testFeatureBindingPriority() {
+ final InjectionManager injectionManager = Injections.createInjectionManager();
+ final ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
+ config.register(new OrderedFeature(Priorities.USER){}, Priorities.USER);
+ config.register(new OrderedFeature(Priorities.USER - 100){}, Priorities.USER - 100);
+ config.register(new OrderedFeature(Priorities.USER + 100){}, Priorities.USER + 100);
+ config.configureMetaProviders(injectionManager, finalizer);
+ int value = (int) config.getProperty(OrderedFeature.PROPERTY_NAME);
+
+ assertEquals(Priorities.USER + 100, value);
+ }
+
+ private static class OrderedFeature implements Feature {
+ private final int orderId;
+ private static final String PROPERTY_NAME = "ORDER_ID";
+
+ private OrderedFeature(int orderId) {
+ this.orderId = orderId;
+ }
+
+ @Override
+ public boolean configure(FeatureContext context) {
+ Integer previousId = (Integer) context.getConfiguration().getProperty(PROPERTY_NAME);
+ if (previousId != null) {
+ assertThat(previousId, lessThan(orderId));
+ }
+ context.property(PROPERTY_NAME, orderId);
+ return false;
+ }
+ }
+
+ @Test
+ public void testFeatureAnnotatedPriority() {
+ final InjectionManager injectionManager = Injections.createInjectionManager();
+ final ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
+ config.register(PriorityFeature1.class);
+ config.register(PriorityFeature2.class);
+ config.register(PriorityFeature3.class);
+ config.configureMetaProviders(injectionManager, finalizer);
+ int value = (int) config.getProperty(OrderedFeature.PROPERTY_NAME);
+
+ assertEquals(Priorities.USER + 100, value);
+ }
+
+ @Priority(Priorities.USER)
+ private static class PriorityFeature1 extends OrderedFeature {
+ private PriorityFeature1() {
+ super(Priorities.USER);
+ }
+ }
+
+ @Priority(Priorities.USER - 100)
+ private static class PriorityFeature2 extends OrderedFeature {
+ private PriorityFeature2() {
+ super(Priorities.USER - 100);
+ }
+ }
+
+ @Priority(Priorities.USER + 100)
+ private static class PriorityFeature3 extends OrderedFeature {
+ private PriorityFeature3() {
+ super(Priorities.USER + 100);
+ }
+ }
+
}
diff --git a/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/CompletionStageTest.java b/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/CompletionStageTest.java
index 698add9..f4d1209 100644
--- a/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/CompletionStageTest.java
+++ b/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/CompletionStageTest.java
@@ -127,12 +127,30 @@
assertThat(response.readEntity(String.class), is(ENTITY));
}
+ @Test
+ public void test4463() {
+ Response response = target("cs/exceptionally").request().get();
+
+ assertThat(response.getStatus(), is(406));
+ }
+
@Path("/cs")
public static class CompletionStageResource {
private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool();
@GET
+ @Path("exceptionally")
+ public CompletionStage<String> failAsyncLater() {
+ CompletableFuture<String> fail = new CompletableFuture<>();
+ fail.completeExceptionally(new IllegalStateException("Uh-oh"));
+
+ return fail.exceptionally(ex -> {
+ throw new WebApplicationException("OOPS", Response.Status.NOT_ACCEPTABLE.getStatusCode());
+ });
+ }
+
+ @GET
@Path("/completed")
public CompletionStage<String> getCompleted() {
return CompletableFuture.completedFuture(ENTITY);
diff --git a/tests/integration/jersey-4542/pom.xml b/tests/integration/jersey-4542/pom.xml
new file mode 100644
index 0000000..aa7c16b
--- /dev/null
+++ b/tests/integration/jersey-4542/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>project</artifactId>
+ <groupId>org.glassfish.jersey.tests.integration</groupId>
+ <version>3.0.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>jersey-4542</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-bean-validation</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-external</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.weld.se</groupId>
+ <artifactId>weld-se-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/tests/integration/jersey-4542/src/main/java/org.glassfish.jersey.tests.integration.jersey4542/ValidationInflector.java b/tests/integration/jersey-4542/src/main/java/org.glassfish.jersey.tests.integration.jersey4542/ValidationInflector.java
new file mode 100644
index 0000000..2ad1c70
--- /dev/null
+++ b/tests/integration/jersey-4542/src/main/java/org.glassfish.jersey.tests.integration.jersey4542/ValidationInflector.java
@@ -0,0 +1,51 @@
+/*
+ * 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.integration.jersey4542;
+
+import java.io.IOException;
+
+import jakarta.ws.rs.container.ContainerRequestContext;
+
+import jakarta.validation.constraints.NotNull;
+
+import org.glassfish.jersey.message.internal.ReaderWriter;
+import org.glassfish.jersey.process.Inflector;
+
+/**
+ * @author Michal Gajdos
+ */
+public class ValidationInflector implements Inflector<ContainerRequestContext, String> {
+
+ @NotNull
+ @Override
+ public String apply(final ContainerRequestContext requestContext) {
+ return get(requestContext);
+ }
+
+ @NotNull
+ public String get(@NotNull final ContainerRequestContext requestContext) {
+ try {
+ final String entity = ReaderWriter.readFromAsString(
+ requestContext.getEntityStream(),
+ requestContext.getMediaType());
+
+ return entity.isEmpty() ? null : entity;
+ } catch (IOException e) {
+ return "error";
+ }
+ }
+}
diff --git a/tests/integration/jersey-4542/src/test/java/org.glassfish.jersey.tests.integration.jersey4542/ProgrammaticValidationTest.java b/tests/integration/jersey-4542/src/test/java/org.glassfish.jersey.tests.integration.jersey4542/ProgrammaticValidationTest.java
new file mode 100644
index 0000000..9dd9797
--- /dev/null
+++ b/tests/integration/jersey-4542/src/test/java/org.glassfish.jersey.tests.integration.jersey4542/ProgrammaticValidationTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.integration.jersey4542;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+import org.glassfish.jersey.inject.hk2.Hk2InjectionManagerFactory;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.test.JerseyTest;
+
+import static org.junit.Assert.assertEquals;
+
+import org.glassfish.jersey.test.external.ExternalTestContainerFactory;
+import org.jboss.weld.environment.se.Weld;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Bean Validation tests for programmatically created resources.
+ *
+ * @author Michal Gajdos
+ */
+public class ProgrammaticValidationTest extends JerseyTest {
+
+ Weld weld;
+
+ @Before
+ public void setup() {
+ Assume.assumeTrue(Hk2InjectionManagerFactory.isImmediateStrategy());
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ if (Hk2InjectionManagerFactory.isImmediateStrategy()) {
+ if (!ExternalTestContainerFactory.class.isAssignableFrom(getTestContainerFactory().getClass())) {
+ weld = new Weld();
+ weld.initialize();
+ }
+ super.setUp();
+ }
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ if (Hk2InjectionManagerFactory.isImmediateStrategy()) {
+ if (!ExternalTestContainerFactory.class.isAssignableFrom(getTestContainerFactory().getClass())) {
+ weld.shutdown();
+ }
+ super.tearDown();
+ }
+ }
+
+ @Override
+ protected Application configure() {
+ final Set<Resource> resources = new HashSet<>();
+
+ Resource.Builder resourceBuilder = Resource.builder("class");
+ resourceBuilder
+ .addMethod("POST")
+ .handledBy(ValidationInflector.class);
+ resources.add(resourceBuilder.build());
+
+ return new ResourceConfig().register(LoggingFeature.class).registerResources(resources);
+ }
+
+ @Test
+ public void testInflectorClass() throws Exception {
+ final Response response = target("class").request().post(Entity.entity("value", MediaType.TEXT_PLAIN_TYPE));
+
+ assertEquals(200, response.getStatus());
+ assertEquals("value", response.readEntity(String.class));
+ }
+
+ @Test
+ public void testInflectorClassNegative() throws Exception {
+ final Response response = target("class").request().post(Entity.entity(null, MediaType.TEXT_PLAIN_TYPE));
+
+ assertEquals(500, response.getStatus());
+ }
+}
diff --git a/tests/integration/jersey-4542/src/test/resources/META-INF/beans.xml b/tests/integration/jersey-4542/src/test/resources/META-INF/beans.xml
new file mode 100644
index 0000000..d773c46
--- /dev/null
+++ b/tests/integration/jersey-4542/src/test/resources/META-INF/beans.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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
+
+-->
+
+<beans/>
\ No newline at end of file
diff --git a/tests/integration/pom.xml b/tests/integration/pom.xml
index 55a42c2..ea6d13c 100644
--- a/tests/integration/pom.xml
+++ b/tests/integration/pom.xml
@@ -86,6 +86,7 @@
<module>jersey-4099</module>
<module>jersey-4321</module>
<module>jersey-4507</module>
+ <module>jersey-4542</module>
<module>jetty-response-close</module>
<!-- <module>microprofile</module> --> <!--TODO remove when Jakartified -->
<module>property-check</module>
@@ -130,6 +131,7 @@
<!-- <module>spring4</module>-->
<!-- <module>spring5</module>-->
<module>tracing-support</module>
+ <module>reactive-streams</module>
</modules>
<profiles>
diff --git a/tests/integration/reactive-streams/pom.xml b/tests/integration/reactive-streams/pom.xml
new file mode 100644
index 0000000..6fe4df1
--- /dev/null
+++ b/tests/integration/reactive-streams/pom.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>project</artifactId>
+ <groupId>org.glassfish.jersey.tests.integration</groupId>
+ <version>3.0.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>pom</packaging>
+
+ <groupId>org.glassfish.jersey.tests.integration.reactive</groupId>
+ <artifactId>reactive-streams-integration-project</artifactId>
+ <name>reactive-streams-integration-project</name>
+ <modules>
+ <module>sse</module>
+ </modules>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-install-plugin</artifactId>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
\ No newline at end of file
diff --git a/tests/integration/reactive-streams/sse/pom.xml b/tests/integration/reactive-streams/sse/pom.xml
new file mode 100644
index 0000000..59fce17
--- /dev/null
+++ b/tests/integration/reactive-streams/sse/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>reactive-streams-integration-project</artifactId>
+ <groupId>org.glassfish.jersey.tests.integration.reactive</groupId>
+ <version>3.0.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>sse-reactive-streams-tck</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-sse</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.reactivestreams</groupId>
+ <artifactId>reactive-streams-tck</artifactId>
+ <version>1.0.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.reactivex.rxjava2</groupId>
+ <artifactId>rxjava</artifactId>
+ <version>${rxjava2.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-binding</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <includes>
+ <include>**/*TckTest.java</include>
+ </includes>
+ </configuration>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.surefire</groupId>
+ <artifactId>surefire-testng</artifactId>
+ <version>3.0.0-M3</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ </build>
+
+
+</project>
\ No newline at end of file
diff --git a/tests/integration/reactive-streams/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyEventSinkBlackBoxSubscriberTckTest.java b/tests/integration/reactive-streams/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyEventSinkBlackBoxSubscriberTckTest.java
new file mode 100644
index 0000000..6e6a1d5
--- /dev/null
+++ b/tests/integration/reactive-streams/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyEventSinkBlackBoxSubscriberTckTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.media.sse.internal;
+
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.tck.SubscriberBlackboxVerification;
+import org.reactivestreams.tck.TestEnvironment;
+
+public class JerseyEventSinkBlackBoxSubscriberTckTest extends SubscriberBlackboxVerification<Object> {
+
+ static final TestEnvironment env = new TestEnvironment(250);
+
+ public JerseyEventSinkBlackBoxSubscriberTckTest() {
+ super(env);
+ }
+
+ @Override
+ public Subscriber<Object> createSubscriber() {
+ JerseyEventSink jerseyEventSink = new JerseyEventSink(null);
+ return JerseyFlowAdapters.toSubscriber(jerseyEventSink);
+ }
+
+ @Override
+ public String createElement(final int i) {
+ return "test" + i;
+ }
+}
diff --git a/tests/integration/reactive-streams/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyEventSinkWhiteBoxSubscriberTckTest.java b/tests/integration/reactive-streams/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyEventSinkWhiteBoxSubscriberTckTest.java
new file mode 100644
index 0000000..6aa5a86
--- /dev/null
+++ b/tests/integration/reactive-streams/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyEventSinkWhiteBoxSubscriberTckTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.media.sse.internal;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static org.testng.Assert.fail;
+
+import org.glassfish.jersey.internal.jsr166.Flow;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.tck.SubscriberWhiteboxVerification;
+import org.reactivestreams.tck.TestEnvironment;
+import org.testng.TestException;
+import org.testng.annotations.Test;
+
+public class JerseyEventSinkWhiteBoxSubscriberTckTest extends SubscriberWhiteboxVerification<Object> {
+
+ static final TestEnvironment env = new TestEnvironment(250);
+
+ public JerseyEventSinkWhiteBoxSubscriberTckTest() {
+ super(env);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void noopOnNextAfterClose() throws InterruptedException {
+ WhiteboxTestStage stage = new WhiteboxTestStage(env, true);
+ SubscriberPuppet puppet = stage.puppet();
+ WhiteboxSubscriberProbe<Object> probe = stage.probe;
+ JerseyEventSink eventSink = (JerseyEventSink)
+ ((JerseyFlowAdapters.AdaptedSubscriber<Object>) stage.sub()).jerseySubscriber;
+ puppet.triggerRequest(2);
+ stage.expectRequest();
+ probe.expectNext(stage.signalNext());
+ probe.expectNext(stage.signalNext());
+
+ puppet.triggerRequest(3000);
+ eventSink.close();
+ stage.expectCancelling();
+ stage.signalNext();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void noopOnCompleteAfterClose() throws InterruptedException {
+ WhiteboxTestStage stage = new WhiteboxTestStage(env, true);
+ SubscriberPuppet puppet = stage.puppet();
+ WhiteboxSubscriberProbe<Object> probe = stage.probe;
+ JerseyEventSink eventSink = (JerseyEventSink)
+ ((JerseyFlowAdapters.AdaptedSubscriber<Object>) stage.sub()).jerseySubscriber;
+ puppet.triggerRequest(2);
+ stage.expectRequest();
+ probe.expectNext(stage.signalNext());
+ probe.expectNext(stage.signalNext());
+
+ puppet.triggerRequest(3000);
+ eventSink.close();
+ stage.sendCompletion();
+ probe.expectCompletion();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void noopOnErrorAfterClose() throws InterruptedException {
+ WhiteboxTestStage stage = new WhiteboxTestStage(env, true);
+ SubscriberPuppet puppet = stage.puppet();
+ WhiteboxSubscriberProbe<Object> probe = stage.probe;
+ JerseyEventSink eventSink = (JerseyEventSink)
+ ((JerseyFlowAdapters.AdaptedSubscriber<Object>) stage.sub()).jerseySubscriber;
+ puppet.triggerRequest(2);
+ stage.expectRequest();
+ probe.expectNext(stage.signalNext());
+ probe.expectNext(stage.signalNext());
+
+ puppet.triggerRequest(3000);
+ eventSink.close();
+
+ TestException testException = new TestException("BOOM JERSEY!");
+
+ stage.sendError(testException);
+ probe.expectError(testException);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void cancelSubscriptionAfterClose() throws InterruptedException {
+ WhiteboxTestStage stage = new WhiteboxTestStage(env, true);
+ SubscriberPuppet puppet = stage.puppet();
+ WhiteboxSubscriberProbe<Object> probe = stage.probe;
+ JerseyEventSink eventSink = (JerseyEventSink)
+ ((JerseyFlowAdapters.AdaptedSubscriber<Object>) stage.sub()).jerseySubscriber;
+ puppet.triggerRequest(2);
+ stage.expectRequest();
+ probe.expectNext(stage.signalNext());
+ probe.expectNext(stage.signalNext());
+
+ puppet.triggerRequest(3000);
+ eventSink.close();
+
+ stage.expectCancelling();
+
+ CompletableFuture<Void> cancelled2ndSubscription = new CompletableFuture<>();
+
+ eventSink.onSubscribe(new Flow.Subscription() {
+ @Override
+ public void request(final long n) {
+
+ }
+
+ @Override
+ public void cancel() {
+ cancelled2ndSubscription.complete(null);
+ }
+ });
+
+ try {
+ cancelled2ndSubscription.get(env.defaultTimeoutMillis(), TimeUnit.MILLISECONDS);
+ } catch (ExecutionException | TimeoutException e) {
+ fail("Cancel is expected on subscription on closed JerseyEventSink");
+ }
+ }
+
+ @Override
+ public Subscriber<Object> createSubscriber(final WhiteboxSubscriberProbe<Object> probe) {
+ JerseyEventSink jerseyEventSink = new JerseyEventSink(null) {
+ @Override
+ public void onSubscribe(final Flow.Subscription subscription) {
+ super.onSubscribe(subscription);
+ probe.registerOnSubscribe(new SubscriberPuppet() {
+ @Override
+ public void triggerRequest(final long elements) {
+ subscription.request(elements);
+ }
+
+ @Override
+ public void signalCancel() {
+ subscription.cancel();
+ }
+ });
+ }
+
+ @Override
+ public void onNext(final Object item) {
+ super.onNext(item);
+ probe.registerOnNext(item);
+ }
+
+ @Override
+ public void onError(final Throwable throwable) {
+ super.onError(throwable);
+ probe.registerOnError(throwable);
+ }
+
+ @Override
+ public void onComplete() {
+ super.onComplete();
+ probe.registerOnComplete();
+ }
+ };
+ return JerseyFlowAdapters.toSubscriber(jerseyEventSink);
+ }
+
+ @Override
+ public String createElement(final int i) {
+ return "test" + i;
+ }
+}
diff --git a/tests/integration/reactive-streams/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyFlowAdapters.java b/tests/integration/reactive-streams/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyFlowAdapters.java
new file mode 100644
index 0000000..5740359
--- /dev/null
+++ b/tests/integration/reactive-streams/sse/src/test/java/org/glassfish/jersey/media/sse/internal/JerseyFlowAdapters.java
@@ -0,0 +1,73 @@
+/*
+ * 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.media.sse.internal;
+
+import org.glassfish.jersey.internal.jsr166.Flow;
+
+public class JerseyFlowAdapters {
+
+ /**
+ * Adapt {@link org.glassfish.jersey.internal.jsr166.Flow.Subscriber} to
+ * {@link org.reactivestreams.Subscriber}.
+ *
+ * @param jerseySubscriber Jersey's repackaged {@link org.glassfish.jersey.internal.jsr166.Flow.Subscriber}
+ * @param <T> payload type
+ * @return Reactive Streams's {@link org.reactivestreams.Subscriber}
+ */
+ static <T> org.reactivestreams.Subscriber<T> toSubscriber(Flow.Subscriber<T> jerseySubscriber) {
+ return new AdaptedSubscriber<T>(jerseySubscriber);
+ }
+
+ public static class AdaptedSubscriber<T> implements org.reactivestreams.Subscriber<T> {
+
+ public final Flow.Subscriber<T> jerseySubscriber;
+
+ public AdaptedSubscriber(Flow.Subscriber<T> jerseySubscriber) {
+ this.jerseySubscriber = jerseySubscriber;
+ }
+
+ @Override
+ public void onSubscribe(final org.reactivestreams.Subscription subscription) {
+ jerseySubscriber.onSubscribe(new Flow.Subscription() {
+ @Override
+ public void request(final long n) {
+ subscription.request(n);
+ }
+
+ @Override
+ public void cancel() {
+ subscription.cancel();
+ }
+ });
+ }
+
+ @Override
+ public void onNext(final T t) {
+ jerseySubscriber.onNext(t);
+ }
+
+ @Override
+ public void onError(final Throwable throwable) {
+ jerseySubscriber.onError(throwable);
+ }
+
+ @Override
+ public void onComplete() {
+ jerseySubscriber.onComplete();
+ }
+ }
+}
diff --git a/tests/integration/reactive-streams/sse/src/test/java/org/glassfish/jersey/media/sse/internal/SseSubscriberTest.java b/tests/integration/reactive-streams/sse/src/test/java/org/glassfish/jersey/media/sse/internal/SseSubscriberTest.java
new file mode 100644
index 0000000..ef2ce16
--- /dev/null
+++ b/tests/integration/reactive-streams/sse/src/test/java/org/glassfish/jersey/media/sse/internal/SseSubscriberTest.java
@@ -0,0 +1,313 @@
+/*
+ * 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.media.sse.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import jakarta.inject.Singleton;
+import jakarta.json.Json;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.sse.SseEventSource;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import io.reactivex.Flowable;
+import org.glassfish.jersey.internal.jsr166.Flow;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+/**
+ * @author Daniel Kec
+ */
+public class SseSubscriberTest extends JerseyTest {
+
+ private static final int NUMBER_OF_TEST_MESSAGES = 5;
+ private static final String TEST_MESSAGE = "Jersey";
+ private static final JsonBuilderFactory JSON_BUILDER = Json.createBuilderFactory(Collections.emptyMap());
+
+ @Override
+ protected Application configure() {
+ return new ResourceConfig(SseEndpoint.class);
+ }
+
+ @Singleton
+ @Path("sse")
+ public static class SseEndpoint {
+
+ @GET
+ @Path("short")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void sseShort(@Context Flow.Subscriber<Short> subscriber) {
+ Flowable.interval(20, TimeUnit.MILLISECONDS)
+ .take(NUMBER_OF_TEST_MESSAGES)
+ .map(Long::shortValue)
+ .subscribe(JerseyFlowAdapters.toSubscriber(subscriber));
+ }
+
+ @GET
+ @Path("double")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void sseDouble(@Context Flow.Subscriber<Double> subscriber) {
+ Flowable.interval(20, TimeUnit.MILLISECONDS)
+ .take(NUMBER_OF_TEST_MESSAGES)
+ .map(Long::doubleValue)
+ .subscribe(JerseyFlowAdapters.toSubscriber(subscriber));
+ }
+
+ @GET
+ @Path("byte")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void sseByte(@Context Flow.Subscriber<Byte> subscriber) {
+ Flowable.interval(20, TimeUnit.MILLISECONDS)
+ .take(NUMBER_OF_TEST_MESSAGES)
+ .map(Long::byteValue)
+ .subscribe(JerseyFlowAdapters.toSubscriber(subscriber));
+ }
+
+ @GET
+ @Path("integer")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void sseInteger(@Context Flow.Subscriber<Integer> subscriber) {
+ Flowable.interval(20, TimeUnit.MILLISECONDS)
+ .take(NUMBER_OF_TEST_MESSAGES)
+ .map(Long::intValue)
+ .subscribe(JerseyFlowAdapters.toSubscriber(subscriber));
+ }
+
+ @GET
+ @Path("long")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void sseLong(@Context Flow.Subscriber<Long> subscriber) {
+ Flowable.just(0L, 1L, 2L, 3L, 4L)
+ .take(NUMBER_OF_TEST_MESSAGES)
+ .subscribe(JerseyFlowAdapters.toSubscriber(subscriber));
+ }
+
+ @GET
+ @Path("string")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void sseString(@Context Flow.Subscriber<String> subscriber) {
+ Flowable.interval(20, TimeUnit.MILLISECONDS)
+ .take(NUMBER_OF_TEST_MESSAGES)
+ .map(l -> TEST_MESSAGE + l)
+ .subscribe(JerseyFlowAdapters.toSubscriber(subscriber));
+ }
+
+ @GET
+ @Path("boolean")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void sseBoolean(@Context Flow.Subscriber<Boolean> subscriber) {
+ Flowable.interval(20, TimeUnit.MILLISECONDS)
+ .take(NUMBER_OF_TEST_MESSAGES)
+ .map(l -> (l % 2) == 0)
+ .subscribe(JerseyFlowAdapters.toSubscriber(subscriber));
+ }
+
+ @GET
+ @Path("char")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void sseChar(@Context Flow.Subscriber<Character> subscriber) {
+ Flowable.just("FRANK")
+ .flatMap(s -> Flowable.fromArray(s.chars().mapToObj(ch -> (char) ch).toArray(Character[]::new)))
+ .subscribe(JerseyFlowAdapters.toSubscriber(subscriber));
+ }
+
+ @GET
+ @Path("json-obj")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void sseJsonObj(@Context Flow.Subscriber<JsonObject> subscriber) {
+ Flowable.interval(20, TimeUnit.MILLISECONDS)
+ .take(NUMBER_OF_TEST_MESSAGES)
+ .map(l -> JSON_BUILDER.createObjectBuilder()
+ .add("brand", TEST_MESSAGE)
+ .add("model", "Model " + l)
+ .build())
+ .subscribe(JerseyFlowAdapters.toSubscriber(subscriber));
+ }
+
+ @GET
+ @Path("json")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void sseJson(@Context Flow.Subscriber<Car> subscriber) {
+ Flowable.interval(20, TimeUnit.MILLISECONDS)
+ .take(NUMBER_OF_TEST_MESSAGES)
+ .map(l -> new Car(TEST_MESSAGE, "Model " + l))
+ .subscribe(JerseyFlowAdapters.toSubscriber(subscriber));
+ }
+ }
+
+
+ @Test
+ public void testShort() throws InterruptedException {
+ assertEquals(Arrays.asList((short) 0, (short) 1, (short) 2, (short) 3, (short) 4), receive(Short.class, "sse/short"));
+ }
+
+ @Test
+ public void testDouble() throws InterruptedException {
+ assertEquals(Arrays.asList(0.0, 1.0, 2.0, 3.0, 4.0), receive(Double.class, "sse/double"));
+ }
+
+ @Test
+ public void testByte() throws InterruptedException {
+ assertEquals(Arrays.asList((byte) 0, (byte) 1, (byte) 2, (byte) 3, (byte) 4), receive(Byte.class, "sse/byte"));
+ }
+
+ @Test
+ public void testInteger() throws InterruptedException {
+ assertEquals(Arrays.asList(0, 1, 2, 3, 4), receive(Integer.class, "sse/integer"));
+ }
+
+ @Test
+ public void testBoolean() throws InterruptedException {
+ assertEquals(Arrays.asList(true, false, true, false, true), receive(Boolean.class, "sse/boolean"));
+ }
+
+ @Test
+ public void testLong() throws InterruptedException {
+ assertEquals(Arrays.asList(0L, 1L, 2L, 3L, 4L), receive(Long.class, "sse/long"));
+ }
+
+ @Test
+ public void testString() throws InterruptedException {
+ assertEquals(Arrays.asList(TEST_MESSAGE + 0, TEST_MESSAGE + 1, TEST_MESSAGE + 2, TEST_MESSAGE + 3, TEST_MESSAGE + 4),
+ receive(String.class, "sse/string"));
+ }
+
+ @Test
+ public void testChar() throws InterruptedException {
+ assertEquals(Arrays.asList('F', 'R', 'A', 'N', 'K'),
+ receive(Character.class, "sse/char"));
+ }
+
+ @Test
+ public void testJsonObj() throws InterruptedException {
+ Jsonb jsonb = JsonbBuilder.create();
+ assertEquals(Arrays.asList(
+ new Car(TEST_MESSAGE, "Model 0"),
+ new Car(TEST_MESSAGE, "Model 1"),
+ new Car(TEST_MESSAGE, "Model 2"),
+ new Car(TEST_MESSAGE, "Model 3"),
+ new Car(TEST_MESSAGE, "Model 4")
+ ),
+ receive(String.class, "sse/json-obj")
+ .stream()
+ .map(s -> jsonb.fromJson(s, Car.class))
+ .collect(Collectors.toList()));
+ }
+
+ @Test
+ public void testJson() throws InterruptedException {
+ Jsonb jsonb = JsonbBuilder.create();
+ assertEquals(Arrays.asList(
+ new Car(TEST_MESSAGE, "Model 0"),
+ new Car(TEST_MESSAGE, "Model 1"),
+ new Car(TEST_MESSAGE, "Model 2"),
+ new Car(TEST_MESSAGE, "Model 3"),
+ new Car(TEST_MESSAGE, "Model 4")
+ ),
+ receive(String.class, "sse/json")
+ .stream()
+ .map(s -> jsonb.fromJson(s, Car.class))
+ .collect(Collectors.toList()));
+ }
+
+ private <T> List<T> receive(Class<T> type, String path) throws InterruptedException {
+ WebTarget sseTarget = target(path);
+
+ ArrayList<T> result = new ArrayList<>(NUMBER_OF_TEST_MESSAGES);
+
+ final CountDownLatch eventLatch = new CountDownLatch(NUMBER_OF_TEST_MESSAGES);
+ SseEventSource eventSource = SseEventSource.target(sseTarget).build();
+ eventSource.register((event) -> {
+ System.out.println("### Client received: " + event);
+ result.add(event.readData(type));
+ eventLatch.countDown();
+ });
+ eventSource.open();
+
+ // client waiting for confirmation that resource method ended.
+ assertTrue(eventLatch.await(2, TimeUnit.SECONDS));
+ return result;
+ }
+
+ public static class Car {
+ private String brand;
+ private String model;
+
+ public Car() {
+ }
+
+ public Car(final String brand, final String model) {
+ this.brand = brand;
+ this.model = model;
+ }
+
+ public String getBrand() {
+ return brand;
+ }
+
+ public void setBrand(final String brand) {
+ this.brand = brand;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public void setModel(final String model) {
+ this.model = model;
+ }
+
+ @Override
+ public String toString() {
+ return "Car{brand='" + brand + "', model='" + model + "'}";
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Car car = (Car) o;
+ return Objects.equals(brand, car.brand)
+ && Objects.equals(model, car.model);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(brand, model);
+ }
+ }
+}
diff --git a/tests/integration/reactive-streams/sse/tck-suite.xml b/tests/integration/reactive-streams/sse/tck-suite.xml
new file mode 100644
index 0000000..533b8d0
--- /dev/null
+++ b/tests/integration/reactive-streams/sse/tck-suite.xml
@@ -0,0 +1,28 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+<!--
+
+ 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
+
+-->
+<suite name="reactive-streams-sse-TCK" verbose="2" configfailurepolicy="continue">
+
+ <test name="reactive-streams-sse TCK">
+ <packages>
+ <package name="org.glassfish.jersey.media.sse.internal.*">
+ </package>
+ </packages>
+ </test>
+
+</suite>
\ No newline at end of file
diff --git a/tools/jersey-doc-modulelist-maven-plugin/pom.xml b/tools/jersey-doc-modulelist-maven-plugin/pom.xml
index aa70520..95173ab 100644
--- a/tools/jersey-doc-modulelist-maven-plugin/pom.xml
+++ b/tools/jersey-doc-modulelist-maven-plugin/pom.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Copyright (c) 2013, 2018 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
@@ -24,7 +24,7 @@
<groupId>org.glassfish.jersey.tools.plugins</groupId>
<artifactId>jersey-doc-modulelist-maven-plugin</artifactId>
<packaging>maven-plugin</packaging>
- <version>1.0.1</version>
+ <version>1.0.2</version>
<name>jersey-doc-modulelist-maven-plugin</name>
<description>
@@ -51,7 +51,7 @@
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-dependency-tree</artifactId>
- <version>2.0</version>
+ <version>${maven.shared.version}</version>
</dependency>
</dependencies>
@@ -79,6 +79,7 @@
<properties>
<java.version>1.8</java.version>
- <maven.version>3.1.1</maven.version>
+ <maven.version>3.6.3</maven.version>
+ <maven.shared.version>3.0.1</maven.shared.version>
</properties>
</project>