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}