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