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