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