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&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}