001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest;
018
019import static org.apache.juneau.common.utils.StringUtils.*;
020import static org.apache.juneau.http.HttpHeaders.*;
021import static org.apache.juneau.httppart.HttpPartType.*;
022
023import java.io.*;
024import java.nio.charset.*;
025import java.util.*;
026
027import org.apache.http.*;
028import org.apache.juneau.*;
029import org.apache.juneau.collections.*;
030import org.apache.juneau.common.utils.*;
031import org.apache.juneau.encoders.*;
032import org.apache.juneau.http.header.*;
033import org.apache.juneau.http.response.*;
034import org.apache.juneau.httppart.*;
035import org.apache.juneau.httppart.bean.*;
036import org.apache.juneau.marshaller.*;
037import org.apache.juneau.oapi.*;
038import org.apache.juneau.rest.httppart.*;
039import org.apache.juneau.rest.logger.*;
040import org.apache.juneau.rest.util.*;
041import org.apache.juneau.serializer.*;
042
043import jakarta.servlet.*;
044import jakarta.servlet.http.*;
045
046/**
047 * Represents an HTTP response for a REST resource.
048 *
049 * <p>
050 *    The {@link RestResponse} object is an extension of the <l>HttpServletResponse</l> class
051 *    with various built-in convenience methods for use in building REST interfaces.
052 *    It can be accessed by passing it as a parameter on your REST Java method:
053 * </p>
054 *
055 * <p class='bjava'>
056 *    <ja>@RestPost</ja>(...)
057 *    <jk>public</jk> Object myMethod(RestResponse <jv>res</jv>) {...}
058 * </p>
059 *
060 * <p>
061 *    The primary methods on this class are:
062 * </p>
063 * <ul class='javatree'>
064 *    <li class='jc'>{@link RestResponse}
065 *    <ul class='spaced-list'>
066 *       <li>Methods for setting response headers:
067 *       <ul class='javatreec'>
068 *          <li class='jm'>{@link RestResponse#addHeader(Header) addHeader(Header)}
069 *          <li class='jm'>{@link RestResponse#addHeader(String,String) addHeader(String,String)}
070 *          <li class='jm'>{@link RestResponse#containsHeader(String) containsHeader(String)}
071 *          <li class='jm'>{@link RestResponse#getHeader(String) getHeader(String)}
072 *          <li class='jm'>{@link RestResponse#setCharacterEncoding(String) setCharacterEncoding(String)}
073 *          <li class='jm'>{@link RestResponse#setContentType(String) setContentType(String)}
074 *          <li class='jm'>{@link RestResponse#setHeader(Header) setHeader(Header)}
075 *          <li class='jm'>{@link RestResponse#setHeader(HttpPartSchema,String,Object) setHeader(HttpPartSchema,String,Object)}
076 *          <li class='jm'>{@link RestResponse#setHeader(String,Object) setHeader(String,Object)}
077 *          <li class='jm'>{@link RestResponse#setHeader(String,String) setHeader(String,String)}
078 *          <li class='jm'>{@link RestResponse#setMaxHeaderLength(int) setMaxHeaderLength(int)}
079 *          <li class='jm'>{@link RestResponse#setSafeHeaders() setSafeHeaders()}
080 *       </ul>
081 *       <li>Methods for setting response bodies:
082 *       <ul class='javatreec'>
083 *          <li class='jm'>{@link RestResponse#flushBuffer() flushBuffer()}
084 *          <li class='jm'>{@link RestResponse#getDirectWriter(String) getDirectWriter(String)}
085 *          <li class='jm'>{@link RestResponse#getNegotiatedOutputStream() getNegotiatedOutputStream()}
086 *          <li class='jm'>{@link RestResponse#getNegotiatedWriter() getNegotiatedWriter()}
087 *          <li class='jm'>{@link RestResponse#getSerializerMatch() getSerializerMatch()}
088 *          <li class='jm'>{@link RestResponse#getWriter() getWriter()}
089 *          <li class='jm'>{@link RestResponse#sendPlainText(String) sendPlainText(String)}
090 *          <li class='jm'>{@link RestResponse#sendRedirect(String) sendRedirect(String)}
091 *          <li class='jm'>{@link RestResponse#setContentSchema(HttpPartSchema) setContentSchema(HttpPartSchema)}
092 *          <li class='jm'>{@link RestResponse#setContent(Object) setOutput(Object)}
093 *          <li class='jm'>{@link RestResponse#setResponseBeanMeta(ResponseBeanMeta) setResponseBeanMeta(ResponseBeanMeta)}
094 *          <li class='jm'>{@link RestResponse#setException(Throwable) setException(Throwable)}
095 *       </ul>
096 *       <li>Other:
097 *       <ul class='javatreec'>
098 *          <li class='jm'>{@link RestResponse#getAttributes() getAttributes()}
099 *          <li class='jm'>{@link RestResponse#getContext() getContext()}
100 *          <li class='jm'>{@link RestResponse#getOpContext() getOpContext()}
101 *          <li class='jm'>{@link RestResponse#setAttribute(String,Object) setAttribute(String,Object)}
102 *          <li class='jm'>{@link RestResponse#setDebug() setDebug()}
103 *          <li class='jm'>{@link RestResponse#setNoTrace() setNoTrace()}
104 *          <li class='jm'>{@link RestResponse#setStatus(int) setStatus(int)}
105 *       </ul>
106 *    </ul>
107 * </ul>
108 *
109 * <h5 class='section'>See Also:</h5><ul>
110
111 * </ul>
112 */
113public class RestResponse extends HttpServletResponseWrapper {
114
115   private HttpServletResponse inner;
116   private final RestRequest request;
117
118   private Optional<Object> content;  // The POJO being sent to the output.
119   private ServletOutputStream sos;
120   private FinishableServletOutputStream os;
121   private FinishablePrintWriter w;
122   private ResponseBeanMeta responseBeanMeta;
123   private RestOpContext opContext;
124   private Optional<HttpPartSchema> contentSchema;
125   private Serializer serializer;
126   private Optional<SerializerMatch> serializerMatch;
127   private boolean safeHeaders;
128   private int maxHeaderLength = 8096;
129
130   /**
131    * Constructor.
132    */
133   RestResponse(RestOpContext opContext, RestSession session, RestRequest req) throws Exception {
134      super(session.getResponse());
135
136      inner = session.getResponse();
137      request = req;
138
139      this.opContext = opContext;
140      responseBeanMeta = opContext.getResponseMeta();
141
142      RestContext context = session.getContext();
143
144      try {
145         String passThroughHeaders = request.getHeaderParam("x-response-headers").orElse(null);
146         if (passThroughHeaders != null) {
147            JsonMap m = context.getPartParser().getPartSession().parse(HEADER, null, passThroughHeaders, BeanContext.DEFAULT.getClassMeta(JsonMap.class));
148            for (Map.Entry<String,Object> e : m.entrySet())
149               addHeader(e.getKey(), resolveUris(e.getValue()));
150         }
151      } catch (Exception e1) {
152         throw new BadRequest(e1, "Invalid format for header 'x-response-headers'.  Must be in URL-encoded format.");
153      }
154
155      // Find acceptable charset
156      String h = request.getHeaderParam("accept-charset").orElse(null);
157      Charset charset = null;
158      if (h == null)
159         charset = opContext.getDefaultCharset();
160      else for (StringRange r : StringRanges.of(h).toList()) {
161         if (r.getQValue() > 0) {
162            if (r.getName().equals("*"))
163               charset = opContext.getDefaultCharset();
164            else if (Charset.isSupported(r.getName()))
165               charset = Charset.forName(r.getName());
166            if (charset != null)
167               break;
168         }
169      }
170
171      request.getContext().getDefaultResponseHeaders().forEach(x->addHeader(x.getValue(), resolveUris(x.getValue())));  // Done this way to avoid list/array copy.
172
173      opContext.getDefaultResponseHeaders().forEach(x->addHeader(x.getName(), resolveUris(x.getValue())));
174
175      if (charset == null)
176         throw new NotAcceptable("No supported charsets in header ''Accept-Charset'': ''{0}''", request.getHeaderParam("Accept-Charset").orElse(null));
177      inner.setCharacterEncoding(charset.name());
178
179   }
180
181   /**
182    * Returns access to the inner {@link RestContext} of the class of this method.
183    *
184    * @return The {@link RestContext} of this class.  Never <jk>null</jk>.
185    */
186   public RestContext getContext() {
187      return request.getContext();
188   }
189
190   /**
191    * Returns access to the inner {@link RestOpContext} of this method.
192    *
193    * @return The {@link RestOpContext} of this method.  Never <jk>null</jk>.
194    */
195   public RestOpContext getOpContext() {
196      return request.getOpContext();
197   }
198
199   /**
200    * Sets the HTTP output on the response.
201    *
202    * <p>
203    * The object type can be anything allowed by the registered response handlers.
204    *
205    * <p>
206    * Calling this method is functionally equivalent to returning the object in the REST Java method.
207    *
208    * <h5 class='section'>Example:</h5>
209    * <p class='bjava'>
210    *    <ja>@RestGet</ja>(<js>"/example2/{personId}"</js>)
211    *    <jk>public void</jk> doGet(RestResponse <jv>res</jv>, <ja>@Path</ja> UUID <jv>personId</jv>) {
212    *       Person <jv>person</jv> = getPersonById(<jv>personId</jv>);
213    *       <jv>res</jv>.setOutput(<jv>person</jv>);
214    *    }
215    * </p>
216    *
217    * <h5 class='section'>Notes:</h5><ul>
218    *    <li class='note'>
219    *       Calling this method with a <jk>null</jk> value is NOT the same as not calling this method at all.
220    *       <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>).
221    *       <br>Not calling this method or returning a value means you're handing the response yourself via the underlying stream or writer.
222    * </ul>
223    *
224    * <h5 class='section'>See Also:</h5><ul>
225    *    <li class='jm'>{@link RestContext.Builder#responseProcessors()}
226    *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestOpAnnotatedMethodBasics">@RestOp-Annotated Method Basics</a>
227    * </ul>
228    *
229    * @param output The output to serialize to the connection.
230    * @return This object.
231    */
232   public RestResponse setContent(Object output) {
233      this.content = Utils.opt(output);
234      return this;
235   }
236
237   /**
238    * Shortcut for calling <c>getRequest().getAttributes()</c>.
239    *
240    * @return The request attributes object.
241    */
242   public RequestAttributes getAttributes() {
243      return request.getAttributes();
244   }
245
246   /**
247    * Shortcut for calling <c>getRequest().setAttribute(String,Object)</c>.
248    *
249    * @param name The property name.
250    * @param value The property value.
251    * @return This object.
252    */
253   public RestResponse setAttribute(String name, Object value) {
254      request.setAttribute(name, value);
255      return this;
256   }
257
258   /**
259    * Returns the output that was set by calling {@link #setContent(Object)}.
260    *
261    * <p>
262    * If it's null, then {@link #setContent(Object)} wasn't called.
263    * <br>If it contains an empty, then <c>setObject(<jk>null</jk>)</c> was called.
264    * <br>Otherwise, {@link #setContent(Object)} was called with a non-null value.
265    *
266    * @return The output object, or <jk>null</jk> if {@link #setContent(Object)} was never called.
267    */
268   public Optional<Object> getContent() {
269      return content;
270   }
271
272   /**
273    * Returns <jk>true</jk> if the response contains output.
274    *
275    * <p>
276    * This implies {@link #setContent(Object)} has been called on this object.
277    *
278    * <p>
279    * Note that this also returns <jk>true</jk> even if {@link #setContent(Object)} was called with a <jk>null</jk>
280    * value as this means the response contains an output value of <jk>null</jk> as opposed to no value at all.
281    *
282    * @return <jk>true</jk> if the response contains output.
283    */
284   public boolean hasContent() {
285      return content != null;
286   }
287
288   /**
289    * Sets the output to a plain-text message regardless of the content type.
290    *
291    * @param text The output text to send.
292    * @return This object.
293    * @throws IOException If a problem occurred trying to write to the writer.
294    */
295   public RestResponse sendPlainText(String text) throws IOException {
296      setContentType("text/plain");
297      getNegotiatedWriter().write(text);
298      return this;
299   }
300
301   /**
302    * Equivalent to {@link HttpServletResponse#getOutputStream()}, except wraps the output stream if an {@link Encoder}
303    * was found that matched the <c>Accept-Encoding</c> header.
304    *
305    * @return A negotiated output stream.
306    * @throws NotAcceptable If unsupported Accept-Encoding value specified.
307    * @throws IOException Thrown by underlying stream.
308    */
309   public FinishableServletOutputStream getNegotiatedOutputStream() throws NotAcceptable, IOException {
310      if (os == null) {
311         Encoder encoder = null;
312         EncoderSet encoders = request.getOpContext().getEncoders();
313
314         String ae = request.getHeaderParam("Accept-Encoding").orElse(null);
315         if (! (ae == null || ae.isEmpty())) {
316            EncoderMatch match = encoders.getEncoderMatch(ae);
317            if (match == null) {
318               // Identity should always match unless "identity;q=0" or "*;q=0" is specified.
319               if (ae.matches(".*(identity|\\*)\\s*;\\s*q\\s*=\\s*(0(?!\\.)|0\\.0).*")) {
320                  throw new NotAcceptable(
321                     "Unsupported encoding in request header ''Accept-Encoding'': ''{0}''\n\tSupported codings: {1}",
322                     ae, Json5.of(encoders.getSupportedEncodings())
323                  );
324               }
325            } else {
326               encoder = match.getEncoder();
327               String encoding = match.getEncoding().toString();
328
329               // Some clients don't recognize identity as an encoding, so don't set it.
330               if (! encoding.equals("identity"))
331                  setHeader("content-encoding", encoding);
332            }
333         }
334         @SuppressWarnings("resource")
335         ServletOutputStream sos = getOutputStream();
336         os = new FinishableServletOutputStream(encoder == null ? sos : encoder.getOutputStream(sos));
337      }
338      return os;
339   }
340
341   /**
342    * Returns a ServletOutputStream suitable for writing binary data in the response.
343    *
344    * <p>
345    * The servlet container does not encode the binary data.
346    *
347    * <p>
348    * Calling <c>flush()</c> on the ServletOutputStream commits the response.
349    * Either this method or <c>getWriter</c> may be called to write the content, not both, except when reset has been called.
350    *
351    * @return The stream.
352    * @throws IOException If stream could not be accessed.
353    */
354   @Override
355   public ServletOutputStream getOutputStream() throws IOException {
356      if (sos == null)
357         sos = inner.getOutputStream();
358      return sos;
359   }
360
361   /**
362    * Returns <jk>true</jk> if {@link #getOutputStream()} has been called.
363    *
364    * @return <jk>true</jk> if {@link #getOutputStream()} has been called.
365    */
366   public boolean getOutputStreamCalled() {
367      return sos != null;
368   }
369
370   /**
371    * Returns the writer to the response content.
372    *
373    * <p>
374    * This methods bypasses any specified encoders and returns a regular unbuffered writer.
375    * Use the {@link #getNegotiatedWriter()} method if you want to use the matched encoder (if any).
376    *
377    * @return The writer.
378    * @throws IOException If writer could not be accessed.
379    */
380   @Override
381   public PrintWriter getWriter() throws IOException {
382      return getWriter(true, false);
383   }
384
385   /**
386    * Convenience method meant to be used when rendering directly to a browser with no buffering.
387    *
388    * <p>
389    * Sets the header <js>"x-content-type-options=nosniff"</js> so that output is rendered immediately on IE and Chrome
390    * without any buffering for content-type sniffing.
391    *
392    * <p>
393    * This can be useful if you want to render a streaming 'console' on a web page.
394    *
395    * @param contentType The value to set as the <c>Content-Type</c> on the response.
396    * @return The raw writer.
397    * @throws IOException Thrown by underlying stream.
398    */
399   public PrintWriter getDirectWriter(String contentType) throws IOException {
400      setContentType(contentType);
401      setHeader("X-Content-Type-Options", "nosniff");
402      setHeader("Content-Encoding", "identity");
403      return getWriter(true, true);
404   }
405
406   /**
407    * Equivalent to {@link HttpServletResponse#getWriter()}, except wraps the output stream if an {@link Encoder} was
408    * found that matched the <c>Accept-Encoding</c> header and sets the <c>Content-Encoding</c>
409    * header to the appropriate value.
410    *
411    * @return The negotiated writer.
412    * @throws NotAcceptable If unsupported charset in request header Accept-Charset.
413    * @throws IOException Thrown by underlying stream.
414    */
415   public FinishablePrintWriter getNegotiatedWriter() throws NotAcceptable, IOException {
416      return getWriter(false, false);
417   }
418
419   @SuppressWarnings("resource")
420   private FinishablePrintWriter getWriter(boolean raw, boolean autoflush) throws NotAcceptable, IOException {
421      if (w != null)
422         return w;
423
424      // If plain text requested, override it now.
425      if (request.isPlainText())
426         setHeader("Content-Type", "text/plain");
427
428      try {
429         OutputStream out = (raw ? getOutputStream() : getNegotiatedOutputStream());
430         w = new FinishablePrintWriter(out, getCharacterEncoding(), autoflush);
431         return w;
432      } catch (UnsupportedEncodingException e) {
433         String ce = getCharacterEncoding();
434         setCharacterEncoding("UTF-8");
435         throw new NotAcceptable("Unsupported charset in request header ''Accept-Charset'': ''{0}''", ce);
436      }
437   }
438
439   /**
440    * Returns the <c>Content-Type</c> header stripped of the charset attribute if present.
441    *
442    * @return The <c>media-type</c> portion of the <c>Content-Type</c> header.
443    */
444   public MediaType getMediaType() {
445      return MediaType.of(getContentType());
446   }
447
448   /**
449    * Wrapper around {@link #getCharacterEncoding()} that converts the value to a {@link Charset}.
450    *
451    * @return The request character encoding converted to a {@link Charset}.
452    */
453   public Charset getCharset() {
454      String s = getCharacterEncoding();
455      return s == null ? null : Charset.forName(s);
456   }
457
458   /**
459    * Redirects to the specified URI.
460    *
461    * <p>
462    * Relative URIs are always interpreted as relative to the context root.
463    * This is similar to how WAS handles redirect requests, and is different from how Tomcat handles redirect requests.
464    *
465    * @param uri The redirection URL.
466    * @throws IOException If an input or output exception occurs
467    */
468   @Override
469   public void sendRedirect(String uri) throws IOException {
470      char c = (uri.length() > 0 ? uri.charAt(0) : 0);
471      if (c != '/' && uri.indexOf("://") == -1)
472         uri = request.getContextPath() + '/' + uri;
473      inner.sendRedirect(uri);
474   }
475
476   /**
477    * Sets a response header with the given name and value.
478    *
479    * <p>
480    * If the header had already been set, the new value overwrites the previous one.
481    *
482    * <p>
483    * The {@link #containsHeader(String)} method can be used to test for the presence of a header before setting its value.
484    *
485    * @param name The header name.
486    * @param value The header value.
487    */
488   @Override
489   public void setHeader(String name, String value) {
490
491      // Jetty doesn't set the content type correctly if set through this method.
492      // Tomcat/WAS does.
493      if (name.equalsIgnoreCase("Content-Type")) {
494         inner.setContentType(value);
495         ContentType ct = contentType(value);
496         if (ct != null && ct.getParameter("charset") != null)
497            inner.setCharacterEncoding(ct.getParameter("charset"));
498      } else {
499         if (safeHeaders)
500            value = stripInvalidHttpHeaderChars(value);
501         value = abbreviate(value, maxHeaderLength);
502         inner.setHeader(name, value);
503      }
504   }
505
506   /**
507    * Sets a header on the request.
508    *
509    * @param name The header name.
510    * @param value The header value.
511    *    <ul>
512    *       <li>Can be any POJO.
513    *       <li>Converted to a string using the specified part serializer.
514    *    </ul>
515    * @return This object.
516    * @throws SchemaValidationException Header failed schema validation.
517    * @throws SerializeException Header could not be serialized.
518    */
519   public RestResponse setHeader(String name, Object value) throws SchemaValidationException, SerializeException {
520      setHeader(name, request.getPartSerializerSession().serialize(HEADER, null, value));
521      return this;
522   }
523
524   /**
525    * Sets a header on the request.
526    *
527    * @param schema
528    *    The schema to use to serialize the header, or <jk>null</jk> to use the default schema.
529    * @param name The header name.
530    * @param value The header value.
531    *    <ul>
532    *       <li>Can be any POJO.
533    *       <li>Converted to a string using the specified part serializer.
534    *    </ul>
535    * @return This object.
536    * @throws SchemaValidationException Header failed schema validation.
537    * @throws SerializeException Header could not be serialized.
538    */
539   public RestResponse setHeader(HttpPartSchema schema, String name, Object value) throws SchemaValidationException, SerializeException {
540      setHeader(name, request.getPartSerializerSession().serialize(HEADER, schema, value));
541      return this;
542   }
543
544   /**
545    * Specifies the schema for the response content.
546    *
547    * <p>
548    * Used by schema-aware serializers such as {@link OpenApiSerializer}.  Ignored by other serializers.
549    *
550    * @param schema The content schema
551    * @return This object.
552    */
553   public RestResponse setContentSchema(HttpPartSchema schema) {
554      this.contentSchema = Utils.opt(schema);
555      return this;
556   }
557
558   /**
559    * Sets the <js>"Exception"</js> attribute to the specified throwable.
560    *
561    * <p>
562    * This exception is used by {@link CallLogger} for logging purposes.
563    *
564    * @param t The attribute value.
565    * @return This object.
566    */
567   public RestResponse setException(Throwable t) {
568      request.setException(t);
569      return this;
570   }
571
572   /**
573    * Sets the <js>"NoTrace"</js> attribute to the specified boolean.
574    *
575    * <p>
576    * This flag is used by {@link CallLogger} and tells it not to log the current request.
577    *
578    * @param b The attribute value.
579    * @return This object.
580    */
581   public RestResponse setNoTrace(Boolean b) {
582      request.setNoTrace(b);
583      return this;
584   }
585
586   /**
587    * Shortcut for calling <c>setNoTrace(<jk>true</jk>)</c>.
588    *
589    * @return This object.
590    */
591   public RestResponse setNoTrace() {
592      return setNoTrace(true);
593   }
594
595   /**
596    * Sets the <js>"Debug"</js> attribute to the specified boolean.
597    *
598    * <p>
599    * This flag is used by {@link CallLogger} to help determine how a request should be logged.
600    *
601    * @param b The attribute value.
602    * @return This object.
603    * @throws IOException If bodies could not be cached.
604    */
605   public RestResponse setDebug(Boolean b) throws IOException {
606      request.setDebug(b);
607      if (b)
608         inner = CachingHttpServletResponse.wrap(inner);
609      return this;
610   }
611
612   /**
613    * Shortcut for calling <c>setDebug(<jk>true</jk>)</c>.
614    *
615    * @return This object.
616    * @throws IOException If bodies could not be cached.
617    */
618   public RestResponse setDebug() throws IOException {
619      return setDebug(true);
620   }
621
622   /**
623    * Returns the metadata about this response.
624    *
625    * @return
626    *    The metadata about this response.
627    *    <br>Never <jk>null</jk>.
628    */
629   public ResponseBeanMeta getResponseBeanMeta() {
630      return responseBeanMeta;
631   }
632
633   /**
634    * Sets metadata about this response.
635    *
636    * @param rbm The metadata about this response.
637    * @return This object.
638    */
639   public RestResponse setResponseBeanMeta(ResponseBeanMeta rbm) {
640      this.responseBeanMeta = rbm;
641      return this;
642   }
643
644   /**
645    * Returns <jk>true</jk> if this response object is of the specified type.
646    *
647    * @param c The type to check against.
648    * @return <jk>true</jk> if this response object is of the specified type.
649    */
650   public boolean isContentOfType(Class<?> c) {
651      return c.isInstance(getRawOutput());
652   }
653
654   /**
655    * Returns this value cast to the specified class.
656    *
657    * @param <T> The class to cast to.
658    * @param c The class to cast to.
659    * @return This value cast to the specified class, or <jk>null</jk> if the object doesn't exist or isn't the specified type.
660    */
661   @SuppressWarnings("unchecked")
662   public <T> T getContent(Class<T> c) {
663      if (isContentOfType(c))
664         return (T)getRawOutput();
665      return null;
666   }
667
668   /**
669    * Returns the wrapped servlet request.
670    *
671    * @return The wrapped servlet request.
672    */
673   public HttpServletResponse getHttpServletResponse() {
674      return inner;
675   }
676
677   /**
678    * Forces any content in the buffer to be written to the client.
679    *
680    * <p>
681    * A call to this method automatically commits the response, meaning the status code and headers will be written.
682    *
683    * @throws IOException If an I/O error occurred.
684    */
685   @Override
686   public void flushBuffer() throws IOException {
687      if (w != null)
688         w.flush();
689      if (os != null)
690         os.flush();
691      inner.flushBuffer();
692   }
693
694   private Object getRawOutput() {
695      return content == null ? null : content.orElse(null);
696   }
697
698   /**
699    * Enabled safe-header mode.
700    *
701    * <p>
702    * When enabled, invalid characters such as CTRL characters will be stripped from header values
703    * before they get set.
704    *
705    * @return This object.
706    */
707   public RestResponse setSafeHeaders() {
708      this.safeHeaders = true;
709      return this;
710   }
711
712   /**
713    * Specifies the maximum length for header values.
714    *
715    * <p>
716    * Header values that exceed this length will get truncated.
717    *
718    * @param value The new value for this setting.  The default is <c>8096</c>.
719    * @return This object.
720    */
721   public RestResponse setMaxHeaderLength(int value) {
722      this.maxHeaderLength = value;
723      return this;
724   }
725
726   /**
727    * Adds a response header with the given name and value.
728    *
729    * <p>
730    * This method allows response headers to have multiple values.
731    *
732    * <p>
733    * A no-op of either the name or value is <jk>null</jk>.
734    *
735    * <p>
736    * Note that per <a class='doclink' href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2'>RFC2616</a>,
737    * only headers defined as comma-delimited lists [i.e., #(values)] should be defined as multiple message header fields.
738    *
739    * @param name The header name.
740    * @param value The header value.
741    */
742   @Override
743   public void addHeader(String name, String value) {
744      if (name != null && value != null) {
745         if (name.equalsIgnoreCase("Content-Type"))
746            setHeader(name, value);
747         else {
748            if (safeHeaders)
749               value = stripInvalidHttpHeaderChars(value);
750            value = abbreviate(value, maxHeaderLength);
751            inner.addHeader(name, value);
752         }
753      }
754   }
755
756   /**
757    * Sets a response header.
758    *
759    * <p>
760    * Any previous header values are removed.
761    *
762    * <p>
763    * Value is added at the end of the headers.
764    *
765    * @param header The header.
766    * @return This object.
767    */
768   public RestResponse setHeader(Header header) {
769      if (header == null) {
770         // Do nothing.
771      } else if (header instanceof BasicUriHeader) {
772         BasicUriHeader x = (BasicUriHeader)header;
773         setHeader(x.getName(), resolveUris(x.getValue()));
774      } else if (header instanceof SerializedHeader) {
775         SerializedHeader x = ((SerializedHeader)header).copyWith(request.getPartSerializerSession(), null);
776         String v = x.getValue();
777         if (v != null && v.indexOf("://") != -1)
778            v = resolveUris(v);
779         setHeader(x.getName(), v);
780      } else {
781         setHeader(header.getName(), header.getValue());
782      }
783      return this;
784   }
785
786   /**
787    * Adds a response header.
788    *
789    * <p>
790    * Any previous header values are preserved.
791    *
792    * <p>
793    * Value is added at the end of the headers.
794    *
795    * <p>
796    * If the header is a {@link BasicUriHeader}, the URI will be resolved using the {@link RestRequest#getUriResolver()} object.
797    *
798    * <p>
799    * If the header is a {@link SerializedHeader} and the serializer session is not set, it will be set to the one returned by {@link RestRequest#getPartSerializerSession()} before serialization.
800    *
801    * <p>
802    * Note that per <a class='doclink' href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2'>RFC2616</a>,
803    * only headers defined as comma-delimited lists [i.e., #(values)] should be defined as multiple message header fields.
804    *
805    * @param header The header.
806    * @return This object.
807    */
808   public RestResponse addHeader(Header header) {
809      if (header == null) {
810         // Do nothing.
811      } else if (header instanceof BasicUriHeader) {
812         BasicUriHeader x = (BasicUriHeader)header;
813         addHeader(x.getName(), resolveUris(x.getValue()));
814      } else if (header instanceof SerializedHeader) {
815         SerializedHeader x = ((SerializedHeader)header).copyWith(request.getPartSerializerSession(), null);
816         addHeader(x.getName(), resolveUris(x.getValue()));
817      } else {
818         addHeader(header.getName(), header.getValue());
819      }
820      return this;
821   }
822
823   private String resolveUris(Object value) {
824      String s = Utils.s(value);
825      return request.getUriResolver().resolve(s);
826   }
827
828   /**
829    * Returns the matching serializer and media type for this response.
830    *
831    * @return The matching serializer, never <jk>null</jk>.
832    */
833   public Optional<SerializerMatch> getSerializerMatch() {
834      if (serializerMatch != null)
835         return serializerMatch;
836      if (serializer != null) {
837         serializerMatch = Utils.opt(new SerializerMatch(getMediaType(), serializer));
838      } else {
839         serializerMatch = Utils.opt(opContext.getSerializers().getSerializerMatch(request.getHeaderParam("Accept").orElse("*/*")));
840      }
841      return serializerMatch;
842   }
843
844   /**
845    * Returns the schema of the response content.
846    *
847    * @return The schema of the response content, never <jk>null</jk>.
848    */
849   public Optional<HttpPartSchema> getContentSchema() {
850      if (contentSchema != null)
851         return contentSchema;
852      if (responseBeanMeta != null)
853         contentSchema = Utils.opt(responseBeanMeta.getSchema());
854      else {
855         ResponseBeanMeta rbm = opContext.getResponseBeanMeta(getContent(Object.class));
856         if (rbm != null)
857            contentSchema = Utils.opt(rbm.getSchema());
858         else
859            contentSchema = Utils.opte();
860      }
861      return contentSchema;
862   }
863}