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 ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding; 038import org.apache.commons.lang3.exception.ExceptionUtils; 039import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; 040 041import ca.uhn.fhir.context.FhirContext; 042import ca.uhn.fhir.rest.api.Constants; 043import ca.uhn.fhir.rest.api.SummaryEnum; 044import ca.uhn.fhir.rest.api.server.IRestfulResponse; 045import ca.uhn.fhir.rest.api.server.RequestDetails; 046import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 047import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 048import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException; 049import ca.uhn.fhir.util.OperationOutcomeUtil; 050 051public class ExceptionHandlingInterceptor extends InterceptorAdapter { 052 053 public static final String PROCESSING = Constants.OO_INFOSTATUS_PROCESSING; 054 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExceptionHandlingInterceptor.class); 055 private Class<?>[] myReturnStackTracesForExceptionTypes; 056 057 @Override 058 public boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException { 059 Closeable writer = (Closeable) handleException(theRequestDetails, theException); 060 writer.close(); 061 return false; 062 } 063 064 public Object handleException(RequestDetails theRequestDetails, BaseServerResponseException theException) 065 throws ServletException, IOException { 066 IRestfulResponse response = theRequestDetails.getResponse(); 067 068 FhirContext ctx = theRequestDetails.getServer().getFhirContext(); 069 070 IBaseOperationOutcome oo = theException.getOperationOutcome(); 071 if (oo == null) { 072 oo = createOperationOutcome(theException, ctx); 073 } 074 075 int statusCode = theException.getStatusCode(); 076 077 // Add headers associated with the specific error code 078 if (theException.hasResponseHeaders()) { 079 Map<String, List<String>> additional = theException.getResponseHeaders(); 080 for (Entry<String, List<String>> next : additional.entrySet()) { 081 if (isNotBlank(next.getKey()) && next.getValue() != null) { 082 String nextKey = next.getKey(); 083 for (String nextValue : next.getValue()) { 084 response.addHeader(nextKey, nextValue); 085 } 086 } 087 } 088 } 089 090 String statusMessage = null; 091 if (theException instanceof UnclassifiedServerFailureException) { 092 String sm = theException.getMessage(); 093 if (isNotBlank(sm) && sm.indexOf('\n') == -1) { 094 statusMessage = sm; 095 } 096 } 097 098 BaseResourceReturningMethodBinding.callOutgoingFailureOperationOutcomeHook(theRequestDetails, oo); 099 return response.streamResponseAsResource(oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, statusMessage, false, false); 100 101 } 102 103 @Override 104 public BaseServerResponseException preProcessOutgoingException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest) throws ServletException { 105 BaseServerResponseException retVal; 106 if (theException instanceof DataFormatException) { 107 // Wrapping the DataFormatException as an InvalidRequestException so that it gets sent back to the client as a 400 response. 108 retVal = new InvalidRequestException(theException); 109 } else if (!(theException instanceof BaseServerResponseException)) { 110 retVal = new InternalErrorException(theException); 111 } else { 112 retVal = (BaseServerResponseException) theException; 113 } 114 115 if (retVal.getOperationOutcome() == null) { 116 retVal.setOperationOutcome(createOperationOutcome(theException, theRequestDetails.getServer().getFhirContext())); 117 } 118 119 return retVal; 120 } 121 122 private IBaseOperationOutcome createOperationOutcome(Throwable theException, FhirContext ctx) throws ServletException { 123 IBaseOperationOutcome oo = null; 124 if (theException instanceof BaseServerResponseException) { 125 oo = ((BaseServerResponseException) theException).getOperationOutcome(); 126 } 127 128 /* 129 * Generate an OperationOutcome to return, unless the exception throw by the resource provider had one 130 */ 131 if (oo == null) { 132 try { 133 oo = OperationOutcomeUtil.newInstance(ctx); 134 135 if (theException instanceof InternalErrorException) { 136 ourLog.error("Failure during REST processing", theException); 137 populateDetails(ctx, theException, oo); 138 } else if (theException instanceof BaseServerResponseException) { 139 int statusCode = ((BaseServerResponseException) theException).getStatusCode(); 140 141 // No stack traces for non-server internal errors 142 if (statusCode < 500) { 143 ourLog.warn("Failure during REST processing: {}", theException.toString()); 144 } else { 145 ourLog.warn("Failure during REST processing", theException); 146 } 147 148 BaseServerResponseException baseServerResponseException = (BaseServerResponseException) theException; 149 populateDetails(ctx, theException, oo); 150 if (baseServerResponseException.getAdditionalMessages() != null) { 151 for (String next : baseServerResponseException.getAdditionalMessages()) { 152 OperationOutcomeUtil.addIssue(ctx, oo, "error", next, null, PROCESSING); 153 } 154 } 155 } else { 156 ourLog.error("Failure during REST processing: " + theException.toString(), theException); 157 populateDetails(ctx, theException, oo); 158 } 159 } catch (Exception e1) { 160 ourLog.error("Failed to instantiate OperationOutcome resource instance", e1); 161 throw new ServletException("Failed to instantiate OperationOutcome resource instance", e1); 162 } 163 } else { 164 ourLog.error("Unknown error during processing", theException); 165 } 166 return oo; 167 } 168 169 private void populateDetails(FhirContext theCtx, Throwable theException, IBaseOperationOutcome theOo) { 170 if (myReturnStackTracesForExceptionTypes != null) { 171 for (Class<?> next : myReturnStackTracesForExceptionTypes) { 172 if (next.isAssignableFrom(theException.getClass())) { 173 String detailsValue = theException.getMessage() + "\n\n" + ExceptionUtils.getStackTrace(theException); 174 OperationOutcomeUtil.addIssue(theCtx, theOo, "error", detailsValue, null, PROCESSING); 175 return; 176 } 177 } 178 } 179 180 OperationOutcomeUtil.addIssue(theCtx, theOo, "error", theException.getMessage(), null, PROCESSING); 181 } 182 183 /** 184 * 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 185 * issues, but may not be desirable for production situations. 186 * 187 * @param theExceptionTypes 188 * The exception types for which to return the stack trace to the user. 189 * @return Returns an instance of this interceptor, to allow for easy method chaining. 190 */ 191 public ExceptionHandlingInterceptor setReturnStackTracesForExceptionTypes(Class<?>... theExceptionTypes) { 192 myReturnStackTracesForExceptionTypes = theExceptionTypes; 193 return this; 194 } 195 196}