001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.rest;
014
015import static org.apache.juneau.internal.CollectionUtils.*;
016import static org.apache.juneau.common.internal.StringUtils.*;
017import static org.apache.juneau.http.HttpHeaders.*;
018import static org.apache.juneau.httppart.HttpPartType.*;
019
020import java.io.*;
021import java.nio.charset.*;
022import java.util.*;
023
024import jakarta.servlet.*;
025import jakarta.servlet.http.*;
026
027import org.apache.http.*;
028import org.apache.juneau.*;
029import org.apache.juneau.collections.*;
030import org.apache.juneau.encoders.*;
031import org.apache.juneau.httppart.*;
032import org.apache.juneau.httppart.bean.*;
033import org.apache.juneau.marshaller.*;
034import org.apache.juneau.oapi.*;
035import org.apache.juneau.http.header.*;
036import org.apache.juneau.http.response.*;
037import org.apache.juneau.rest.httppart.*;
038import org.apache.juneau.rest.logger.*;
039import org.apache.juneau.rest.util.*;
040import org.apache.juneau.serializer.*;
041
042/**
043 * Represents an HTTP response for a REST resource.
044 *
045 * <p>
046 *    The {@link RestResponse} object is an extension of the <l>HttpServletResponse</l> class
047 *    with various built-in convenience methods for use in building REST interfaces.
048 *    It can be accessed by passing it as a parameter on your REST Java method:
049 * </p>
050 *
051 * <p class='bjava'>
052 *    <ja>@RestPost</ja>(...)
053 *    <jk>public</jk> Object myMethod(RestResponse <jv>res</jv>) {...}
054 * </p>
055 *
056 * <p>
057 *    The primary methods on this class are:
058 * </p>
059 * <ul class='javatree'>
060 *    <li class='jc'>{@link RestResponse}
061 *    <ul class='spaced-list'>
062 *       <li>Methods for setting response headers:
063 *       <ul class='javatreec'>
064 *          <li class='jm'>{@link RestResponse#addHeader(Header) addHeader(Header)}
065 *          <li class='jm'>{@link RestResponse#addHeader(String,String) addHeader(String,String)}
066 *          <li class='jm'>{@link RestResponse#containsHeader(String) containsHeader(String)}
067 *          <li class='jm'>{@link RestResponse#getHeader(String) getHeader(String)}
068 *          <li class='jm'>{@link RestResponse#setCharacterEncoding(String) setCharacterEncoding(String)}
069 *          <li class='jm'>{@link RestResponse#setContentType(String) setContentType(String)}
070 *          <li class='jm'>{@link RestResponse#setHeader(Header) setHeader(Header)}
071 *          <li class='jm'>{@link RestResponse#setHeader(HttpPartSchema,String,Object) setHeader(HttpPartSchema,String,Object)}
072 *          <li class='jm'>{@link RestResponse#setHeader(String,Object) setHeader(String,Object)}
073 *          <li class='jm'>{@link RestResponse#setHeader(String,String) setHeader(String,String)}
074 *          <li class='jm'>{@link RestResponse#setMaxHeaderLength(int) setMaxHeaderLength(int)}
075 *          <li class='jm'>{@link RestResponse#setSafeHeaders() setSafeHeaders()}
076 *       </ul>
077 *       <li>Methods for setting response bodies:
078 *       <ul class='javatreec'>
079 *          <li class='jm'>{@link RestResponse#flushBuffer() flushBuffer()}
080 *          <li class='jm'>{@link RestResponse#getDirectWriter(String) getDirectWriter(String)}
081 *          <li class='jm'>{@link RestResponse#getNegotiatedOutputStream() getNegotiatedOutputStream()}
082 *          <li class='jm'>{@link RestResponse#getNegotiatedWriter() getNegotiatedWriter()}
083 *          <li class='jm'>{@link RestResponse#getSerializerMatch() getSerializerMatch()}
084 *          <li class='jm'>{@link RestResponse#getWriter() getWriter()}
085 *          <li class='jm'>{@link RestResponse#sendPlainText(String) sendPlainText(String)}
086 *          <li class='jm'>{@link RestResponse#sendRedirect(String) sendRedirect(String)}
087 *          <li class='jm'>{@link RestResponse#setContentSchema(HttpPartSchema) setContentSchema(HttpPartSchema)}
088 *          <li class='jm'>{@link RestResponse#setContent(Object) setOutput(Object)}
089 *          <li class='jm'>{@link RestResponse#setResponseBeanMeta(ResponseBeanMeta) setResponseBeanMeta(ResponseBeanMeta)}
090 *          <li class='jm'>{@link RestResponse#setException(Throwable) setException(Throwable)}
091 *       </ul>
092 *       <li>Other:
093 *       <ul class='javatreec'>
094 *          <li class='jm'>{@link RestResponse#getAttributes() getAttributes()}
095 *          <li class='jm'>{@link RestResponse#getContext() getContext()}
096 *          <li class='jm'>{@link RestResponse#getOpContext() getOpContext()}
097 *          <li class='jm'>{@link RestResponse#setAttribute(String,Object) setAttribute(String,Object)}
098 *          <li class='jm'>{@link RestResponse#setDebug() setDebug()}
099 *          <li class='jm'>{@link RestResponse#setNoTrace() setNoTrace()}
100 *          <li class='jm'>{@link RestResponse#setStatus(int) setStatus(int)}
101 *       </ul>
102 *    </ul>
103 * </ul>
104 *
105 * <h5 class='section'>See Also:</h5><ul>
106
107 * </ul>
108 */
109public final class RestResponse extends HttpServletResponseWrapper {
110
111   private HttpServletResponse inner;
112   private final RestRequest request;
113
114   private Optional<Object> content;  // The POJO being sent to the output.
115   private ServletOutputStream sos;
116   private FinishableServletOutputStream os;
117   private FinishablePrintWriter w;
118   private ResponseBeanMeta responseBeanMeta;
119   private RestOpContext opContext;
120   private Optional<HttpPartSchema> contentSchema;
121   private Serializer serializer;
122   private Optional<SerializerMatch> serializerMatch;
123   private boolean safeHeaders;
124   private int maxHeaderLength = 8096;
125
126   /**
127    * Constructor.
128    */
129   RestResponse(RestOpContext opContext, RestSession session, RestRequest req) throws Exception {
130      super(session.getResponse());
131
132      inner = session.getResponse();
133      request = req;
134
135      this.opContext = opContext;
136      responseBeanMeta = opContext.getResponseMeta();
137
138      RestContext context = session.getContext();
139
140      try {
141         String passThroughHeaders = request.getHeaderParam("x-response-headers").orElse(null);
142         if (passThroughHeaders != null) {
143            JsonMap m = context.getPartParser().getPartSession().parse(HEADER, null, passThroughHeaders, BeanContext.DEFAULT.getClassMeta(JsonMap.class));
144            for (Map.Entry<String,Object> e : m.entrySet())
145               addHeader(e.getKey(), resolveUris(e.getValue()));
146         }
147      } catch (Exception e1) {
148         throw new BadRequest(e1, "Invalid format for header 'x-response-headers'.  Must be in URL-encoded format.");
149      }
150
151      // Find acceptable charset
152      String h = request.getHeaderParam("accept-charset").orElse(null);
153      Charset charset = null;
154      if (h == null)
155         charset = opContext.getDefaultCharset();
156      else for (StringRange r : StringRanges.of(h).toList()) {
157         if (r.getQValue() > 0) {
158            if (r.getName().equals("*"))
159               charset = opContext.getDefaultCharset();
160            else if (Charset.isSupported(r.getName()))
161               charset = Charset.forName(r.getName());
162            if (charset != null)
163               break;
164         }
165      }
166
167      request.getContext().getDefaultResponseHeaders().forEach(x->addHeader(x.getValue(), resolveUris(x.getValue())));  // Done this way to avoid list/array copy.
168
169      opContext.getDefaultResponseHeaders().forEach(x->addHeader(x.getName(), resolveUris(x.getValue())));
170
171      if (charset == null)
172         throw new NotAcceptable("No supported charsets in header ''Accept-Charset'': ''{0}''", request.getHeaderParam("Accept-Charset").orElse(null));
173      inner.setCharacterEncoding(charset.name());
174
175   }
176
177   /**
178    * Returns access to the inner {@link RestContext} of the class of this method.
179    *
180    * @return The {@link RestContext} of this class.  Never <jk>null</jk>.
181    */
182   public RestContext getContext() {
183      return request.getContext();
184   }
185
186   /**
187    * Returns access to the inner {@link RestOpContext} of this method.
188    *
189    * @return The {@link RestOpContext} of this method.  Never <jk>null</jk>.
190    */
191   public RestOpContext getOpContext() {
192      return request.getOpContext();
193   }
194
195   /**
196    * Sets the HTTP output on the response.
197    *
198    * <p>
199    * The object type can be anything allowed by the registered response handlers.
200    *
201    * <p>
202    * Calling this method is functionally equivalent to returning the object in the REST Java method.
203    *
204    * <h5 class='section'>Example:</h5>
205    * <p class='bjava'>
206    *    <ja>@RestGet</ja>(<js>"/example2/{personId}"</js>)
207    *    <jk>public void</jk> doGet(RestResponse <jv>res</jv>, <ja>@Path</ja> UUID <jv>personId</jv>) {
208    *       Person <jv>person</jv> = getPersonById(<jv>personId</jv>);
209    *       <jv>res</jv>.setOutput(<jv>person</jv>);
210    *    }
211    * </p>
212    *
213    * <h5 class='section'>Notes:</h5><ul>
214    *    <li class='note'>
215    *       Calling this method with a <jk>null</jk> value is NOT the same as not calling this method at all.
216    *       <br>A <jk>null</jk> output value means we want to serialize <jk>null</jk> as a response (e.g. as a JSON <c>null</c>).
217    *       <br>Not calling this method or returning a value means you're handing the response yourself via the underlying stream or writer.
218    * </ul>
219    *
220    * <h5 class='section'>See Also:</h5><ul>
221    *    <li class='jm'>{@link RestContext.Builder#responseProcessors()}
222    *    <li class='link'><a class="doclink" href="../../../../index.html#jrs.RestOpAnnotatedMethods">@RestOp-Annotated Methods</a>
223    * </ul>
224    *
225    * @param output The output to serialize to the connection.
226    * @return This object.
227    */
228   public RestResponse setContent(Object output) {
229      this.content = optional(output);
230      return this;
231   }
232
233   /**
234    * Shortcut for calling <c>getRequest().getAttributes()</c>.
235    *
236    * @return The request attributes object.
237    */
238   public RequestAttributes getAttributes() {
239      return request.getAttributes();
240   }
241
242   /**
243    * Shortcut for calling <c>getRequest().setAttribute(String,Object)</c>.
244    *
245    * @param name The property name.
246    * @param value The property value.
247    * @return This object.
248    */
249   public RestResponse setAttribute(String name, Object value) {
250      request.setAttribute(name, value);
251      return this;
252   }
253
254   /**
255    * Returns the output that was set by calling {@link #setContent(Object)}.
256    *
257    * <p>
258    * If it's null, then {@link #setContent(Object)} wasn't called.
259    * <br>If it contains an empty, then <c>setObject(<jk>null</jk>)</c> was called.
260    * <br>Otherwise, {@link #setContent(Object)} was called with a non-null value.
261    *
262    * @return The output object, or <jk>null</jk> if {@link #setContent(Object)} was never called.
263    */
264   public Optional<Object> getContent() {
265      return content;
266   }
267
268   /**
269    * Returns <jk>true</jk> if the response contains output.
270    *
271    * <p>
272    * This implies {@link #setContent(Object)} has been called on this object.
273    *
274    * <p>
275    * Note that this also returns <jk>true</jk> even if {@link #setContent(Object)} was called with a <jk>null</jk>
276    * value as this means the response contains an output value of <jk>null</jk> as opposed to no value at all.
277    *
278    * @return <jk>true</jk> if the response contains output.
279    */
280   public boolean hasContent() {
281      return content != null;
282   }
283
284   /**
285    * Sets the output to a plain-text message regardless of the content type.
286    *
287    * @param text The output text to send.
288    * @return This object.
289    * @throws IOException If a problem occurred trying to write to the writer.
290    */
291   public RestResponse sendPlainText(String text) throws IOException {
292      setContentType("text/plain");
293      getNegotiatedWriter().write(text);
294      return this;
295   }
296
297   /**
298    * Equivalent to {@link HttpServletResponse#getOutputStream()}, except wraps the output stream if an {@link Encoder}
299    * was found that matched the <c>Accept-Encoding</c> header.
300    *
301    * @return A negotiated output stream.
302    * @throws NotAcceptable If unsupported Accept-Encoding value specified.
303    * @throws IOException Thrown by underlying stream.
304    */
305   public FinishableServletOutputStream getNegotiatedOutputStream() throws NotAcceptable, IOException {
306      if (os == null) {
307         Encoder encoder = null;
308         EncoderSet encoders = request.getOpContext().getEncoders();
309
310         String ae = request.getHeaderParam("Accept-Encoding").orElse(null);
311         if (! (ae == null || ae.isEmpty())) {
312            EncoderMatch match = encoders.getEncoderMatch(ae);
313            if (match == null) {
314               // Identity should always match unless "identity;q=0" or "*;q=0" is specified.
315               if (ae.matches(".*(identity|\\*)\\s*;\\s*q\\s*=\\s*(0(?!\\.)|0\\.0).*")) {
316                  throw new NotAcceptable(
317                     "Unsupported encoding in request header ''Accept-Encoding'': ''{0}''\n\tSupported codings: {1}",
318                     ae, Json5.of(encoders.getSupportedEncodings())
319                  );
320               }
321            } else {
322               encoder = match.getEncoder();
323               String encoding = match.getEncoding().toString();
324
325               // Some clients don't recognize identity as an encoding, so don't set it.
326               if (! encoding.equals("identity"))
327                  setHeader("content-encoding", encoding);
328            }
329         }
330         @SuppressWarnings("resource")
331         ServletOutputStream sos = getOutputStream();
332         os = new FinishableServletOutputStream(encoder == null ? sos : encoder.getOutputStream(sos));
333      }
334      return os;
335   }
336
337   /**
338    * Returns a ServletOutputStream suitable for writing binary data in the response.
339    *
340    * <p>
341    * The servlet container does not encode the binary data.
342    *
343    * <p>
344    * Calling <c>flush()</c> on the ServletOutputStream commits the response.
345    * Either this method or <c>getWriter</c> may be called to write the content, not both, except when reset has been called.
346    *
347    * @return The stream.
348    * @throws IOException If stream could not be accessed.
349    */
350   @Override
351   public ServletOutputStream getOutputStream() throws IOException {
352      if (sos == null)
353         sos = inner.getOutputStream();
354      return sos;
355   }
356
357   /**
358    * Returns <jk>true</jk> if {@link #getOutputStream()} has been called.
359    *
360    * @return <jk>true</jk> if {@link #getOutputStream()} has been called.
361    */
362   public boolean getOutputStreamCalled() {
363      return sos != null;
364   }
365
366   /**
367    * Returns the writer to the response content.
368    *
369    * <p>
370    * This methods bypasses any specified encoders and returns a regular unbuffered writer.
371    * Use the {@link #getNegotiatedWriter()} method if you want to use the matched encoder (if any).
372    *
373    * @return The writer.
374    * @throws IOException If writer could not be accessed.
375    */
376   @Override
377   public PrintWriter getWriter() throws IOException {
378      return getWriter(true, false);
379   }
380
381   /**
382    * Convenience method meant to be used when rendering directly to a browser with no buffering.
383    *
384    * <p>
385    * Sets the header <js>"x-content-type-options=nosniff"</js> so that output is rendered immediately on IE and Chrome
386    * without any buffering for content-type sniffing.
387    *
388    * <p>
389    * This can be useful if you want to render a streaming 'console' on a web page.
390    *
391    * @param contentType The value to set as the <c>Content-Type</c> on the response.
392    * @return The raw writer.
393    * @throws IOException Thrown by underlying stream.
394    */
395   public PrintWriter getDirectWriter(String contentType) throws IOException {
396      setContentType(contentType);
397      setHeader("X-Content-Type-Options", "nosniff");
398      setHeader("Content-Encoding", "identity");
399      return getWriter(true, true);
400   }
401
402   /**
403    * Equivalent to {@link HttpServletResponse#getWriter()}, except wraps the output stream if an {@link Encoder} was
404    * found that matched the <c>Accept-Encoding</c> header and sets the <c>Content-Encoding</c>
405    * header to the appropriate value.
406    *
407    * @return The negotiated writer.
408    * @throws NotAcceptable If unsupported charset in request header Accept-Charset.
409    * @throws IOException Thrown by underlying stream.
410    */
411   public FinishablePrintWriter getNegotiatedWriter() throws NotAcceptable, IOException {
412      return getWriter(false, false);
413   }
414
415   @SuppressWarnings("resource")
416   private FinishablePrintWriter getWriter(boolean raw, boolean autoflush) throws NotAcceptable, IOException {
417      if (w != null)
418         return w;
419
420      // If plain text requested, override it now.
421      if (request.isPlainText())
422         setHeader("Content-Type", "text/plain");
423
424      try {
425         OutputStream out = (raw ? getOutputStream() : getNegotiatedOutputStream());
426         w = new FinishablePrintWriter(out, getCharacterEncoding(), autoflush);
427         return w;
428      } catch (UnsupportedEncodingException e) {
429         String ce = getCharacterEncoding();
430         setCharacterEncoding("UTF-8");
431         throw new NotAcceptable("Unsupported charset in request header ''Accept-Charset'': ''{0}''", ce);
432      }
433   }
434
435   /**
436    * Returns the <c>Content-Type</c> header stripped of the charset attribute if present.
437    *
438    * @return The <c>media-type</c> portion of the <c>Content-Type</c> header.
439    */
440   public MediaType getMediaType() {
441      return MediaType.of(getContentType());
442   }
443
444   /**
445    * Wrapper around {@link #getCharacterEncoding()} that converts the value to a {@link Charset}.
446    *
447    * @return The request character encoding converted to a {@link Charset}.
448    */
449   public Charset getCharset() {
450      String s = getCharacterEncoding();
451      return s == null ? null : Charset.forName(s);
452   }
453
454   /**
455    * Redirects to the specified URI.
456    *
457    * <p>
458    * Relative URIs are always interpreted as relative to the context root.
459    * This is similar to how WAS handles redirect requests, and is different from how Tomcat handles redirect requests.
460    *
461    * @param uri The redirection URL.
462    * @throws IOException If an input or output exception occurs
463    */
464   @Override
465   public void sendRedirect(String uri) throws IOException {
466      char c = (uri.length() > 0 ? uri.charAt(0) : 0);
467      if (c != '/' && uri.indexOf("://") == -1)
468         uri = request.getContextPath() + '/' + uri;
469      inner.sendRedirect(uri);
470   }
471
472   /**
473    * Sets a response header with the given name and value.
474    *
475    * <p>
476    * If the header had already been set, the new value overwrites the previous one.
477    *
478    * <p>
479    * The {@link #containsHeader(String)} method can be used to test for the presence of a header before setting its value.
480    *
481    * @param name The header name.
482    * @param value The header value.
483    */
484   @Override
485   public void setHeader(String name, String value) {
486
487      // Jetty doesn't set the content type correctly if set through this method.
488      // Tomcat/WAS does.
489      if (name.equalsIgnoreCase("Content-Type")) {
490         inner.setContentType(value);
491         ContentType ct = contentType(value);
492         if (ct != null && ct.getParameter("charset") != null)
493            inner.setCharacterEncoding(ct.getParameter("charset"));
494      } else {
495         if (safeHeaders)
496            value = stripInvalidHttpHeaderChars(value);
497         value = abbreviate(value, maxHeaderLength);
498         inner.setHeader(name, value);
499      }
500   }
501
502   /**
503    * Sets a header on the request.
504    *
505    * @param name The header name.
506    * @param value The header value.
507    *    <ul>
508    *       <li>Can be any POJO.
509    *       <li>Converted to a string using the specified part serializer.
510    *    </ul>
511    * @return This object.
512    * @throws SchemaValidationException Header failed schema validation.
513    * @throws SerializeException Header could not be serialized.
514    */
515   public RestResponse setHeader(String name, Object value) throws SchemaValidationException, SerializeException {
516      setHeader(name, request.getPartSerializerSession().serialize(HEADER, null, value));
517      return this;
518   }
519
520   /**
521    * Sets a header on the request.
522    *
523    * @param schema
524    *    The schema to use to serialize the header, or <jk>null</jk> to use the default schema.
525    * @param name The header name.
526    * @param value The header value.
527    *    <ul>
528    *       <li>Can be any POJO.
529    *       <li>Converted to a string using the specified part serializer.
530    *    </ul>
531    * @return This object.
532    * @throws SchemaValidationException Header failed schema validation.
533    * @throws SerializeException Header could not be serialized.
534    */
535   public RestResponse setHeader(HttpPartSchema schema, String name, Object value) throws SchemaValidationException, SerializeException {
536      setHeader(name, request.getPartSerializerSession().serialize(HEADER, schema, value));
537      return this;
538   }
539
540   /**
541    * Specifies the schema for the response content.
542    *
543    * <p>
544    * Used by schema-aware serializers such as {@link OpenApiSerializer}.  Ignored by other serializers.
545    *
546    * @param schema The content schema
547    * @return This object.
548    */
549   public RestResponse setContentSchema(HttpPartSchema schema) {
550      this.contentSchema = optional(schema);
551      return this;
552   }
553
554   /**
555    * Sets the <js>"Exception"</js> attribute to the specified throwable.
556    *
557    * <p>
558    * This exception is used by {@link CallLogger} for logging purposes.
559    *
560    * @param t The attribute value.
561    * @return This object.
562    */
563   public RestResponse setException(Throwable t) {
564      request.setException(t);
565      return this;
566   }
567
568   /**
569    * Sets the <js>"NoTrace"</js> attribute to the specified boolean.
570    *
571    * <p>
572    * This flag is used by {@link CallLogger} and tells it not to log the current request.
573    *
574    * @param b The attribute value.
575    * @return This object.
576    */
577   public RestResponse setNoTrace(Boolean b) {
578      request.setNoTrace(b);
579      return this;
580   }
581
582   /**
583    * Shortcut for calling <c>setNoTrace(<jk>true</jk>)</c>.
584    *
585    * @return This object.
586    */
587   public RestResponse setNoTrace() {
588      return setNoTrace(true);
589   }
590
591   /**
592    * Sets the <js>"Debug"</js> attribute to the specified boolean.
593    *
594    * <p>
595    * This flag is used by {@link CallLogger} to help determine how a request should be logged.
596    *
597    * @param b The attribute value.
598    * @return This object.
599    * @throws IOException If bodies could not be cached.
600    */
601   public RestResponse setDebug(Boolean b) throws IOException {
602      request.setDebug(b);
603      if (b)
604         inner = CachingHttpServletResponse.wrap(inner);
605      return this;
606   }
607
608   /**
609    * Shortcut for calling <c>setDebug(<jk>true</jk>)</c>.
610    *
611    * @return This object.
612    * @throws IOException If bodies could not be cached.
613    */
614   public RestResponse setDebug() throws IOException {
615      return setDebug(true);
616   }
617
618   /**
619    * Returns the metadata about this response.
620    *
621    * @return
622    *    The metadata about this response.
623    *    <br>Never <jk>null</jk>.
624    */
625   public ResponseBeanMeta getResponseBeanMeta() {
626      return responseBeanMeta;
627   }
628
629   /**
630    * Sets metadata about this response.
631    *
632    * @param rbm The metadata about this response.
633    * @return This object.
634    */
635   public RestResponse setResponseBeanMeta(ResponseBeanMeta rbm) {
636      this.responseBeanMeta = rbm;
637      return this;
638   }
639
640   /**
641    * Returns <jk>true</jk> if this response object is of the specified type.
642    *
643    * @param c The type to check against.
644    * @return <jk>true</jk> if this response object is of the specified type.
645    */
646   public boolean isContentOfType(Class<?> c) {
647      return c.isInstance(getRawOutput());
648   }
649
650   /**
651    * Returns this value cast to the specified class.
652    *
653    * @param <T> The class to cast to.
654    * @param c The class to cast to.
655    * @return This value cast to the specified class, or <jk>null</jk> if the object doesn't exist or isn't the specified type.
656    */
657   @SuppressWarnings("unchecked")
658   public <T> T getContent(Class<T> c) {
659      if (isContentOfType(c))
660         return (T)getRawOutput();
661      return null;
662   }
663
664   /**
665    * Returns the wrapped servlet request.
666    *
667    * @return The wrapped servlet request.
668    */
669   public HttpServletResponse getHttpServletResponse() {
670      return inner;
671   }
672
673   /**
674    * Forces any content in the buffer to be written to the client.
675    *
676    * <p>
677    * A call to this method automatically commits the response, meaning the status code and headers will be written.
678    *
679    * @throws IOException If an I/O error occurred.
680    */
681   @Override
682   public void flushBuffer() throws IOException {
683      if (w != null)
684         w.flush();
685      if (os != null)
686         os.flush();
687      inner.flushBuffer();
688   }
689
690   private Object getRawOutput() {
691      return content == null ? null : content.orElse(null);
692   }
693
694   /**
695    * Enabled safe-header mode.
696    *
697    * <p>
698    * When enabled, invalid characters such as CTRL characters will be stripped from header values
699    * before they get set.
700    *
701    * @return This object.
702    */
703   public RestResponse setSafeHeaders() {
704      this.safeHeaders = true;
705      return this;
706   }
707
708   /**
709    * Specifies the maximum length for header values.
710    *
711    * <p>
712    * Header values that exceed this length will get truncated.
713    *
714    * @param value The new value for this setting.  The default is <c>8096</c>.
715    * @return This object.
716    */
717   public RestResponse setMaxHeaderLength(int value) {
718      this.maxHeaderLength = value;
719      return this;
720   }
721
722   /**
723    * Adds a response header with the given name and value.
724    *
725    * <p>
726    * This method allows response headers to have multiple values.
727    *
728    * <p>
729    * A no-op of either the name or value is <jk>null</jk>.
730    *
731    * <p>
732    * Note that per <a class='doclink' href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2'>RFC2616</a>,
733    * only headers defined as comma-delimited lists [i.e., #(values)] should be defined as multiple message header fields.
734    *
735    * @param name The header name.
736    * @param value The header value.
737    */
738   @Override
739   public void addHeader(String name, String value) {
740      if (name != null && value != null) {
741         if (name.equalsIgnoreCase("Content-Type"))
742            setHeader(name, value);
743         else {
744            if (safeHeaders)
745               value = stripInvalidHttpHeaderChars(value);
746            value = abbreviate(value, maxHeaderLength);
747            inner.addHeader(name, value);
748         }
749      }
750   }
751
752   /**
753    * Sets a response header.
754    *
755    * <p>
756    * Any previous header values are removed.
757    *
758    * <p>
759    * Value is added at the end of the headers.
760    *
761    * @param header The header.
762    * @return This object.
763    */
764   public RestResponse setHeader(Header header) {
765      if (header == null) {
766         // Do nothing.
767      } else if (header instanceof BasicUriHeader) {
768         BasicUriHeader x = (BasicUriHeader)header;
769         setHeader(x.getName(), resolveUris(x.getValue()));
770      } else if (header instanceof SerializedHeader) {
771         SerializedHeader x = ((SerializedHeader)header).copyWith(request.getPartSerializerSession(), null);
772         String v = x.getValue();
773         if (v != null && v.indexOf("://") != -1)
774            v = resolveUris(v);
775         setHeader(x.getName(), v);
776      } else {
777         setHeader(header.getName(), header.getValue());
778      }
779      return this;
780   }
781
782   /**
783    * Adds a response header.
784    *
785    * <p>
786    * Any previous header values are preserved.
787    *
788    * <p>
789    * Value is added at the end of the headers.
790    *
791    * <p>
792    * If the header is a {@link BasicUriHeader}, the URI will be resolved using the {@link RestRequest#getUriResolver()} object.
793    *
794    * <p>
795    * If the header is a {@link SerializedHeader} and the serializer session is not set, it will be set to the one returned by {@link RestRequest#getPartSerializerSession()} before serialization.
796    *
797    * <p>
798    * Note that per <a class='doclink' href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2'>RFC2616</a>,
799    * only headers defined as comma-delimited lists [i.e., #(values)] should be defined as multiple message header fields.
800    *
801    * @param header The header.
802    * @return This object.
803    */
804   public RestResponse addHeader(Header header) {
805      if (header == null) {
806         // Do nothing.
807      } else if (header instanceof BasicUriHeader) {
808         BasicUriHeader x = (BasicUriHeader)header;
809         addHeader(x.getName(), resolveUris(x.getValue()));
810      } else if (header instanceof SerializedHeader) {
811         SerializedHeader x = ((SerializedHeader)header).copyWith(request.getPartSerializerSession(), null);
812         addHeader(x.getName(), resolveUris(x.getValue()));
813      } else {
814         addHeader(header.getName(), header.getValue());
815      }
816      return this;
817   }
818
819   private String resolveUris(Object value) {
820      String s = stringify(value);
821      return request.getUriResolver().resolve(s);
822   }
823
824   /**
825    * Returns the matching serializer and media type for this response.
826    *
827    * @return The matching serializer, never <jk>null</jk>.
828    */
829   public Optional<SerializerMatch> getSerializerMatch() {
830      if (serializerMatch != null)
831         return serializerMatch;
832      if (serializer != null) {
833         serializerMatch = optional(new SerializerMatch(getMediaType(), serializer));
834      } else {
835         serializerMatch = optional(opContext.getSerializers().getSerializerMatch(request.getHeaderParam("Accept").orElse("*/*")));
836      }
837      return serializerMatch;
838   }
839
840   /**
841    * Returns the schema of the response content.
842    *
843    * @return The schema of the response content, never <jk>null</jk>.
844    */
845   public Optional<HttpPartSchema> getContentSchema() {
846      if (contentSchema != null)
847         return contentSchema;
848      if (responseBeanMeta != null)
849         contentSchema = optional(responseBeanMeta.getSchema());
850      else {
851         ResponseBeanMeta rbm = opContext.getResponseBeanMeta(getContent(Object.class));
852         if (rbm != null)
853            contentSchema = optional(rbm.getSchema());
854         else
855            contentSchema = empty();
856      }
857      return contentSchema;
858   }
859}