001    package com.nimbusds.oauth2.sdk.http;
002    
003    
004    import java.io.IOException;
005    import java.io.PrintWriter;
006    
007    import java.net.URL;
008    
009    import javax.servlet.http.HttpServletResponse;
010    
011    import net.jcip.annotations.ThreadSafe;
012    
013    import net.minidev.json.JSONObject;
014    
015    import com.nimbusds.jwt.JWT;
016    import com.nimbusds.jwt.JWTParser;
017    
018    import com.nimbusds.oauth2.sdk.ParseException;
019    
020    import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
021    
022    
023    /**
024     * HTTP response with support for the parameters required to construct an 
025     * {@link com.nimbusds.oauth2.sdk.Response OAuth 2.0 response message}. This
026     * class is thread-safe.
027     *
028     * <p>Provided HTTP status code constants:
029     *
030     * <ul>
031     *     <li>{@link #SC_OK HTTP 200 OK}
032     *     <li>{@link #SC_FOUND HTTP 302 Redirect}
033     *     <li>{@link #SC_BAD_REQUEST HTTP 400 Bad request}
034     *     <li>{@link #SC_UNAUTHORIZED HTTP 401 Unauthorized}
035     *     <li>{@link #SC_FORBIDDEN HTTP 403 Forbidden}
036     * </ul>
037     *
038     * <p>Supported response headers:
039     *
040     * <ul>
041     *     <li>Location
042     *     <li>Content-Type
043     *     <li>Cache-Control
044     *     <li>Pragma
045     *     <li>Www-Authenticate
046     * </ul>
047     *
048     * @author Vladimir Dzhuvinov
049     * @version $version$ (2013-05-10)
050     */
051    @ThreadSafe
052    public class HTTPResponse extends HTTPMessage {
053    
054            
055            /**
056             * HTTP status code (200) indicating the request succeeded normally.
057             */
058            public static final int SC_OK = 200;
059            
060            
061            /**
062             * HTTP status code (302) indicating that the resource reside 
063             * temporarily under a different URI (redirect).
064             */
065            public static final int SC_FOUND = 302;
066            
067            
068            /**
069             * HTTP status code (400) indicating a bad request.
070             */
071            public static final int SC_BAD_REQUEST = 400;
072            
073            
074            /**
075             * HTTP status code (401) indicating that the request requires HTTP 
076             * authentication.
077             */
078            public static final int SC_UNAUTHORIZED = 401;
079            
080            
081            /**
082             * HTTP status code (403) indicating that access to the resource was
083             * forbidden.
084             */
085            public static final int SC_FORBIDDEN = 403;
086    
087    
088            /**
089             * The HTTP status code.
090             */
091            private final int statusCode;
092            
093            
094            /**
095             * Specifies a {@code Location} header value (for redirects).
096             */
097            private URL location = null;
098            
099            
100            /**
101             * Specifies a {@code Cache-Control} header value.
102             */
103            private String cacheControl = null;
104            
105            
106            /**
107             * Specifies a {@code Pragma} header value.
108             */
109            private String pragma = null;
110            
111            
112            /**
113             * Specifies a {@code Www-Authenticate} header value.
114             */
115            private String wwwAuthenticate = null;
116            
117            
118            /**
119             * The raw response content.
120             */
121            private String content = null;
122            
123            
124            /**
125             * Creates a new minimal HTTP response with the specified status code.
126             *
127             * @param statusCode The HTTP status code.
128             */
129            public HTTPResponse(final int statusCode) {
130            
131                    this.statusCode = statusCode;
132            }
133            
134            
135            /**
136             * Gets the HTTP status code.
137             *
138             * @return The HTTP status code.
139             */
140            public int getStatusCode() {
141            
142                    return statusCode;
143            }
144            
145            
146            /**
147             * Ensures this HTTP response has the specified {@link #getStatusCode
148             * status code}.
149             *
150             * @param statusCode The expected status code.
151             *
152             * @throws ParseException If the status code of this HTTP response 
153             *                        doesn't match the expected.
154             */ 
155            public void ensureStatusCode(final int statusCode)
156                    throws ParseException {
157            
158                    if (this.statusCode != statusCode)
159                            throw new ParseException("Unexpected HTTP status code, must be " +  statusCode);
160            }
161    
162    
163            /**
164             * Ensures this HTTP response does not have a {@link #SC_OK 200 OK} 
165             * status code.
166             *
167             * @throws ParseException If the status code of this HTTP response is
168             *                        200 OK.
169             */
170            public void ensureStatusCodeNotOK()
171                    throws ParseException {
172    
173                    if (statusCode == SC_OK)
174                            throw new ParseException("Unexpected HTTP status code, must not be 200 (OK)");
175            }
176            
177            
178            /**
179             * Gets the {@code Location} header value (for redirects).
180             *
181             * @return The header value, {@code null} if not specified.
182             */
183            public URL getLocation() {
184            
185                    return location;
186            }
187            
188            
189            /**
190             * Sets the {@code Location} header value (for redirects).
191             *
192             * @param location The header value, {@code null} if not specified.
193             */
194            public void setLocation(final URL location) {
195            
196                    this.location = location;
197            }
198            
199            
200            /**
201             * Gets the {@code Cache-Control} header value.
202             *
203             * @return The header value, {@code null} if not specified.
204             */
205            public String getCacheControl() {
206            
207                    return cacheControl;
208            }
209    
210    
211            /**
212             * Sets the {@code Cache-Control} header value.
213             *
214             * @param cacheControl The header value, {@code null} if not specified.
215             */
216            public void setCacheControl(final String cacheControl) {
217            
218                    this.cacheControl = cacheControl;
219            }
220            
221            
222            /**
223             * Gets the {@code Pragma} header value.
224             *
225             * @return The header value, {@code null} if not specified.
226             */
227            public String getPragma() {
228            
229                    return pragma;
230            }
231            
232            
233            /**
234             * Sets the {@code Pragma} header value.
235             *
236             * @param pragma The header value, {@code null} if not specified.
237             */
238            public void setPragma(final String pragma) {
239            
240                    this.pragma = pragma;
241            }
242            
243            
244            /**
245             * Gets the {@code Www-Authenticate} header value.
246             *
247             * @return The header value, {@code null} if not specified.
248             */
249            public String getWWWAuthenticate() {
250            
251                    return wwwAuthenticate;
252            }
253            
254            
255            /**
256             * Sets the {@code Www-Authenticate} header value.
257             *
258             * @param wwwAuthenticate The header value, {@code null} if not 
259             *                        specified.
260             */
261            public void setWWWAuthenticate(final String wwwAuthenticate) {
262            
263                    this.wwwAuthenticate = wwwAuthenticate;
264            }
265            
266            
267            /**
268             * Ensures this HTTP response has a specified content body.
269             *
270             * @throws ParseException If the content body is missing or empty.
271             */
272            private void ensureContent()
273                    throws ParseException {
274                    
275                    if (content == null || content.isEmpty())
276                            throw new ParseException("Missing or empty HTTP response body");
277            }
278            
279            
280            /**
281             * Gets the raw response content.
282             *
283             * @return The raw response content, {@code null} if none.
284             */
285            public String getContent() {
286            
287                    return content;
288            }
289            
290            
291            /**
292             * Gets the response content as a JSON object.
293             *
294             * @return The response content as a JSON object.
295             *
296             * @throws ParseException If the Content-Type header isn't 
297             *                        {@code application/json}, the response content
298             *                        is {@code null}, empty or couldn't be parsed
299             *                        to a valid JSON object.
300             */
301            public JSONObject getContentAsJSONObject()
302                    throws ParseException {
303                    
304                    ensureContentType(CommonContentTypes.APPLICATION_JSON);
305                    
306                    ensureContent();
307                    
308                    return JSONObjectUtils.parseJSONObject(content);
309            }
310            
311            
312            /**
313             * Gets the response content as a JSON Web Token (JWT).
314             *
315             * @return The response content as a JSON Web Token (JWT).
316             *
317             * @throws ParseException If the Content-Type header isn't
318             *                        {@code application/jwt}, the response content 
319             *                        is {@code null}, empty or couldn't be parsed
320             *                        to a valid JSON Web Token (JWT).
321             */
322            public JWT getContentAsJWT()
323                    throws ParseException {
324                    
325                    ensureContentType(CommonContentTypes.APPLICATION_JWT);
326                    
327                    ensureContent();
328                    
329                    try {
330                            return JWTParser.parse(content);
331                            
332                    } catch (java.text.ParseException e) {
333                    
334                            throw new ParseException(e.getMessage(), e);
335                    }
336            }
337            
338            
339            /**
340             * Sets the raw response content.
341             *
342             * @param content The raw response content, {@code null} if none.
343             */
344            public void setContent(final String content) {
345            
346                    this.content = content;
347            }
348            
349            
350            /**
351             * Applies the status code, headers and content of this HTTP response
352             * object to the specified HTTP servlet response.
353             *
354             * @param sr The HTTP servlet response to apply. Must not be 
355             *           {@code null}.
356             *
357             * @throws IOException If the response content couldn't be written.
358             */
359            public void applyTo(final HttpServletResponse sr)
360                    throws IOException {
361            
362                    // Set the status code
363                    sr.setStatus(statusCode);
364            
365            
366                    // Set the headers, but only if explicitly specified    
367                    if (location != null)
368                            sr.setHeader("Location", location.toString());
369                    
370                    if (getContentType() != null)
371                            sr.setContentType(getContentType().toString());
372                    
373                    if (cacheControl != null)
374                            sr.setHeader("Cache-Control", cacheControl);
375                    
376                    if (pragma != null)
377                            sr.setHeader("Pragma", pragma);
378                    
379                    
380                    if (wwwAuthenticate != null)
381                            sr.setHeader("Www-Authenticate", wwwAuthenticate);
382            
383            
384                    // Write out the content
385            
386                    if (content != null) {
387                    
388                            PrintWriter writer = sr.getWriter();
389                    
390                            writer.println(content);
391                            
392                            writer.close();
393                    }
394            }
395    }