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         setHeader(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               setHeader(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 RestResource#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      // Jetty doesn't set the content type correctly if set through this method.
540      // Tomcat/WAS does.
541      if (name.equalsIgnoreCase("Content-Type"))
542         super.setContentType(value);
543      else
544         super.setHeader(name, value);
545   }
546
547   /**
548    * Same as {@link #setHeader(String, String)} but header is defined as a response part
549    *
550    * @param h Header to set.
551    * @throws SchemaValidationException Header part did not pass validation.
552    * @throws SerializeException Header part could not be serialized.
553    */
554   public void setHeader(HttpPart h) throws SchemaValidationException, SerializeException {
555      setHeader(h.getName(), h.asString());
556   }
557
558   /**
559    * Sets the <js>"Exception"</js> attribute to the specified throwable.
560    *
561    * <p>
562    * This exception is used by {@link BasicRestCallLogger} for logging purposes.
563    *
564    * @param t The attribute value.
565    * @return This object (for method chaining).
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 BasicRestCallLogger} and tells it not to log the current request.
577    *
578    * @param b The attribute value.
579    * @return This object (for method chaining).
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 (for method chaining).
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 BasicRestCallLogger} to help determine how a request should be logged.
600    *
601    * @param b The attribute value.
602    * @return This object (for method chaining).
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 (for method chaining).
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    *    <jk>Never <jk>null</jk>.
628    */
629   public ResponseBeanMeta getResponseMeta() {
630      return responseMeta;
631   }
632
633   /**
634    * Sets metadata about this response.
635    *
636    * @param rbm The metadata about this response.
637    * @return This object (for method chaining).
638    */
639   public RestResponse setResponseMeta(ResponseBeanMeta rbm) {
640      this.responseMeta = 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 isOutputType(Class<?> c) {
651      return c.isInstance(output);
652   }
653
654   /**
655    * Returns this value cast to the specified class.
656    *
657    * @param c The class to cast to.
658    * @return This value cast to the specified class.
659    */
660   @SuppressWarnings("unchecked")
661   public <T> T getOutput(Class<T> c) {
662      return (T)output;
663   }
664
665   /**
666    * Returns the wrapped servlet request.
667    *
668    * @return The wrapped servlet request.
669    */
670   protected HttpServletResponse getInner() {
671      return inner;
672   }
673
674   @Override /* ServletResponse */
675   public void flushBuffer() throws IOException {
676      if (w != null)
677         w.flush();
678      if (os != null)
679         os.flush();
680      super.flushBuffer();
681   }
682}