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.StringUtils.*;
016import static org.apache.juneau.httppart.HttpPartType.*;
017
018import java.io.*;
019import java.nio.charset.*;
020import java.util.*;
021
022import javax.servlet.*;
023import javax.servlet.http.*;
024
025import org.apache.http.*;
026import org.apache.juneau.*;
027import org.apache.juneau.collections.*;
028import org.apache.juneau.encoders.*;
029import org.apache.juneau.html.annotation.*;
030import org.apache.juneau.http.*;
031import org.apache.juneau.httppart.*;
032import org.apache.juneau.httppart.bean.*;
033import org.apache.juneau.rest.annotation.*;
034import org.apache.juneau.http.exception.*;
035import org.apache.juneau.http.header.*;
036import org.apache.juneau.rest.util.*;
037import org.apache.juneau.serializer.*;
038
039/**
040 * Represents an HTTP response for a REST resource.
041 *
042 * <p>
043 * Essentially an extended {@link HttpServletResponse} with some special convenience methods that allow you to easily
044 * output POJOs as responses.
045 *
046 * <p>
047 * Since this class extends {@link HttpServletResponse}, developers are free to use these convenience methods, or
048 * revert to using lower level methods like any other servlet response.
049 *
050 * <h5 class='section'>Example:</h5>
051 * <p class='bcode w800'>
052 *    <ja>@RestMethod</ja>(name=<jsf>GET</jsf>)
053 *    <jk>public void</jk> doGet(RestRequest req, RestResponse res) {
054 *       res.setOutput(<js>"Simple string response"</js>);
055 *    }
056 * </p>
057 *
058 * <ul class='seealso'>
059 *    <li class='link'>{@doc RestmRestResponse}
060 * </ul>
061 */
062public final class RestResponse extends HttpServletResponseWrapper {
063
064   private HttpServletResponse inner;
065   private final RestRequest request;
066   private RestMethodContext restJavaMethod;
067   private Object output;                       // The POJO being sent to the output.
068   private boolean isNullOutput;                // The output is null (as opposed to not being set at all)
069   @SuppressWarnings("deprecation")
070   private RequestProperties properties;                // Response properties
071   private ServletOutputStream sos;
072   private FinishableServletOutputStream os;
073   private FinishablePrintWriter w;
074   @SuppressWarnings("deprecation")
075   private HtmlDocBuilder htmlDocBuilder;
076
077   private ResponseBeanMeta responseMeta;
078
079   /**
080    * Constructor.
081    */
082   RestResponse(RestCall call) throws BadRequest {
083      super(call.getResponse());
084      this.inner = call.getResponse();
085      this.request = call.getRestRequest();
086      call.restResponse(this);
087      RestContext context = call.getContext();
088
089      for (Map.Entry<String,Object> e : context.getResHeaders().entrySet())
090         setHeaderSafe(e.getKey(), stringify(e.getValue()));
091
092      try {
093         String passThroughHeaders = request.getHeader("x-response-headers");
094         if (passThroughHeaders != null) {
095            HttpPartParser p = context.getPartParser();
096            OMap m = p.createPartSession(request.getParserSessionArgs()).parse(HEADER, null, passThroughHeaders, context.getClassMeta(OMap.class));
097            for (Map.Entry<String,Object> e : m.entrySet())
098               setHeaderSafe(e.getKey(), e.getValue().toString());
099         }
100      } catch (Exception e1) {
101         throw new BadRequest(e1, "Invalid format for header 'x-response-headers'.  Must be in URL-encoded format.");
102      }
103   }
104
105   /*
106    * Called from RestServlet after a match has been made but before the guard or method invocation.
107    */
108   final void init(RestMethodContext rjm, @SuppressWarnings("deprecation") RequestProperties properties) throws NotAcceptable, IOException {
109      this.restJavaMethod = rjm;
110      this.properties = properties;
111
112      if (request.isDebug())
113         setDebug();
114
115      // Find acceptable charset
116      String h = request.getHeader("accept-charset");
117      String charset = null;
118      if (h == null)
119         charset = rjm.defaultCharset;
120      else for (StringRange r : StringRanges.of(h).getRanges()) {
121         if (r.getQValue() > 0) {
122            if (r.getName().equals("*"))
123               charset = rjm.defaultCharset;
124            else if (Charset.isSupported(r.getName()))
125               charset = r.getName();
126            if (charset != null)
127               break;
128         }
129      }
130
131      if (charset == null)
132         throw new NotAcceptable("No supported charsets in header ''Accept-Charset'': ''{0}''", request.getHeader("Accept-Charset"));
133      super.setCharacterEncoding(charset);
134
135      this.responseMeta = rjm.responseMeta;
136   }
137
138   /**
139    * Gets the serializer group for the response.
140    *
141    * <ul class='seealso'>
142    *    <li class='link'>{@doc RestSerializers}
143    * </ul>
144    *
145    * @return The serializer group for the response.
146    */
147   public SerializerGroup getSerializers() {
148      return restJavaMethod == null ? SerializerGroup.EMPTY : restJavaMethod.serializers;
149   }
150
151   /**
152    * Returns the media types that are valid for <c>Accept</c> headers on the request.
153    *
154    * @return The set of media types registered in the parser group of this request.
155    */
156   public List<MediaType> getSupportedMediaTypes() {
157      return restJavaMethod == null ? Collections.<MediaType>emptyList() : restJavaMethod.supportedAcceptTypes;
158   }
159
160   /**
161    * Returns the codings that are valid for <c>Accept-Encoding</c> and <c>Content-Encoding</c> headers on
162    * the request.
163    *
164    * @return The set of media types registered in the parser group of this request.
165    */
166   public List<String> getSupportedEncodings() {
167      return restJavaMethod == null ? Collections.<String>emptyList() : restJavaMethod.encoders.getSupportedEncodings();
168   }
169
170   /**
171    * Sets the HTTP output on the response.
172    *
173    * <p>
174    * The object type can be anything allowed by the registered response handlers.
175    *
176    * <p>
177    * Calling this method is functionally equivalent to returning the object in the REST Java method.
178    *
179    * <h5 class='section'>Example:</h5>
180    * <p class='bcode w800'>
181    *    <ja>@RestMethod</ja>(..., path=<js>"/example2/{personId}"</js>)
182    *    <jk>public void</jk> doGet2(RestResponse res, <ja>@Path</ja> UUID personId) {
183    *       Person p = getPersonById(personId);
184    *       res.setOutput(p);
185    *    }
186    * </p>
187    *
188    * <ul class='notes'>
189    *    <li>
190    *       Calling this method with a <jk>null</jk> value is NOT the same as not calling this method at all.
191    *       <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>).
192    *       <br>Not calling this method or returning a value means you're handing the response yourself via the underlying stream or writer.
193    *       <br>This distinction affects the {@link #hasOutput()} method behavior.
194    * </ul>
195    *
196    * <ul class='seealso'>
197    *    <li class='jf'>{@link RestContext#REST_responseHandlers}
198    *    <li class='link'>{@doc RestmReturnTypes}
199    * </ul>
200    *
201    * @param output The output to serialize to the connection.
202    * @return This object (for method chaining).
203    */
204   public RestResponse setOutput(Object output) {
205      this.output = output;
206      this.isNullOutput = output == null;
207      return this;
208   }
209
210   /**
211    * Returns a programmatic interface for setting properties for the HTML doc view.
212    *
213    * <div class='warn'>
214    *    <b>Deprecated</b> - Use {@link HtmlDocConfig}
215    * </div>
216    *
217    * <p>
218    * This is the programmatic equivalent to the {@link RestMethod#htmldoc() @RestMethod(htmldoc)} annotation.
219    *
220    * <h5 class='section'>Example:</h5>
221    * <p class='bcode w800'>
222    *    <jc>// Declarative approach.</jc>
223    *  <ja>@HtmlDocConfig</ja>(
224    *       header={
225    *          <js>"&lt;p&gt;This is my REST interface&lt;/p&gt;"</js>
226    *       },
227    *       aside={
228    *          <js>"&lt;p&gt;Custom aside content&lt;/p&gt;"</js>
229    *       }
230    *    )
231    *    <jk>public</jk> Object doGet(RestResponse res) {
232    *
233    *       <jc>// Equivalent programmatic approach.</jc>
234    *       res.getHtmlDocBuilder()
235    *          .header(<js>"&lt;p&gt;This is my REST interface&lt;/p&gt;"</js>)
236    *          .aside(<js>"&lt;p&gt;Custom aside content&lt;/p&gt;"</js>);
237    *    }
238    * </p>
239    *
240    * <ul class='seealso'>
241    *    <li class='ja'>{@link RestMethod#htmldoc()}
242    *    <li class='link'>{@doc RestHtmlDocAnnotation}
243    * </ul>
244    *
245    * @return A new programmatic interface for setting properties for the HTML doc view.
246    */
247   @Deprecated
248   public HtmlDocBuilder getHtmlDocBuilder() {
249      if (htmlDocBuilder == null)
250         htmlDocBuilder = new HtmlDocBuilder(PropertyStore.create());
251      return htmlDocBuilder;
252   }
253
254   /**
255    * Retrieve the properties active for this request.
256    *
257    * <div class='warn'>
258    *    <b>Deprecated</b> - Use {@link RestResponse#getAttributes()}
259    * </div>
260    *
261    * <p>
262    * This contains all resource and method level properties from the following:
263    * <ul class='javatree'>
264    *    <li class='ja'>{@link Rest#properties()}
265    *    <li class='ja'>{@link RestMethod#properties()}
266    *    <li class='jm'>{@link RestContextBuilder#set(String, Object)}
267    * </ul>
268    *
269    * <p>
270    * The returned object is modifiable and allows you to override session-level properties before
271    * they get passed to the serializers.
272    * <br>However, properties are open-ended, and can be used for any purpose.
273    *
274    * <h5 class='section'>Example:</h5>
275    * <p class='bcode w800'>
276    *    <ja>@RestMethod</ja>(
277    *       properties={
278    *          <ja>@Property</ja>(name=<jsf>SERIALIZER_sortMaps</jsf>, value=<js>"false"</js>)
279    *       }
280    *    )
281    *    <jk>public</jk> Map doGet(RestResponse res, <ja>@Query</ja>(<js>"sortMaps"</js>) Boolean sortMaps) {
282    *
283    *       <jc>// Override value if specified through query parameter.</jc>
284    *       <jk>if</jk> (sortMaps != <jk>null</jk>)
285    *          res.getProperties().put(<jsf>SERIALIZER_sortMaps</jsf>, sortMaps);
286    *
287    *       <jk>return</jk> <jsm>getMyMap</jsm>();
288    *    }
289    * </p>
290    *
291    * <ul class='seealso'>
292    *    <li class='jm'>{@link #prop(String, Object)}
293    *    <li class='link'>{@doc RestConfigurableProperties}
294    * </ul>
295    *
296    * @return The properties active for this request.
297    */
298   @Deprecated
299   public RequestProperties getProperties() {
300      return properties;
301   }
302
303   /**
304    * Shortcut for calling <c>getProperties().append(name, value);</c> fluently.
305    *
306    * <div class='warn'>
307    *    <b>Deprecated</b> - Use {@link #attr(String,Object)}
308    * </div>
309    *
310    * @param name The property name.
311    * @param value The property value.
312    * @return This object (for method chaining).
313    */
314   @Deprecated
315   public RestResponse prop(String name, Object value) {
316      this.properties.append(name, value);
317      return this;
318   }
319
320   /**
321    * Shortcut for calling <c>getRequest().getAttributes()</c>.
322    *
323    * @return The request attributes object.
324    */
325   public RequestAttributes getAttributes() {
326      return request.getAttributes();
327   }
328
329   /**
330    * Shortcut for calling <c>getRequest().setAttribute(String,Object)</c>.
331    *
332    * @param name The property name.
333    * @param value The property value.
334    * @return This object (for method chaining).
335    */
336   public RestResponse attr(String name, Object value) {
337      request.setAttribute(name, value);
338      return this;
339   }
340
341   /**
342    * Shortcut method that allows you to use var-args to simplify setting array output.
343    *
344    * <h5 class='section'>Example:</h5>
345    * <p class='bcode w800'>
346    *    <jc>// Instead of...</jc>
347    *    response.setOutput(<jk>new</jk> Object[]{x,y,z});
348    *
349    *    <jc>// ...call this...</jc>
350    *    response.setOutput(x,y,z);
351    * </p>
352    *
353    * @param output The output to serialize to the connection.
354    * @return This object (for method chaining).
355    */
356   public RestResponse setOutputs(Object...output) {
357      this.output = output;
358      return this;
359   }
360
361   /**
362    * Returns the output that was set by calling {@link #setOutput(Object)}.
363    *
364    * @return The output object.
365    */
366   public Object getOutput() {
367      return output;
368   }
369
370   /**
371    * Returns <jk>true</jk> if this response has any output associated with it.
372    *
373    * @return <jk>true</jk> if {@link #setOutput(Object)} has been called, even if the value passed was <jk>null</jk>.
374    */
375   public boolean hasOutput() {
376      return output != null || isNullOutput;
377   }
378
379   /**
380    * Sets the output to a plain-text message regardless of the content type.
381    *
382    * @param text The output text to send.
383    * @return This object (for method chaining).
384    * @throws IOException If a problem occurred trying to write to the writer.
385    */
386   public RestResponse sendPlainText(String text) throws IOException {
387      setContentType("text/plain");
388      getNegotiatedWriter().write(text);
389      return this;
390   }
391
392   /**
393    * Equivalent to {@link HttpServletResponse#getOutputStream()}, except wraps the output stream if an {@link Encoder}
394    * was found that matched the <c>Accept-Encoding</c> header.
395    *
396    * @return A negotiated output stream.
397    * @throws NotAcceptable If unsupported Accept-Encoding value specified.
398    * @throws IOException Thrown by underlying stream.
399    */
400   public FinishableServletOutputStream getNegotiatedOutputStream() throws NotAcceptable, IOException {
401      if (os == null) {
402         Encoder encoder = null;
403         EncoderGroup encoders = restJavaMethod == null ? EncoderGroup.DEFAULT : restJavaMethod.encoders;
404
405         String ae = request.getHeader("Accept-Encoding");
406         if (! (ae == null || ae.isEmpty())) {
407            EncoderMatch match = encoders.getEncoderMatch(ae);
408            if (match == null) {
409               // Identity should always match unless "identity;q=0" or "*;q=0" is specified.
410               if (ae.matches(".*(identity|\\*)\\s*;\\s*q\\s*=\\s*(0(?!\\.)|0\\.0).*")) {
411                  throw new NotAcceptable(
412                     "Unsupported encoding in request header ''Accept-Encoding'': ''{0}''\n\tSupported codings: {1}",
413                     ae, encoders.getSupportedEncodings()
414                  );
415               }
416            } else {
417               encoder = match.getEncoder();
418               String encoding = match.getEncoding().toString();
419
420               // Some clients don't recognize identity as an encoding, so don't set it.
421               if (! encoding.equals("identity"))
422                  setHeader("content-encoding", encoding);
423            }
424         }
425         @SuppressWarnings("resource")
426         ServletOutputStream sos = getOutputStream();
427         os = new FinishableServletOutputStream(encoder == null ? sos : encoder.getOutputStream(sos));
428      }
429      return os;
430   }
431
432   @Override /* ServletResponse */
433   public ServletOutputStream getOutputStream() throws IOException {
434      if (sos == null)
435         sos = inner.getOutputStream();
436      return sos;
437   }
438
439   /**
440    * Returns <jk>true</jk> if {@link #getOutputStream()} has been called.
441    *
442    * @return <jk>true</jk> if {@link #getOutputStream()} has been called.
443    */
444   public boolean getOutputStreamCalled() {
445      return sos != null;
446   }
447
448   /**
449    * Returns the writer to the response body.
450    *
451    * <p>
452    * This methods bypasses any specified encoders and returns a regular unbuffered writer.
453    * Use the {@link #getNegotiatedWriter()} method if you want to use the matched encoder (if any).
454    */
455   @Override /* ServletResponse */
456   public PrintWriter getWriter() throws IOException {
457      return getWriter(true, false);
458   }
459
460   /**
461    * Convenience method meant to be used when rendering directly to a browser with no buffering.
462    *
463    * <p>
464    * Sets the header <js>"x-content-type-options=nosniff"</js> so that output is rendered immediately on IE and Chrome
465    * without any buffering for content-type sniffing.
466    *
467    * <p>
468    * This can be useful if you want to render a streaming 'console' on a web page.
469    *
470    * @param contentType The value to set as the <c>Content-Type</c> on the response.
471    * @return The raw writer.
472    * @throws IOException Thrown by underlying stream.
473    */
474   public PrintWriter getDirectWriter(String contentType) throws IOException {
475      setContentType(contentType);
476      setHeader("X-Content-Type-Options", "nosniff");
477      setHeader("Content-Encoding", "identity");
478      return getWriter(true, true);
479   }
480
481   /**
482    * Equivalent to {@link HttpServletResponse#getWriter()}, except wraps the output stream if an {@link Encoder} was
483    * found that matched the <c>Accept-Encoding</c> header and sets the <c>Content-Encoding</c>
484    * header to the appropriate value.
485    *
486    * @return The negotiated writer.
487    * @throws NotAcceptable If unsupported charset in request header Accept-Charset.
488    * @throws IOException Thrown by underlying stream.
489    */
490   public FinishablePrintWriter getNegotiatedWriter() throws NotAcceptable, IOException {
491      return getWriter(false, false);
492   }
493
494   @SuppressWarnings("resource")
495   private FinishablePrintWriter getWriter(boolean raw, boolean autoflush) throws NotAcceptable, IOException {
496      if (w != null)
497         return w;
498
499      // If plain text requested, override it now.
500      if (request.isPlainText())
501         setHeader("Content-Type", "text/plain");
502
503      try {
504         OutputStream out = (raw ? getOutputStream() : getNegotiatedOutputStream());
505         w = new FinishablePrintWriter(out, getCharacterEncoding(), autoflush);
506         return w;
507      } catch (UnsupportedEncodingException e) {
508         String ce = getCharacterEncoding();
509         setCharacterEncoding("UTF-8");
510         throw new NotAcceptable("Unsupported charset in request header ''Accept-Charset'': ''{0}''", ce);
511      }
512   }
513
514   /**
515    * Returns the <c>Content-Type</c> header stripped of the charset attribute if present.
516    *
517    * @return The <c>media-type</c> portion of the <c>Content-Type</c> header.
518    */
519   public MediaType getMediaType() {
520      return MediaType.of(getContentType());
521   }
522
523   /**
524    * Wrapper around {@link #getCharacterEncoding()} that converts the value to a {@link Charset}.
525    *
526    * @return The request character encoding converted to a {@link Charset}.
527    */
528   public Charset getCharset() {
529      String s = getCharacterEncoding();
530      return s == null ? null : Charset.forName(s);
531   }
532
533   /**
534    * Redirects to the specified URI.
535    *
536    * <p>
537    * Relative URIs are always interpreted as relative to the context root.
538    * This is similar to how WAS handles redirect requests, and is different from how Tomcat handles redirect requests.
539    */
540   @Override /* ServletResponse */
541   public void sendRedirect(String uri) throws IOException {
542      char c = (uri.length() > 0 ? uri.charAt(0) : 0);
543      if (c != '/' && uri.indexOf("://") == -1)
544         uri = request.getContextPath() + '/' + uri;
545      super.sendRedirect(uri);
546   }
547
548   @Override /* ServletResponse */
549   public void setHeader(String name, String value) {
550
551      // Jetty doesn't set the content type correctly if set through this method.
552      // Tomcat/WAS does.
553      if (name.equalsIgnoreCase("Content-Type")) {
554         super.setContentType(value);
555         ContentType ct = ContentType.of(value);
556         if (ct != null && ct.getParameter("charset") != null)
557            super.setCharacterEncoding(ct.getParameter("charset"));
558      } else {
559         super.setHeader(name, value);
560      }
561   }
562
563   /**
564    * Same as {@link #setHeader(String, String)} but strips invalid characters from the value if present.
565    *
566    * These include CTRL characters, newlines, and non-ISO8859-1 characters.
567    * Also limits the string length to 1024 characters.
568    *
569    * @param name Header name.
570    * @param value Header value.
571    */
572   public void setHeaderSafe(String name, String value) {
573      setHeaderSafe(name, value, 1024);
574   }
575
576   /**
577    * Same as {@link #setHeader(String, String)} but strips invalid characters from the value if present.
578    *
579    * These include CTRL characters, newlines, and non-ISO8859-1 characters.
580    *
581    * @param name Header name.
582    * @param value Header value.
583    * @param maxLength
584    *    The maximum length of the header value.
585    *    Will be truncated with <js>"..."</js> added if the value exceeds the length.
586    */
587   public void setHeaderSafe(String name, String value, int maxLength) {
588
589      // Jetty doesn't set the content type correctly if set through this method.
590      // Tomcat/WAS does.
591      if (name.equalsIgnoreCase("Content-Type"))
592         super.setContentType(value);
593      else
594         super.setHeader(name, abbreviate(stripInvalidHttpHeaderChars(value), maxLength));
595   }
596
597   /**
598    * Sets a header on the request.
599    *
600    * @param name The header name.
601    * @param value The header value.
602    *    <ul>
603    *       <li>Can be any POJO.
604    *       <li>Converted to a string using the specified part serializer.
605    *    </ul>
606    * @return This object (for method chaining).
607    * @throws SchemaValidationException Header failed schema validation.
608    * @throws SerializeException Header could not be serialized.
609    */
610   public RestResponse header(String name, Object value) throws SchemaValidationException, SerializeException {
611      return header(null, null, name, value);
612   }
613
614   /**
615    * Sets a header from a {@link NameValuePair}.
616    *
617    * <p>
618    * Note that this bypasses the part serializer and set the header value directly.
619    *
620    * @param pair The header to set.  Nulls are ignored.
621    * @return This object (for method chaining).
622    */
623   public RestResponse header(NameValuePair pair) {
624      if (pair != null)
625         setHeader(pair.getName(), pair.getValue());
626      return this;
627   }
628
629   /**
630    * Sets a header on the request.
631    *
632    * @param schema
633    *    The schema to use to serialize the header, or <jk>null</jk> to use the default schema.
634    * @param name The header name.
635    * @param value The header value.
636    *    <ul>
637    *       <li>Can be any POJO.
638    *       <li>Converted to a string using the specified part serializer.
639    *    </ul>
640    * @return This object (for method chaining).
641    * @throws SchemaValidationException Header failed schema validation.
642    * @throws SerializeException Header could not be serialized.
643    */
644   public RestResponse header(HttpPartSchema schema, String name, Object value) throws SchemaValidationException, SerializeException {
645      return header(null, schema, name, value);
646   }
647
648   /**
649    * Sets a header on the request.
650    * @param serializer
651    *    The serializer to use to serialize the header, or <jk>null</jk> to use the part serializer on the request.
652    * @param schema
653    *    The schema to use to serialize the header, or <jk>null</jk> to use the default schema.
654    * @param name The header name.
655    * @param value The header value.
656    *    <ul>
657    *       <li>Can be any POJO.
658    *       <li>Converted to a string using the specified part serializer.
659    *    </ul>
660    * @return This object (for method chaining).
661    * @throws SchemaValidationException Header failed schema validation.
662    * @throws SerializeException Header could not be serialized.
663    */
664   public RestResponse header(HttpPartSerializerSession serializer, HttpPartSchema schema, String name, Object value) throws SchemaValidationException, SerializeException {
665      if (serializer == null)
666         serializer = request.getPartSerializer();
667      setHeader(name, serializer.serialize(HEADER, schema, value));
668      return this;
669   }
670
671   /**
672    * Same as {@link #setHeader(String, String)} but header is defined as a response part
673    *
674    * @param h Header to set.
675    * @throws SchemaValidationException Header part did not pass validation.
676    * @throws SerializeException Header part could not be serialized.
677    */
678   public void setHeader(HttpPart h) throws SchemaValidationException, SerializeException {
679      setHeaderSafe(h.getName(), h.getValue());
680   }
681
682   /**
683    * Sets the <js>"Exception"</js> attribute to the specified throwable.
684    *
685    * <p>
686    * This exception is used by {@link BasicRestCallLogger} for logging purposes.
687    *
688    * @param t The attribute value.
689    * @return This object (for method chaining).
690    */
691   public RestResponse setException(Throwable t) {
692      request.setException(t);
693      return this;
694   }
695
696   /**
697    * Sets the <js>"NoTrace"</js> attribute to the specified boolean.
698    *
699    * <p>
700    * This flag is used by {@link BasicRestCallLogger} and tells it not to log the current request.
701    *
702    * @param b The attribute value.
703    * @return This object (for method chaining).
704    */
705   public RestResponse setNoTrace(Boolean b) {
706      request.setNoTrace(b);
707      return this;
708   }
709
710   /**
711    * Shortcut for calling <c>setNoTrace(<jk>true</jk>)</c>.
712    *
713    * @return This object (for method chaining).
714    */
715   public RestResponse setNoTrace() {
716      return setNoTrace(true);
717   }
718
719   /**
720    * Sets the <js>"Debug"</js> attribute to the specified boolean.
721    *
722    * <p>
723    * This flag is used by {@link BasicRestCallLogger} to help determine how a request should be logged.
724    *
725    * @param b The attribute value.
726    * @return This object (for method chaining).
727    * @throws IOException If bodies could not be cached.
728    */
729   public RestResponse setDebug(Boolean b) throws IOException {
730      request.setDebug(b);
731      if (b)
732         inner = CachingHttpServletResponse.wrap(inner);
733      return this;
734   }
735
736   /**
737    * Shortcut for calling <c>setDebug(<jk>true</jk>)</c>.
738    *
739    * @return This object (for method chaining).
740    * @throws IOException If bodies could not be cached.
741    */
742   public RestResponse setDebug() throws IOException {
743      return setDebug(true);
744   }
745
746   /**
747    * Returns the metadata about this response.
748    *
749    * @return
750    *    The metadata about this response.
751    *    <jk>Never <jk>null</jk>.
752    */
753   public ResponseBeanMeta getResponseMeta() {
754      return responseMeta;
755   }
756
757   /**
758    * Sets metadata about this response.
759    *
760    * @param rbm The metadata about this response.
761    * @return This object (for method chaining).
762    */
763   public RestResponse setResponseMeta(ResponseBeanMeta rbm) {
764      this.responseMeta = rbm;
765      return this;
766   }
767
768   /**
769    * Returns <jk>true</jk> if this response object is of the specified type.
770    *
771    * @param c The type to check against.
772    * @return <jk>true</jk> if this response object is of the specified type.
773    */
774   public boolean isOutputType(Class<?> c) {
775      return c.isInstance(output);
776   }
777
778   /**
779    * Returns this value cast to the specified class.
780    *
781    * @param c The class to cast to.
782    * @return This value cast to the specified class.
783    */
784   @SuppressWarnings("unchecked")
785   public <T> T getOutput(Class<T> c) {
786      return (T)output;
787   }
788
789   /**
790    * Returns the wrapped servlet request.
791    *
792    * @return The wrapped servlet request.
793    */
794   protected HttpServletResponse getInner() {
795      return inner;
796   }
797
798   @Override /* ServletResponse */
799   public void flushBuffer() throws IOException {
800      if (w != null)
801         w.flush();
802      if (os != null)
803         os.flush();
804      super.flushBuffer();
805   }
806}