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; 014 015import java.io.*; 016import java.util.function.*; 017 018import org.apache.http.*; 019import org.apache.juneau.assertions.*; 020import org.apache.juneau.http.header.*; 021import org.apache.juneau.internal.*; 022 023/** 024 * An extension of {@link org.apache.http.entity.BasicHttpEntity} with additional features. 025 * 026 * Provides the following features: 027 * <ul class='spaced-list'> 028 * <li> 029 * Default support for various streams and readers. 030 * <li> 031 * Content from {@link Supplier Suppliers}. 032 * <li> 033 * Caching. 034 * <li> 035 * Fluent setters. 036 * <li> 037 * Fluent assertions. 038 * </ul> 039 */ 040public class BasicHttpEntity extends org.apache.http.entity.BasicHttpEntity { 041 private Object content; 042 private boolean cache; 043 044 /** 045 * Creator. 046 * 047 * @param content 048 * The content. 049 * <br>Can be any of the following: 050 * <ul> 051 * <li><c>InputStream</c> 052 * <li><c>Reader</c> - Converted to UTF-8 bytes. 053 * <li><c>File</c> 054 * <li><c>CharSequence</c> - Converted to UTF-8 bytes. 055 * <li><c><jk>byte</jk>[]</c>. 056 * <li>A {@link Supplier} of anything on this list. 057 * </ul> 058 * </ul> 059 * @return A new empty {@link BasicHttpEntity} object. 060 */ 061 public static BasicHttpEntity of(Object content) { 062 return new BasicHttpEntity(content); 063 } 064 065 /** 066 * Creator. 067 * 068 * @param content 069 * The content. 070 * <br>Can be any of the following: 071 * <ul> 072 * <li><c>InputStream</c> 073 * <li><c>Reader</c> - Converted to UTF-8 bytes. 074 * <li><c>File</c> 075 * <li><c>CharSequence</c> - Converted to UTF-8 bytes. 076 * <li><c><jk>byte</jk>[]</c>. 077 * <li>A {@link Supplier} of anything on this list. 078 * </ul> 079 * </ul> 080 * @return A new empty {@link BasicHttpEntity} object. 081 */ 082 public static BasicHttpEntity of(Supplier<?> content) { 083 return new BasicHttpEntity(content); 084 } 085 086 /** 087 * Creates a new basic entity. 088 * 089 * @param content 090 * The content. 091 * <br>Can be any of the following: 092 * <ul> 093 * <li><c>InputStream</c> 094 * <li><c>Reader</c> - Converted to UTF-8 bytes. 095 * <li><c>File</c> 096 * <li><c>CharSequence</c> - Converted to UTF-8 bytes. 097 * <li><c><jk>byte</jk>[]</c>. 098 * <li>A {@link Supplier} of anything on this list. 099 * </ul> 100 * </ul> 101 */ 102 public BasicHttpEntity(Object content) { 103 this(content, null, null); 104 } 105 106 /** 107 * Constructor. 108 * 109 * @param content 110 * The content. 111 * <br>Can be any of the following: 112 * <ul> 113 * <li><c>InputStream</c> 114 * <li><c>Reader</c> - Converted to UTF-8 bytes. 115 * <li><c>File</c> 116 * <li><c>CharSequence</c> - Converted to UTF-8 bytes. 117 * <li><c><jk>byte</jk>[]</c>. 118 * <li>A {@link Supplier} of anything on this list. 119 * </ul> 120 * </ul> 121 * @param contentType 122 * The content type of the contents. 123 * <br>Can be <jk>null</jk>. 124 * @param contentEncoding 125 * The content encoding of the contents. 126 * <br>Can be <jk>null</jk>. 127 */ 128 public BasicHttpEntity(Object content, ContentType contentType, ContentEncoding contentEncoding) { 129 super(); 130 this.content = content; 131 contentType(contentType); 132 contentEncoding(contentEncoding); 133 } 134 135 /** 136 * Shortcut for calling {@link #setContentType(String)}. 137 * 138 * @param value The new <c>Content-Type</ header, or <jk>null</jk> to unset. 139 * @return This object (for method chaining). 140 */ 141 @FluentSetter 142 public BasicHttpEntity contentType(String value) { 143 super.setContentType(value); 144 return this; 145 } 146 147 /** 148 * Shortcut for calling {@link #setContentType(Header)}. 149 * 150 * @param value The new <c>Content-Type</ header, or <jk>null</jk> to unset. 151 * @return This object (for method chaining). 152 */ 153 @FluentSetter 154 public BasicHttpEntity contentType(Header value) { 155 super.setContentType(value); 156 return this; 157 } 158 159 /** 160 * Shortcut for calling {@link #setContentLength(long)}. 161 * 162 * @param value The new <c>Content-Length</c> header value. 163 * @return This object (for method chaining). 164 */ 165 @FluentSetter 166 public BasicHttpEntity contentLength(long value) { 167 super.setContentLength(value); 168 return this; 169 } 170 171 /** 172 * Shortcut for calling {@link #setContentEncoding(String)}. 173 * 174 * @param value The new <c>Content-Encoding</ header, or <jk>null</jk> to unset. 175 * @return This object (for method chaining). 176 */ 177 @FluentSetter 178 public BasicHttpEntity contentEncoding(String value) { 179 super.setContentEncoding(value); 180 return this; 181 } 182 183 /** 184 * Shortcut for calling {@link #setContentEncoding(Header)}. 185 * 186 * @param value The new <c>Content-Encoding</ header, or <jk>null</jk> to unset. 187 * @return This object (for method chaining). 188 */ 189 @FluentSetter 190 public BasicHttpEntity contentEncoding(Header value) { 191 super.setContentEncoding(value); 192 return this; 193 } 194 195 /** 196 * Shortcut for calling {@link #setChunked(boolean)} with <jk>true</jk>. 197 * 198 * <ul class='notes'> 199 * <li>If the {@link #getContentLength()} method returns a negative value, the HttpClient code will always 200 * use chunked encoding. 201 * </ul> 202 * 203 * @return This object (for method chaining). 204 */ 205 @FluentSetter 206 public BasicHttpEntity chunked() { 207 return chunked(true); 208 } 209 210 /** 211 * Shortcut for calling {@link #setChunked(boolean)}. 212 * 213 * <ul class='notes'> 214 * <li>If the {@link #getContentLength()} method returns a negative value, the HttpClient code will always 215 * use chunked encoding. 216 * </ul> 217 * 218 * @param value The new value for this flag. 219 * @return This object (for method chaining). 220 */ 221 @FluentSetter 222 public BasicHttpEntity chunked(boolean value) { 223 super.setChunked(value); 224 return this; 225 } 226 227 /** 228 * Specifies that the contents of this resource should be cached into an internal byte array so that it can 229 * be read multiple times. 230 * 231 * @return This object (for method chaining). 232 */ 233 @FluentSetter 234 public BasicHttpEntity cache() { 235 return cache(true); 236 } 237 238 /** 239 * Specifies that the contents of this resource should be cached into an internal byte array so that it can 240 * be read multiple times. 241 * 242 * @param value The new value for this flag. 243 * @return This object (for method chaining). 244 */ 245 @FluentSetter 246 public BasicHttpEntity cache(boolean value) { 247 this.cache = value; 248 return this; 249 } 250 251 /** 252 * Converts the contents of this entity as a byte array. 253 * 254 * @return The contents of this entity as a byte array. 255 * @throws IOException If a problem occurred while trying to read the byte array. 256 */ 257 public String asString() throws IOException { 258 return IOUtils.read(getContent()); 259 } 260 261 /** 262 * Converts the contents of this entity as a byte array. 263 * 264 * @return The contents of this entity as a byte array. 265 * @throws IOException If a problem occurred while trying to read the byte array. 266 */ 267 public byte[] asBytes() throws IOException { 268 try (InputStream o = getContent()) { 269 return o == null ? null : IOUtils.readBytes(o); 270 } 271 } 272 273 /** 274 * Returns an assertion on the contents of this entity. 275 * 276 * @return A new fluent assertion. 277 * @throws IOException If a problem occurred while trying to read the byte array. 278 */ 279 public FluentStringAssertion<BasicHttpEntity> assertString() throws IOException { 280 return new FluentStringAssertion<>(asString(), this); 281 } 282 283 /** 284 * Returns an assertion on the contents of this entity. 285 * 286 * @return A new fluent assertion. 287 * @throws IOException If a problem occurred while trying to read the byte array. 288 */ 289 public FluentByteArrayAssertion<BasicHttpEntity> assertBytes() throws IOException { 290 return new FluentByteArrayAssertion<>(asBytes(), this); 291 } 292 293 @Override 294 public boolean isRepeatable() { 295 Object o = getRawContent(); 296 return cache || o instanceof File || o instanceof CharSequence || o instanceof byte[]; 297 } 298 299 @Override 300 public long getContentLength() { 301 long x = super.getContentLength(); 302 if (x != -1) 303 return x; 304 try { 305 tryCache(); 306 } catch (IOException e) {} 307 Object o = getRawContent(); 308 if (o instanceof byte[]) 309 return ((byte[])o).length; 310 if (o instanceof File) 311 return ((File)o).length(); 312 if (o instanceof CharSequence) 313 return ((CharSequence)o).length(); 314 return -1; 315 } 316 317 @Override 318 public InputStream getContent() { 319 try { 320 tryCache(); 321 Object o = getRawContent(); 322 if (o == null) 323 return null; 324 if (o instanceof File) 325 return new FileInputStream((File)o); 326 if (o instanceof byte[]) 327 return new ByteArrayInputStream((byte[])o); 328 if (o instanceof Reader) 329 return new ReaderInputStream((Reader)o, IOUtils.UTF8); 330 if (o instanceof InputStream) 331 return (InputStream)o; 332 return new ReaderInputStream(new StringReader(o.toString()),IOUtils.UTF8); 333 } catch (Exception e) { 334 throw new RuntimeException(e); 335 } 336 } 337 338 @Override 339 public void writeTo(OutputStream os) throws IOException { 340 tryCache(); 341 Object o = getRawContent(); 342 if (o != null) { 343 IOUtils.pipe(o, os); 344 } 345 os.flush(); 346 } 347 348 @Override 349 public boolean isStreaming() { 350 Object o = getRawContent(); 351 return (o instanceof InputStream || o instanceof Reader); 352 } 353 354 /** 355 * Returns the raw content of this resource. 356 * 357 * @return The raw content of this resource. 358 */ 359 protected Object getRawContent() { 360 return unwrap(content); 361 } 362 363 private void tryCache() throws IOException { 364 Object o = getRawContent(); 365 if (cache && isCacheable(o)) 366 this.content = readBytes(o); 367 } 368 369 /** 370 * Returns <jk>true</jk> if the specified object is cachable as a byte array. 371 * 372 * <p> 373 * The default implementation returns <jk>true</jk> for the following types: 374 * <ul> 375 * <li>{@link File} 376 * <li>{@link InputStream} 377 * <li>{@link Reader} 378 * 379 * @param o The object to check. 380 * @return <jk>true</jk> if the specified object is cachable as a byte array. 381 */ 382 protected boolean isCacheable(Object o) { 383 return (o instanceof File || o instanceof InputStream || o instanceof Reader); 384 } 385 386 /** 387 * Reads the contents of the specified object as a byte array. 388 * 389 * @param o The object to read. 390 * @return The byte array contents. 391 * @throws IOException If object could not be read. 392 */ 393 protected byte[] readBytes(Object o) throws IOException { 394 return IOUtils.readBytes(o); 395 } 396 397 /** 398 * If the specified object is a {@link Supplier}, returns the supplied value, otherwise the same value. 399 * 400 * @param o The object to unwrap. 401 * @return The unwrapped object. 402 */ 403 protected Object unwrap(Object o) { 404 while (o instanceof Supplier) 405 o = ((Supplier<?>)o).get(); 406 return o; 407 } 408 409 // <FluentSetters> 410 411 // </FluentSetters> 412}