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