001package ca.uhn.fhir.rest.server.exceptions; 002 003import java.lang.reflect.InvocationTargetException; 004import java.util.Arrays; 005import java.util.Collections; 006import java.util.HashMap; 007import java.util.List; 008import java.util.Map; 009 010import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; 011 012import ca.uhn.fhir.rest.server.IResourceProvider; 013 014/* 015 * #%L 016 * HAPI FHIR - Core Library 017 * %% 018 * Copyright (C) 2014 - 2016 University Health Network 019 * %% 020 * Licensed under the Apache License, Version 2.0 (the "License"); 021 * you may not use this file except in compliance with the License. 022 * You may obtain a copy of the License at 023 * 024 * http://www.apache.org/licenses/LICENSE-2.0 025 * 026 * Unless required by applicable law or agreed to in writing, software 027 * distributed under the License is distributed on an "AS IS" BASIS, 028 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 029 * See the License for the specific language governing permissions and 030 * limitations under the License. 031 * #L% 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 {@link IResourceProvider resource provider} 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/jamesagnew/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(AuthenticationException.STATUS_CODE, AuthenticationException.class); 057 registerExceptionType(InternalErrorException.STATUS_CODE, InternalErrorException.class); 058 registerExceptionType(InvalidRequestException.STATUS_CODE, InvalidRequestException.class); 059 registerExceptionType(MethodNotAllowedException.STATUS_CODE, MethodNotAllowedException.class); 060 registerExceptionType(NotImplementedOperationException.STATUS_CODE, NotImplementedOperationException.class); 061 registerExceptionType(NotModifiedException.STATUS_CODE, NotModifiedException.class); 062 registerExceptionType(ResourceNotFoundException.STATUS_CODE, ResourceNotFoundException.class); 063 registerExceptionType(ResourceGoneException.STATUS_CODE, ResourceGoneException.class); 064 registerExceptionType(PreconditionFailedException.STATUS_CODE, PreconditionFailedException.class); 065 registerExceptionType(ResourceVersionConflictException.STATUS_CODE, ResourceVersionConflictException.class); 066 registerExceptionType(UnprocessableEntityException.STATUS_CODE, UnprocessableEntityException.class); 067 registerExceptionType(ForbiddenOperationException.STATUS_CODE, ForbiddenOperationException.class); 068 } 069 070 private List<String> myAdditionalMessages = null; 071 private IBaseOperationOutcome myBaseOperationOutcome; 072 private String myResponseBody; 073 private String myResponseMimeType; 074 private int myStatusCode; 075 076 public static void main (String[] args) { 077 BaseServerResponseException.class.getName(); 078 } 079 080 /** 081 * Constructor 082 * 083 * @param theStatusCode 084 * The HTTP status code corresponding to this problem 085 * @param theMessage 086 * The message 087 */ 088 public BaseServerResponseException(int theStatusCode, String theMessage) { 089 super(theMessage); 090 myStatusCode = theStatusCode; 091 myBaseOperationOutcome = null; 092 } 093 094 /** 095 * Constructor 096 * 097 * @param theStatusCode 098 * The HTTP status code corresponding to this problem 099 * @param theMessages 100 * The messages 101 */ 102 public BaseServerResponseException(int theStatusCode, String... theMessages) { 103 super(theMessages != null && theMessages.length > 0 ? theMessages[0] : null); 104 myStatusCode = theStatusCode; 105 myBaseOperationOutcome = null; 106 if (theMessages != null && theMessages.length > 1) { 107 myAdditionalMessages = Arrays.asList(Arrays.copyOfRange(theMessages, 1, theMessages.length, String[].class)); 108 } 109 } 110 111 /** 112 * Constructor 113 * 114 * @param theStatusCode 115 * The HTTP status code corresponding to this problem 116 * @param theMessage 117 * The message 118 * @param theBaseOperationOutcome 119 * An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client) 120 */ 121 public BaseServerResponseException(int theStatusCode, String theMessage, IBaseOperationOutcome theBaseOperationOutcome) { 122 super(theMessage); 123 myStatusCode = theStatusCode; 124 myBaseOperationOutcome = theBaseOperationOutcome; 125 } 126 127 /** 128 * Constructor 129 * 130 * @param theStatusCode 131 * The HTTP status code corresponding to this problem 132 * @param theMessage 133 * The message 134 * @param theCause 135 * The cause 136 */ 137 public BaseServerResponseException(int theStatusCode, String theMessage, Throwable theCause) { 138 super(theMessage, theCause); 139 myStatusCode = theStatusCode; 140 myBaseOperationOutcome = null; 141 } 142 143 /** 144 * Constructor 145 * 146 * @param theStatusCode 147 * The HTTP status code corresponding to this problem 148 * @param theMessage 149 * The message 150 * @param theCause 151 * The underlying cause exception 152 * @param theBaseOperationOutcome 153 * An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client) 154 */ 155 public BaseServerResponseException(int theStatusCode, String theMessage, Throwable theCause, IBaseOperationOutcome theBaseOperationOutcome) { 156 super(theMessage, theCause); 157 myStatusCode = theStatusCode; 158 myBaseOperationOutcome = theBaseOperationOutcome; 159 } 160 161 /** 162 * Constructor 163 * 164 * @param theStatusCode 165 * The HTTP status code corresponding to this problem 166 * @param theCause 167 * The underlying cause exception 168 */ 169 public BaseServerResponseException(int theStatusCode, Throwable theCause) { 170 super(theCause.toString(), theCause); 171 myStatusCode = theStatusCode; 172 myBaseOperationOutcome = null; 173 } 174 175 /** 176 * Constructor 177 * 178 * @param theStatusCode 179 * The HTTP status code corresponding to this problem 180 * @param theCause 181 * The underlying cause exception 182 * @param theBaseOperationOutcome 183 * An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client) 184 */ 185 public BaseServerResponseException(int theStatusCode, Throwable theCause, IBaseOperationOutcome theBaseOperationOutcome) { 186 super(theCause.toString(), theCause); 187 myStatusCode = theStatusCode; 188 myBaseOperationOutcome = theBaseOperationOutcome; 189 } 190 191 public List<String> getAdditionalMessages() { 192 return myAdditionalMessages; 193 } 194 195 /** 196 * Returns the HTTP headers associated with this exception. 197 */ 198 public Map<String, String[]> getAssociatedHeaders() { 199 return Collections.emptyMap(); 200 } 201 202 /** 203 * Returns the {@link IBaseOperationOutcome} resource if any which was supplied in the response, or <code>null</code> 204 */ 205 public IBaseOperationOutcome getOperationOutcome() { 206 return myBaseOperationOutcome; 207 } 208 209 /** 210 * 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. 211 * <p> 212 * In a restful server, this method is currently ignored. 213 * </p> 214 */ 215 public String getResponseBody() { 216 return myResponseBody; 217 } 218 219 /** 220 * In a RESTful client, this method will be populated with the HTTP status code that was returned with the HTTP response. 221 * <p> 222 * In a restful server, this method is currently ignored. 223 * </p> 224 */ 225 public String getResponseMimeType() { 226 return myResponseMimeType; 227 } 228 229 /** 230 * Returns the HTTP status code corresponding to this problem 231 */ 232 public int getStatusCode() { 233 return myStatusCode; 234 } 235 236 /** 237 * Sets the BaseOperationOutcome resource associated with this exception. In server implementations, this is the OperartionOutcome resource to include with the HTTP response. In client 238 * implementations you should not call this method. 239 * 240 * @param theBaseOperationOutcome 241 * The BaseOperationOutcome resource Sets the BaseOperationOutcome resource associated with this exception. In server implementations, this is the OperartionOutcome resource to include 242 * with the HTTP response. In client implementations you should not call this method. 243 */ 244 public void setOperationOutcome(IBaseOperationOutcome theBaseOperationOutcome) { 245 myBaseOperationOutcome = theBaseOperationOutcome; 246 } 247 248 /** 249 * This method is currently only called internally by HAPI, it should not be called by user code. 250 */ 251 public void setResponseBody(String theResponseBody) { 252 myResponseBody = theResponseBody; 253 } 254 255 /** 256 * This method is currently only called internally by HAPI, it should not be called by user code. 257 */ 258 public void setResponseMimeType(String theResponseMimeType) { 259 myResponseMimeType = theResponseMimeType; 260 } 261 262 /** 263 * For unit tests only 264 */ 265 static boolean isExceptionTypeRegistered(Class<?> theType) { 266 return ourStatusCodeToExceptionType.values().contains(theType); 267 } 268 269 public static BaseServerResponseException newInstance(int theStatusCode, String theMessage) { 270 if (ourStatusCodeToExceptionType.containsKey(theStatusCode)) { 271 try { 272 return ourStatusCodeToExceptionType.get(theStatusCode).getConstructor(new Class[] { String.class }).newInstance(theMessage); 273 } catch (InstantiationException e) { 274 throw new InternalErrorException(e); 275 } catch (IllegalAccessException e) { 276 throw new InternalErrorException(e); 277 } catch (IllegalArgumentException e) { 278 throw new InternalErrorException(e); 279 } catch (InvocationTargetException e) { 280 throw new InternalErrorException(e); 281 } catch (NoSuchMethodException e) { 282 throw new InternalErrorException(e); 283 } catch (SecurityException e) { 284 throw new InternalErrorException(e); 285 } 286 } else { 287 return new UnclassifiedServerFailureException(theStatusCode, theMessage); 288 } 289 } 290 291 static void registerExceptionType(int theStatusCode, Class<? extends BaseServerResponseException> theType) { 292 if (ourStatusCodeToExceptionType.containsKey(theStatusCode)) { 293 throw new Error("Can not register " + theType + " to status code " + theStatusCode + " because " + ourStatusCodeToExceptionType.get(theStatusCode) + " already registers that code"); 294 } 295 ourStatusCodeToExceptionType.put(theStatusCode, theType); 296 } 297 298}