001/*
002 * #%L
003 * HAPI FHIR JAX-RS Server
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jaxrs.server.interceptor;
021
022import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider;
023import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
024import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
025import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
026import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor;
027import jakarta.interceptor.AroundInvoke;
028import jakarta.interceptor.InvocationContext;
029import jakarta.servlet.ServletException;
030import jakarta.ws.rs.core.Response;
031
032import java.io.IOException;
033
034/**
035 * An interceptor that catches the jax-rs exceptions
036 *
037 * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
038 */
039public class JaxRsExceptionInterceptor {
040
041        /** the existing exception handler which is able to convert exception into responses*/
042        private final ExceptionHandlingInterceptor exceptionHandler;
043
044        /**
045         * The default constructor
046         */
047        public JaxRsExceptionInterceptor() {
048                this.exceptionHandler = new ExceptionHandlingInterceptor();
049        }
050
051        /**
052         * A utility constructor for unit testing
053         * @param exceptionHandler the handler for the exception conversion
054         */
055        JaxRsExceptionInterceptor(final ExceptionHandlingInterceptor exceptionHandler) {
056                this.exceptionHandler = exceptionHandler;
057        }
058
059        /**
060         * This interceptor will catch all exception and convert them using the exceptionhandler
061         * @param ctx the invocation context
062         * @return the result
063         * @throws JaxRsResponseException an exception that can be handled by a jee container
064         */
065        @AroundInvoke
066        public Object intercept(final InvocationContext ctx) throws JaxRsResponseException {
067                try {
068                        return ctx.proceed();
069                } catch (final Exception theException) {
070                        final AbstractJaxRsProvider theServer = (AbstractJaxRsProvider) ctx.getTarget();
071                        throw convertException(theServer, theException);
072                }
073        }
074
075        /**
076         * This method convert an exception to a JaxRsResponseException
077         * @param theServer the provider
078         * @param theException the exception to convert
079         * @return JaxRsResponseException
080         */
081        public JaxRsResponseException convertException(
082                        final AbstractJaxRsProvider theServer, final Throwable theException) {
083                if (theServer.withStackTrace()) {
084                        exceptionHandler.setReturnStackTracesForExceptionTypes(Throwable.class);
085                }
086                final JaxRsRequest requestDetails = theServer.getRequest(null, null).build();
087                final BaseServerResponseException convertedException = preprocessException(theException, requestDetails);
088                return new JaxRsResponseException(convertedException);
089        }
090
091        /**
092         * This method converts an exception into a response
093         * @param theRequest the request
094         * @param theException the thrown exception
095         * @return the response describing the error
096         * @throws IOException
097         */
098        public Response convertExceptionIntoResponse(
099                        final JaxRsRequest theRequest, final JaxRsResponseException theException) throws IOException {
100                return handleExceptionWithoutServletError(theRequest, theException);
101        }
102
103        private BaseServerResponseException preprocessException(
104                        final Throwable theException, final JaxRsRequest requestDetails) {
105                try {
106                        Throwable theExceptionToConvert = theException;
107                        if (!(theException instanceof BaseServerResponseException)
108                                        && (theException.getCause() instanceof BaseServerResponseException)) {
109                                theExceptionToConvert = theException.getCause();
110                        }
111                        return exceptionHandler.preProcessOutgoingException(requestDetails, theExceptionToConvert, null);
112                } catch (final ServletException e) {
113                        return new InternalErrorException(e);
114                }
115        }
116
117        private Response handleExceptionWithoutServletError(
118                        final JaxRsRequest theRequest, final BaseServerResponseException theException) throws IOException {
119                try {
120                        return (Response) exceptionHandler.handleException(theRequest, theException);
121                } catch (final ServletException e) {
122                        final BaseServerResponseException newException =
123                                        preprocessException(new InternalErrorException(e), theRequest);
124                        return handleExceptionWithoutServletError(theRequest, newException);
125                }
126        }
127}