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.classic.entity;
018
019import static org.apache.juneau.commons.utils.IoUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.io.*;
023import java.util.*;
024
025import org.apache.juneau.http.*;
026import org.apache.juneau.http.classic.header.*;
027
028/**
029 * A streamed, non-repeatable entity that obtains its content from an {@link InputStream}.
030 *
031 * <h5 class='section'>See Also:</h5><ul>
032 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a>
033 * </ul>
034 */
035@SuppressWarnings({
036   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
037   "java:S9365", // Copy ctor delegates to super; remaining fields are content caches defaulted empty by design.
038   "resource" // Resource management handled externally
039})
040public class StreamEntity extends BasicHttpEntity<StreamEntity> {
041
042   private byte[] byteCache;
043   private String stringCache;
044
045   /**
046    * Constructor.
047    */
048   public StreamEntity() {}
049
050   /**
051    * Constructor.
052    *
053    * @param contentType The entity content type.  Can be <jk>null</jk> to omit an explicit <c>Content-Type</c> header.
054    * @param content The entity contents.  Can be <jk>null</jk>, in which case a {@link NullPointerException} is thrown when the entity's content is read.
055    */
056   public StreamEntity(ContentType contentType, InputStream content) {
057      super(contentType, content);
058   }
059
060   /**
061    * Copy constructor.
062    *
063    * @param copyFrom The bean being copied.  Must not be <jk>null</jk>.
064    */
065   protected StreamEntity(StreamEntity copyFrom) {
066      super(copyFrom);
067   }
068
069   @Override /* Overridden from AbstractHttpEntity */
070   public byte[] asBytes() throws IOException {
071      if (isCached() && byteCache == null) {
072         try (InputStream is = content()) {
073            byteCache = readBytes(is, getMaxLength());
074         }
075      }
076      if (nn(byteCache))
077         return cp(byteCache);
078      try (InputStream is = content()) {
079         return readBytes(is, getMaxLength());
080      }
081   }
082
083   @Override /* Overridden from AbstractHttpEntity */
084   public String asString() throws IOException {
085      if (isCached() && stringCache == null)
086         stringCache = read(content(), getCharset());
087      if (nn(stringCache))
088         return stringCache;
089      return read(content());
090   }
091
092   @Override /* Overridden from BasicHttpEntity */
093   public StreamEntity copy() {
094      return new StreamEntity(this);
095   }
096
097   @Override /* Overridden from HttpEntity */
098   public InputStream getContent() throws IOException {
099      if (isCached())
100         return new ByteArrayInputStream(asBytes());
101      return content();
102   }
103
104   @Override /* Overridden from HttpEntity */
105   public long getContentLength() {
106      if (isCached())
107         return asSafeBytes().length;
108      return super.getContentLength();
109   }
110
111   @Override /* Overridden from HttpEntity */
112   public boolean isRepeatable() { return isCached(); }
113
114   @Override /* Overridden from HttpEntity */
115   public boolean isStreaming() { return ! isCached(); }
116
117   @Override /* Overridden from BasicHttpEntity */
118   public StreamEntity unmodifiable() {
119      return this instanceof UnmodifiableBean ? this : new Unmodifiable(this);
120   }
121
122   /**
123    * Writes bytes from the {@code InputStream} this entity was constructed
124    * with to an {@code OutputStream}.  The content length
125    * determines how many bytes are written.  If the length is unknown ({@code -1}), the
126    * stream will be completely consumed (to the end of the stream).
127    */
128   @Override
129   public void writeTo(OutputStream out) throws IOException {
130      reqnn("out", out);
131
132      if (isCached()) {
133         out.write(asBytes());
134      } else {
135         try (InputStream is = getContent()) {
136            pipe(is, out, getMaxLength());
137         }
138      }
139   }
140
141   @Override /* Overridden from Object */
142   public boolean equals(Object o) {
143      // Leaf-type gate: keeps this entity from comparing equal to a different leaf with equal base content, while
144      // preserving content equality (StreamEntity.Unmodifiable IS-A StreamEntity, so bean.equals(bean.unmodifiable()) still holds).
145      // byteCache/stringCache are memoized derivations of the inherited content/charset and are excluded from equality.
146      return o instanceof StreamEntity && super.equals(o);
147   }
148
149   @Override /* Overridden from Object */
150   public int hashCode() {
151      // Fold in the leaf type so distinct leaves land in different buckets; stable across StreamEntity and its
152      // Unmodifiable snapshot (both report StreamEntity.class), keeping the equals/hashCode contract intact.
153      return h(StreamEntity.class, super.hashCode());
154   }
155
156   private InputStream content() {
157      return Objects.requireNonNull(contentOrElse((InputStream)null), "Input stream is null.");
158   }
159
160   /**
161    * Unmodifiable point-in-time snapshot of the enclosing {@link StreamEntity}.
162    *
163    * <p>
164    * Its only behavioral override is {@link #modify(Runnable)}, which throws — because all mutation is funneled through
165    * {@code modify(...)}, this single override freezes the entire mutation surface.
166    */
167   public static class Unmodifiable extends StreamEntity implements UnmodifiableBean {
168
169      /**
170       * Constructor.
171       *
172       * @param copyFrom The entity to snapshot.  Must not be <jk>null</jk>.
173       */
174      @SuppressWarnings({
175         "java:S1699" // Paradigm intentionally calls the overridable freeze() from the ctor to deep-freeze sub-beans.
176      })
177      protected Unmodifiable(StreamEntity copyFrom) {
178         super(copyFrom);
179         freeze();
180      }
181
182      @Override /* Overridden from BasicHttpEntity */
183      protected StreamEntity modify(Runnable mutation) {
184         throw uoex("Bean is unmodifiable.");
185      }
186   }
187}