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