001package ca.uhn.fhir.rest.server.interceptor; 002 003/* 004 * #%L 005 * HAPI FHIR - Server Framework 006 * %% 007 * Copyright (C) 2014 - 2019 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022import static org.apache.commons.lang3.StringUtils.isNotBlank; 023 024import java.io.Closeable; 025import java.io.IOException; 026import java.util.Collections; 027import java.util.List; 028import java.util.Map; 029import java.util.Map.Entry; 030 031import javax.servlet.ServletException; 032import javax.servlet.http.HttpServletRequest; 033import javax.servlet.http.HttpServletResponse; 034 035import ca.uhn.fhir.parser.DataFormatException; 036import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 037import org.apache.commons.lang3.exception.ExceptionUtils; 038import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; 039 040import ca.uhn.fhir.context.FhirContext; 041import ca.uhn.fhir.rest.api.Constants; 042import ca.uhn.fhir.rest.api.SummaryEnum; 043import ca.uhn.fhir.rest.api.server.IRestfulResponse; 044import ca.uhn.fhir.rest.api.server.RequestDetails; 045import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 046import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 047import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException; 048import ca.uhn.fhir.util.OperationOutcomeUtil; 049 050public class ExceptionHandlingInterceptor extends InterceptorAdapter { 051 052 public static final String PROCESSING = Constants.OO_INFOSTATUS_PROCESSING; 053 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExceptionHandlingInterceptor.class); 054 private Class<?>[] myReturnStackTracesForExceptionTypes; 055 056 @Override 057 public boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException { 058 Closeable writer = (Closeable) handleException(theRequestDetails, theException); 059 writer.close(); 060 return false; 061 } 062 063 public Object handleException(RequestDetails theRequestDetails, BaseServerResponseException theException) 064 throws ServletException, IOException { 065 IRestfulResponse response = theRequestDetails.getResponse(); 066 067 FhirContext ctx = theRequestDetails.getServer().getFhirContext(); 068 069 IBaseOperationOutcome oo = theException.getOperationOutcome(); 070 if (oo == null) { 071 oo = createOperationOutcome(theException, ctx); 072 } 073 074 int statusCode = theException.getStatusCode(); 075 076 // Add headers associated with the specific error code 077 if (theException.hasResponseHeaders()) { 078 Map<String, List<String>> additional = theException.getResponseHeaders(); 079 for (Entry<String, List<String>> next : additional.entrySet()) { 080 if (isNotBlank(next.getKey()) && next.getValue() != null) { 081 String nextKey = next.getKey(); 082 for (String nextValue : next.getValue()) { 083 response.addHeader(nextKey, nextValue); 084 } 085 } 086 } 087 } 088 089 String statusMessage = null; 090 if (theException instanceof UnclassifiedServerFailureException) { 091 String sm = theException.getMessage(); 092 if (isNotBlank(sm) && sm.indexOf('\n') == -1) { 093 statusMessage = sm; 094 } 095 } 096 097 return response.streamResponseAsResource(oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, statusMessage, false, false); 098 099 } 100 101 @Override 102 public BaseServerResponseException preProcessOutgoingException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest) throws ServletException { 103 BaseServerResponseException retVal; 104 if (theException instanceof DataFormatException) { 105 // Wrapping the DataFormatException as an InvalidRequestException so that it gets sent back to the client as a 400 response. 106 retVal = new InvalidRequestException(theException); 107 } else if (!(theException instanceof BaseServerResponseException)) { 108 retVal = new InternalErrorException(theException); 109 } else { 110 retVal = (BaseServerResponseException) theException; 111 } 112 113 if (retVal.getOperationOutcome() == null) { 114 retVal.setOperationOutcome(createOperationOutcome(theException, theRequestDetails.getServer().getFhirContext())); 115 } 116 117 return retVal; 118 } 119 120 private IBaseOperationOutcome createOperationOutcome(Throwable theException, FhirContext ctx) throws ServletException { 121 IBaseOperationOutcome oo = null; 122 if (theException instanceof BaseServerResponseException) { 123 oo = ((BaseServerResponseException) theException).getOperationOutcome(); 124 } 125 126 /* 127 * Generate an OperationOutcome to return, unless the exception throw by the resource provider had one 128 */ 129 if (oo == null) { 130 try { 131 oo = OperationOutcomeUtil.newInstance(ctx); 132 133 if (theException instanceof InternalErrorException) { 134 ourLog.error("Failure during REST processing", theException); 135 populateDetails(ctx, theException, oo); 136 } else if (theException instanceof BaseServerResponseException) { 137 int statusCode = ((BaseServerResponseException) theException).getStatusCode(); 138 139 // No stack traces for non-server internal errors 140 if (statusCode < 500) { 141 ourLog.warn("Failure during REST processing: {}", theException.toString()); 142 } else { 143 ourLog.warn("Failure during REST processing", theException); 144 } 145 146 BaseServerResponseException baseServerResponseException = (BaseServerResponseException) theException; 147 populateDetails(ctx, theException, oo); 148 if (baseServerResponseException.getAdditionalMessages() != null) { 149 for (String next : baseServerResponseException.getAdditionalMessages()) { 150 OperationOutcomeUtil.addIssue(ctx, oo, "error", next, null, PROCESSING); 151 } 152 } 153 } else { 154 ourLog.error("Failure during REST processing: " + theException.toString(), theException); 155 populateDetails(ctx, theException, oo); 156 } 157 } catch (Exception e1) { 158 ourLog.error("Failed to instantiate OperationOutcome resource instance", e1); 159 throw new ServletException("Failed to instantiate OperationOutcome resource instance", e1); 160 } 161 } else { 162 ourLog.error("Unknown error during processing", theException); 163 } 164 return oo; 165 } 166 167 private void populateDetails(FhirContext theCtx, Throwable theException, IBaseOperationOutcome theOo) { 168 if (myReturnStackTracesForExceptionTypes != null) { 169 for (Class<?> next : myReturnStackTracesForExceptionTypes) { 170 if (next.isAssignableFrom(theException.getClass())) { 171 String detailsValue = theException.getMessage() + "\n\n" + ExceptionUtils.getStackTrace(theException); 172 OperationOutcomeUtil.addIssue(theCtx, theOo, "error", detailsValue, null, PROCESSING); 173 return; 174 } 175 } 176 } 177 178 OperationOutcomeUtil.addIssue(theCtx, theOo, "error", theException.getMessage(), null, PROCESSING); 179 } 180 181 /** 182 * If any server methods throw an exception which extends any of the given exception types, the exception stack trace will be returned to the user. This can be useful for helping to diagnose 183 * issues, but may not be desirable for production situations. 184 * 185 * @param theExceptionTypes 186 * The exception types for which to return the stack trace to the user. 187 * @return Returns an instance of this interceptor, to allow for easy method chaining. 188 */ 189 public ExceptionHandlingInterceptor setReturnStackTracesForExceptionTypes(Class<?>... theExceptionTypes) { 190 myReturnStackTracesForExceptionTypes = theExceptionTypes; 191 return this; 192 } 193 194}