View Javadoc
1   package ca.uhn.fhir.rest.server.exceptions;
2   
3   import java.lang.reflect.InvocationTargetException;
4   import java.util.*;
5   
6   import org.apache.commons.lang3.Validate;
7   import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
8   
9   
10  /*
11   * #%L
12   * HAPI FHIR - Core Library
13   * %%
14   * Copyright (C) 2014 - 2018 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  
73  	/**
74  	 * Constructor
75  	 * 
76  	 * @param theStatusCode
77  	 *            The HTTP status code corresponding to this problem
78  	 * @param theMessage
79  	 *            The message
80  	 */
81  	public BaseServerResponseException(int theStatusCode, String theMessage) {
82  		super(theMessage);
83  		myStatusCode = theStatusCode;
84  		myBaseOperationOutcome = null;
85  	}
86  	
87  	/**
88  	 * Constructor
89  	 * 
90  	 * @param theStatusCode
91  	 *            The HTTP status code corresponding to this problem
92  	 * @param theMessages
93  	 *            The messages
94  	 */
95  	public BaseServerResponseException(int theStatusCode, String... theMessages) {
96  		super(theMessages != null && theMessages.length > 0 ? theMessages[0] : null);
97  		myStatusCode = theStatusCode;
98  		myBaseOperationOutcome = null;
99  		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&lt;String, String[]&gt;</code> to <code>Map&lt;String, List&lt;String&gt;&gt;</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 }