View Javadoc
1   package ca.uhn.fhir.rest.server.exceptions;
2   
3   import org.apache.commons.lang3.Validate;
4   import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
5   
6   import java.lang.reflect.InvocationTargetException;
7   import java.util.*;
8   
9   
10  /*
11   * #%L
12   * HAPI FHIR - Core Library
13   * %%
14   * Copyright (C) 2014 - 2019 University Health Network
15   * %%
16   * Licensed under the Apache License, Version 2.0 (the "License");
17   * you may not use this file except in compliance with the License.
18   * You may obtain a copy of the License at
19   * 
20   *      http://www.apache.org/licenses/LICENSE-2.0
21   * 
22   * Unless required by applicable law or agreed to in writing, software
23   * distributed under the License is distributed on an "AS IS" BASIS,
24   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25   * See the License for the specific language governing permissions and
26   * limitations under the License.
27   * #L%
28   */
29  
30  /**
31   * 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
32   * subclasses of this exception type.
33   * <p>
34   * HAPI provides a number of subclasses of BaseServerResponseException, and each one corresponds to a specific
35   * HTTP status code. For example, if a IResourceProvider method throws
36   * {@link ResourceNotFoundException}, this is a signal to the server that an <code>HTTP 404</code> should
37   * be returned to the client.
38   * </p>
39   * <p>
40   * <b>See:</b> A complete list of available exceptions is in the <a href="./package-summary.html">package summary</a>.
41   * If an exception doesn't exist for a condition you want to represent, let us know by filing an
42   * <a href="https://github.com/jamesagnew/hapi-fhir/issues">issue in our tracker</a>. You may also
43   * use {@link UnclassifiedServerFailureException} to represent any error code you want.
44   * </p>
45   */
46  public abstract class BaseServerResponseException extends RuntimeException {
47  
48  	private static final Map<Integer, Class<? extends BaseServerResponseException>> ourStatusCodeToExceptionType = new HashMap<Integer, Class<? extends BaseServerResponseException>>();
49  	private static final long serialVersionUID = 1L;
50  
51  	static {
52  		registerExceptionType(AuthenticationException.STATUS_CODE, AuthenticationException.class);
53  		registerExceptionType(InternalErrorException.STATUS_CODE, InternalErrorException.class);
54  		registerExceptionType(InvalidRequestException.STATUS_CODE, InvalidRequestException.class);
55  		registerExceptionType(MethodNotAllowedException.STATUS_CODE, MethodNotAllowedException.class);
56  		registerExceptionType(NotImplementedOperationException.STATUS_CODE, NotImplementedOperationException.class);
57  		registerExceptionType(NotModifiedException.STATUS_CODE, NotModifiedException.class);
58  		registerExceptionType(ResourceNotFoundException.STATUS_CODE, ResourceNotFoundException.class);
59  		registerExceptionType(ResourceGoneException.STATUS_CODE, ResourceGoneException.class);
60  		registerExceptionType(PreconditionFailedException.STATUS_CODE, PreconditionFailedException.class);
61  		registerExceptionType(ResourceVersionConflictException.STATUS_CODE, ResourceVersionConflictException.class);
62  		registerExceptionType(UnprocessableEntityException.STATUS_CODE, UnprocessableEntityException.class);
63  		registerExceptionType(ForbiddenOperationException.STATUS_CODE, ForbiddenOperationException.class);
64  	}
65  
66  	private List<String> myAdditionalMessages = null;
67  	private IBaseOperationOutcome myBaseOperationOutcome;
68  	private String myResponseBody;
69  	private Map<String, List<String>> myResponseHeaders;
70  	private String myResponseMimeType;
71  	private int myStatusCode;
72  	private boolean myErrorMessageTrusted;
73  
74  	/**
75  	 * Constructor
76  	 *
77  	 * @param theStatusCode The HTTP status code corresponding to this problem
78  	 * @param theMessage    The message
79  	 */
80  	public BaseServerResponseException(int theStatusCode, String theMessage) {
81  		super(theMessage);
82  		myStatusCode = theStatusCode;
83  		myBaseOperationOutcome = null;
84  	}
85  
86  	/**
87  	 * Constructor
88  	 *
89  	 * @param theStatusCode The HTTP status code corresponding to this problem
90  	 * @param theMessages   The messages
91  	 */
92  	public BaseServerResponseException(int theStatusCode, String... theMessages) {
93  		super(theMessages != null && theMessages.length > 0 ? theMessages[0] : null);
94  		myStatusCode = theStatusCode;
95  		myBaseOperationOutcome = null;
96  		if (theMessages != null && theMessages.length > 1) {
97  			myAdditionalMessages = Arrays.asList(Arrays.copyOfRange(theMessages, 1, theMessages.length, String[].class));
98  		}
99  	}
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&lt;String, String[]&gt;</code> to <code>Map&lt;String, List&lt;String&gt;&gt;</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 }