001package ca.uhn.fhir.rest.server.exceptions; 002 003import org.apache.commons.lang3.Validate; 004import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; 005 006import java.lang.reflect.InvocationTargetException; 007import java.util.*; 008 009 010/* 011 * #%L 012 * HAPI FHIR - Core Library 013 * %% 014 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 015 * %% 016 * Licensed under the Apache License, Version 2.0 (the "License"); 017 * you may not use this file except in compliance with the License. 018 * You may obtain a copy of the License at 019 * 020 * http://www.apache.org/licenses/LICENSE-2.0 021 * 022 * Unless required by applicable law or agreed to in writing, software 023 * distributed under the License is distributed on an "AS IS" BASIS, 024 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 025 * See the License for the specific language governing permissions and 026 * limitations under the License. 027 * #L% 028 */ 029 030/** 031 * Base class for RESTful client and server exceptions. RESTful client methods will only throw exceptions which are subclasses of this exception type, and RESTful server methods should also only call 032 * subclasses of this exception type. 033 * <p> 034 * HAPI provides a number of subclasses of BaseServerResponseException, and each one corresponds to a specific 035 * HTTP status code. For example, if a IResourceProvider method throws 036 * {@link ResourceNotFoundException}, this is a signal to the server that an <code>HTTP 404</code> should 037 * be returned to the client. 038 * </p> 039 * <p> 040 * <b>See:</b> A complete list of available exceptions is in the <a href="./package-summary.html">package summary</a>. 041 * If an exception doesn't exist for a condition you want to represent, let us know by filing an 042 * <a href="https://github.com/hapifhir/hapi-fhir/issues">issue in our tracker</a>. You may also 043 * use {@link UnclassifiedServerFailureException} to represent any error code you want. 044 * </p> 045 */ 046public abstract class BaseServerResponseException extends RuntimeException { 047 048 private static final Map<Integer, Class<? extends BaseServerResponseException>> ourStatusCodeToExceptionType = new HashMap<Integer, Class<? extends BaseServerResponseException>>(); 049 private static final long serialVersionUID = 1L; 050 051 static { 052 registerExceptionType(PayloadTooLargeException.STATUS_CODE, PayloadTooLargeException.class); 053 registerExceptionType(AuthenticationException.STATUS_CODE, AuthenticationException.class); 054 registerExceptionType(InternalErrorException.STATUS_CODE, InternalErrorException.class); 055 registerExceptionType(InvalidRequestException.STATUS_CODE, InvalidRequestException.class); 056 registerExceptionType(MethodNotAllowedException.STATUS_CODE, MethodNotAllowedException.class); 057 registerExceptionType(NotImplementedOperationException.STATUS_CODE, NotImplementedOperationException.class); 058 registerExceptionType(NotModifiedException.STATUS_CODE, NotModifiedException.class); 059 registerExceptionType(ResourceNotFoundException.STATUS_CODE, ResourceNotFoundException.class); 060 registerExceptionType(ResourceGoneException.STATUS_CODE, ResourceGoneException.class); 061 registerExceptionType(PreconditionFailedException.STATUS_CODE, PreconditionFailedException.class); 062 registerExceptionType(ResourceVersionConflictException.STATUS_CODE, ResourceVersionConflictException.class); 063 registerExceptionType(UnprocessableEntityException.STATUS_CODE, UnprocessableEntityException.class); 064 registerExceptionType(ForbiddenOperationException.STATUS_CODE, ForbiddenOperationException.class); 065 } 066 067 private List<String> myAdditionalMessages = null; 068 private IBaseOperationOutcome myBaseOperationOutcome; 069 private String myResponseBody; 070 private Map<String, List<String>> myResponseHeaders; 071 private String myResponseMimeType; 072 private int myStatusCode; 073 private boolean myErrorMessageTrusted; 074 075 /** 076 * Constructor 077 * 078 * @param theStatusCode The HTTP status code corresponding to this problem 079 * @param theMessage The message 080 */ 081 public /** 082 * Interceptor hook method. This method should not be called directly. 083 */ 084 BaseServerResponseException(int theStatusCode, String theMessage) { 085 super(theMessage); 086 myStatusCode = theStatusCode; 087 myBaseOperationOutcome = null; 088 } 089 090 /** 091 * Constructor 092 * 093 * @param theStatusCode The HTTP status code corresponding to this problem 094 * @param theMessages The messages 095 */ 096 public BaseServerResponseException(int theStatusCode, String... theMessages) { 097 super(theMessages != null && theMessages.length > 0 ? theMessages[0] : null); 098 myStatusCode = theStatusCode; 099 myBaseOperationOutcome = null; 100 if (theMessages != null && theMessages.length > 1) { 101 myAdditionalMessages = Arrays.asList(Arrays.copyOfRange(theMessages, 1, theMessages.length, String[].class)); 102 } 103 } 104 105 /** 106 * Constructor 107 * 108 * @param theStatusCode The HTTP status code corresponding to this problem 109 * @param theMessage The message 110 * @param theBaseOperationOutcome An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client) 111 */ 112 public BaseServerResponseException(int theStatusCode, String theMessage, IBaseOperationOutcome theBaseOperationOutcome) { 113 super(theMessage); 114 myStatusCode = theStatusCode; 115 myBaseOperationOutcome = theBaseOperationOutcome; 116 } 117 118 /** 119 * Constructor 120 * 121 * @param theStatusCode The HTTP status code corresponding to this problem 122 * @param theMessage The message 123 * @param theCause The cause 124 */ 125 public BaseServerResponseException(int theStatusCode, String theMessage, Throwable theCause) { 126 super(theMessage, theCause); 127 myStatusCode = theStatusCode; 128 myBaseOperationOutcome = null; 129 } 130 131 /** 132 * Constructor 133 * 134 * @param theStatusCode The HTTP status code corresponding to this problem 135 * @param theMessage The message 136 * @param theCause The underlying cause exception 137 * @param theBaseOperationOutcome An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client) 138 */ 139 public BaseServerResponseException(int theStatusCode, String theMessage, Throwable theCause, IBaseOperationOutcome theBaseOperationOutcome) { 140 super(theMessage, theCause); 141 myStatusCode = theStatusCode; 142 myBaseOperationOutcome = theBaseOperationOutcome; 143 } 144 145 /** 146 * Constructor 147 * 148 * @param theStatusCode The HTTP status code corresponding to this problem 149 * @param theCause The underlying cause exception 150 */ 151 public BaseServerResponseException(int theStatusCode, Throwable theCause) { 152 super(theCause.getMessage(), theCause); 153 myStatusCode = theStatusCode; 154 myBaseOperationOutcome = null; 155 } 156 157 /** 158 * Constructor 159 * 160 * @param theStatusCode The HTTP status code corresponding to this problem 161 * @param theCause The underlying cause exception 162 * @param theBaseOperationOutcome An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client) 163 */ 164 public BaseServerResponseException(int theStatusCode, Throwable theCause, IBaseOperationOutcome theBaseOperationOutcome) { 165 super(theCause.toString(), theCause); 166 myStatusCode = theStatusCode; 167 myBaseOperationOutcome = theBaseOperationOutcome; 168 } 169 170 /** 171 * This flag can be used to signal to server infrastructure that the message supplied 172 * to this exception (ie to the constructor) is considered trusted and is safe to 173 * return to the calling client. 174 */ 175 public boolean isErrorMessageTrusted() { 176 return myErrorMessageTrusted; 177 } 178 179 /** 180 * This flag can be used to signal to server infrastructure that the message supplied 181 * to this exception (ie to the constructor) is considered trusted and is safe to 182 * return to the calling client. 183 */ 184 public BaseServerResponseException setErrorMessageTrusted(boolean theErrorMessageTrusted) { 185 myErrorMessageTrusted = theErrorMessageTrusted; 186 return this; 187 } 188 189 /** 190 * Add a header which will be added to any responses 191 * 192 * @param theName The header name 193 * @param theValue The header value 194 * @return Returns a reference to <code>this</code> for easy method chaining 195 * @since 2.0 196 */ 197 public BaseServerResponseException addResponseHeader(String theName, String theValue) { 198 Validate.notBlank(theName, "theName must not be null or empty"); 199 Validate.notBlank(theValue, "theValue must not be null or empty"); 200 if (getResponseHeaders().containsKey(theName) == false) { 201 getResponseHeaders().put(theName, new ArrayList<>()); 202 } 203 getResponseHeaders().get(theName).add(theValue); 204 return this; 205 } 206 207 public List<String> getAdditionalMessages() { 208 return myAdditionalMessages; 209 } 210 211 /** 212 * Returns the {@link IBaseOperationOutcome} resource if any which was supplied in the response, or <code>null</code> 213 */ 214 public IBaseOperationOutcome getOperationOutcome() { 215 return myBaseOperationOutcome; 216 } 217 218 /** 219 * Sets the BaseOperationOutcome resource associated with this exception. In server implementations, this is the OperartionOutcome resource to include with the HTTP response. In client 220 * implementations you should not call this method. 221 * 222 * @param theBaseOperationOutcome The BaseOperationOutcome resource Sets the BaseOperationOutcome resource associated with this exception. In server implementations, this is the OperartionOutcome resource to include 223 * with the HTTP response. In client implementations you should not call this method. 224 */ 225 public void setOperationOutcome(IBaseOperationOutcome theBaseOperationOutcome) { 226 myBaseOperationOutcome = theBaseOperationOutcome; 227 } 228 229 /** 230 * In a RESTful client, this method will be populated with the body of the HTTP respone if one was provided by the server, or <code>null</code> otherwise. 231 * <p> 232 * In a restful server, this method is currently ignored. 233 * </p> 234 */ 235 public String getResponseBody() { 236 return myResponseBody; 237 } 238 239 /** 240 * This method is currently only called internally by HAPI, it should not be called by user code. 241 */ 242 public void setResponseBody(String theResponseBody) { 243 myResponseBody = theResponseBody; 244 } 245 246 /** 247 * Returns a map containing any headers which should be added to the outgoing 248 * response. This methos creates the map if none exists, so it will never 249 * return <code>null</code> 250 * 251 * @since 2.0 (note that this method existed in previous versions of HAPI but the method 252 * signature has been changed from <code>Map<String, String[]></code> to <code>Map<String, List<String>></code> 253 */ 254 public Map<String, List<String>> getResponseHeaders() { 255 if (myResponseHeaders == null) { 256 myResponseHeaders = new HashMap<>(); 257 } 258 return myResponseHeaders; 259 } 260 261 /** 262 * In a RESTful client, this method will be populated with the HTTP status code that was returned with the HTTP response. 263 * <p> 264 * In a restful server, this method is currently ignored. 265 * </p> 266 */ 267 public String getResponseMimeType() { 268 return myResponseMimeType; 269 } 270 271 /** 272 * This method is currently only called internally by HAPI, it should not be called by user code. 273 */ 274 public void setResponseMimeType(String theResponseMimeType) { 275 myResponseMimeType = theResponseMimeType; 276 } 277 278 /** 279 * Returns the HTTP status code corresponding to this problem 280 */ 281 public int getStatusCode() { 282 return myStatusCode; 283 } 284 285 /** 286 * Does the exception have any headers which should be added to the outgoing response? 287 * 288 * @see #getResponseHeaders() 289 * @since 2.0 290 */ 291 public boolean hasResponseHeaders() { 292 return myResponseHeaders != null && myResponseHeaders.isEmpty() == false; 293 } 294 295 /** 296 * For unit tests only 297 */ 298 static boolean isExceptionTypeRegistered(Class<?> theType) { 299 return ourStatusCodeToExceptionType.values().contains(theType); 300 } 301 302 public static BaseServerResponseException newInstance(int theStatusCode, String theMessage) { 303 if (ourStatusCodeToExceptionType.containsKey(theStatusCode)) { 304 try { 305 return ourStatusCodeToExceptionType.get(theStatusCode).getConstructor(new Class[]{String.class}).newInstance(theMessage); 306 } catch (InstantiationException e) { 307 throw new InternalErrorException(e); 308 } catch (IllegalAccessException e) { 309 throw new InternalErrorException(e); 310 } catch (IllegalArgumentException e) { 311 throw new InternalErrorException(e); 312 } catch (InvocationTargetException e) { 313 throw new InternalErrorException(e); 314 } catch (NoSuchMethodException e) { 315 throw new InternalErrorException(e); 316 } catch (SecurityException e) { 317 throw new InternalErrorException(e); 318 } 319 } 320 return new UnclassifiedServerFailureException(theStatusCode, theMessage); 321 } 322 323 static void registerExceptionType(int theStatusCode, Class<? extends BaseServerResponseException> theType) { 324 if (ourStatusCodeToExceptionType.containsKey(theStatusCode)) { 325 throw new Error("Can not register " + theType + " to status code " + theStatusCode + " because " + ourStatusCodeToExceptionType.get(theStatusCode) + " already registers that code"); 326 } 327 ourStatusCodeToExceptionType.put(theStatusCode, theType); 328 } 329 330}