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}