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.getDefaultResponseHeaders().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    * <p>
209    * This is the programmatic equivalent to the {@link RestMethod#htmldoc() @RestMethod(htmldoc)} annotation.
210    *
211    * <h5 class='section'>Example:</h5>
212    * <p class='bcode w800'>
213    *    <jc>// Declarative approach.</jc>
214    *    <ja>@RestMethod</ja>(
215    *       htmldoc=<ja>@HtmlDoc</ja>(
216    *          header={
217    *             <js>"&lt;p&gt;This is my REST interface&lt;/p&gt;"</js>
218    *          },
219    *          aside={
220    *             <js>"&lt;p&gt;Custom aside content&lt;/p&gt;"</js>
221    *          }
222    *       )
223    *    )
224    *    <jk>public</jk> Object doGet(RestResponse res) {
225    *
226    *       <jc>// Equivalent programmatic approach.</jc>
227    *       res.getHtmlDocBuilder()
228    *          .header(<js>"&lt;p&gt;This is my REST interface&lt;/p&gt;"</js>)
229    *          .aside(<js>"&lt;p&gt;Custom aside content&lt;/p&gt;"</js>);
230    *    }
231    * </p>
232    *
233    * <ul class='seealso'>
234    *    <li class='ja'>{@link RestMethod#htmldoc()}
235    *    <li class='link'>{@doc juneau-rest-server.HtmlDocAnnotation}
236    * </ul>
237    *
238    * @return A new programmatic interface for setting properties for the HTML doc view.
239    *
240    * @deprecated Use {@link HtmlDocConfig}
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    * <p>
253    * This contains all resource and method level properties from the following:
254    * <ul class='javatree'>
255    *    <li class='ja'>{@link Rest#properties()}
256    *    <li class='ja'>{@link RestMethod#properties()}
257    *    <li class='jm'>{@link RestContextBuilder#set(String, Object)}
258    * </ul>
259    *
260    * <p>
261    * The returned object is modifiable and allows you to override session-level properties before
262    * they get passed to the serializers.
263    * <br>However, properties are open-ended, and can be used for any purpose.
264    *
265    * <h5 class='section'>Example:</h5>
266    * <p class='bcode w800'>
267    *    <ja>@RestMethod</ja>(
268    *       properties={
269    *          <ja>@Property</ja>(name=<jsf>SERIALIZER_sortMaps</jsf>, value=<js>"false"</js>)
270    *       }
271    *    )
272    *    <jk>public</jk> Map doGet(RestResponse res, <ja>@Query</ja>(<js>"sortMaps"</js>) Boolean sortMaps) {
273    *
274    *       <jc>// Override value if specified through query parameter.</jc>
275    *       <jk>if</jk> (sortMaps != <jk>null</jk>)
276    *          res.getProperties().put(<jsf>SERIALIZER_sortMaps</jsf>, sortMaps);
277    *
278    *       <jk>return</jk> <jsm>getMyMap</jsm>();
279    *    }
280    * </p>
281    *
282    * <ul class='seealso'>
283    *    <li class='jm'>{@link #prop(String, Object)}
284    *    <li class='link'>{@doc juneau-rest-server.ConfigurableProperties}
285    * </ul>
286    *
287    * @return The properties active for this request.
288    * @deprecated Use {@link RestResponse#getAttributes()}.
289    */
290   @Deprecated
291   public RequestProperties getProperties() {
292      return properties;
293   }
294
295   /**
296    * Shortcut for calling <c>getProperties().append(name, value);</c> fluently.
297    *
298    * @param name The property name.
299    * @param value The property value.
300    * @return This object (for method chaining).
301    * @deprecated Use {@link #attr(String,Object)}.
302    */
303   @Deprecated
304   public RestResponse prop(String name, Object value) {
305      this.properties.append(name, value);
306      return this;
307   }
308
309   /**
310    * Shortcut for calling <c>getRequest().getAttributes()</c>.
311    *
312    * @return The request attributes object.
313    */
314   public RequestAttributes getAttributes() {
315      return request.getAttributes();
316   }
317
318   /**
319    * Shortcut for calling <c>getRequest().setAttribute(String,Object)</c>.
320    *
321    * @param name The property name.
322    * @param value The property value.
323    * @return This object (for method chaining).
324    */
325   public RestResponse attr(String name, Object value) {
326      request.setAttribute(name, value);
327      return this;
328   }
329
330   /**
331    * Shortcut method that allows you to use var-args to simplify setting array output.
332    *
333    * <h5 class='section'>Example:</h5>
334    * <p class='bcode w800'>
335    *    <jc>// Instead of...</jc>
336    *    response.setOutput(<jk>new</jk> Object[]{x,y,z});
337    *
338    *    <jc>// ...call this...</jc>
339    *    response.setOutput(x,y,z);
340    * </p>
341    *
342    * @param output The output to serialize to the connection.
343    * @return This object (for method chaining).
344    */
345   public RestResponse setOutputs(Object...output) {
346      this.output = output;
347      return this;
348   }
349
350   /**
351    * Returns the output that was set by calling {@link #setOutput(Object)}.
352    *
353    * @return The output object.
354    */
355   public Object getOutput() {
356      return output;
357   }
358
359   /**
360    * Returns <jk>true</jk> if this response has any output associated with it.
361    *
362    * @return <jk>true</jk> if {@link #setOutput(Object)} has been called, even if the value passed was <jk>null</jk>.
363    */
364   public boolean hasOutput() {
365      return output != null || isNullOutput;
366   }
367
368   /**
369    * Sets the output to a plain-text message regardless of the content type.
370    *
371    * @param text The output text to send.
372    * @return This object (for method chaining).
373    * @throws IOException If a problem occurred trying to write to the writer.
374    */
375   public RestResponse sendPlainText(String text) throws IOException {
376      setContentType("text/plain");
377      getNegotiatedWriter().write(text);
378      return this;
379   }
380
381   /**
382    * Equivalent to {@link HttpServletResponse#getOutputStream()}, except wraps the output stream if an {@link Encoder}
383    * was found that matched the <c>Accept-Encoding</c> header.
384    *
385    * @return A negotiated output stream.
386    * @throws NotAcceptable If unsupported Accept-Encoding value specified.
387    * @throws IOException Thrown by underlying stream.
388    */
389   public FinishableServletOutputStream getNegotiatedOutputStream() throws NotAcceptable, IOException {
390      if (os == null) {
391         Encoder encoder = null;
392         EncoderGroup encoders = restJavaMethod == null ? EncoderGroup.DEFAULT : restJavaMethod.encoders;
393
394         String ae = request.getHeader("Accept-Encoding");
395         if (! (ae == null || ae.isEmpty())) {
396            EncoderMatch match = encoders.getEncoderMatch(ae);
397            if (match == null) {
398               // Identity should always match unless "identity;q=0" or "*;q=0" is specified.
399               if (ae.matches(".*(identity|\\*)\\s*;\\s*q\\s*=\\s*(0(?!\\.)|0\\.0).*")) {
400                  throw new NotAcceptable(
401                     "Unsupported encoding in request header ''Accept-Encoding'': ''{0}''\n\tSupported codings: {1}",
402                     ae, encoders.getSupportedEncodings()
403                  );
404               }
405            } else {
406               encoder = match.getEncoder();
407               String encoding = match.getEncoding().toString();
408
409               // Some clients don't recognize identity as an encoding, so don't set it.
410               if (! encoding.equals("identity"))
411                  setHeader("content-encoding", encoding);
412            }
413         }
414         @SuppressWarnings("resource")
415         ServletOutputStream sos = getOutputStream();
416         os = new FinishableServletOutputStream(encoder == null ? sos : encoder.getOutputStream(sos));
417      }
418      return os;
419   }
420
421   @Override /* ServletResponse */
422   public ServletOutputStream getOutputStream() throws IOException {
423      if (sos == null)
424         sos = inner.getOutputStream();
425      return sos;
426   }
427
428   /**
429    * Returns <jk>true</jk> if {@link #getOutputStream()} has been called.
430    *
431    * @return <jk>true</jk> if {@link #getOutputStream()} has been called.
432    */
433   public boolean getOutputStreamCalled() {
434      return sos != null;
435   }
436
437   /**
438    * Returns the writer to the response body.
439    *
440    * <p>
441    * This methods bypasses any specified encoders and returns a regular unbuffered writer.
442    * Use the {@link #getNegotiatedWriter()} method if you want to use the matched encoder (if any).
443    */
444   @Override /* ServletResponse */
445   public PrintWriter getWriter() throws IOException {
446      return getWriter(true, false);
447   }
448
449   /**
450    * Convenience method meant to be used when rendering directly to a browser with no buffering.
451    *
452    * <p>
453    * Sets the header <js>"x-content-type-options=nosniff"</js> so that output is rendered immediately on IE and Chrome
454    * without any buffering for content-type sniffing.
455    *
456    * <p>
457    * This can be useful if you want to render a streaming 'console' on a web page.
458    *
459    * @param contentType The value to set as the <c>Content-Type</c> on the response.
460    * @return The raw writer.
461    * @throws IOException Thrown by underlying stream.
462    */
463   public PrintWriter getDirectWriter(String contentType) throws IOException {
464      setContentType(contentType);
465      setHeader("X-Content-Type-Options", "nosniff");
466      setHeader("Content-Encoding", "identity");
467      return getWriter(true, true);
468   }
469
470   /**
471    * Equivalent to {@link HttpServletResponse#getWriter()}, except wraps the output stream if an {@link Encoder} was
472    * found that matched the <c>Accept-Encoding</c> header and sets the <c>Content-Encoding</c>
473    * header to the appropriate value.
474    *
475    * @return The negotiated writer.
476    * @throws NotAcceptable If unsupported charset in request header Accept-Charset.
477    * @throws IOException Thrown by underlying stream.
478    */
479   public FinishablePrintWriter getNegotiatedWriter() throws NotAcceptable, IOException {
480      return getWriter(false, false);
481   }
482
483   @SuppressWarnings("resource")
484   private FinishablePrintWriter getWriter(boolean raw, boolean autoflush) throws NotAcceptable, IOException {
485      if (w != null)
486         return w;
487
488      // If plain text requested, override it now.
489      if (request.isPlainText())
490         setHeader("Content-Type", "text/plain");
491
492      try {
493         OutputStream out = (raw ? getOutputStream() : getNegotiatedOutputStream());
494         w = new FinishablePrintWriter(out, getCharacterEncoding(), autoflush);
495         return w;
496      } catch (UnsupportedEncodingException e) {
497         String ce = getCharacterEncoding();
498         setCharacterEncoding("UTF-8");
499         throw new NotAcceptable("Unsupported charset in request header ''Accept-Charset'': ''{0}''", ce);
500      }
501   }
502
503   /**
504    * Returns the <c>Content-Type</c> header stripped of the charset attribute if present.
505    *
506    * @return The <c>media-type</c> portion of the <c>Content-Type</c> header.
507    */
508   public MediaType getMediaType() {
509      return MediaType.forString(getContentType());
510   }
511
512   /**
513    * Wrapper around {@link #getCharacterEncoding()} that converts the value to a {@link Charset}.
514    *
515    * @return The request character encoding converted to a {@link Charset}.
516    */
517   public Charset getCharset() {
518      String s = getCharacterEncoding();
519      return s == null ? null : Charset.forName(s);
520   }
521
522   /**
523    * Redirects to the specified URI.
524    *
525    * <p>
526    * Relative URIs are always interpreted as relative to the context root.
527    * This is similar to how WAS handles redirect requests, and is different from how Tomcat handles redirect requests.
528    */
529   @Override /* ServletResponse */
530   public void sendRedirect(String uri) throws IOException {
531      char c = (uri.length() > 0 ? uri.charAt(0) : 0);
532      if (c != '/' && uri.indexOf("://") == -1)
533         uri = request.getContextPath() + '/' + uri;
534      super.sendRedirect(uri);
535   }
536
537   @Override /* ServletResponse */
538   public void setHeader(String name, String value) {
539
540      // Jetty doesn't set the content type correctly if set through this method.
541      // Tomcat/WAS does.
542      if (name.equalsIgnoreCase("Content-Type"))
543         super.setContentType(value);
544      else
545         super.setHeader(name, value);
546   }
547
548   /**
549    * Same as {@link #setHeader(String, String)} but strips invalid characters from the value if present.
550    *
551    * These include CTRL characters, newlines, and non-ISO8859-1 characters.
552    * Also limits the string length to 1024 characters.
553    *
554    * @param name Header name.
555    * @param value Header value.
556    */
557   public void setHeaderSafe(String name, String value) {
558      setHeaderSafe(name, value, 1024);
559   }
560
561   /**
562    * Same as {@link #setHeader(String, String)} but strips invalid characters from the value if present.
563    *
564    * These include CTRL characters, newlines, and non-ISO8859-1 characters.
565    *
566    * @param name Header name.
567    * @param value Header value.
568    * @param maxLength
569    *    The maximum length of the header value.
570    *    Will be truncated with <js>"..."</js> added if the value exceeds the length.
571    */
572   public void setHeaderSafe(String name, String value, int maxLength) {
573
574      // Jetty doesn't set the content type correctly if set through this method.
575      // Tomcat/WAS does.
576      if (name.equalsIgnoreCase("Content-Type"))
577         super.setContentType(value);
578      else
579         super.setHeader(name, abbreviate(stripInvalidHttpHeaderChars(value), maxLength));
580   }
581
582   /**
583    * Same as {@link #setHeader(String, String)} but header is defined as a response part
584    *
585    * @param h Header to set.
586    * @throws SchemaValidationException Header part did not pass validation.
587    * @throws SerializeException Header part could not be serialized.
588    */
589   public void setHeader(HttpPart h) throws SchemaValidationException, SerializeException {
590      setHeaderSafe(h.getName(), h.asString());
591   }
592
593   /**
594    * Sets the <js>"Exception"</js> attribute to the specified throwable.
595    *
596    * <p>
597    * This exception is used by {@link BasicRestCallLogger} for logging purposes.
598    *
599    * @param t The attribute value.
600    * @return This object (for method chaining).
601    */
602   public RestResponse setException(Throwable t) {
603      request.setException(t);
604      return this;
605   }
606
607   /**
608    * Sets the <js>"NoTrace"</js> attribute to the specified boolean.
609    *
610    * <p>
611    * This flag is used by {@link BasicRestCallLogger} and tells it not to log the current request.
612    *
613    * @param b The attribute value.
614    * @return This object (for method chaining).
615    */
616   public RestResponse setNoTrace(Boolean b) {
617      request.setNoTrace(b);
618      return this;
619   }
620
621   /**
622    * Shortcut for calling <c>setNoTrace(<jk>true</jk>)</c>.
623    *
624    * @return This object (for method chaining).
625    */
626   public RestResponse setNoTrace() {
627      return setNoTrace(true);
628   }
629
630   /**
631    * Sets the <js>"Debug"</js> attribute to the specified boolean.
632    *
633    * <p>
634    * This flag is used by {@link BasicRestCallLogger} to help determine how a request should be logged.
635    *
636    * @param b The attribute value.
637    * @return This object (for method chaining).
638    * @throws IOException If bodies could not be cached.
639    */
640   public RestResponse setDebug(Boolean b) throws IOException {
641      request.setDebug(b);
642      if (b)
643         inner = CachingHttpServletResponse.wrap(inner);
644      return this;
645   }
646
647   /**
648    * Shortcut for calling <c>setDebug(<jk>true</jk>)</c>.
649    *
650    * @return This object (for method chaining).
651    * @throws IOException If bodies could not be cached.
652    */
653   public RestResponse setDebug() throws IOException {
654      return setDebug(true);
655   }
656
657   /**
658    * Returns the metadata about this response.
659    *
660    * @return
661    *    The metadata about this response.
662    *    <jk>Never <jk>null</jk>.
663    */
664   public ResponseBeanMeta getResponseMeta() {
665      return responseMeta;
666   }
667
668   /**
669    * Sets metadata about this response.
670    *
671    * @param rbm The metadata about this response.
672    * @return This object (for method chaining).
673    */
674   public RestResponse setResponseMeta(ResponseBeanMeta rbm) {
675      this.responseMeta = rbm;
676      return this;
677   }
678
679   /**
680    * Returns <jk>true</jk> if this response object is of the specified type.
681    *
682    * @param c The type to check against.
683    * @return <jk>true</jk> if this response object is of the specified type.
684    */
685   public boolean isOutputType(Class<?> c) {
686      return c.isInstance(output);
687   }
688
689   /**
690    * Returns this value cast to the specified class.
691    *
692    * @param c The class to cast to.
693    * @return This value cast to the specified class.
694    */
695   @SuppressWarnings("unchecked")
696   public <T> T getOutput(Class<T> c) {
697      return (T)output;
698   }
699
700   /**
701    * Returns the wrapped servlet request.
702    *
703    * @return The wrapped servlet request.
704    */
705   protected HttpServletResponse getInner() {
706      return inner;
707   }
708
709   @Override /* ServletResponse */
710   public void flushBuffer() throws IOException {
711      if (w != null)
712         w.flush();
713      if (os != null)
714         os.flush();
715      super.flushBuffer();
716   }
717}