001package ca.uhn.fhir.rest.server.exceptions; 002 003import java.lang.reflect.InvocationTargetException; 004import java.util.*; 005 006import org.apache.commons.lang3.Validate; 007import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; 008 009 010/* 011 * #%L 012 * HAPI FHIR - Core Library 013 * %% 014 * Copyright (C) 2014 - 2017 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 073 /** 074 * Constructor 075 * 076 * @param theStatusCode 077 * The HTTP status code corresponding to this problem 078 * @param theMessage 079 * 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 091 * The HTTP status code corresponding to this problem 092 * @param theMessages 093 * The messages 094 */ 095 public BaseServerResponseException(int theStatusCode, String... theMessages) { 096 super(theMessages != null && theMessages.length > 0 ? theMessages[0] : null); 097 myStatusCode = theStatusCode; 098 myBaseOperationOutcome = null; 099 if (theMessages != null && theMessages.length > 1) { 100 myAdditionalMessages = Arrays.asList(Arrays.copyOfRange(theMessages, 1, theMessages.length, String[].class)); 101 } 102 } 103 104 /** 105 * Constructor 106 * 107 * @param theStatusCode 108 * The HTTP status code corresponding to this problem 109 * @param theMessage 110 * The message 111 * @param theBaseOperationOutcome 112 * An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client) 113 */ 114 public BaseServerResponseException(int theStatusCode, String theMessage, IBaseOperationOutcome theBaseOperationOutcome) { 115 super(theMessage); 116 myStatusCode = theStatusCode; 117 myBaseOperationOutcome = theBaseOperationOutcome; 118 } 119 120 /** 121 * Constructor 122 * 123 * @param theStatusCode 124 * The HTTP status code corresponding to this problem 125 * @param theMessage 126 * The message 127 * @param theCause 128 * 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 140 * The HTTP status code corresponding to this problem 141 * @param theMessage 142 * The message 143 * @param theCause 144 * The underlying cause exception 145 * @param theBaseOperationOutcome 146 * An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client) 147 */ 148 public BaseServerResponseException(int theStatusCode, String theMessage, Throwable theCause, IBaseOperationOutcome theBaseOperationOutcome) { 149 super(theMessage, theCause); 150 myStatusCode = theStatusCode; 151 myBaseOperationOutcome = theBaseOperationOutcome; 152 } 153 154 /** 155 * Constructor 156 * 157 * @param theStatusCode 158 * The HTTP status code corresponding to this problem 159 * @param theCause 160 * The underlying cause exception 161 */ 162 public BaseServerResponseException(int theStatusCode, Throwable theCause) { 163 super(theCause.toString(), theCause); 164 myStatusCode = theStatusCode; 165 myBaseOperationOutcome = null; 166 } 167 168 /** 169 * Constructor 170 * 171 * @param theStatusCode 172 * The HTTP status code corresponding to this problem 173 * @param theCause 174 * The underlying cause exception 175 * @param theBaseOperationOutcome 176 * An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client) 177 */ 178 public BaseServerResponseException(int theStatusCode, Throwable theCause, IBaseOperationOutcome theBaseOperationOutcome) { 179 super(theCause.toString(), theCause); 180 myStatusCode = theStatusCode; 181 myBaseOperationOutcome = theBaseOperationOutcome; 182 } 183 184 /** 185 * Add a header which will be added to any responses 186 * 187 * @param theName The header name 188 * @param theValue The header value 189 * @return Returns a reference to <code>this</code> for easy method chaining 190 * @since 2.0 191 */ 192 public BaseServerResponseException addResponseHeader(String theName, String theValue) { 193 Validate.notBlank(theName, "theName must not be null or empty"); 194 Validate.notBlank(theValue, "theValue must not be null or empty"); 195 if (getResponseHeaders().containsKey(theName) == false) { 196 getResponseHeaders().put(theName, new ArrayList<String>()); 197 } 198 getResponseHeaders().get(theName).add(theValue); 199 return this; 200 } 201 202 public List<String> getAdditionalMessages() { 203 return myAdditionalMessages; 204 } 205 206 /** 207 * Returns the {@link IBaseOperationOutcome} resource if any which was supplied in the response, or <code>null</code> 208 */ 209 public IBaseOperationOutcome getOperationOutcome() { 210 return myBaseOperationOutcome; 211 } 212 213 /** 214 * 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. 215 * <p> 216 * In a restful server, this method is currently ignored. 217 * </p> 218 */ 219 public String getResponseBody() { 220 return myResponseBody; 221 } 222 223 /** 224 * Returns a map containing any headers which should be added to the outgoing 225 * response. This methos creates the map if none exists, so it will never 226 * return <code>null</code> 227 * 228 * @since 2.0 (note that this method existed in previous versions of HAPI but the method 229 * signature has been changed from <code>Map<String, String[]></code> to <code>Map<String, List<String>></code> 230 */ 231 public Map<String, List<String>> getResponseHeaders() { 232 if (myResponseHeaders == null) { 233 myResponseHeaders = new HashMap<String, List<String>>(); 234 } 235 return myResponseHeaders; 236 } 237 238 /** 239 * In a RESTful client, this method will be populated with the HTTP status code that was returned with the HTTP response. 240 * <p> 241 * In a restful server, this method is currently ignored. 242 * </p> 243 */ 244 public String getResponseMimeType() { 245 return myResponseMimeType; 246 } 247 248 /** 249 * Returns the HTTP status code corresponding to this problem 250 */ 251 public int getStatusCode() { 252 return myStatusCode; 253 } 254 255 /** 256 * Does the exception have any headers which should be added to the outgoing response? 257 * 258 * @see #getResponseHeaders() 259 * @since 2.0 260 */ 261 public boolean hasResponseHeaders() { 262 return myResponseHeaders != null && myResponseHeaders.isEmpty() == false; 263 } 264 265 /** 266 * Sets the BaseOperationOutcome resource associated with this exception. In server implementations, this is the OperartionOutcome resource to include with the HTTP response. In client 267 * implementations you should not call this method. 268 * 269 * @param theBaseOperationOutcome 270 * The BaseOperationOutcome resource Sets the BaseOperationOutcome resource associated with this exception. In server implementations, this is the OperartionOutcome resource to include 271 * with the HTTP response. In client implementations you should not call this method. 272 */ 273 public void setOperationOutcome(IBaseOperationOutcome theBaseOperationOutcome) { 274 myBaseOperationOutcome = theBaseOperationOutcome; 275 } 276 277 /** 278 * This method is currently only called internally by HAPI, it should not be called by user code. 279 */ 280 public void setResponseBody(String theResponseBody) { 281 myResponseBody = theResponseBody; 282 } 283 284 /** 285 * This method is currently only called internally by HAPI, it should not be called by user code. 286 */ 287 public void setResponseMimeType(String theResponseMimeType) { 288 myResponseMimeType = theResponseMimeType; 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}