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