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