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