0

I have a glassfish 5 with jersey 2.27 server application that has a custom response filter that in some cases throws a custom business exception.

The response filter looks like :

public class CustomResponseFilter implements ContainerResponseFilter {

   ....

    @Override
    public void filter(ContainerRequestContext request, ContainerResponseContext response) {
        ....
        if (something_is_true) {
            throw new CustomException(A_CUSTOM_MESSAGE);
        }
    }

    ...

}

The Exception and the Exception Mapper are define like:

public class CustomException extends RuntimeException {

    public CustomException(String message) {
        super(message);
    }
}
@Provider
public class CustomException400Mapper implements ExceptionMapper<CustomException> {

    ...

    @Override
    public Response toResponse(CustomException t) {
        Error entity = new Error(HttpServletResponse.SC_BAD_REQUEST,
                HEADER_INVALID, CONTRACT_NUMBER_HEADER);
        return Response.status(HttpServletResponse.SC_BAD_REQUEST).
                entity(entity).
                type(MediaType.APPLICATION_JSON).
                build();
    }
}

A request goes through the endpoint and then when it reaches the custom response filter it throws the Custom Exception above.

The Exception is catch-ed and handled by the jersey framework. It is converted into a response using the exception mapper.

My problem is that the response is not returned to the client but instead a processResponse(response) method is called witch calls all the response filters again. This basically throws the same error again, witch is caught and wrapped into an Internal Server Error.

You can see the jersey framework code bellow with some comments from my side.

package org.glassfish.jersey.server;

...

public class ServerRuntime {

    ...

    public void process(final Throwable throwable) {
            final ContainerRequest request = processingContext.request();

            ...

            ContainerResponse response = null;
            try {
                final Response exceptionResponse = mapException(throwable);
                try {
                    try {
                        response = convertResponse(exceptionResponse);
                        if (!runtime.disableLocationHeaderRelativeUriResolution) {
                            ensureAbsolute(response.getLocation(), response.getHeaders(), request,
                                    runtime.rfc7231LocationHeaderRelativeUriResolution);
                        }
                        processingContext.monitoringEventBuilder().setContainerResponse(response)
                                .setResponseSuccessfullyMapped(true);
                    } finally {
                        processingContext.triggerEvent(RequestEvent.Type.EXCEPTION_MAPPING_FINISHED);
                    }

                    // response is correctly mapped from CustomException400Mapper
                    // this triggers the response filters to be runned again
                    // throws the same Exception again
                    processResponse(response);
                } catch (final Throwable respError) { // the exception is catched here and retrown again
                    LOGGER.log(Level.SEVERE, LocalizationMessages.ERROR_PROCESSING_RESPONSE_FROM_ALREADY_MAPPED_EXCEPTION());
                    processingContext.monitoringEventBuilder()
                            .setException(respError, RequestEvent.ExceptionCause.MAPPED_RESPONSE);
                    processingContext.triggerEvent(RequestEvent.Type.ON_EXCEPTION);
                    throw respError;
                }
            } catch (final Throwable responseError) { // the exception is catched here
                if (throwable != responseError
                        && !(throwable instanceof MappableException && throwable.getCause() == responseError)) {
                    LOGGER.log(Level.FINE, LocalizationMessages.ERROR_EXCEPTION_MAPPING_ORIGINAL_EXCEPTION(), throwable);
                }

                // I`ve tried to use processResponseError method to return the response exception but it calls processResponse(...) method witch call`s the response filters and it fails and returns false. 
                if (!processResponseError(responseError)) {
                    // Pass the exception to the container.
                    LOGGER.log(Level.FINE, LocalizationMessages.ERROR_EXCEPTION_MAPPING_THROWN_TO_CONTAINER(), responseError);

                    try {

                        // here it returns the exception mapped into an Internal Server Error Response
                        request.getResponseWriter().failure(responseError);
                    } finally {
                        completionCallbackRunner.onComplete(responseError);
                    }
                }
            } finally {
                release(response);
            }
        }

My question is how can I return a response from a custom exception thrown in my response filters ?

Thx!

Ionut Ursan
  • 187
  • 1
  • 2
  • 11
  • Why do you need to throw an exception just to try to get to an exception mapper so you can set a response? You can change anything you want in the response from inside the response filter? – Paul Samsotha May 14 '19 at 15:19
  • We are upgrading from Jersey 1.x to Jersey 2.x, and we have a lot of business exceptions that are being thrown from the response filters. I`m hopping there is a better solution than being forced to change the code in the response filters and write using exception mappers directly into the ResponseContext, or even ignoring the filter if an exception is already on the response. – Ionut Ursan May 17 '19 at 08:18
  • 1
    You can cast the `ContainerResponseContext` to Jersey [`ContainerResponse`](https://jersey.github.io/apidocs/2.27/jersey/org/glassfish/jersey/server/ContainerResponse.html) and check [`cr.isMappedFromException()`](https://jersey.github.io/apidocs/2.27/jersey/org/glassfish/jersey/server/ContainerResponse.html#isMappedFromException--) at the beginning of the `filter()` method. If true, you can just return. – Paul Samsotha May 17 '19 at 14:59
  • Yes, this is our current workaround but we are still looking for a better solution. Thank you! – Ionut Ursan May 20 '19 at 07:28

0 Answers0