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>"<p>This is my REST interface</p>"</js> 221 * }, 222 * aside={ 223 * <js>"<p>Custom aside content</p>"</js> 224 * } 225 * ) 226 * <jk>public</jk> Object doGet(RestResponse res) { 227 * 228 * <jc>// Equivalent programmatic approach.</jc> 229 * res.getHtmlDocBuilder() 230 * .header(<js>"<p>This is my REST interface</p>"</js>) 231 * .aside(<js>"<p>Custom aside content</p>"</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}