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