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.nio.charset.*; 024import java.util.function.*; 025 026import org.apache.http.*; 027import org.apache.juneau.commons.bean.*; 028import org.apache.juneau.http.UnmodifiableBean; 029import org.apache.juneau.http.classic.header.*; 030import org.apache.juneau.bct.assertions.*; 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 * <p> 048 * Immutability is expressed with the "funnel + nested {@code Unmodifiable} snapshot" paradigm: every fluent setter 049 * routes through the single protected {@link #modify(Runnable)} choke-point, and each concrete leaf gets a nested 050 * {@code X.Unmodifiable} whose only behavioral override is a throwing {@code modify(...)}. A leaf's 051 * {@link #unmodifiable()} factory returns a point-in-time snapshot of type {@code X.Unmodifiable}; any attempt to set a 052 * property on it throws an {@link UnsupportedOperationException}. 053 * 054 * <p> 055 * This is a self-typed (CRTP) root: a leaf declares <c>class StringEntity <jk>extends</jk> BasicHttpEntity<StringEntity></c> 056 * and inherits leaf-typed fluent setters with no covariant overrides. 057 * 058 * <h5 class='section'>Notes:</h5><ul> 059 * <li class='warn'>Beans are not thread safe unless they're marked as unmodifiable. 060 * </ul> 061 * 062 * <h5 class='section'>See Also:</h5><ul> 063 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a> 064 * </ul> 065 * 066 * @param <SELF> The self type for fluent setters. 067 */ 068@BeanIgnore 069 070@SuppressWarnings({ 071 "java:S119", // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name. 072 "resource", // Content may be streams; only reference equality is used for the content field. 073 "unchecked" // contentOrElse() casts the stored content to the caller's T, and self() is the standard CRTP cast. 074}) 075public abstract class BasicHttpEntity<SELF extends BasicHttpEntity<SELF>> implements HttpEntity { 076 077 /** 078 * An empty HttpEntity. 079 */ 080 @SuppressWarnings({ 081 "java:S2390" // Convenience constant, not a static-init cycle: ByteArrayEntity()/unmodifiable() touch no static state of either class (only per-instance fields), so nothing here observes a not-yet-initialized value. 082 }) 083 public static final BasicHttpEntity<?> EMPTY = new ByteArrayEntity().unmodifiable(); 084 085 private boolean cached; 086 private boolean chunked; 087 private Object content; 088 private Supplier<?> contentSupplier; 089 private ContentType contentType; 090 private ContentEncoding contentEncoding; 091 private Charset charset; 092 private long contentLength = -1; 093 private int maxLength = -1; 094 095 /** 096 * Constructor. 097 */ 098 protected BasicHttpEntity() {} 099 100 /** 101 * Copy constructor. 102 * 103 * @param copyFrom The bean being copied. Must not be <jk>null</jk>. 104 */ 105 protected BasicHttpEntity(BasicHttpEntity<?> copyFrom) { 106 this.cached = copyFrom.cached; 107 this.chunked = copyFrom.chunked; 108 this.content = copyFrom.content; 109 this.contentSupplier = copyFrom.contentSupplier; 110 this.contentType = copyFrom.contentType; 111 this.contentEncoding = copyFrom.contentEncoding; 112 this.contentLength = copyFrom.contentLength; 113 this.charset = copyFrom.charset; 114 this.maxLength = copyFrom.maxLength; 115 } 116 117 /** 118 * Constructor. 119 * 120 * @param contentType The entity content type. Can be <jk>null</jk> to omit an explicit <c>Content-Type</c> header. 121 * @param content The entity content. Can be <jk>null</jk> (the effect depends on the concrete leaf type: e.g. treated as empty for {@link StringEntity}/{@link ByteArrayEntity}, or a {@link NullPointerException} on read for {@link StreamEntity}/{@link ReaderEntity}/{@link FileEntity}). 122 */ 123 protected BasicHttpEntity(ContentType contentType, Object content) { 124 this.contentType = contentType; 125 this.content = content; 126 } 127 128 /** 129 * Converts the contents of this entity as a byte array. 130 * 131 * <p> 132 * Note that this may exhaust the content on non-repeatable, non-cached entities. 133 * 134 * @return The contents of this entity as a byte array. 135 * @throws IOException If a problem occurred while trying to read the content. 136 */ 137 public byte[] asBytes() throws IOException { 138 return readBytes(getContent()); 139 } 140 141 /** 142 * Returns an assertion on the contents of this entity. 143 * 144 * <p> 145 * Note that this may exhaust the content on non-repeatable, non-cached entities. 146 * 147 * @return A new fluent assertion. 148 * @throws IOException If a problem occurred while trying to read the byte array. 149 */ 150 public FluentByteArrayAssertion<BasicHttpEntity<?>> assertBytes() throws IOException { 151 return new FluentByteArrayAssertion<>(asBytes(), this); 152 } 153 154 /** 155 * Returns an assertion on the contents of this entity. 156 * 157 * <p> 158 * Note that this may exhaust the content on non-repeatable, non-cached entities. 159 * 160 * @return A new fluent assertion. 161 * @throws IOException If a problem occurred while trying to read the byte array. 162 */ 163 public FluentStringAssertion<BasicHttpEntity<?>> assertString() throws IOException { 164 return new FluentStringAssertion<>(asString(), this); 165 } 166 167 /** 168 * Converts the contents of this entity as a string. 169 * 170 * <p> 171 * Note that this may exhaust the content on non-repeatable, non-cached entities. 172 * 173 * @return The contents of this entity as a string. 174 * @throws IOException If a problem occurred while trying to read the content. 175 */ 176 public String asString() throws IOException { 177 return read(getContent()); 178 } 179 180 @Override /* Overridden from HttpEntity */ 181 public void consumeContent() throws IOException { 182 // No-op: Intentional empty implementation for optional interface method 183 } 184 185 /** 186 * Creates a builder for this class initialized with the contents of this bean. 187 * 188 * <p> 189 * Allows you to create a modifiable copy of this bean. 190 * 191 * @return A new builder bean. 192 */ 193 public abstract SELF copy(); 194 195 /** 196 * Returns the charset to use when converting to and from stream-based resources. 197 * 198 * @return The charset to use when converting to and from stream-based resources. 199 */ 200 public Charset getCharset() { return charset == null ? UTF8 : charset; } 201 202 @Override /* Overridden from HttpEntity */ 203 public InputStream getContent() throws IOException, UnsupportedOperationException { return EMPTY_INPUT_STREAM; } 204 205 @Override /* Overridden from HttpEntity */ 206 public Header getContentEncoding() { return contentEncoding; } 207 208 @Override /* Overridden from HttpEntity */ 209 public long getContentLength() { return contentLength; } 210 211 @Override /* Overridden from HttpEntity */ 212 public Header getContentType() { return contentType; } 213 214 /** 215 * Returns the maximum number of bytes to read or write to and from stream-based resources. 216 * 217 * @return The maximum number of bytes to read or write to and from stream-based resources. 218 */ 219 public int getMaxLength() { return maxLength; } 220 221 /** 222 * Returns <jk>true</jk> if this entity is cached in-memory. 223 * 224 * @return <jk>true</jk> if this entity is cached in-memory. 225 */ 226 public boolean isCached() { return cached; } 227 228 @Override /* Overridden from HttpEntity */ 229 public boolean isChunked() { return chunked; } 230 231 @Override /* Overridden from HttpEntity */ 232 public boolean isRepeatable() { return false; } 233 234 @Override /* Overridden from HttpEntity */ 235 public boolean isStreaming() { return false; } 236 237 /** 238 * Returns <jk>true</jk> if this bean is unmodifiable. 239 * 240 * @return <jk>true</jk> if this bean is an {@link UnmodifiableBean} snapshot. 241 */ 242 public boolean isUnmodifiable() { return this instanceof UnmodifiableBean; } 243 244 /** 245 * Specifies that the contents of this resource should be cached into an internal byte array so that it can 246 * be read multiple times. 247 * 248 * @return This object. 249 * @throws IOException If entity could not be read into memory. 250 */ 251 public SELF setCached() throws IOException { 252 return modify(() -> cached = true); 253 } 254 255 /** 256 * Specifies the charset to use when converting to and from stream-based resources. 257 * 258 * @param value The new value. If <jk>null</jk>, <c>UTF-8</c> is assumed. 259 * @return This object. 260 */ 261 public SELF setCharset(Charset value) { 262 return modify(() -> charset = value); 263 } 264 265 /** 266 * Sets the 'chunked' flag value to <jk>true</jk>. 267 * 268 * <h5 class='section'>Notes:</h5><ul> 269 * <li class='note'>If the {@link HttpEntity#getContentLength()} method returns a negative value, the HttpClient code will always 270 * use chunked encoding. 271 * </ul> 272 * 273 * @return This object. 274 */ 275 public SELF setChunked() { 276 return setChunked(true); 277 } 278 279 /** 280 * Sets the 'chunked' flag value. 281 * 282 * <h5 class='section'>Notes:</h5><ul> 283 * <li class='note'>If the {@link HttpEntity#getContentLength()} method returns a negative value, the HttpClient code will always 284 * use chunked encoding. 285 * </ul> 286 * 287 * @param value The new value for this flag. 288 * @return This object. 289 */ 290 public SELF setChunked(boolean value) { 291 return modify(() -> chunked = value); 292 } 293 294 /** 295 * Sets the content on this entity bean. 296 * 297 * @param value The entity content. Can be <jk>null</jk> (the effect depends on the concrete leaf type: e.g. treated as empty for {@link StringEntity}/{@link ByteArrayEntity}, or a {@link NullPointerException} on read for {@link StreamEntity}/{@link ReaderEntity}/{@link FileEntity}). 298 * @return This object. 299 */ 300 public SELF setContent(Object value) { 301 return modify(() -> content = value); 302 } 303 304 /** 305 * Sets the content on this entity bean from a supplier. 306 * 307 * <p> 308 * Repeatable entities such as {@link StringEntity} use this to allow the entity content to be resolved at 309 * serialization time. 310 * 311 * @param value The entity content, can be <jk>null</jk> (treated as a supplier that always returns <jk>null</jk>). 312 * @return This object. 313 */ 314 public SELF setContent(Supplier<?> value) { 315 return modify(() -> contentSupplier = value == null ? () -> null : value); 316 } 317 318 /** 319 * Sets the content encoding header on this entity bean. 320 * 321 * @param value The new <c>Content-Encoding</c> header, or <jk>null</jk> to unset. 322 * @return This object. 323 */ 324 public SELF setContentEncoding(ContentEncoding value) { 325 return modify(() -> contentEncoding = value); 326 } 327 328 /** 329 * Sets the content encoding header on this entity bean. 330 * 331 * @param value The new <c>Content-Encoding</c> header, or <jk>null</jk> to unset. 332 * @return This object. 333 */ 334 public SELF setContentEncoding(String value) { 335 return setContentEncoding(ContentEncoding.of(value)); 336 } 337 338 /** 339 * Sets the content length on this entity bean. 340 * 341 * @param value The new <c>Content-Length</c> header value, or <c>-1</c> to unset. 342 * @return This object. 343 */ 344 public SELF setContentLength(long value) { 345 return modify(() -> contentLength = value); 346 } 347 348 /** 349 * Sets the content type on this entity bean. 350 * 351 * @param value The new <c>Content-Type</c> header, or <jk>null</jk> to unset. 352 * @return This object. 353 */ 354 public SELF setContentType(ContentType value) { 355 return modify(() -> contentType = value); 356 } 357 358 /** 359 * Sets the content type on this entity bean. 360 * 361 * @param value The new <c>Content-Type</c> header, or <jk>null</jk> to unset. 362 * @return This object. 363 */ 364 public SELF setContentType(String value) { 365 return setContentType(ContentType.of(value)); 366 } 367 368 /** 369 * Specifies the maximum number of bytes to read or write to and from stream-based resources. 370 * 371 * <p> 372 * Implementation is not universal. 373 * 374 * @param value The new value. The default is <c>-1</c> which means read everything. 375 * @return This object. 376 */ 377 public SELF setMaxLength(int value) { 378 return modify(() -> maxLength = value); 379 } 380 381 /** 382 * Returns an unmodifiable snapshot of this bean. 383 * 384 * <p> 385 * The returned instance is a point-in-time copy of the leaf's nested {@code Unmodifiable} type; any attempt to set a 386 * property on it throws an {@link UnsupportedOperationException}. This method is idempotent: if this bean is already 387 * unmodifiable it returns itself rather than taking another snapshot. The receiver is left unchanged (and still 388 * modifiable unless it was already unmodifiable). 389 * 390 * @return An unmodifiable snapshot of this bean, or this bean if it is already unmodifiable. 391 */ 392 public abstract SELF unmodifiable(); 393 394 @Override /* Overridden from HttpEntity */ 395 public void writeTo(OutputStream outStream) throws IOException { 396 // No-op: Intentional empty implementation for optional interface method 397 } 398 399 /** 400 * Compares the shared base content of two entities. 401 * 402 * <p> 403 * This base implementation compares only the fields declared on {@link BasicHttpEntity} and deliberately accepts any 404 * {@code BasicHttpEntity} subtype so that concrete leaves can delegate to it via {@code super.equals(...)}. It does 405 * <b>not</b> gate on the concrete leaf type; each concrete leaf overrides {@code equals} to first apply an 406 * {@code instanceof <ThatLeaf>} gate (so two unrelated leaves with equal base content are not equal) and then calls 407 * {@code super.equals(...)} to compare the inherited content. Using {@code instanceof <ThatLeaf>} (rather than 408 * {@code getClass() ==}) is what preserves the immutability invariant that {@code bean.equals(bean.unmodifiable())} 409 * holds, since each {@code X.Unmodifiable} snapshot is a subclass of its leaf {@code X}. 410 */ 411 @Override /* Overridden from Object */ 412 public boolean equals(Object o) { 413 return o instanceof BasicHttpEntity<?> other && eq(this, other, (x, y) -> 414 eq(x.cached, y.cached) && eq(x.chunked, y.chunked) && eq(x.content, y.content) 415 && eq(x.contentSupplier, y.contentSupplier) && eq(x.contentType, y.contentType) 416 && eq(x.contentEncoding, y.contentEncoding) && eq(x.charset, y.charset) 417 && eq(x.contentLength, y.contentLength) && eq(x.maxLength, y.maxLength)); 418 } 419 420 @Override /* Overridden from Object */ 421 public int hashCode() { 422 return h(cached, chunked, content, contentSupplier, contentType, contentEncoding, charset, contentLength, maxLength); 423 } 424 425 /** 426 * Same as {@link #asBytes()} but wraps {@link IOException IOExceptions} inside a {@link RuntimeException}. 427 * 428 * @return The contents of this entity as a byte array. 429 */ 430 protected byte[] asSafeBytes() { 431 try { 432 return asBytes(); 433 } catch (IOException e) { 434 throw new UncheckedIOException(e); 435 } 436 } 437 438 /** 439 * Returns the content of this entity. 440 * 441 * @param <T> The value type. 442 * @param def The default value if <jk>null</jk>. 443 * @return The content object. 444 */ 445 protected <T> T contentOrElse(T def) { 446 Object o = content; 447 if (o == null && nn(contentSupplier)) 448 o = contentSupplier.get(); 449 return (o == null ? def : (T)o); 450 } 451 452 /** 453 * Returns <jk>true</jk> if the contents of this entity are provided through an external supplier. 454 * 455 * <p> 456 * Externally supplied content generally means the content length cannot be reliably determined 457 * based on the content. 458 * 459 * @return <jk>true</jk> if the contents of this entity are provided through an external supplier. 460 */ 461 protected boolean isSupplied() { return nn(contentSupplier); } 462 463 /** 464 * Returns this object cast to the self type. 465 * 466 * @return This object. 467 */ 468 protected final SELF self() { return (SELF) this; } 469 470 /** 471 * Single mutation funnel — the only choke-point through which all state changes on this bean flow. 472 * 473 * <p> 474 * Every fluent setter routes through this method. The nested {@code Unmodifiable} snapshot overrides only this 475 * method to throw, which freezes the entire mutation surface with a single override. 476 * 477 * @param mutation The state change to apply. 478 * @return This object. 479 */ 480 protected SELF modify(Runnable mutation) { 481 mutation.run(); 482 return self(); 483 } 484 485 /** 486 * Deep-freeze hook invoked from the nested {@code Unmodifiable} constructor after the snapshot copy completes. 487 * 488 * <p> 489 * Entities hold no mutable sub-beans (the content-type / content-encoding headers are immutable value objects, and 490 * the content buffer follows the copy-constructor's reference-copy behavior), so this is a documented no-op — the 491 * single throwing {@link #modify(Runnable)} override on the nested {@code Unmodifiable} already freezes the entire 492 * mutation surface. A leaf that adds its own mutable sub-bean would override this method (calling 493 * {@code super.freeze()} first). 494 */ 495 protected void freeze() { 496 // No-op: entities have no mutable sub-beans to deep-freeze. 497 } 498}