001package ca.uhn.fhir.rest.server.exceptions;
002
003import java.lang.reflect.InvocationTargetException;
004import java.util.Arrays;
005import java.util.Collections;
006import java.util.HashMap;
007import java.util.List;
008import java.util.Map;
009
010import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
011
012import ca.uhn.fhir.rest.server.IResourceProvider;
013
014/*
015 * #%L
016 * HAPI FHIR - Core Library
017 * %%
018 * Copyright (C) 2014 - 2016 University Health Network
019 * %%
020 * Licensed under the Apache License, Version 2.0 (the "License");
021 * you may not use this file except in compliance with the License.
022 * You may obtain a copy of the License at
023 * 
024 *      http://www.apache.org/licenses/LICENSE-2.0
025 * 
026 * Unless required by applicable law or agreed to in writing, software
027 * distributed under the License is distributed on an "AS IS" BASIS,
028 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
029 * See the License for the specific language governing permissions and
030 * limitations under the License.
031 * #L%
032 */
033
034/**
035 * 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
036 * subclasses of this exception type.
037 * <p>
038 * HAPI provides a number of subclasses of BaseServerResponseException, and each one corresponds to a specific
039 * HTTP status code. For example, if a {@link IResourceProvider resource provider} method throws 
040 * {@link ResourceNotFoundException}, this is a signal to the server that an <code>HTTP 404</code> should
041 * be returned to the client.
042 * </p>
043 * <p>
044 * <b>See:</b> A complete list of available exceptions is in the <a href="./package-summary.html">package summary</a>.
045 * If an exception doesn't exist for a condition you want to represent, let us know by filing an
046 * <a href="https://github.com/jamesagnew/hapi-fhir/issues">issue in our tracker</a>. You may also
047 * use {@link UnclassifiedServerFailureException} to represent any error code you want.
048 * </p>
049 */
050public abstract class BaseServerResponseException extends RuntimeException {
051
052        private static final Map<Integer, Class<? extends BaseServerResponseException>> ourStatusCodeToExceptionType = new HashMap<Integer, Class<? extends BaseServerResponseException>>();
053        private static final long serialVersionUID = 1L;
054
055        static {
056                registerExceptionType(AuthenticationException.STATUS_CODE, AuthenticationException.class);
057                registerExceptionType(InternalErrorException.STATUS_CODE, InternalErrorException.class);
058                registerExceptionType(InvalidRequestException.STATUS_CODE, InvalidRequestException.class);
059                registerExceptionType(MethodNotAllowedException.STATUS_CODE, MethodNotAllowedException.class);
060                registerExceptionType(NotImplementedOperationException.STATUS_CODE, NotImplementedOperationException.class);
061                registerExceptionType(NotModifiedException.STATUS_CODE, NotModifiedException.class);
062                registerExceptionType(ResourceNotFoundException.STATUS_CODE, ResourceNotFoundException.class);
063                registerExceptionType(ResourceGoneException.STATUS_CODE, ResourceGoneException.class);
064                registerExceptionType(PreconditionFailedException.STATUS_CODE, PreconditionFailedException.class);
065                registerExceptionType(ResourceVersionConflictException.STATUS_CODE, ResourceVersionConflictException.class);
066                registerExceptionType(UnprocessableEntityException.STATUS_CODE, UnprocessableEntityException.class);
067                registerExceptionType(ForbiddenOperationException.STATUS_CODE, ForbiddenOperationException.class);
068        }
069
070        private List<String> myAdditionalMessages = null;
071        private IBaseOperationOutcome myBaseOperationOutcome;
072        private String myResponseBody;
073        private String myResponseMimeType;
074        private int myStatusCode;
075
076        public static void main (String[] args) {
077                BaseServerResponseException.class.getName();
078        }
079        
080        /**
081         * Constructor
082         * 
083         * @param theStatusCode
084         *            The HTTP status code corresponding to this problem
085         * @param theMessage
086         *            The message
087         */
088        public BaseServerResponseException(int theStatusCode, String theMessage) {
089                super(theMessage);
090                myStatusCode = theStatusCode;
091                myBaseOperationOutcome = null;
092        }
093
094        /**
095         * Constructor
096         * 
097         * @param theStatusCode
098         *            The HTTP status code corresponding to this problem
099         * @param theMessages
100         *            The messages
101         */
102        public BaseServerResponseException(int theStatusCode, String... theMessages) {
103                super(theMessages != null && theMessages.length > 0 ? theMessages[0] : null);
104                myStatusCode = theStatusCode;
105                myBaseOperationOutcome = null;
106                if (theMessages != null && theMessages.length > 1) {
107                        myAdditionalMessages = Arrays.asList(Arrays.copyOfRange(theMessages, 1, theMessages.length, String[].class));
108                }
109        }
110
111        /**
112         * Constructor
113         * 
114         * @param theStatusCode
115         *            The HTTP status code corresponding to this problem
116         * @param theMessage
117         *            The message
118         * @param theBaseOperationOutcome
119         *            An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client)
120         */
121        public BaseServerResponseException(int theStatusCode, String theMessage, IBaseOperationOutcome theBaseOperationOutcome) {
122                super(theMessage);
123                myStatusCode = theStatusCode;
124                myBaseOperationOutcome = theBaseOperationOutcome;
125        }
126
127        /**
128         * Constructor
129         * 
130         * @param theStatusCode
131         *            The HTTP status code corresponding to this problem
132         * @param theMessage
133         *            The message
134         * @param theCause
135         *            The cause
136         */
137        public BaseServerResponseException(int theStatusCode, String theMessage, Throwable theCause) {
138                super(theMessage, theCause);
139                myStatusCode = theStatusCode;
140                myBaseOperationOutcome = null;
141        }
142
143        /**
144         * Constructor
145         * 
146         * @param theStatusCode
147         *            The HTTP status code corresponding to this problem
148         * @param theMessage
149         *            The message
150         * @param theCause
151         *            The underlying cause exception
152         * @param theBaseOperationOutcome
153         *            An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client)
154         */
155        public BaseServerResponseException(int theStatusCode, String theMessage, Throwable theCause, IBaseOperationOutcome theBaseOperationOutcome) {
156                super(theMessage, theCause);
157                myStatusCode = theStatusCode;
158                myBaseOperationOutcome = theBaseOperationOutcome;
159        }
160
161        /**
162         * Constructor
163         * 
164         * @param theStatusCode
165         *            The HTTP status code corresponding to this problem
166         * @param theCause
167         *            The underlying cause exception
168         */
169        public BaseServerResponseException(int theStatusCode, Throwable theCause) {
170                super(theCause.toString(), theCause);
171                myStatusCode = theStatusCode;
172                myBaseOperationOutcome = null;
173        }
174
175        /**
176         * Constructor
177         * 
178         * @param theStatusCode
179         *            The HTTP status code corresponding to this problem
180         * @param theCause
181         *            The underlying cause exception
182         * @param theBaseOperationOutcome
183         *            An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client)
184         */
185        public BaseServerResponseException(int theStatusCode, Throwable theCause, IBaseOperationOutcome theBaseOperationOutcome) {
186                super(theCause.toString(), theCause);
187                myStatusCode = theStatusCode;
188                myBaseOperationOutcome = theBaseOperationOutcome;
189        }
190
191        public List<String> getAdditionalMessages() {
192                return myAdditionalMessages;
193        }
194
195        /**
196         * Returns the HTTP headers associated with this exception.
197         */
198        public Map<String, String[]> getAssociatedHeaders() {
199                return Collections.emptyMap();
200        }
201
202        /**
203         * Returns the {@link IBaseOperationOutcome} resource if any which was supplied in the response, or <code>null</code>
204         */
205        public IBaseOperationOutcome getOperationOutcome() {
206                return myBaseOperationOutcome;
207        }
208
209        /**
210         * 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.
211         * <p>
212         * In a restful server, this method is currently ignored.
213         * </p>
214         */
215        public String getResponseBody() {
216                return myResponseBody;
217        }
218
219        /**
220         * In a RESTful client, this method will be populated with the HTTP status code that was returned with the HTTP response.
221         * <p>
222         * In a restful server, this method is currently ignored.
223         * </p>
224         */
225        public String getResponseMimeType() {
226                return myResponseMimeType;
227        }
228
229        /**
230         * Returns the HTTP status code corresponding to this problem
231         */
232        public int getStatusCode() {
233                return myStatusCode;
234        }
235
236        /**
237         * Sets the BaseOperationOutcome resource associated with this exception. In server implementations, this is the OperartionOutcome resource to include with the HTTP response. In client
238         * implementations you should not call this method.
239         * 
240         * @param theBaseOperationOutcome
241         *            The BaseOperationOutcome resource Sets the BaseOperationOutcome resource associated with this exception. In server implementations, this is the OperartionOutcome resource to include
242         *            with the HTTP response. In client implementations you should not call this method.
243         */
244        public void setOperationOutcome(IBaseOperationOutcome theBaseOperationOutcome) {
245                myBaseOperationOutcome = theBaseOperationOutcome;
246        }
247
248        /**
249         * This method is currently only called internally by HAPI, it should not be called by user code.
250         */
251        public void setResponseBody(String theResponseBody) {
252                myResponseBody = theResponseBody;
253        }
254
255        /**
256         * This method is currently only called internally by HAPI, it should not be called by user code.
257         */
258        public void setResponseMimeType(String theResponseMimeType) {
259                myResponseMimeType = theResponseMimeType;
260        }
261
262        /**
263         * For unit tests only
264         */
265        static boolean isExceptionTypeRegistered(Class<?> theType) {
266                return ourStatusCodeToExceptionType.values().contains(theType);
267        }
268
269        public static BaseServerResponseException newInstance(int theStatusCode, String theMessage) {
270                if (ourStatusCodeToExceptionType.containsKey(theStatusCode)) {
271                        try {
272                                return ourStatusCodeToExceptionType.get(theStatusCode).getConstructor(new Class[] { String.class }).newInstance(theMessage);
273                        } catch (InstantiationException e) {
274                                throw new InternalErrorException(e);
275                        } catch (IllegalAccessException e) {
276                                throw new InternalErrorException(e);
277                        } catch (IllegalArgumentException e) {
278                                throw new InternalErrorException(e);
279                        } catch (InvocationTargetException e) {
280                                throw new InternalErrorException(e);
281                        } catch (NoSuchMethodException e) {
282                                throw new InternalErrorException(e);
283                        } catch (SecurityException e) {
284                                throw new InternalErrorException(e);
285                        }
286                } else {
287                        return new UnclassifiedServerFailureException(theStatusCode, theMessage);
288                }
289        }
290
291        static void registerExceptionType(int theStatusCode, Class<? extends BaseServerResponseException> theType) {
292                if (ourStatusCodeToExceptionType.containsKey(theStatusCode)) {
293                        throw new Error("Can not register " + theType + " to status code " + theStatusCode + " because " + ourStatusCodeToExceptionType.get(theStatusCode) + " already registers that code");
294                }
295                ourStatusCodeToExceptionType.put(theStatusCode, theType);
296        }
297
298}