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.http;
014
015import static org.apache.juneau.internal.CollectionUtils.*;
016import static org.apache.juneau.internal.IOUtils.*;
017
018import java.io.*;
019import java.util.*;
020
021import org.apache.juneau.*;
022import org.apache.juneau.http.annotation.*;
023
024/**
025 * Represents the contents of a text file with convenience methods for resolving SVL variables and adding
026 * HTTP response headers.
027 *
028 * <p>
029 * <br>These objects can be returned as responses by REST methods.
030 *
031 * <p>
032 * <l>ReaderResources</l> are meant to be thread-safe and reusable objects.
033 * <br>The contents of the request passed into the constructor are immediately converted to read-only strings.
034 *
035 * <p>
036 * Instances of this class can be built using {@link Builder}.
037 *
038 * <h5 class='section'>See Also:</h5>
039 * <ul>
040 *    <li class='link'>{@doc juneau-rest-server.RestMethod.ReaderResource}
041 * </ul>
042 */
043@Response
044public class ReaderResource implements Writable {
045
046   private final MediaType mediaType;
047   private final Map<String,Object> headers;
048
049   @SuppressWarnings("javadoc")
050   protected final Object[] contents;
051
052   /**
053    * Constructor.
054    *
055    * @param b Builder containing values to initialize this object with.
056    * @throws IOException
057    */
058   protected ReaderResource(Builder b) throws IOException {
059      this(b.mediaType, b.headers, b.cached, b.contents.toArray());
060   }
061
062   /**
063    * Constructor.
064    *
065    * @param mediaType The resource media type.
066    * @param headers The HTTP response headers for this streamed resource.
067    * @param cached
068    *    Identifies if this resource is cached in memory.
069    *    <br>If <jk>true</jk>, the contents will be loaded into a String for fast retrieval.
070    * @param contents
071    *    The resource contents.
072    *    <br>If multiple contents are specified, the results will be concatenated.
073    *    <br>Contents can be any of the following:
074    *    <ul>
075    *       <li><code>InputStream</code>
076    *       <li><code>Reader</code> - Converted to UTF-8 bytes.
077    *       <li><code>File</code>
078    *       <li><code>CharSequence</code> - Converted to UTF-8 bytes.
079    *    </ul>
080    * @throws IOException
081    */
082   public ReaderResource(MediaType mediaType, Map<String,Object> headers, boolean cached, Object...contents) throws IOException {
083      this.mediaType = mediaType;
084      this.headers = immutableMap(headers);
085      this.contents = cached ? new Object[]{read(contents)} : contents;
086   }
087
088   //-----------------------------------------------------------------------------------------------------------------
089   // Builder
090   //-----------------------------------------------------------------------------------------------------------------
091
092   /**
093    * Creates a new instance of a {@link Builder} for this class.
094    *
095    * @return A new instance of a {@link Builder}.
096    */
097   public static Builder create() {
098      return new Builder();
099   }
100
101   /**
102    * Builder class for constructing {@link ReaderResource} objects.
103    *
104    * <h5 class='section'>See Also:</h5>
105    * <ul>
106    *    <li class='link'>{@doc juneau-rest-server.RestMethod.ReaderResource}
107    * </ul>
108    */
109   @SuppressWarnings("javadoc")
110   public static class Builder {
111
112      public ArrayList<Object> contents = new ArrayList<>();
113      public MediaType mediaType;
114      public Map<String,Object> headers = new LinkedHashMap<>();
115      public boolean cached;
116
117      /**
118       * Specifies the resource media type string.
119       *
120       * @param mediaType The resource media type string.
121       * @return This object (for method chaining).
122       */
123      public Builder mediaType(String mediaType) {
124         this.mediaType = MediaType.forString(mediaType);
125         return this;
126      }
127
128      /**
129       * Specifies the resource media type string.
130       *
131       * @param mediaType The resource media type string.
132       * @return This object (for method chaining).
133       */
134      public Builder mediaType(MediaType mediaType) {
135         this.mediaType = mediaType;
136         return this;
137      }
138
139      /**
140       * Specifies the contents for this resource.
141       *
142       * <p>
143       * This method can be called multiple times to add more content.
144       *
145       * @param contents
146       *    The resource contents.
147       *    <br>If multiple contents are specified, the results will be concatenated.
148       *    <br>Contents can be any of the following:
149       *    <ul>
150       *       <li><code>InputStream</code>
151       *       <li><code>Reader</code> - Converted to UTF-8 bytes.
152       *       <li><code>File</code>
153       *       <li><code>CharSequence</code> - Converted to UTF-8 bytes.
154       *    </ul>
155       * @return This object (for method chaining).
156       */
157      public Builder contents(Object...contents) {
158         this.contents.addAll(Arrays.asList(contents));
159         return this;
160      }
161
162      /**
163       * Specifies an HTTP response header value.
164       *
165       * @param name The HTTP header name.
166       * @param value
167       *    The HTTP header value.
168       *    <br>Will be converted to a <code>String</code> using {@link Object#toString()}.
169       * @return This object (for method chaining).
170       */
171      public Builder header(String name, Object value) {
172         this.headers.put(name, value);
173         return this;
174      }
175
176      /**
177       * Specifies HTTP response header values.
178       *
179       * @param headers
180       *    The HTTP headers.
181       *    <br>Values will be converted to <code>Strings</code> using {@link Object#toString()}.
182       * @return This object (for method chaining).
183       */
184      public Builder headers(Map<String,Object> headers) {
185         this.headers.putAll(headers);
186         return this;
187      }
188
189      /**
190       * Specifies that this resource is intended to be cached.
191       *
192       * <p>
193       * This will trigger the contents to be loaded into a String for fast serializing.
194       *
195       * @return This object (for method chaining).
196       */
197      public Builder cached() {
198         this.cached = true;
199         return this;
200      }
201
202      /**
203       * Create a new {@link ReaderResource} using values in this builder.
204       *
205       * @return A new immutable {@link ReaderResource} object.
206       * @throws IOException
207       */
208      public ReaderResource build() throws IOException {
209         return new ReaderResource(this);
210      }
211   }
212
213   //-----------------------------------------------------------------------------------------------------------------
214   // Properties
215   //-----------------------------------------------------------------------------------------------------------------
216
217   /**
218    * Get the HTTP response headers.
219    *
220    * @return
221    *    The HTTP response headers.
222    *    <br>An unmodifiable map.
223    *    <br>Never <jk>null</jk>.
224    */
225   @ResponseHeader("*")
226   public Map<String,Object> getHeaders() {
227      return headers;
228   }
229
230   @ResponseBody
231   @Override /* Writeable */
232   public Writer writeTo(Writer w) throws IOException {
233      for (Object o : contents)
234         pipe(o, w);
235      return w;
236   }
237
238   @ResponseHeader("Content-Type")
239   @Override /* Writeable */
240   public MediaType getMediaType() {
241      return mediaType;
242   }
243
244   @Override /* Object */
245   public String toString() {
246      try {
247         if (contents.length == 1)
248            return read(contents[0]);
249         return writeTo(new StringWriter()).toString();
250      } catch (IOException e) {
251         throw new RuntimeException(e);
252      }
253   }
254
255   /**
256    * Same as {@link #toString()} but strips comments from the text before returning it.
257    *
258    * <p>
259    * Supports stripping comments from the following media types: HTML, XHTML, XML, JSON, Javascript, CSS.
260    *
261    * @return The resource contents stripped of any comments.
262    */
263   public String toCommentStrippedString() {
264      String s = toString();
265      String subType = mediaType.getSubType();
266      if ("html".equals(subType) || "xhtml".equals(subType) || "xml".equals(subType))
267         s = s.replaceAll("(?s)<!--(.*?)-->\\s*", "");
268      else if ("json".equals(subType) || "javascript".equals(subType) || "css".equals(subType))
269         s = s.replaceAll("(?s)\\/\\*(.*?)\\*\\/\\s*", "");
270      return s;
271   }
272
273   /**
274    * Returns the contents of this resource.
275    *
276    * @return The contents of this resource.
277    */
278   public Reader getContents() {
279      if (contents.length == 1 && contents[0] instanceof Reader) {
280         return (Reader)contents[0];
281      }
282      return new StringReader(toString());
283   }
284}