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