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