001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk;
019
020
021import java.net.URI;
022
023import net.jcip.annotations.Immutable;
024
025import net.minidev.json.JSONObject;
026
027import com.nimbusds.oauth2.sdk.http.HTTPResponse;
028import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
029
030
031/**
032 * Error object, used to encapsulate OAuth 2.0 and other errors.
033 *
034 * <p>Example error object as HTTP response:
035 *
036 * <pre>
037 * HTTP/1.1 400 Bad Request
038 * Content-Type: application/json;charset=UTF-8
039 * Cache-Control: no-store
040 * Pragma: no-cache
041 *
042 * {
043 *   "error" : "invalid_request"
044 * }
045 * </pre>
046 */
047@Immutable
048public class ErrorObject {
049        
050        
051        /**
052         * The error code, may not always be defined.
053         */
054        private final String code;
055
056
057        /**
058         * Optional error description.
059         */
060        private final String description;
061
062
063        /**
064         * Optional HTTP status code, 0 if not specified.
065         */
066        private final int httpStatusCode;
067
068
069        /**
070         * Optional URI of a web page that includes additional information 
071         * about the error.
072         */
073        private final URI uri;
074
075
076        /**
077         * Creates a new error with the specified code.
078         *
079         * @param code The error code, {@code null} if not specified.
080         */
081        public ErrorObject(final String code) {
082        
083                this(code, null, 0, null);
084        }
085        
086        
087        /**
088         * Creates a new error with the specified code and description.
089         *
090         * @param code        The error code, {@code null} if not specified.
091         * @param description The error description, {@code null} if not
092         *                    specified.
093         */
094        public ErrorObject(final String code, final String description) {
095        
096                this(code, description, 0, null);
097        }
098
099
100        /**
101         * Creates a new error with the specified code, description and HTTP 
102         * status code.
103         *
104         * @param code           The error code, {@code null} if not specified.
105         * @param description    The error description, {@code null} if not
106         *                       specified.
107         * @param httpStatusCode The HTTP status code, zero if not specified.
108         */
109        public ErrorObject(final String code, final String description, 
110                           final int httpStatusCode) {
111        
112                this(code, description, httpStatusCode, null);
113        }
114
115
116        /**
117         * Creates a new error with the specified code, description, HTTP 
118         * status code and page URI.
119         *
120         * @param code           The error code, {@code null} if not specified.
121         * @param description    The error description, {@code null} if not
122         *                       specified.
123         * @param httpStatusCode The HTTP status code, zero if not specified.
124         * @param uri            The error page URI, {@code null} if not
125         *                       specified.
126         */
127        public ErrorObject(final String code, final String description, 
128                           final int httpStatusCode, final URI uri) {
129        
130                this.code = code;
131                this.description = description;
132                this.httpStatusCode = httpStatusCode;
133                this.uri = uri;
134        }
135
136
137        /**
138         * Gets the error code.
139         *
140         * @return The error code, {@code null} if not specified.
141         */
142        public String getCode() {
143
144                return code;
145        }
146        
147        
148        /**
149         * Gets the error description.
150         *
151         * @return The error description, {@code null} if not specified.
152         */
153        public String getDescription() {
154        
155                return description;
156        }
157
158
159        /**
160         * Sets the error description.
161         *
162         * @param description The error description, {@code null} if not 
163         *                    specified.
164         *
165         * @return A copy of this error with the specified description.
166         */
167        public ErrorObject setDescription(final String description) {
168
169                return new ErrorObject(getCode(), description, getHTTPStatusCode(), getURI());
170        }
171
172
173        /**
174         * Appends the specified text to the error description.
175         *
176         * @param text The text to append to the error description, 
177         *             {@code null} if not specified.
178         *
179         * @return A copy of this error with the specified appended 
180         *         description.
181         */
182        public ErrorObject appendDescription(final String text) {
183
184                String newDescription;
185
186                if (getDescription() != null)
187                        newDescription = getDescription() + text;
188                else
189                        newDescription = text;
190
191                return new ErrorObject(getCode(), newDescription, getHTTPStatusCode(), getURI());
192        }
193
194
195        /**
196         * Gets the HTTP status code.
197         *
198         * @return The HTTP status code, zero if not specified.
199         */
200        public int getHTTPStatusCode() {
201
202                return httpStatusCode;
203        }
204
205
206        /**
207         * Sets the HTTP status code.
208         *
209         * @param httpStatusCode  The HTTP status code, zero if not specified.
210         *
211         * @return A copy of this error with the specified HTTP status code.
212         */
213        public ErrorObject setHTTPStatusCode(final int httpStatusCode) {
214
215                return new ErrorObject(getCode(), getDescription(), httpStatusCode, getURI());
216        }
217
218
219        /**
220         * Gets the error page URI.
221         *
222         * @return The error page URI, {@code null} if not specified.
223         */
224        public URI getURI() {
225
226                return uri;
227        }
228
229
230        /**
231         * Sets the error page URI.
232         *
233         * @param uri The error page URI, {@code null} if not specified.
234         *
235         * @return A copy of this error with the specified page URI.
236         */
237        public ErrorObject setURI(final URI uri) {
238
239                return new ErrorObject(getCode(), getDescription(), getHTTPStatusCode(), uri);
240        }
241
242
243        /**
244         * Returns a JSON object representation of this error object.
245         *
246         * <p>Example:
247         *
248         * <pre>
249         * {
250         *   "error"             : "invalid_grant",
251         *   "error_description" : "Invalid resource owner credentials"
252           }
253         * </pre>
254         *
255         * @return The JSON object.
256         */
257        public JSONObject toJSONObject() {
258
259                JSONObject o = new JSONObject();
260
261                if (code != null) {
262                        o.put("error", code);
263                }
264
265                if (description != null) {
266                        o.put("error_description", description);
267                }
268
269                if (uri != null) {
270                        o.put("error_uri", uri.toString());
271                }
272
273                return o;
274        }
275
276
277        /**
278         * @see #getCode
279         */
280        @Override
281        public String toString() {
282
283                return code != null ? code : "null";
284        }
285
286
287        @Override
288        public int hashCode() {
289
290                return code != null ? code.hashCode() : "null".hashCode();
291        }
292
293
294        @Override
295        public boolean equals(final Object object) {
296        
297                return object instanceof ErrorObject &&
298                       this.toString().equals(object.toString());
299        }
300
301
302        /**
303         * Parses an error object from the specified JSON object.
304         *
305         * @param jsonObject The JSON object to parse. Must not be
306         *                   {@code null}.
307         *
308         * @return The error object.
309         */
310        public static ErrorObject parse(final JSONObject jsonObject) {
311
312                String code = null;
313                String description = null;
314                URI uri = null;
315
316                try {
317                        if (jsonObject.containsKey("error")) {
318                                code = JSONObjectUtils.getString(jsonObject, "error");
319                        }
320
321                        if (jsonObject.containsKey("error_description")) {
322                                description = JSONObjectUtils.getString(jsonObject, "error_description");
323                        }
324
325                        if (jsonObject.containsKey("error_uri")) {
326                                uri = JSONObjectUtils.getURI(jsonObject, "error_uri");
327                        }
328                } catch (ParseException e) {
329                        // ignore and continue
330                }
331
332                return new ErrorObject(code, description, 0, uri);
333        }
334
335
336        /**
337         * Parses an error object from the specified HTTP response.
338         *
339         * @param httpResponse The HTTP response to parse. Must not be
340         *                     {@code null}.
341         *
342         * @return The error object.
343         */
344        public static ErrorObject parse(final HTTPResponse httpResponse) {
345
346                JSONObject jsonObject;
347
348                try {
349                        jsonObject = httpResponse.getContentAsJSONObject();
350
351                } catch (ParseException e) {
352
353                        return new ErrorObject(null, null, httpResponse.getStatusCode());
354                }
355
356                ErrorObject intermediary = parse(jsonObject);
357
358                return new ErrorObject(
359                        intermediary.getCode(),
360                        intermediary.description,
361                        httpResponse.getStatusCode(),
362                        intermediary.getURI());
363        }
364}