Skip to content

Commit

Permalink
Offer the Client (partial) response in ProcessingException. (#4460)
Browse files Browse the repository at this point in the history
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 <jan.supol@oracle.com>
  • Loading branch information
jansupol committed May 14, 2020
1 parent 1c68a58 commit 11d6c11
Show file tree
Hide file tree
Showing 8 changed files with 522 additions and 136 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<Response>) () -> new InboundJaxrsResponse(runtime.invoke(requestForCall(requestContext)),
requestScope));

return runInScope(((Producer<Response>) () ->
new InboundJaxrsResponse(runtime.invoke(requestForCall(requestContext)), requestScope)),
requestScope);
}

@Override
Expand All @@ -624,17 +627,9 @@ public <T> T invoke(final Class<T> 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
Expand All @@ -644,41 +639,37 @@ public <T> T invoke(final GenericType<T> 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> T runInScope(Producer<T> producer, RequestScope scope) throws ProcessingException, WebApplicationException {
return scope.runInScope(() -> call(producer, scope));
}

private <T> T call(Producer<T> 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<Response> submit() {
final CompletableFuture<Response> 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;
}
Expand All @@ -689,35 +680,10 @@ public <T> Future<T> submit(final Class<T> responseType) {
throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL());
}
final CompletableFuture<T> 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<T>(responseFuture, (request, scope) -> translate(request, scope, responseType))));

return responseFuture;
}
Expand Down Expand Up @@ -753,36 +719,10 @@ public <T> Future<T> submit(final GenericType<T> responseType) {
throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL());
}
final CompletableFuture<T> 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<T>(responseFuture, (request, scope) -> translate(request, scope, responseType))));

return responseFuture;
}
Expand Down Expand Up @@ -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
);
}
}
};
Expand All @@ -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) {
Expand Down Expand Up @@ -1006,4 +959,45 @@ ClientRequest request() {
public String toString() {
return "JerseyInvocation [" + request().getMethod() + ' ' + request().getUri() + "]";
}

private class InvocationResponseCallback<R> implements ResponseCallback {
private final CompletableFuture<R> responseFuture;
private final BiFunction<ClientResponse, RequestScope, R> producer;

private InvocationResponseCallback(CompletableFuture<R> responseFuture,
BiFunction<ClientResponse, RequestScope, R> 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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 11d6c11

Please sign in to comment.