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