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 java.io.*;
016import java.util.function.*;
017
018import org.apache.http.*;
019import org.apache.juneau.assertions.*;
020import org.apache.juneau.http.header.*;
021import org.apache.juneau.internal.*;
022
023/**
024 * An extension of {@link org.apache.http.entity.BasicHttpEntity} with additional features.
025 *
026 * Provides the following features:
027 * <ul class='spaced-list'>
028 *    <li>
029 *       Default support for various streams and readers.
030 *    <li>
031 *       Content from {@link Supplier Suppliers}.
032 *    <li>
033 *       Caching.
034 *    <li>
035 *       Fluent setters.
036 *    <li>
037 *       Fluent assertions.
038 * </ul>
039 */
040public class BasicHttpEntity extends org.apache.http.entity.BasicHttpEntity {
041   private Object content;
042   private boolean cache;
043
044   /**
045    * Creator.
046    *
047    * @param content
048    *    The content.
049    *    <br>Can be any of the following:
050    *    <ul>
051    *       <li><c>InputStream</c>
052    *       <li><c>Reader</c> - Converted to UTF-8 bytes.
053    *       <li><c>File</c>
054    *       <li><c>CharSequence</c> - Converted to UTF-8 bytes.
055    *       <li><c><jk>byte</jk>[]</c>.
056    *       <li>A {@link Supplier} of anything on this list.
057    *    </ul>
058    * </ul>
059    * @return A new empty {@link BasicHttpEntity} object.
060    */
061   public static BasicHttpEntity of(Object content) {
062      return new BasicHttpEntity(content);
063   }
064
065   /**
066    * Creator.
067    *
068    * @param content
069    *    The content.
070    *    <br>Can be any of the following:
071    *    <ul>
072    *       <li><c>InputStream</c>
073    *       <li><c>Reader</c> - Converted to UTF-8 bytes.
074    *       <li><c>File</c>
075    *       <li><c>CharSequence</c> - Converted to UTF-8 bytes.
076    *       <li><c><jk>byte</jk>[]</c>.
077    *       <li>A {@link Supplier} of anything on this list.
078    *    </ul>
079    * </ul>
080    * @return A new empty {@link BasicHttpEntity} object.
081    */
082   public static BasicHttpEntity of(Supplier<?> content) {
083      return new BasicHttpEntity(content);
084   }
085
086   /**
087    * Creates a new basic entity.
088    *
089    * @param content
090    *    The content.
091    *    <br>Can be any of the following:
092    *    <ul>
093    *       <li><c>InputStream</c>
094    *       <li><c>Reader</c> - Converted to UTF-8 bytes.
095    *       <li><c>File</c>
096    *       <li><c>CharSequence</c> - Converted to UTF-8 bytes.
097    *       <li><c><jk>byte</jk>[]</c>.
098    *       <li>A {@link Supplier} of anything on this list.
099    *    </ul>
100    * </ul>
101    */
102   public BasicHttpEntity(Object content) {
103      this(content, null, null);
104   }
105
106   /**
107    * Constructor.
108    *
109    * @param content
110    *    The content.
111    *    <br>Can be any of the following:
112    *    <ul>
113    *       <li><c>InputStream</c>
114    *       <li><c>Reader</c> - Converted to UTF-8 bytes.
115    *       <li><c>File</c>
116    *       <li><c>CharSequence</c> - Converted to UTF-8 bytes.
117    *       <li><c><jk>byte</jk>[]</c>.
118    *       <li>A {@link Supplier} of anything on this list.
119    *    </ul>
120    * </ul>
121    * @param contentType
122    *    The content type of the contents.
123    *    <br>Can be <jk>null</jk>.
124    * @param contentEncoding
125    *    The content encoding of the contents.
126    *    <br>Can be <jk>null</jk>.
127    */
128   public BasicHttpEntity(Object content, ContentType contentType, ContentEncoding contentEncoding) {
129      super();
130      this.content = content;
131      contentType(contentType);
132      contentEncoding(contentEncoding);
133   }
134
135   /**
136    * Shortcut for calling {@link #setContentType(String)}.
137    *
138    * @param value The new <c>Content-Type</ header, or <jk>null</jk> to unset.
139    * @return This object (for method chaining).
140    */
141   @FluentSetter
142   public BasicHttpEntity contentType(String value) {
143      super.setContentType(value);
144      return this;
145   }
146
147   /**
148    * Shortcut for calling {@link #setContentType(Header)}.
149    *
150    * @param value The new <c>Content-Type</ header, or <jk>null</jk> to unset.
151    * @return This object (for method chaining).
152    */
153   @FluentSetter
154   public BasicHttpEntity contentType(Header value) {
155      super.setContentType(value);
156      return this;
157   }
158
159   /**
160    * Shortcut for calling {@link #setContentLength(long)}.
161    *
162    * @param value The new <c>Content-Length</c> header value.
163    * @return This object (for method chaining).
164    */
165   @FluentSetter
166   public BasicHttpEntity contentLength(long value) {
167      super.setContentLength(value);
168      return this;
169   }
170
171   /**
172    * Shortcut for calling {@link #setContentEncoding(String)}.
173    *
174    * @param value The new <c>Content-Encoding</ header, or <jk>null</jk> to unset.
175    * @return This object (for method chaining).
176    */
177   @FluentSetter
178   public BasicHttpEntity contentEncoding(String value) {
179      super.setContentEncoding(value);
180      return this;
181   }
182
183   /**
184    * Shortcut for calling {@link #setContentEncoding(Header)}.
185    *
186    * @param value The new <c>Content-Encoding</ header, or <jk>null</jk> to unset.
187    * @return This object (for method chaining).
188    */
189   @FluentSetter
190   public BasicHttpEntity contentEncoding(Header value) {
191      super.setContentEncoding(value);
192      return this;
193   }
194
195   /**
196    * Shortcut for calling {@link #setChunked(boolean)} with <jk>true</jk>.
197    *
198    * <ul class='notes'>
199    *    <li>If the {@link #getContentLength()} method returns a negative value, the HttpClient code will always
200    *       use chunked encoding.
201    * </ul>
202    *
203    * @return This object (for method chaining).
204    */
205   @FluentSetter
206   public BasicHttpEntity chunked() {
207      return chunked(true);
208   }
209
210   /**
211    * Shortcut for calling {@link #setChunked(boolean)}.
212    *
213    * <ul class='notes'>
214    *    <li>If the {@link #getContentLength()} method returns a negative value, the HttpClient code will always
215    *       use chunked encoding.
216    * </ul>
217    *
218    * @param value The new value for this flag.
219    * @return This object (for method chaining).
220    */
221   @FluentSetter
222   public BasicHttpEntity chunked(boolean value) {
223      super.setChunked(value);
224      return this;
225   }
226
227   /**
228    * Specifies that the contents of this resource should be cached into an internal byte array so that it can
229    * be read multiple times.
230    *
231    * @return This object (for method chaining).
232    */
233   @FluentSetter
234   public BasicHttpEntity cache() {
235      return cache(true);
236   }
237
238   /**
239    * Specifies that the contents of this resource should be cached into an internal byte array so that it can
240    * be read multiple times.
241    *
242    * @param value The new value for this flag.
243    * @return This object (for method chaining).
244    */
245   @FluentSetter
246   public BasicHttpEntity cache(boolean value) {
247      this.cache = value;
248      return this;
249   }
250
251   /**
252    * Converts the contents of this entity as a byte array.
253    *
254    * @return The contents of this entity as a byte array.
255    * @throws IOException If a problem occurred while trying to read the byte array.
256    */
257   public String asString() throws IOException {
258      return IOUtils.read(getContent());
259   }
260
261   /**
262    * Converts the contents of this entity as a byte array.
263    *
264    * @return The contents of this entity as a byte array.
265    * @throws IOException If a problem occurred while trying to read the byte array.
266    */
267   public byte[] asBytes() throws IOException {
268      try (InputStream o = getContent()) {
269         return o == null ? null : IOUtils.readBytes(o);
270      }
271   }
272
273   /**
274    * Returns an assertion on the contents of this entity.
275    *
276    * @return A new fluent assertion.
277    * @throws IOException If a problem occurred while trying to read the byte array.
278    */
279   public FluentStringAssertion<BasicHttpEntity> assertString() throws IOException {
280      return new FluentStringAssertion<>(asString(), this);
281   }
282
283   /**
284    * Returns an assertion on the contents of this entity.
285    *
286    * @return A new fluent assertion.
287    * @throws IOException If a problem occurred while trying to read the byte array.
288    */
289   public FluentByteArrayAssertion<BasicHttpEntity> assertBytes() throws IOException {
290      return new FluentByteArrayAssertion<>(asBytes(), this);
291   }
292
293   @Override
294   public boolean isRepeatable() {
295      Object o = getRawContent();
296      return cache || o instanceof File || o instanceof CharSequence || o instanceof byte[];
297   }
298
299   @Override
300   public long getContentLength() {
301      long x = super.getContentLength();
302      if (x != -1)
303         return x;
304      try {
305         tryCache();
306      } catch (IOException e) {}
307      Object o = getRawContent();
308      if (o instanceof byte[])
309         return ((byte[])o).length;
310      if (o instanceof File)
311         return ((File)o).length();
312      if (o instanceof CharSequence)
313         return ((CharSequence)o).length();
314      return -1;
315   }
316
317   @Override
318   public InputStream getContent() {
319      try {
320         tryCache();
321         Object o = getRawContent();
322         if (o == null)
323            return null;
324         if (o instanceof File)
325            return new FileInputStream((File)o);
326         if (o instanceof byte[])
327            return new ByteArrayInputStream((byte[])o);
328         if (o instanceof Reader)
329            return new ReaderInputStream((Reader)o, IOUtils.UTF8);
330         if (o instanceof InputStream)
331            return (InputStream)o;
332         return new ReaderInputStream(new StringReader(o.toString()),IOUtils.UTF8);
333      } catch (Exception e) {
334         throw new RuntimeException(e);
335      }
336   }
337
338   @Override
339   public void writeTo(OutputStream os) throws IOException {
340      tryCache();
341      Object o = getRawContent();
342      if (o != null) {
343         IOUtils.pipe(o, os);
344      }
345      os.flush();
346   }
347
348   @Override
349   public boolean isStreaming() {
350      Object o = getRawContent();
351      return (o instanceof InputStream || o instanceof Reader);
352   }
353
354   /**
355    * Returns the raw content of this resource.
356    *
357    * @return The raw content of this resource.
358    */
359   protected Object getRawContent() {
360      return unwrap(content);
361   }
362
363   private void tryCache() throws IOException {
364      Object o = getRawContent();
365      if (cache && isCacheable(o))
366         this.content = readBytes(o);
367   }
368
369   /**
370    * Returns <jk>true</jk> if the specified object is cachable as a byte array.
371    *
372    * <p>
373    * The default implementation returns <jk>true</jk> for the following types:
374    * <ul>
375    *    <li>{@link File}
376    *    <li>{@link InputStream}
377    *    <li>{@link Reader}
378    *
379    * @param o The object to check.
380    * @return <jk>true</jk> if the specified object is cachable as a byte array.
381    */
382   protected boolean isCacheable(Object o) {
383      return (o instanceof File || o instanceof InputStream || o instanceof Reader);
384   }
385
386   /**
387    * Reads the contents of the specified object as a byte array.
388    *
389    * @param o The object to read.
390    * @return The byte array contents.
391    * @throws IOException If object could not be read.
392    */
393   protected byte[] readBytes(Object o) throws IOException {
394      return IOUtils.readBytes(o);
395   }
396
397   /**
398    * If the specified object is a {@link Supplier}, returns the supplied value, otherwise the same value.
399    *
400    * @param o The object to unwrap.
401    * @return The unwrapped object.
402    */
403   protected Object unwrap(Object o) {
404      while (o instanceof Supplier)
405         o = ((Supplier<?>)o).get();
406      return o;
407   }
408
409   // <FluentSetters>
410
411   // </FluentSetters>
412}