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.entity;
014
015import static org.apache.juneau.common.internal.IOUtils.*;
016
017import java.io.*;
018import java.nio.charset.*;
019import java.util.function.*;
020
021import org.apache.http.*;
022import org.apache.juneau.annotation.*;
023import org.apache.juneau.assertions.*;
024import org.apache.juneau.common.internal.*;
025import org.apache.juneau.http.header.*;
026import org.apache.juneau.internal.*;
027
028/**
029 * A basic {@link org.apache.http.HttpEntity} implementation with additional features.
030 *
031 * Provides the following features:
032 * <ul class='spaced-list'>
033 *    <li>
034 *       Caching.
035 *    <li>
036 *       Fluent setters.
037 *    <li>
038 *       Fluent assertions.
039 *    <li>
040 *       Externally-supplied/dynamic content.
041 * </ul>
042 *
043 * <h5 class='section'>See Also:</h5><ul>
044 *    <li class='link'><a class="doclink" href="../../../../../index.html#juneau-rest-common">juneau-rest-common</a>
045 * </ul>
046 */
047@BeanIgnore
048@FluentSetters
049public class BasicHttpEntity implements HttpEntity {
050
051   //-----------------------------------------------------------------------------------------------------------------
052   // Static
053   //-----------------------------------------------------------------------------------------------------------------
054
055   /**
056    * An empty HttpEntity.
057    */
058   public static final BasicHttpEntity EMPTY = new BasicHttpEntity().setUnmodifiable();
059
060   //-----------------------------------------------------------------------------------------------------------------
061   // Instance
062   //-----------------------------------------------------------------------------------------------------------------
063
064   private boolean cached, chunked, unmodifiable;
065   private Object content;
066   private Supplier<?> contentSupplier;
067   private ContentType contentType;
068   private ContentEncoding contentEncoding;
069   private Charset charset;
070   private long contentLength = -1;
071   private int maxLength = -1;
072
073   /**
074    * Constructor.
075    */
076   public BasicHttpEntity() {
077   }
078
079   /**
080    * Constructor.
081    *
082    * @param contentType The entity content type.
083    * @param content The entity content.
084    */
085   public BasicHttpEntity(ContentType contentType, Object content) {
086      this.contentType = contentType;
087      this.content = content;
088   }
089
090   /**
091    * Copy constructor.
092    *
093    * @param copyFrom The bean being copied.
094    */
095   public BasicHttpEntity(BasicHttpEntity copyFrom) {
096      this.cached = copyFrom.cached;
097      this.chunked = copyFrom.chunked;
098      this.content = copyFrom.content;
099      this.contentSupplier = copyFrom.contentSupplier;
100      this.contentType = copyFrom.contentType;
101      this.contentEncoding = copyFrom.contentEncoding;
102      this.contentLength = copyFrom.contentLength;
103      this.charset = copyFrom.charset;
104      this.maxLength = copyFrom.maxLength;
105      this.unmodifiable = false;
106   }
107
108   /**
109    * Creates a builder for this class initialized with the contents of this bean.
110    *
111    * <p>
112    * Allows you to create a modifiable copy of this bean.
113    *
114    * @return A new builder bean.
115    */
116   public BasicHttpEntity copy() {
117      return new BasicHttpEntity(this);
118   }
119
120   //-----------------------------------------------------------------------------------------------------------------
121   // Properties
122   //-----------------------------------------------------------------------------------------------------------------
123
124   /**
125    * Specifies whether this bean should be unmodifiable.
126    * <p>
127    * When enabled, attempting to set any properties on this bean will cause an {@link UnsupportedOperationException}.
128    *
129    * @return This object.
130    */
131   @FluentSetter
132   public BasicHttpEntity setUnmodifiable() {
133      unmodifiable = true;
134      return this;
135   }
136
137   /**
138    * Returns <jk>true</jk> if this bean is unmodifiable.
139    *
140    * @return <jk>true</jk> if this bean is unmodifiable.
141    */
142   public boolean isUnmodifiable() {
143      return unmodifiable;
144   }
145
146   /**
147    * Throws an {@link UnsupportedOperationException} if the unmodifiable flag is set on this bean.
148    */
149   protected final void assertModifiable() {
150      if (unmodifiable)
151         throw new UnsupportedOperationException("Bean is read-only");
152   }
153
154   /**
155    * Sets the content on this entity bean.
156    *
157    * @param value The entity content, can be <jk>null</jk>.
158    * @return This object.
159    */
160   @FluentSetter
161   public BasicHttpEntity setContent(Object value) {
162      assertModifiable();
163      this.content = value;
164      return this;
165   }
166
167   /**
168    * Sets the content on this entity bean from a supplier.
169    *
170    * <p>
171    * Repeatable entities such as {@link StringEntity} use this to allow the entity content to be resolved at
172    * serialization time.
173    *
174    * @param value The entity content, can be <jk>null</jk>.
175    * @return This object.
176    */
177   @FluentSetter
178   public BasicHttpEntity setContent(Supplier<?> value) {
179      assertModifiable();
180      this.contentSupplier = value == null ? ()->null : value;
181      return this;
182   }
183
184   /**
185    * Sets the content type on this entity bean.
186    *
187    * @param value The new <c>Content-Type</c> header, or <jk>null</jk> to unset.
188    * @return This object.
189    */
190   @FluentSetter
191   public BasicHttpEntity setContentType(String value) {
192      return setContentType(ContentType.of(value));
193   }
194
195   /**
196    * Sets the content type on this entity bean.
197    *
198    * @param value The new <c>Content-Type</c> header, or <jk>null</jk> to unset.
199    * @return This object.
200    */
201   @FluentSetter
202   public BasicHttpEntity setContentType(ContentType value) {
203      assertModifiable();
204      contentType = value;
205      return this;
206   }
207
208   /**
209    * Sets the content length on this entity bean.
210    *
211    * @param value The new <c>Content-Length</c> header value, or <c>-1</c> to unset.
212    * @return This object.
213    */
214   @FluentSetter
215   public BasicHttpEntity setContentLength(long value) {
216      assertModifiable();
217      contentLength = value;
218      return this;
219   }
220
221   /**
222    * Sets the content encoding header on this entity bean.
223    *
224    * @param value The new <c>Content-Encoding</c> header, or <jk>null</jk> to unset.
225    * @return This object.
226    */
227   @FluentSetter
228   public BasicHttpEntity setContentEncoding(String value) {
229      return setContentEncoding(ContentEncoding.of(value));
230   }
231
232   /**
233    * Sets the content encoding header on this entity bean.
234    *
235    * @param value The new <c>Content-Encoding</c> header, or <jk>null</jk> to unset.
236    * @return This object.
237    */
238   @FluentSetter
239   public BasicHttpEntity setContentEncoding(ContentEncoding value) {
240      assertModifiable();
241      contentEncoding = value;
242      return this;
243   }
244
245   /**
246    * Sets the 'chunked' flag value to <jk>true</jk>.
247    *
248    * <h5 class='section'>Notes:</h5><ul>
249    *    <li class='note'>If the {@link HttpEntity#getContentLength()} method returns a negative value, the HttpClient code will always
250    *       use chunked encoding.
251    * </ul>
252    *
253    * @return This object.
254    */
255   @FluentSetter
256   public BasicHttpEntity setChunked() {
257      return setChunked(true);
258   }
259
260   /**
261    * Sets the 'chunked' flag value.
262    *
263    * <h5 class='section'>Notes:</h5><ul>
264    *    <li class='note'>If the {@link HttpEntity#getContentLength()} method returns a negative value, the HttpClient code will always
265    *       use chunked encoding.
266    * </ul>
267    *
268    * @param value The new value for this flag.
269    * @return This object.
270    */
271   @FluentSetter
272   public BasicHttpEntity setChunked(boolean value) {
273      assertModifiable();
274      chunked = value;
275      return this;
276   }
277
278   /**
279    * Specifies that the contents of this resource should be cached into an internal byte array so that it can
280    * be read multiple times.
281    *
282    * @return This object.
283    * @throws IOException If entity could not be read into memory.
284    */
285   @FluentSetter
286   public BasicHttpEntity setCached() throws IOException {
287      assertModifiable();
288      cached = true;
289      return this;
290   }
291
292   /**
293    * Returns <jk>true</jk> if this entity is cached in-memory.
294    *
295    * @return <jk>true</jk> if this entity is cached in-memory.
296    */
297   public boolean isCached() {
298      return cached;
299   }
300
301   /**
302    * Specifies the charset to use when converting to and from stream-based resources.
303    *
304    * @param value The new value.  If <jk>null</jk>, <c>UTF-8</c> is assumed.
305    * @return This object.
306    */
307   @FluentSetter
308   public BasicHttpEntity setCharset(Charset value) {
309      assertModifiable();
310      this.charset = value;
311      return this;
312   }
313
314   /**
315    * Returns the charset to use when converting to and from stream-based resources.
316    *
317    * @return The charset to use when converting to and from stream-based resources.
318    */
319   public Charset getCharset() {
320      return charset == null ? UTF8 : charset;
321   }
322
323   /**
324    * Specifies the maximum number of bytes to read or write to and from stream-based resources.
325    *
326    * <p>
327    * Implementation is not universal.
328    *
329    * @param value The new value.  The default is <c>-1</c> which means read everything.
330    * @return This object.
331    */
332   @FluentSetter
333   public BasicHttpEntity setMaxLength(int value) {
334      assertModifiable();
335      this.maxLength = value;
336      return this;
337   }
338
339   /**
340    * Returns the maximum number of bytes to read or write to and from stream-based resources.
341    *
342    * @return The maximum number of bytes to read or write to and from stream-based resources.
343    */
344   public int getMaxLength() {
345      return maxLength;
346   }
347
348   //-----------------------------------------------------------------------------------------------------------------
349   // Other methods
350   //-----------------------------------------------------------------------------------------------------------------
351
352   /**
353    * Converts the contents of this entity as a string.
354    *
355    * <p>
356    * Note that this may exhaust the content on non-repeatable, non-cached entities.
357    *
358    * @return The contents of this entity as a string.
359    * @throws IOException If a problem occurred while trying to read the content.
360    */
361   public String asString() throws IOException {
362      return read(getContent());
363   }
364
365   /**
366    * Converts the contents of this entity as a byte array.
367    *
368    * <p>
369    * Note that this may exhaust the content on non-repeatable, non-cached entities.
370    *
371    * @return The contents of this entity as a byte array.
372    * @throws IOException If a problem occurred while trying to read the content.
373    */
374   public byte[] asBytes() throws IOException {
375      return readBytes(getContent());
376   }
377
378   /**
379    * Same as {@link #asBytes()} but wraps {@link IOException IOExceptions} inside a {@link RuntimeException}.
380    *
381    * @return The contents of this entity as a byte array.
382    */
383   protected byte[] asSafeBytes() {
384      try {
385         return asBytes();
386      } catch (IOException e) {
387         throw new RuntimeException(e);
388      }
389   }
390
391   /**
392    * Returns an assertion on the contents of this entity.
393    *
394    * <p>
395    * Note that this may exhaust the content on non-repeatable, non-cached entities.
396    *
397    * @return A new fluent assertion.
398    * @throws IOException If a problem occurred while trying to read the byte array.
399    */
400   public FluentStringAssertion<BasicHttpEntity> assertString() throws IOException {
401      return new FluentStringAssertion<>(asString(), this);
402   }
403
404   /**
405    * Returns an assertion on the contents of this entity.
406    *
407    * <p>
408    * Note that this may exhaust the content on non-repeatable, non-cached entities.
409    *
410    * @return A new fluent assertion.
411    * @throws IOException If a problem occurred while trying to read the byte array.
412    */
413   public FluentByteArrayAssertion<BasicHttpEntity> assertBytes() throws IOException {
414      return new FluentByteArrayAssertion<>(asBytes(), this);
415   }
416
417   /**
418    * Returns the content of this entity.
419    *
420    * @param <T> The value type.
421    * @param def The default value if <jk>null</jk>.
422    * @return The content object.
423    */
424   @SuppressWarnings("unchecked")
425   protected <T> T contentOrElse(T def) {
426      Object o = content;
427      if (o == null && contentSupplier != null)
428         o = contentSupplier.get();
429      return (o == null ? def : (T)o);
430   }
431
432   /**
433    * Returns <jk>true</jk> if the contents of this entity are provided through an external supplier.
434    *
435    * <p>
436    * Externally supplied content generally means the content length cannot be reliably determined
437    * based on the content.
438    *
439    * @return <jk>true</jk> if the contents of this entity are provided through an external supplier.
440    */
441   protected boolean isSupplied() {
442      return contentSupplier != null;
443   }
444
445   @Override /* HttpEntity */
446   public long getContentLength() {
447      return contentLength;
448   }
449
450   @Override /* HttpEntity */
451   public boolean isRepeatable() {
452      return false;
453   }
454
455   @Override /* HttpEntity */
456   public boolean isChunked() {
457      return chunked;
458   }
459
460   @Override /* HttpEntity */
461   public Header getContentType() {
462      return contentType;
463   }
464
465   @Override /* HttpEntity */
466   public Header getContentEncoding() {
467      return contentEncoding;
468   }
469
470   @Override /* HttpEntity */
471   public boolean isStreaming() {
472      return false;
473   }
474
475   @Override /* HttpEntity */
476   public void consumeContent() throws IOException {}
477
478   @Override /* HttpEntity */
479   public InputStream getContent() throws IOException, UnsupportedOperationException {
480      return IOUtils.EMPTY_INPUT_STREAM;
481   }
482
483   @Override /* HttpEntity */
484   public void writeTo(OutputStream outStream) throws IOException {}
485
486   // <FluentSetters>
487
488   // </FluentSetters>
489}