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