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(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        private boolean myErrorMessageTrusted;
073
074        /**
075         * Constructor
076         *
077         * @param theStatusCode The HTTP status code corresponding to this problem
078         * @param theMessage    The message
079         */
080        public BaseServerResponseException(int theStatusCode, String theMessage) {
081                super(theMessage);
082                myStatusCode = theStatusCode;
083                myBaseOperationOutcome = null;
084        }
085
086        /**
087         * Constructor
088         *
089         * @param theStatusCode The HTTP status code corresponding to this problem
090         * @param theMessages   The messages
091         */
092        public BaseServerResponseException(int theStatusCode, String... theMessages) {
093                super(theMessages != null && theMessages.length > 0 ? theMessages[0] : null);
094                myStatusCode = theStatusCode;
095                myBaseOperationOutcome = null;
096                if (theMessages != null && theMessages.length > 1) {
097                        myAdditionalMessages = Arrays.asList(Arrays.copyOfRange(theMessages, 1, theMessages.length, String[].class));
098                }
099        }
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}