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