001package ca.uhn.fhir.rest.server.exceptions;
002
003import org.apache.commons.lang3.Validate;
004import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
005
006import java.lang.reflect.InvocationTargetException;
007import java.util.*;
008
009
010/*
011 * #%L
012 * HAPI FHIR - Core Library
013 * %%
014 * Copyright (C) 2014 - 2019 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(PayloadTooLargeException.STATUS_CODE, PayloadTooLargeException.class);
053                registerExceptionType(AuthenticationException.STATUS_CODE, AuthenticationException.class);
054                registerExceptionType(InternalErrorException.STATUS_CODE, InternalErrorException.class);
055                registerExceptionType(InvalidRequestException.STATUS_CODE, InvalidRequestException.class);
056                registerExceptionType(MethodNotAllowedException.STATUS_CODE, MethodNotAllowedException.class);
057                registerExceptionType(NotImplementedOperationException.STATUS_CODE, NotImplementedOperationException.class);
058                registerExceptionType(NotModifiedException.STATUS_CODE, NotModifiedException.class);
059                registerExceptionType(ResourceNotFoundException.STATUS_CODE, ResourceNotFoundException.class);
060                registerExceptionType(ResourceGoneException.STATUS_CODE, ResourceGoneException.class);
061                registerExceptionType(PreconditionFailedException.STATUS_CODE, PreconditionFailedException.class);
062                registerExceptionType(ResourceVersionConflictException.STATUS_CODE, ResourceVersionConflictException.class);
063                registerExceptionType(UnprocessableEntityException.STATUS_CODE, UnprocessableEntityException.class);
064                registerExceptionType(ForbiddenOperationException.STATUS_CODE, ForbiddenOperationException.class);
065        }
066
067        private List<String> myAdditionalMessages = null;
068        private IBaseOperationOutcome myBaseOperationOutcome;
069        private String myResponseBody;
070        private Map<String, List<String>> myResponseHeaders;
071        private String myResponseMimeType;
072        private int myStatusCode;
073        private boolean myErrorMessageTrusted;
074
075        /**
076         * Constructor
077         *
078         * @param theStatusCode The HTTP status code corresponding to this problem
079         * @param theMessage    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 The HTTP status code corresponding to this problem
091         * @param theMessages   The messages
092         */
093        public BaseServerResponseException(int theStatusCode, String... theMessages) {
094                super(theMessages != null && theMessages.length > 0 ? theMessages[0] : null);
095                myStatusCode = theStatusCode;
096                myBaseOperationOutcome = null;
097                if (theMessages != null && theMessages.length > 1) {
098                        myAdditionalMessages = Arrays.asList(Arrays.copyOfRange(theMessages, 1, theMessages.length, String[].class));
099                }
100        }
101
102        /**
103         * Constructor
104         *
105         * @param theStatusCode           The HTTP status code corresponding to this problem
106         * @param theMessage              The message
107         * @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)
108         */
109        public BaseServerResponseException(int theStatusCode, String theMessage, IBaseOperationOutcome theBaseOperationOutcome) {
110                super(theMessage);
111                myStatusCode = theStatusCode;
112                myBaseOperationOutcome = theBaseOperationOutcome;
113        }
114
115        /**
116         * Constructor
117         *
118         * @param theStatusCode The HTTP status code corresponding to this problem
119         * @param theMessage    The message
120         * @param theCause      The cause
121         */
122        public BaseServerResponseException(int theStatusCode, String theMessage, Throwable theCause) {
123                super(theMessage, theCause);
124                myStatusCode = theStatusCode;
125                myBaseOperationOutcome = null;
126        }
127
128        /**
129         * Constructor
130         *
131         * @param theStatusCode           The HTTP status code corresponding to this problem
132         * @param theMessage              The message
133         * @param theCause                The underlying cause exception
134         * @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)
135         */
136        public BaseServerResponseException(int theStatusCode, String theMessage, Throwable theCause, IBaseOperationOutcome theBaseOperationOutcome) {
137                super(theMessage, theCause);
138                myStatusCode = theStatusCode;
139                myBaseOperationOutcome = theBaseOperationOutcome;
140        }
141
142        /**
143         * Constructor
144         *
145         * @param theStatusCode The HTTP status code corresponding to this problem
146         * @param theCause      The underlying cause exception
147         */
148        public BaseServerResponseException(int theStatusCode, Throwable theCause) {
149                super(theCause.getMessage(), theCause);
150                myStatusCode = theStatusCode;
151                myBaseOperationOutcome = null;
152        }
153
154        /**
155         * Constructor
156         *
157         * @param theStatusCode           The HTTP status code corresponding to this problem
158         * @param theCause                The underlying cause exception
159         * @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)
160         */
161        public BaseServerResponseException(int theStatusCode, Throwable theCause, IBaseOperationOutcome theBaseOperationOutcome) {
162                super(theCause.toString(), theCause);
163                myStatusCode = theStatusCode;
164                myBaseOperationOutcome = theBaseOperationOutcome;
165        }
166
167        /**
168         * This flag can be used to signal to server infrastructure that the message supplied
169         * to this exception (ie to the constructor) is considered trusted and is safe to
170         * return to the calling client.
171         */
172        public boolean isErrorMessageTrusted() {
173                return myErrorMessageTrusted;
174        }
175
176        /**
177         * This flag can be used to signal to server infrastructure that the message supplied
178         * to this exception (ie to the constructor) is considered trusted and is safe to
179         * return to the calling client.
180         */
181        public BaseServerResponseException setErrorMessageTrusted(boolean theErrorMessageTrusted) {
182                myErrorMessageTrusted = theErrorMessageTrusted;
183                return this;
184        }
185
186        /**
187         * Add a header which will be added to any responses
188         *
189         * @param theName  The header name
190         * @param theValue The header value
191         * @return Returns a reference to <code>this</code> for easy method chaining
192         * @since 2.0
193         */
194        public BaseServerResponseException addResponseHeader(String theName, String theValue) {
195                Validate.notBlank(theName, "theName must not be null or empty");
196                Validate.notBlank(theValue, "theValue must not be null or empty");
197                if (getResponseHeaders().containsKey(theName) == false) {
198                        getResponseHeaders().put(theName, new ArrayList<>());
199                }
200                getResponseHeaders().get(theName).add(theValue);
201                return this;
202        }
203
204        public List<String> getAdditionalMessages() {
205                return myAdditionalMessages;
206        }
207
208        /**
209         * Returns the {@link IBaseOperationOutcome} resource if any which was supplied in the response, or <code>null</code>
210         */
211        public IBaseOperationOutcome getOperationOutcome() {
212                return myBaseOperationOutcome;
213        }
214
215        /**
216         * Sets the BaseOperationOutcome resource associated with this exception. In server implementations, this is the OperartionOutcome resource to include with the HTTP response. In client
217         * implementations you should not call this method.
218         *
219         * @param theBaseOperationOutcome The BaseOperationOutcome resource Sets the BaseOperationOutcome resource associated with this exception. In server implementations, this is the OperartionOutcome resource to include
220         *                                with the HTTP response. In client implementations you should not call this method.
221         */
222        public void setOperationOutcome(IBaseOperationOutcome theBaseOperationOutcome) {
223                myBaseOperationOutcome = theBaseOperationOutcome;
224        }
225
226        /**
227         * 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.
228         * <p>
229         * In a restful server, this method is currently ignored.
230         * </p>
231         */
232        public String getResponseBody() {
233                return myResponseBody;
234        }
235
236        /**
237         * This method is currently only called internally by HAPI, it should not be called by user code.
238         */
239        public void setResponseBody(String theResponseBody) {
240                myResponseBody = theResponseBody;
241        }
242
243        /**
244         * Returns a map containing any headers which should be added to the outgoing
245         * response. This methos creates the map if none exists, so it will never
246         * return <code>null</code>
247         *
248         * @since 2.0 (note that this method existed in previous versions of HAPI but the method
249         * signature has been changed from <code>Map&lt;String, String[]&gt;</code> to <code>Map&lt;String, List&lt;String&gt;&gt;</code>
250         */
251        public Map<String, List<String>> getResponseHeaders() {
252                if (myResponseHeaders == null) {
253                        myResponseHeaders = new HashMap<>();
254                }
255                return myResponseHeaders;
256        }
257
258        /**
259         * In a RESTful client, this method will be populated with the HTTP status code that was returned with the HTTP response.
260         * <p>
261         * In a restful server, this method is currently ignored.
262         * </p>
263         */
264        public String getResponseMimeType() {
265                return myResponseMimeType;
266        }
267
268        /**
269         * This method is currently only called internally by HAPI, it should not be called by user code.
270         */
271        public void setResponseMimeType(String theResponseMimeType) {
272                myResponseMimeType = theResponseMimeType;
273        }
274
275        /**
276         * Returns the HTTP status code corresponding to this problem
277         */
278        public int getStatusCode() {
279                return myStatusCode;
280        }
281
282        /**
283         * Does the exception have any headers which should be added to the outgoing response?
284         *
285         * @see #getResponseHeaders()
286         * @since 2.0
287         */
288        public boolean hasResponseHeaders() {
289                return myResponseHeaders != null && myResponseHeaders.isEmpty() == false;
290        }
291
292        /**
293         * For unit tests only
294         */
295        static boolean isExceptionTypeRegistered(Class<?> theType) {
296                return ourStatusCodeToExceptionType.values().contains(theType);
297        }
298
299        public static BaseServerResponseException newInstance(int theStatusCode, String theMessage) {
300                if (ourStatusCodeToExceptionType.containsKey(theStatusCode)) {
301                        try {
302                                return ourStatusCodeToExceptionType.get(theStatusCode).getConstructor(new Class[]{String.class}).newInstance(theMessage);
303                        } catch (InstantiationException e) {
304                                throw new InternalErrorException(e);
305                        } catch (IllegalAccessException e) {
306                                throw new InternalErrorException(e);
307                        } catch (IllegalArgumentException e) {
308                                throw new InternalErrorException(e);
309                        } catch (InvocationTargetException e) {
310                                throw new InternalErrorException(e);
311                        } catch (NoSuchMethodException e) {
312                                throw new InternalErrorException(e);
313                        } catch (SecurityException e) {
314                                throw new InternalErrorException(e);
315                        }
316                }
317                return new UnclassifiedServerFailureException(theStatusCode, theMessage);
318        }
319
320        static void registerExceptionType(int theStatusCode, Class<? extends BaseServerResponseException> theType) {
321                if (ourStatusCodeToExceptionType.containsKey(theStatusCode)) {
322                        throw new Error("Can not register " + theType + " to status code " + theStatusCode + " because " + ourStatusCodeToExceptionType.get(theStatusCode) + " already registers that code");
323                }
324                ourStatusCodeToExceptionType.put(theStatusCode, theType);
325        }
326
327}