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