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