001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.http.entity;
018
019import static org.apache.juneau.commons.utils.IoUtils.*;
020import static org.apache.juneau.commons.utils.ThrowableUtils.*;
021import static org.apache.juneau.commons.utils.Utils.*;
022
023import java.io.*;
024import java.nio.charset.*;
025import java.util.function.*;
026
027import org.apache.http.*;
028import org.apache.juneau.annotation.*;
029import org.apache.juneau.assertions.*;
030import org.apache.juneau.http.header.*;
031
032/**
033 * A basic {@link org.apache.http.HttpEntity} implementation with additional features.
034 *
035 * Provides the following features:
036 * <ul class='spaced-list'>
037 *    <li>
038 *       Caching.
039 *    <li>
040 *       Fluent setters.
041 *    <li>
042 *       Fluent assertions.
043 *    <li>
044 *       Externally-supplied/dynamic content.
045 * </ul>
046 *
047 * <h5 class='section'>See Also:</h5><ul>
048 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommonBasics">juneau-rest-common Basics</a>
049 * </ul>
050 */
051@BeanIgnore
052@SuppressWarnings("resource")
053public class BasicHttpEntity implements HttpEntity {
054   /**
055    * An empty HttpEntity.
056    */
057   public static final BasicHttpEntity EMPTY = new BasicHttpEntity().setUnmodifiable();
058   private boolean cached, chunked, unmodifiable;
059   private Object content;
060   private Supplier<?> contentSupplier;
061   private ContentType contentType;
062   private ContentEncoding contentEncoding;
063   private Charset charset;
064   private long contentLength = -1;
065   private int maxLength = -1;
066
067   /**
068    * Constructor.
069    */
070   public BasicHttpEntity() {}
071
072   /**
073    * Copy constructor.
074    *
075    * @param copyFrom The bean being copied.
076    */
077   public BasicHttpEntity(BasicHttpEntity copyFrom) {
078      this.cached = copyFrom.cached;
079      this.chunked = copyFrom.chunked;
080      this.content = copyFrom.content;
081      this.contentSupplier = copyFrom.contentSupplier;
082      this.contentType = copyFrom.contentType;
083      this.contentEncoding = copyFrom.contentEncoding;
084      this.contentLength = copyFrom.contentLength;
085      this.charset = copyFrom.charset;
086      this.maxLength = copyFrom.maxLength;
087      this.unmodifiable = false;
088   }
089
090   /**
091    * Constructor.
092    *
093    * @param contentType The entity content type.
094    * @param content The entity content.
095    */
096   public BasicHttpEntity(ContentType contentType, Object content) {
097      this.contentType = contentType;
098      this.content = content;
099   }
100
101   /**
102    * Converts the contents of this entity as a byte array.
103    *
104    * <p>
105    * Note that this may exhaust the content on non-repeatable, non-cached entities.
106    *
107    * @return The contents of this entity as a byte array.
108    * @throws IOException If a problem occurred while trying to read the content.
109    */
110   public byte[] asBytes() throws IOException {
111      return readBytes(getContent());
112   }
113
114   /**
115    * Returns an assertion on the contents of this entity.
116    *
117    * <p>
118    * Note that this may exhaust the content on non-repeatable, non-cached entities.
119    *
120    * @return A new fluent assertion.
121    * @throws IOException If a problem occurred while trying to read the byte array.
122    */
123   public FluentByteArrayAssertion<BasicHttpEntity> assertBytes() throws IOException {
124      return new FluentByteArrayAssertion<>(asBytes(), this);
125   }
126
127   /**
128    * Returns an assertion on the contents of this entity.
129    *
130    * <p>
131    * Note that this may exhaust the content on non-repeatable, non-cached entities.
132    *
133    * @return A new fluent assertion.
134    * @throws IOException If a problem occurred while trying to read the byte array.
135    */
136   public FluentStringAssertion<BasicHttpEntity> assertString() throws IOException {
137      return new FluentStringAssertion<>(asString(), this);
138   }
139
140   /**
141    * Converts the contents of this entity as a string.
142    *
143    * <p>
144    * Note that this may exhaust the content on non-repeatable, non-cached entities.
145    *
146    * @return The contents of this entity as a string.
147    * @throws IOException If a problem occurred while trying to read the content.
148    */
149   public String asString() throws IOException {
150      return read(getContent());
151   }
152
153   @Override /* Overridden from HttpEntity */
154   public void consumeContent() throws IOException {}
155
156   /**
157    * Creates a builder for this class initialized with the contents of this bean.
158    *
159    * <p>
160    * Allows you to create a modifiable copy of this bean.
161    *
162    * @return A new builder bean.
163    */
164   public BasicHttpEntity copy() {
165      return new BasicHttpEntity(this);
166   }
167
168   /**
169    * Returns the charset to use when converting to and from stream-based resources.
170    *
171    * @return The charset to use when converting to and from stream-based resources.
172    */
173   public Charset getCharset() { return charset == null ? UTF8 : charset; }
174
175   @Override /* Overridden from HttpEntity */
176   public InputStream getContent() throws IOException, UnsupportedOperationException { return EMPTY_INPUT_STREAM; }
177
178   @Override /* Overridden from HttpEntity */
179   public Header getContentEncoding() { return contentEncoding; }
180
181   @Override /* Overridden from HttpEntity */
182   public long getContentLength() { return contentLength; }
183
184   @Override /* Overridden from HttpEntity */
185   public Header getContentType() { return contentType; }
186
187   /**
188    * Returns the maximum number of bytes to read or write to and from stream-based resources.
189    *
190    * @return The maximum number of bytes to read or write to and from stream-based resources.
191    */
192   public int getMaxLength() { return maxLength; }
193
194   /**
195    * Returns <jk>true</jk> if this entity is cached in-memory.
196    *
197    * @return <jk>true</jk> if this entity is cached in-memory.
198    */
199   public boolean isCached() { return cached; }
200
201   @Override /* Overridden from HttpEntity */
202   public boolean isChunked() { return chunked; }
203
204   @Override /* Overridden from HttpEntity */
205   public boolean isRepeatable() { return false; }
206
207   @Override /* Overridden from HttpEntity */
208   public boolean isStreaming() { return false; }
209
210   /**
211    * Returns <jk>true</jk> if this bean is unmodifiable.
212    *
213    * @return <jk>true</jk> if this bean is unmodifiable.
214    */
215   public boolean isUnmodifiable() { return unmodifiable; }
216
217   /**
218    * Specifies that the contents of this resource should be cached into an internal byte array so that it can
219    * be read multiple times.
220    *
221    * @return This object.
222    * @throws IOException If entity could not be read into memory.
223    */
224   public BasicHttpEntity setCached() throws IOException {
225      assertModifiable();
226      cached = true;
227      return this;
228   }
229
230   /**
231    * Specifies the charset to use when converting to and from stream-based resources.
232    *
233    * @param value The new value.  If <jk>null</jk>, <c>UTF-8</c> is assumed.
234    * @return This object.
235    */
236   public BasicHttpEntity setCharset(Charset value) {
237      assertModifiable();
238      charset = value;
239      return this;
240   }
241
242   /**
243    * Sets the 'chunked' flag value to <jk>true</jk>.
244    *
245    * <h5 class='section'>Notes:</h5><ul>
246    *    <li class='note'>If the {@link HttpEntity#getContentLength()} method returns a negative value, the HttpClient code will always
247    *       use chunked encoding.
248    * </ul>
249    *
250    * @return This object.
251    */
252   public BasicHttpEntity setChunked() {
253      return setChunked(true);
254   }
255
256   /**
257    * Sets the 'chunked' flag value.
258    *
259    * <h5 class='section'>Notes:</h5><ul>
260    *    <li class='note'>If the {@link HttpEntity#getContentLength()} method returns a negative value, the HttpClient code will always
261    *       use chunked encoding.
262    * </ul>
263    *
264    * @param value The new value for this flag.
265    * @return This object.
266    */
267   public BasicHttpEntity setChunked(boolean value) {
268      assertModifiable();
269      chunked = value;
270      return this;
271   }
272
273   /**
274    * Sets the content on this entity bean.
275    *
276    * @param value The entity content, can be <jk>null</jk>.
277    * @return This object.
278    */
279   public BasicHttpEntity setContent(Object value) {
280      assertModifiable();
281      content = value;
282      return this;
283   }
284
285   /**
286    * Sets the content on this entity bean from a supplier.
287    *
288    * <p>
289    * Repeatable entities such as {@link StringEntity} use this to allow the entity content to be resolved at
290    * serialization time.
291    *
292    * @param value The entity content, can be <jk>null</jk>.
293    * @return This object.
294    */
295   public BasicHttpEntity setContent(Supplier<?> value) {
296      assertModifiable();
297      this.contentSupplier = value == null ? () -> null : value;
298      return this;
299   }
300
301   /**
302    * Sets the content encoding header on this entity bean.
303    *
304    * @param value The new <c>Content-Encoding</c> header, or <jk>null</jk> to unset.
305    * @return This object.
306    */
307   public BasicHttpEntity setContentEncoding(ContentEncoding value) {
308      assertModifiable();
309      contentEncoding = value;
310      return this;
311   }
312
313   /**
314    * Sets the content encoding header on this entity bean.
315    *
316    * @param value The new <c>Content-Encoding</c> header, or <jk>null</jk> to unset.
317    * @return This object.
318    */
319   public BasicHttpEntity setContentEncoding(String value) {
320      return setContentEncoding(ContentEncoding.of(value));
321   }
322
323   /**
324    * Sets the content length on this entity bean.
325    *
326    * @param value The new <c>Content-Length</c> header value, or <c>-1</c> to unset.
327    * @return This object.
328    */
329   public BasicHttpEntity setContentLength(long value) {
330      assertModifiable();
331      contentLength = value;
332      return this;
333   }
334
335   /**
336    * Sets the content type on this entity bean.
337    *
338    * @param value The new <c>Content-Type</c> header, or <jk>null</jk> to unset.
339    * @return This object.
340    */
341   public BasicHttpEntity setContentType(ContentType value) {
342      assertModifiable();
343      contentType = value;
344      return this;
345   }
346
347   /**
348    * Sets the content type on this entity bean.
349    *
350    * @param value The new <c>Content-Type</c> header, or <jk>null</jk> to unset.
351    * @return This object.
352    */
353   public BasicHttpEntity setContentType(String value) {
354      return setContentType(ContentType.of(value));
355   }
356
357   /**
358    * Specifies the maximum number of bytes to read or write to and from stream-based resources.
359    *
360    * <p>
361    * Implementation is not universal.
362    *
363    * @param value The new value.  The default is <c>-1</c> which means read everything.
364    * @return This object.
365    */
366   public BasicHttpEntity setMaxLength(int value) {
367      assertModifiable();
368      maxLength = value;
369      return this;
370   }
371
372   /**
373    * Specifies whether this bean should be unmodifiable.
374    * <p>
375    * When enabled, attempting to set any properties on this bean will cause an {@link UnsupportedOperationException}.
376    *
377    * @return This object.
378    */
379   public BasicHttpEntity setUnmodifiable() {
380      unmodifiable = true;
381      return this;
382   }
383
384   @Override /* Overridden from HttpEntity */
385   public void writeTo(OutputStream outStream) throws IOException {}
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    * Throws an {@link UnsupportedOperationException} if the unmodifiable flag is set on this bean.
402    */
403   protected final void assertModifiable() {
404      if (unmodifiable)
405         throw unsupportedOp("Bean is read-only");
406   }
407
408   /**
409    * Returns the content of this entity.
410    *
411    * @param <T> The value type.
412    * @param def The default value if <jk>null</jk>.
413    * @return The content object.
414    */
415   @SuppressWarnings("unchecked")
416   protected <T> T contentOrElse(T def) {
417      Object o = content;
418      if (o == null && nn(contentSupplier))
419         o = contentSupplier.get();
420      return (o == null ? def : (T)o);
421   }
422
423   /**
424    * Returns <jk>true</jk> if the contents of this entity are provided through an external supplier.
425    *
426    * <p>
427    * Externally supplied content generally means the content length cannot be reliably determined
428    * based on the content.
429    *
430    * @return <jk>true</jk> if the contents of this entity are provided through an external supplier.
431    */
432   protected boolean isSupplied() { return nn(contentSupplier); }
433}