From d94718bfa9c07fa9c12e6563bf730f0a025f1ed4 Mon Sep 17 00:00:00 2001 From: Jan Supol Date: Thu, 7 May 2020 13:50:57 +0200 Subject: [PATCH 1/3] Offer the user (partial) response in ProcessingException. In the HttpUrlConnector, when there is a response, throw ResponseProcessingException instead of a simple ProcessingException. Signed-off-by: Jan Supol --- .../jersey/client/ClientRuntime.java | 6 +- .../jersey/client/JerseyInvocation.java | 194 +++++++------ .../ClientResponseProcessingException.java | 50 ++++ .../client/internal/HttpUrlConnector.java | 87 +++--- .../e2e/MessageBodyExceptionWrappingTest.java | 12 +- tests/integration/jersey-4003/pom.xml | 29 ++ .../jersey4003/LostResponseTest.java | 260 ++++++++++++++++++ tests/integration/pom.xml | 1 + 8 files changed, 504 insertions(+), 135 deletions(-) create mode 100644 core-client/src/main/java/org/glassfish/jersey/client/internal/ClientResponseProcessingException.java create mode 100644 tests/integration/jersey-4003/pom.xml create mode 100644 tests/integration/jersey-4003/src/test/java/org/glassfish/jersey/tests/integration/jersey4003/LostResponseTest.java diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientRuntime.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientRuntime.java index 9d5ef02077..e19d787030 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/ClientRuntime.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientRuntime.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -34,6 +34,7 @@ import javax.inject.Provider; +import org.glassfish.jersey.client.internal.ClientResponseProcessingException; import org.glassfish.jersey.client.internal.LocalizationMessages; import org.glassfish.jersey.client.spi.AsyncConnectorCallback; import org.glassfish.jersey.client.spi.Connector; @@ -299,6 +300,9 @@ public ClientResponse invoke(final ClientRequest request) { } response = Stages.process(response, responseProcessingRoot); + } catch (final ClientResponseProcessingException crpe) { + processingException = crpe; + response = crpe.getClientResponse(); } catch (final ProcessingException pe) { processingException = pe; } catch (final Throwable t) { diff --git a/core-client/src/main/java/org/glassfish/jersey/client/JerseyInvocation.java b/core-client/src/main/java/org/glassfish/jersey/client/JerseyInvocation.java index 22ee42ce77..ceb9009c51 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/JerseyInvocation.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/JerseyInvocation.java @@ -25,6 +25,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import java.util.function.BiFunction; import java.util.logging.Logger; import javax.ws.rs.BadRequestException; @@ -57,6 +58,7 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; +import org.glassfish.jersey.client.internal.ClientResponseProcessingException; import org.glassfish.jersey.client.internal.LocalizationMessages; import org.glassfish.jersey.internal.MapPropertiesDelegate; import org.glassfish.jersey.internal.inject.Providers; @@ -612,9 +614,10 @@ private ClientRequest requestForCall(final ClientRequest requestContext) { public Response invoke() throws ProcessingException, WebApplicationException { final ClientRuntime runtime = request().getClientRuntime(); final RequestScope requestScope = runtime.getRequestScope(); - return requestScope.runInScope( - (Producer) () -> new InboundJaxrsResponse(runtime.invoke(requestForCall(requestContext)), - requestScope)); + + return runInScope(((Producer) () -> + new InboundJaxrsResponse(runtime.invoke(requestForCall(requestContext)), requestScope)), + requestScope); } @Override @@ -624,17 +627,9 @@ public T invoke(final Class responseType) throws ProcessingException, Web } final ClientRuntime runtime = request().getClientRuntime(); final RequestScope requestScope = runtime.getRequestScope(); - //noinspection Duplicates - return requestScope.runInScope(() -> { - try { - return translate(runtime.invoke(requestForCall(requestContext)), requestScope, responseType); - } catch (final ProcessingException ex) { - if (ex.getCause() instanceof WebApplicationException) { - throw (WebApplicationException) ex.getCause(); - } - throw ex; - } - }); + + return runInScope(() -> + translate(runtime.invoke(requestForCall(requestContext)), requestScope, responseType), requestScope); } @Override @@ -644,41 +639,37 @@ public T invoke(final GenericType responseType) throws ProcessingExceptio } final ClientRuntime runtime = request().getClientRuntime(); final RequestScope requestScope = runtime.getRequestScope(); - //noinspection Duplicates - return requestScope.runInScope(() -> { - try { - return translate(runtime.invoke(requestForCall(requestContext)), requestScope, responseType); - } catch (final ProcessingException ex) { - if (ex.getCause() instanceof WebApplicationException) { - throw (WebApplicationException) ex.getCause(); - } - throw ex; + + return runInScope(() -> + translate(runtime.invoke(requestForCall(requestContext)), requestScope, responseType), requestScope); + } + + private T runInScope(Producer producer, RequestScope scope) throws ProcessingException, WebApplicationException { + return scope.runInScope(() -> call(producer, scope)); + } + + private T call(Producer producer, RequestScope scope) + throws ProcessingException, WebApplicationException { + try { + return producer.call(); + } catch (final ClientResponseProcessingException crpe) { + throw new ResponseProcessingException( + translate(crpe.getClientResponse(), scope, Response.class), crpe.getCause() + ); + } catch (final ProcessingException ex) { + if (WebApplicationException.class.isInstance(ex.getCause())) { + throw (WebApplicationException) ex.getCause(); } - }); + throw ex; + } } @Override public Future submit() { final CompletableFuture responseFuture = new CompletableFuture<>(); final ClientRuntime runtime = request().getClientRuntime(); - runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), new ResponseCallback() { - - @Override - public void completed(final ClientResponse response, final RequestScope scope) { - if (!responseFuture.isCancelled()) { - responseFuture.complete(new InboundJaxrsResponse(response, scope)); - } else { - response.close(); - } - } - - @Override - public void failed(final ProcessingException error) { - if (!responseFuture.isCancelled()) { - responseFuture.completeExceptionally(error); - } - } - })); + runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), + new InvocationResponseCallback<>(responseFuture, (request, scope) -> translate(request, scope, Response.class)))); return responseFuture; } @@ -689,35 +680,10 @@ public Future submit(final Class responseType) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } final CompletableFuture responseFuture = new CompletableFuture<>(); - //noinspection Duplicates final ClientRuntime runtime = request().getClientRuntime(); - runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), new ResponseCallback() { - @Override - public void completed(final ClientResponse response, final RequestScope scope) { - if (responseFuture.isCancelled()) { - response.close(); - return; - } - try { - responseFuture.complete(translate(response, scope, responseType)); - } catch (final ProcessingException ex) { - failed(ex); - } - } - - @Override - public void failed(final ProcessingException error) { - if (responseFuture.isCancelled()) { - return; - } - if (error.getCause() instanceof WebApplicationException) { - responseFuture.completeExceptionally(error.getCause()); - } else { - responseFuture.completeExceptionally(error); - } - } - })); + runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), + new InvocationResponseCallback(responseFuture, (request, scope) -> translate(request, scope, responseType)))); return responseFuture; } @@ -753,36 +719,10 @@ public Future submit(final GenericType responseType) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } final CompletableFuture responseFuture = new CompletableFuture<>(); - //noinspection Duplicates final ClientRuntime runtime = request().getClientRuntime(); - runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), new ResponseCallback() { - @Override - public void completed(final ClientResponse response, final RequestScope scope) { - if (responseFuture.isCancelled()) { - response.close(); - return; - } - - try { - responseFuture.complete(translate(response, scope, responseType)); - } catch (final ProcessingException ex) { - failed(ex); - } - } - - @Override - public void failed(final ProcessingException error) { - if (responseFuture.isCancelled()) { - return; - } - if (error.getCause() instanceof WebApplicationException) { - responseFuture.completeExceptionally(error.getCause()); - } else { - responseFuture.completeExceptionally(error); - } - } - })); + runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), + new InvocationResponseCallback(responseFuture, (request, scope) -> translate(request, scope, responseType)))); return responseFuture; } @@ -883,14 +823,24 @@ public void completed(final ClientResponse response, final RequestScope scope) { @Override public void failed(final ProcessingException error) { + Exception called = null; try { if (error.getCause() instanceof WebApplicationException) { responseFuture.completeExceptionally(error.getCause()); } else if (!responseFuture.isCancelled()) { - responseFuture.completeExceptionally(error); + try { + call(() -> { throw error; }, null); + } catch (Exception ex) { + called = ex; + responseFuture.completeExceptionally(ex); + } } } finally { - callback.failed(error.getCause() instanceof CancellationException ? error.getCause() : error); + callback.failed( + error.getCause() instanceof CancellationException + ? error.getCause() + : called != null ? called : error + ); } } }; @@ -899,7 +849,10 @@ public void failed(final ProcessingException error) { } catch (final Throwable error) { final ProcessingException ce; //noinspection ChainOfInstanceofChecks - if (error instanceof ProcessingException) { + if (error instanceof ClientResponseProcessingException) { + ce = new ProcessingException(error.getCause()); + responseFuture.completeExceptionally(ce); + } else if (error instanceof ProcessingException) { ce = (ProcessingException) error; responseFuture.completeExceptionally(ce); } else if (error instanceof WebApplicationException) { @@ -1006,4 +959,45 @@ ClientRequest request() { public String toString() { return "JerseyInvocation [" + request().getMethod() + ' ' + request().getUri() + "]"; } + + private class InvocationResponseCallback implements ResponseCallback { + private final CompletableFuture responseFuture; + private final BiFunction producer; + + private InvocationResponseCallback(CompletableFuture responseFuture, + BiFunction producer) { + this.responseFuture = responseFuture; + this.producer = producer; + } + + @Override + public void completed(final ClientResponse response, final RequestScope scope) { + if (responseFuture.isCancelled()) { + response.close(); + return; + } + + + try { + responseFuture.complete(producer.apply(response, scope)); + } catch (final ProcessingException ex) { + failed(ex); + } + } + + @Override + public void failed(final ProcessingException error) { + if (responseFuture.isCancelled()) { + return; + } + + try { + call(() -> { + throw error; + }, null); + } catch (Exception exception) { + responseFuture.completeExceptionally(exception); + } + } + } } diff --git a/core-client/src/main/java/org/glassfish/jersey/client/internal/ClientResponseProcessingException.java b/core-client/src/main/java/org/glassfish/jersey/client/internal/ClientResponseProcessingException.java new file mode 100644 index 0000000000..ef38b636ac --- /dev/null +++ b/core-client/src/main/java/org/glassfish/jersey/client/internal/ClientResponseProcessingException.java @@ -0,0 +1,50 @@ +/* + * 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.internal; + +import org.glassfish.jersey.client.ClientResponse; + +import javax.ws.rs.ProcessingException; + +/** + * This is a representation of a @{link ProcessingException} containing a @{link ClientResponse} instance. + * This exception is meant to be converted to a {@code ResponseProcessingException} at a point where + * {@link ClientResponse} is converted to a {@code Response} before it is delivered to a user. + * @since 2.31 + */ +public class ClientResponseProcessingException extends ProcessingException { + private static final long serialVersionUID = 3389677946623416847L; + private final ClientResponse clientResponse; + + /** + * An instance of {@code ClientResponseProcessingException} containing {@link ClientResponse} and cause {@link Throwable}. + * @param clientResponse a {@link ClientResponse} to be converted to {@code Response}. + * @param cause a cause of the exception. + */ + public ClientResponseProcessingException(ClientResponse clientResponse, Throwable cause) { + super(cause); + this.clientResponse = clientResponse; + } + + /** + * Return a {@link ClientResponse} to be converted to {@code Response} to be put to a {@code ResponseProcessingException}. + * @return a {@link ClientResponse} to be converted to {@code Response}. + */ + public ClientResponse getClientResponse() { + return clientResponse; + } +} 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 b84c5b3a42..a4255a7f7c 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -328,51 +328,61 @@ private ClientResponse _apply(final ClientRequest request) throws IOException { secureConnection(request.getClient(), uc); final Object entity = request.getEntity(); - if (entity != null) { - RequestEntityProcessing entityProcessing = request.resolveProperty( - ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.class); - - if (entityProcessing == null || entityProcessing != RequestEntityProcessing.BUFFERED) { - final long length = request.getLengthLong(); - if (fixLengthStreaming && length > 0) { - // uc.setFixedLengthStreamingMode(long) was introduced in JDK 1.7 and Jersey client supports 1.6+ - if ("1.6".equals(Runtime.class.getPackage().getSpecificationVersion())) { - uc.setFixedLengthStreamingMode(request.getLength()); - } else { + Exception storedException = null; + try { + if (entity != null) { + RequestEntityProcessing entityProcessing = request.resolveProperty( + ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.class); + + if (entityProcessing == null || entityProcessing != RequestEntityProcessing.BUFFERED) { + final long length = request.getLengthLong(); + if (fixLengthStreaming && length > 0) { uc.setFixedLengthStreamingMode(length); + } else if (entityProcessing == RequestEntityProcessing.CHUNKED) { + uc.setChunkedStreamingMode(chunkSize); } - } else if (entityProcessing == RequestEntityProcessing.CHUNKED) { - uc.setChunkedStreamingMode(chunkSize); } - } - uc.setDoOutput(true); + uc.setDoOutput(true); - if ("GET".equalsIgnoreCase(httpMethod)) { - final Logger logger = Logger.getLogger(HttpUrlConnector.class.getName()); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, LocalizationMessages.HTTPURLCONNECTION_REPLACES_GET_WITH_ENTITY()); + if ("GET".equalsIgnoreCase(httpMethod)) { + final Logger logger = Logger.getLogger(HttpUrlConnector.class.getName()); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, LocalizationMessages.HTTPURLCONNECTION_REPLACES_GET_WITH_ENTITY()); + } } - } - request.setStreamProvider(contentLength -> { - setOutboundHeaders(request.getStringHeaders(), uc); - return uc.getOutputStream(); - }); - request.writeEntity(); + request.setStreamProvider(contentLength -> { + setOutboundHeaders(request.getStringHeaders(), uc); + return uc.getOutputStream(); + }); + request.writeEntity(); - } else { - setOutboundHeaders(request.getStringHeaders(), uc); + } else { + setOutboundHeaders(request.getStringHeaders(), uc); + } + } catch (IOException ioe) { + if (uc.getResponseCode() == -1) { + throw ioe; + } else { + storedException = ioe; + } } final int code = uc.getResponseCode(); final String reasonPhrase = uc.getResponseMessage(); final Response.StatusType status = reasonPhrase == null ? Statuses.from(code) : Statuses.from(code, reasonPhrase); - final URI resolvedRequestUri; + + URI resolvedRequestUri = null; try { resolvedRequestUri = uc.getURL().toURI(); } catch (URISyntaxException e) { - throw new ProcessingException(e); + // if there is already an exception stored, the stored exception is what matters most + if (storedException == null) { + storedException = e; + } else { + storedException.addSuppressed(e); + } } ClientResponse responseContext = new ClientResponse(status, request, resolvedRequestUri); @@ -384,7 +394,22 @@ private ClientResponse _apply(final ClientRequest request) throws IOException { .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) ); - responseContext.setEntityStream(getInputStream(uc)); + + try { + InputStream inputStream = getInputStream(uc); + responseContext.setEntityStream(inputStream); + } catch (IOException ioe) { + // allow at least a partial response in a ResponseProcessingException + if (storedException == null) { + storedException = ioe; + } else { + storedException.addSuppressed(ioe); + } + } + + if (storedException != null) { + throw new ClientResponseProcessingException(responseContext, storedException); + } return responseContext; } diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/MessageBodyExceptionWrappingTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/MessageBodyExceptionWrappingTest.java index 2b5d2e6cff..df94cdb57e 100644 --- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/MessageBodyExceptionWrappingTest.java +++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/MessageBodyExceptionWrappingTest.java @@ -72,9 +72,15 @@ public InputStream getInputStream() { try { Response response = resource.request().post(Entity.entity(source, MediaType.TEXT_XML_TYPE)); fail("Exception expected, instead response with " + response.getStatus() + " status has been returned."); - } catch (ProcessingException e) { - assertEquals(WebApplicationException.class, e.getCause().getClass()); - assertEquals(555, ((WebApplicationException) e.getCause()).getResponse().getStatus()); + } catch (WebApplicationException e) { + assertEquals(555, e.getResponse().getStatus()); + } + + try { + Response response = resource.request().post(Entity.entity(source, MediaType.TEXT_XML_TYPE), Response.class); + fail("Exception expected, instead response with " + response.getStatus() + " status has been returned."); + } catch (WebApplicationException e) { + assertEquals(555, e.getResponse().getStatus()); } } diff --git a/tests/integration/jersey-4003/pom.xml b/tests/integration/jersey-4003/pom.xml new file mode 100644 index 0000000000..9dd33b9959 --- /dev/null +++ b/tests/integration/jersey-4003/pom.xml @@ -0,0 +1,29 @@ + + + + project + org.glassfish.jersey.tests.integration + 2.31-SNAPSHOT + + 4.0.0 + + jersey-4003 + jersey-tests-integration-jersey-4003 + + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-bundle + pom + test + + + + org.mockito + mockito-all + test + + + \ No newline at end of file diff --git a/tests/integration/jersey-4003/src/test/java/org/glassfish/jersey/tests/integration/jersey4003/LostResponseTest.java b/tests/integration/jersey-4003/src/test/java/org/glassfish/jersey/tests/integration/jersey4003/LostResponseTest.java new file mode 100644 index 0000000000..2ba8042961 --- /dev/null +++ b/tests/integration/jersey-4003/src/test/java/org/glassfish/jersey/tests/integration/jersey4003/LostResponseTest.java @@ -0,0 +1,260 @@ +/* + * 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.jersey4003; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.client.JerseyClientBuilder; +import org.glassfish.jersey.client.JerseyCompletionStageRxInvoker; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import org.mockito.Mockito; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.InvocationCallback; +import javax.ws.rs.client.ResponseProcessingException; +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +public class LostResponseTest { + + private static final String DUMMY_URL = "http://foo"; + private static final int RESPONSE_CODE = 503; + + private Client client; + private Entity bodyEntity; + + @Before + public void setup() throws IOException { + HttpUrlConnectorProvider.ConnectionFactory connectionFactory = + Mockito.mock(HttpUrlConnectorProvider.ConnectionFactory.class); + HttpURLConnection connection = Mockito.mock(HttpURLConnection.class); + Mockito.when(connectionFactory.getConnection(Mockito.any(URL.class))).thenReturn(connection); + + OutputStream outputStream = Mockito.mock(OutputStream.class); + Mockito.when(connection.getOutputStream()).thenReturn(outputStream); + + Mockito.when(connection.getURL()).thenReturn(new URL(DUMMY_URL)); + Mockito.when(connection.getResponseCode()).thenReturn(RESPONSE_CODE); + + // When the below line is commented, the test succeeds. + Mockito.doThrow(new IOException("Injected Write Failure")) + .when(outputStream) + .write(Mockito.any(byte[].class), Mockito.anyInt(), Mockito.anyInt()); + + ClientConfig clientConfig = new ClientConfig(); + clientConfig.connectorProvider( + new HttpUrlConnectorProvider().connectionFactory(connectionFactory)); + client = JerseyClientBuilder.newBuilder().withConfig(clientConfig).build(); + + ByteArrayInputStream bodyStream = new ByteArrayInputStream(new byte[100]); + bodyEntity = Entity.entity(bodyStream, MediaType.APPLICATION_OCTET_STREAM_TYPE); + } + + @Test + public void putEntityFailure() { + try { + client.target(DUMMY_URL).request().put(bodyEntity); + Assert.fail("Expected ResponseProcessing exception has not been thrown"); + } catch (ResponseProcessingException rpe) { + try (Response response = rpe.getResponse()) { + Assert.assertEquals(RESPONSE_CODE, response.getStatus()); + } + } + } + + @Test + public void putEntityAndClassTypeFailure() { + try { + client.target(DUMMY_URL).request().put(bodyEntity, String.class); + Assert.fail("Expected ResponseProcessing exception has not been thrown"); + } catch (ResponseProcessingException rpe) { + try (Response response = rpe.getResponse()) { + Assert.assertEquals(RESPONSE_CODE, response.getStatus()); + } + } + } + + @Test + public void putEntityAndGenericTypeTypeFailure() { + try { + client.target(DUMMY_URL).request().put(bodyEntity, new GenericType(){}); + Assert.fail("Expected ResponseProcessing exception has not been thrown"); + } catch (ResponseProcessingException rpe) { + try (Response response = rpe.getResponse()) { + Assert.assertEquals(RESPONSE_CODE, response.getStatus()); + } + } + } + + @Test + public void asyncPutEntityFailure() throws InterruptedException { + try { + Future future = client.target(DUMMY_URL).request().async().put(bodyEntity); + future.get(); + Assert.fail("Expected ResponseProcessing exception has not been thrown"); + } catch (ExecutionException ee) { + try { + throw (RuntimeException) ee.getCause(); + } catch (ResponseProcessingException rpe) { + try (Response response = rpe.getResponse()) { + Assert.assertEquals(RESPONSE_CODE, response.getStatus()); + } + } + } + } + + @Test + public void asyncPutEntityAndClassFailure() throws InterruptedException { + try { + Future future = client.target(DUMMY_URL).request().async().put(bodyEntity, String.class); + future.get(); + Assert.fail("Expected ResponseProcessing exception has not been thrown"); + } catch (ExecutionException ee) { + try { + throw (RuntimeException) ee.getCause(); + } catch (ResponseProcessingException rpe) { + try (Response response = rpe.getResponse()) { + Assert.assertEquals(RESPONSE_CODE, response.getStatus()); + } + } + } + } + + @Test + public void asyncPutEntityAndGenericTypeTypeFailure() throws InterruptedException { + try { + Future future = client.target(DUMMY_URL).request().async().put(bodyEntity, new GenericType(){}); + future.get(); + Assert.fail("Expected ResponseProcessing exception has not been thrown"); + } catch (ExecutionException ee) { + try { + throw (RuntimeException) ee.getCause(); + } catch (ResponseProcessingException rpe) { + try (Response response = rpe.getResponse()) { + Assert.assertEquals(RESPONSE_CODE, response.getStatus()); + } + } + } + } + + @Test + public void asyncPutEntityWithCallbackFailure() throws InterruptedException { + AtomicReference callbackThrowable = new AtomicReference<>(); + CountDownLatch failedLatch = new CountDownLatch(1); + try { + Future future = + client.target(DUMMY_URL).request().async().put(bodyEntity, new InvocationCallback() { + @Override + public void completed(Response response) { + Assert.fail("Expected ResponseProcessing exception has not been thrown"); + } + + @Override + public void failed(Throwable throwable) { + callbackThrowable.set(throwable); + failedLatch.countDown(); + } + }); + future.get(); + Assert.fail("Expected ResponseProcessing exception has not been thrown"); + } catch (ExecutionException ee) { + try { + throw (RuntimeException) ee.getCause(); + } catch (ResponseProcessingException rpe) { + try (Response response = rpe.getResponse()) { + Assert.assertEquals(RESPONSE_CODE, response.getStatus()); + } + } + failedLatch.await(5000, TimeUnit.MILLISECONDS); + Throwable ct = callbackThrowable.get(); + Assert.assertTrue("Callback has not been hit", ct != null); + Assert.assertTrue("The exception is " + ct.getClass().getName(), + ResponseProcessingException.class.isInstance(ct)); + } + } + + @Test + public void rxPutEntityFailure() throws InterruptedException { + try { + CompletionStage future = client.target(DUMMY_URL).request().rx().put(bodyEntity); + future.toCompletableFuture().get(); + Assert.fail("Expected ResponseProcessing exception has not been thrown"); + } catch (ExecutionException ee) { + try { + throw (RuntimeException) ee.getCause(); + } catch (ResponseProcessingException rpe) { + try (Response response = rpe.getResponse()) { + Assert.assertEquals(RESPONSE_CODE, response.getStatus()); + } + } + } + } + + @Test + public void rxPutEntityWithCallbackFailure() throws InterruptedException { + AtomicReference callbackThrowable = new AtomicReference<>(); + CountDownLatch failedLatch = new CountDownLatch(1); + try { + Future future = + client.target(DUMMY_URL).request().rx(JerseyCompletionStageRxInvoker.class) + .put(bodyEntity, new InvocationCallback() { + @Override + public void completed(Response response) { + Assert.fail("Expected ResponseProcessing exception has not been thrown"); + } + + @Override + public void failed(Throwable throwable) { + callbackThrowable.set(throwable); + failedLatch.countDown(); + } + }); + future.get(); + Assert.fail("Expected ResponseProcessing exception has not been thrown"); + } catch (ExecutionException ee) { + try { + throw (RuntimeException) ee.getCause(); + } catch (ResponseProcessingException rpe) { + try (Response response = rpe.getResponse()) { + Assert.assertEquals(RESPONSE_CODE, response.getStatus()); + } + } + failedLatch.await(5000, TimeUnit.MILLISECONDS); + Throwable ct = callbackThrowable.get(); + Assert.assertTrue("Callback has not been hit", ct != null); + Assert.assertTrue("The exception is " + ct.getClass().getName(), + ResponseProcessingException.class.isInstance(ct)); + } + } +} diff --git a/tests/integration/pom.xml b/tests/integration/pom.xml index 0fbda7efc3..76a043ba19 100644 --- a/tests/integration/pom.xml +++ b/tests/integration/pom.xml @@ -82,6 +82,7 @@ jersey-780 jersey-3670 jersey-3992 + jersey-4003 jersey-4099 jersey-4321 jetty-response-close From 286b11d009159587ab5b36a91c2ab53449272f10 Mon Sep 17 00:00:00 2001 From: Jan Supol Date: Mon, 11 May 2020 11:47:03 +0200 Subject: [PATCH 2/3] Update copyright year Signed-off-by: Jan Supol --- .../jersey/tests/e2e/MessageBodyExceptionWrappingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/MessageBodyExceptionWrappingTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/MessageBodyExceptionWrappingTest.java index df94cdb57e..c2446f4b9f 100644 --- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/MessageBodyExceptionWrappingTest.java +++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/MessageBodyExceptionWrappingTest.java @@ -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 From e5fe70d3a449e74f1eb5d1aaabab09ac829e610e Mon Sep 17 00:00:00 2001 From: Jan Supol Date: Mon, 11 May 2020 17:13:00 +0200 Subject: [PATCH 3/3] Added copyright header Signed-off-by: Jan Supol --- tests/integration/jersey-4003/pom.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/integration/jersey-4003/pom.xml b/tests/integration/jersey-4003/pom.xml index 9dd33b9959..ae005fca22 100644 --- a/tests/integration/jersey-4003/pom.xml +++ b/tests/integration/jersey-4003/pom.xml @@ -1,4 +1,21 @@ +