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.rest.client.classic; 018 019import static org.apache.juneau.commons.utils.IoUtils.*; 020import static org.apache.juneau.commons.utils.ObjectUtils.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022import static org.apache.juneau.commons.utils.StringUtils.*; 023import static org.apache.juneau.commons.utils.ThrowableUtils.*; 024 025import java.io.*; 026import java.lang.reflect.*; 027import java.nio.charset.*; 028import java.util.concurrent.*; 029import java.util.regex.*; 030 031import org.apache.http.*; 032import org.apache.http.conn.*; 033import org.apache.juneau.commons.conversion.*; 034import org.apache.juneau.commons.http.*; 035import org.apache.juneau.commons.reflect.ConstructorInfo; 036import org.apache.juneau.commons.reflect.ExecutableException; 037import org.apache.juneau.commons.utils.*; 038import org.apache.juneau.http.classic.entity.*; 039import org.apache.juneau.http.classic.resource.*; 040import org.apache.juneau.marshall.*; 041import org.apache.juneau.marshall.collections.*; 042import org.apache.juneau.marshall.httppart.*; 043import org.apache.juneau.marshall.oapi.*; 044import org.apache.juneau.marshall.parser.*; 045import org.apache.juneau.marshall.parser.ParseException; 046import org.apache.juneau.marshall.stream.*; 047import org.apache.juneau.rest.client.classic.assertion.*; 048import org.apache.juneau.bct.assertions.*; 049 050/** 051 * Represents the body of an HTTP response. 052 * 053 * <p> 054 * An extension of an HttpClient {@link HttpEntity} that provides various support for converting the body to POJOs and 055 * other convenience methods. 056 * 057 * <p> 058 * To quickly retrieve a single value from inside a larger JSON document, parse the body into a {@link JsonMap} and use 059 * its path-addressing methods — e.g. <c>getContent().as(JsonMap.<jk>class</jk>).getAt(<jv>path</jv>, <jv>type</jv>)</c> 060 * or the RFC 6901 form <c>getContent().as(JsonMap.<jk>class</jk>).at(<jv>pointer</jv>)</c>. 061 * 062 * <h5 class='section'>See Also:</h5><ul> 063 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestClient">juneau-rest-client-classic</a> 064 * </ul> 065 */ 066@SuppressWarnings({ 067 "java:S115", // Constants use UPPER_snakeCase convention (e.g., HEADER_ContentType) 068 "java:S4144", // Identical methods intentional for different test scenarios 069 "resource" // Resource management handled externally 070}) 071public class ResponseContent implements HttpEntity { 072 073 private static final String HEADER_ContentType = "Content-Type"; 074 private static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain"; 075 076 private static final HttpEntity NULL_ENTITY = new HttpEntity() { 077 078 @Override 079 public void consumeContent() throws IOException { 080 // No-op: Mock implementation - full functionality not required 081 } 082 083 @Override 084 public InputStream getContent() throws IOException, UnsupportedOperationException { return new ByteArrayInputStream(new byte[0]); } 085 086 @Override 087 public Header getContentEncoding() { return ResponseHeader.NULL_HEADER; } 088 089 @Override 090 public long getContentLength() { return -1; } 091 092 @Override 093 public Header getContentType() { return ResponseHeader.NULL_HEADER; } 094 095 @Override 096 public boolean isChunked() { return false; } 097 098 @Override 099 public boolean isRepeatable() { return false; } 100 101 @Override 102 public boolean isStreaming() { return false; } 103 104 @Override 105 public void writeTo(OutputStream outstream) throws IOException { 106 // Fail-fast: writeTo() is never legitimately invoked on this sentinel -- pipeTo() and asBytes()/ 107 // asInputStream() route through getContent()/getContentLength() instead, so a silent no-op here would 108 // mask a caller bypassing those paths. 109 throw new UnsupportedOperationException("NULL_ENTITY.writeTo() should never be called."); 110 } 111 }; 112 113 private final RestClient client; 114 final RestRequest request; 115 final RestResponse response; 116 private final HttpEntity entity; 117 private HttpPartSchema schema; 118 private Parser parser; 119 private byte[] body; 120 private boolean cached; 121 boolean isConsumed; 122 123 /** 124 * Constructor. 125 * 126 * @param client The client used to build this request. 127 * @param request The request object. 128 * @param response The response object. 129 * @param parser The parser to use to consume the body. Can be <jk>null</jk> to use the response's negotiated/default parser. 130 */ 131 public ResponseContent(RestClient client, RestRequest request, RestResponse response, Parser parser) { 132 this.client = client; 133 this.request = request; 134 this.response = response; 135 this.parser = parser; 136 this.entity = coalesce(response.asHttpResponse().getEntity(), NULL_ENTITY); 137 } 138 139 /** 140 * Same as {@link #as(Type,Type...)} except optimized for a non-parameterized class. 141 * 142 * <p> 143 * This is the preferred parse method for simple types since you don't need to cast the results. 144 * 145 * <h5 class='section'>Examples:</h5> 146 * <p class='bjava'> 147 * <jc>// Parse into a string.</jc> 148 * String <jv>string</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).run().getContent().as(String.<jk>class</jk>); 149 * 150 * <jc>// Parse into a bean.</jc> 151 * MyBean <jv>bean</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).run().getContent().as(MyBean.<jk>class</jk>); 152 * 153 * <jc>// Parse into a bean array.</jc> 154 * MyBean[] <jv>beanArray</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).run().getContent().as(MyBean[].<jk>class</jk>); 155 * 156 * <jc>// Parse into a linked-list of objects.</jc> 157 * List <jv>list</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).run().getContent().as(LinkedList.<jk>class</jk>); 158 * 159 * <jc>// Parse into a map of object keys/values.</jc> 160 * Map <jv>map</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).run().getContent().as(TreeMap.<jk>class</jk>); 161 * </p> 162 * 163 * <h5 class='section'>Notes:</h5><ul> 164 * <li class='note'> 165 * You can also specify any of the following types: 166 * <ul class='compact'> 167 * <li>{@link ResponseContent}/{@link HttpEntity} - Returns access to this object. 168 * <li>{@link Reader} - Returns access to the raw reader of the response. 169 * <li>{@link InputStream} - Returns access to the raw input stream of the response. 170 * <li>{@link HttpResource} - Response will be converted to a {@link StreamResource}. 171 * <li>Any type that takes in an {@link HttpResponse} object. 172 * </ul> 173 * <li class='note'> 174 * If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with 175 * other methods that retrieve the content of the response. Otherwise a {@link RestCallException} 176 * with an inner {@link IllegalStateException} will be thrown. 177 * <li class='note'> 178 * The input stream is automatically closed after this call. 179 * </ul> 180 * 181 * @param <T> 182 * The class type of the object being created. 183 * See {@link #as(Type,Type...)} for details. 184 * @param type The object type to create. 185 * @return The parsed object. 186 * @throws RestCallException 187 * If the input contains a syntax error or is malformed, or is not valid for the specified type, or if a connection 188 * error occurred. 189 */ 190 public <T> T as(Class<T> type) throws RestCallException { 191 return as(getClassMeta(type)); 192 } 193 194 /** 195 * Same as {@link #as(Class)} except allows you to predefine complex data types using the {@link ClassMeta} API. 196 * 197 * <h5 class='section'>Examples:</h5> 198 * <p class='bjava'> 199 * MarshallingContext <jv>marshallingContext</jv> = MarshallingContext.<jsf>DEFAULT</jsf>; 200 * 201 * <jc>// Parse into a linked-list of strings.</jc> 202 * ClassMeta<List<String>> <jv>cm1</jv> = <jv>marshallingContext</jv>.getClassMeta(LinkedList.<jk>class</jk>, String.<jk>class</jk>); 203 * List<String> <jv>list1</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).run().getContent().as(<jv>cm1</jv>); 204 * 205 * <jc>// Parse into a linked-list of beans.</jc> 206 * ClassMeta<List<String>> <jv>cm2</jv> = <jv>marshallingContext</jv>.getClassMeta(LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); 207 * List<MyBean> <jv>list2</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).run().getContent().as(<jv>cm2</jv>); 208 * 209 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 210 * ClassMeta<List<String>> <jv>cm3</jv> = <jv>marshallingContext</jv>.getClassMeta(LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 211 * List<List<String>> <jv>list3</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).run().getContent().as(<jv>cm3</jv>); 212 * 213 * <jc>// Parse into a map of string keys/values.</jc> 214 * ClassMeta<List<String>> <jv>cm4</jv> = <jv>marshallingContext</jv>.getClassMeta(TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 215 * Map<String,String> <jv>map4</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).run().getContent().as(<jv>cm4</jv>); 216 * 217 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 218 * ClassMeta<List<String>> <jv>cm5</jv> = <jv>marshallingContext</jv>.getClassMeta(TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 219 * Map<String,List<MyBean>> <jv>map5</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).run().getContent().as(<jv>cm5</jv>); 220 * </p> 221 * 222 * <h5 class='section'>Notes:</h5><ul> 223 * <li class='note'> 224 * If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with 225 * other methods that retrieve the content of the response. Otherwise a {@link RestCallException} 226 * with an inner {@link IllegalStateException} will be thrown. 227 * <li class='note'> 228 * The input stream is automatically closed after this call. 229 * </ul> 230 * 231 * @param <T> The class type of the object to create. 232 * @param type The object type to create. 233 * @return The parsed object. 234 * @throws RestCallException 235 * <ul> 236 * <li>If the input contains a syntax error or is malformed, or is not valid for the specified type. 237 * <li>If a connection error occurred. 238 * </ul> 239 * @see MarshallingSession#getClassMeta(Class) for argument syntax for maps and collections. 240 */ 241 @SuppressWarnings({ 242 "java:S3776", // Cognitive complexity acceptable for this specific logic 243 "unchecked" // Type erasure requires unchecked casts in content parsing 244 }) 245 public <T> T as(ClassMeta<T> type) throws RestCallException { 246 try { 247 if (type.is(ResponseContent.class) || type.is(HttpEntity.class)) 248 return (T)this; 249 250 if (type.is(Reader.class)) 251 return (T)asReader(); 252 253 if (type.is(InputStream.class)) 254 return (T)asInputStream(); 255 256 if (type.isChildOf(RecordReader.class) || type.is(RecordReader.class)) 257 return (T) asCursor(type); 258 259 if (type.is(HttpResponse.class)) 260 return (T)response; 261 262 if (type.is(HttpResource.class) || type.is(BasicResource.class)) 263 type = (ClassMeta<T>)getClassMeta(StreamResource.class); 264 265 var responseCtor = type.getPublicConstructor(x -> x.hasParameterTypes(HttpResponse.class)); 266 if (responseCtor.isPresent()) 267 return construct(responseCtor.get(), type); 268 269 var ct = firstNonEmpty(response.getHeader(HEADER_ContentType).orElse(CONTENT_TYPE_TEXT_PLAIN)); 270 271 if (parser == null) 272 parser = client.getMatchingParser(ct); 273 274 var mt = MediaType.of(ct); 275 276 if ((parser == null || (mt.toString().contains(CONTENT_TYPE_TEXT_PLAIN) && ! parser.canHandle(ct))) && type.hasStringMutater()) 277 return BasicConverter.INSTANCE.to(asString(), type.inner()); 278 279 if (nn(parser)) 280 return parse(type, mt); 281 282 if (type.hasReaderMutater()) 283 return BasicConverter.INSTANCE.to(asReader(), type.inner()); 284 285 if (type.hasInputStreamMutater()) 286 return BasicConverter.INSTANCE.to(asInputStream(), type.inner()); 287 288 ct = response.getStringHeader(HEADER_ContentType).orElse(null); 289 290 if (ct == null && client.hasParsers()) 291 throw new ParseException("Content-Type not specified in response header. Cannot find appropriate parser."); 292 293 throw new ParseException("Unsupported media-type in request header 'Content-Type': '%s'", ct); 294 295 } catch (ParseException | IOException e) { 296 response.close(); 297 throw new RestCallException(response, e, "Could not parse response body."); 298 } 299 } 300 301 /** 302 * Constructs the target type from the response through its {@code (HttpResponse)} constructor. 303 */ 304 private <T> T construct(ConstructorInfo ctor, ClassMeta<T> type) throws RestCallException { 305 try { 306 return ctor.newInstance(response); 307 } catch (ExecutableException e) { 308 // The (HttpResponse) constructor rejected the response (e.g. the status code does not match the class). 309 response.close(); 310 throw new RestCallException(response, e.getCause() == null ? e : e.getCause(), "Could not construct %s from response.", type.inner().getName()); 311 } 312 } 313 314 /** 315 * Parses the response body with the matched parser. 316 */ 317 @SuppressWarnings({ 318 "unchecked" // Type erasure requires unchecked casts in content parsing 319 }) 320 private <T> T parse(ClassMeta<T> type, MediaType mt) throws ParseException, IOException { 321 try (Closeable in = parser.isReaderParser() ? asReader() : asInputStream()) { 322 323 // @formatter:off 324 T t = parser 325 .createSession() 326 .properties(JsonMap.create().inner(request.getSessionProperties())) 327 .locale(response.getLocale()) 328 .mediaType(mt) 329 .schema(schema) 330 .build() 331 .read(in, type); 332 // @formatter:on 333 334 // Some HTTP responses have no body, so try to create these beans if they've got no-arg constructors. 335 if (t == null && ! type.is(String.class)) { 336 var result2 = type.getPublicConstructor(cons -> cons.getParameterCount() == 0).map(c -> safe(() -> (T)c.newInstance())); 337 if (result2.isPresent()) 338 return result2.get(); 339 } 340 341 return t; 342 } 343 } 344 345 /** 346 * Resolves a cursor return type ({@link RecordReader} / {@link TokenReader} or any subtype) 347 * by negotiating the response Content-Type to a parser and calling its 348 * {@code readRecords(...)} / {@code readTokens(...)} factory. 349 * 350 * <p> 351 * Implements two-layer resolution: the declared type filters eligible parsers (a concrete 352 * subtype like {@code JsonlTokenReader} is only assignable from a parser that produces it); 353 * the response's Content-Type negotiates within that filter. Capability is detected via 354 * {@code instanceof} against the {@link RecordReadable} / {@link TokenReadable} role interfaces. 355 * 356 * <p> 357 * The caller owns the returned cursor's lifecycle — wrap it in try-with-resources. 358 * 359 * @param type The declared parameter / return type (must be {@link RecordReader} or a subtype). 360 * @return A new cursor. 361 * @throws RestCallException If no parser produces a cursor assignable to the declared type 362 * for the response's Content-Type, or if the matched parser does not implement the requested 363 * cursor role interface. 364 */ 365 private Object asCursor(ClassMeta<?> type) throws RestCallException { 366 try { 367 var ct = firstNonEmpty(response.getHeader(HEADER_ContentType).orElse(CONTENT_TYPE_TEXT_PLAIN)); 368 var matchedParser = parser != null ? parser : client.getMatchingParser(ct); 369 if (matchedParser == null) 370 throw new RestCallException(response, null, 371 "No registered parser matches Content-Type '%s' for cursor return type '%s'.", 372 ct, type.inner().getName()); 373 374 var isToken = type.isChildOf(TokenReader.class) || type.is(TokenReader.class); 375 var supported = isToken ? matchedParser instanceof TokenReadable : matchedParser instanceof RecordReadable; 376 if (!supported) 377 throw new RestCallException(response, null, 378 "Parser '%s' does not support the %s surface.", 379 matchedParser.getClass().getName(), 380 isToken ? "token-reader" : "record-reader"); 381 382 Object input = matchedParser.isReaderParser() ? asReader() : asInputStream(); 383 Object cursor = isToken 384 ? ((TokenReadable) matchedParser).readTokens(input) 385 : ((RecordReadable) matchedParser).readRecords(input); 386 387 if (!type.inner().isInstance(cursor)) 388 throw new RestCallException(response, null, 389 "Parser '%s' produced cursor type '%s' which is not assignable to the declared type '%s'.", 390 matchedParser.getClass().getName(), 391 cursor == null ? "null" : cursor.getClass().getName(), 392 type.inner().getName()); 393 394 return cursor; 395 } catch (RestCallException e) { 396 throw e; 397 } catch (IOException e) { 398 throw new RestCallException(response, e, "Could not open cursor over response body."); 399 } 400 } 401 402 /** 403 * Parses HTTP body into the specified object type. 404 * 405 * <p> 406 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). 407 * 408 * <h5 class='section'>Examples:</h5> 409 * <p class='bjava'> 410 * <jc>// Parse into a linked-list of strings.</jc> 411 * List<String> <jv>list1</jv> = <jv>client</jv> 412 * .get(<jsf>URI</jsf>) 413 * .run() 414 * .getContent().as(LinkedList.<jk>class</jk>, String.<jk>class</jk>); 415 * 416 * <jc>// Parse into a linked-list of beans.</jc> 417 * List<MyBean> <jv>list2</jv> = <jv>client</jv> 418 * .get(<jsf>URI</jsf>) 419 * .run() 420 * .getContent().as(LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); 421 * 422 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 423 * List<List<String>> <jv>list3</jv> = <jv>client</jv> 424 * .get(<jsf>URI</jsf>) 425 * .run() 426 * .getContent().as(LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 427 * 428 * <jc>// Parse into a map of string keys/values.</jc> 429 * Map<String,String> <jv>map1</jv> = <jv>client</jv> 430 * .get(<jsf>URI</jsf>) 431 * .run() 432 * .getContent().as(TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 433 * 434 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 435 * Map<String,List<MyBean>> <jv>map2</jv> = <jv>client</jv> 436 * .get(<jsf>URI</jsf>) 437 * .run() 438 * .getContent().as(TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 439 * </p> 440 * 441 * <p> 442 * <c>Collection</c> classes are assumed to be followed by zero or one objects indicating the element type. 443 * 444 * <p> 445 * <c>Map</c> classes are assumed to be followed by zero or two meta objects indicating the key and value types. 446 * 447 * <p> 448 * The array can be arbitrarily long to indicate arbitrarily complex data structures. 449 * 450 * <h5 class='section'>Notes:</h5><ul> 451 * <li class='note'> 452 * Use the {@link #as(Class)} method instead if you don't need a parameterized map/collection. 453 * <li class='note'> 454 * You can also specify any of the following types: 455 * <ul class='compact'> 456 * <li>{@link ResponseContent}/{@link HttpEntity} - Returns access to this object. 457 * <li>{@link Reader} - Returns access to the raw reader of the response. 458 * <li>{@link InputStream} - Returns access to the raw input stream of the response. 459 * <li>{@link HttpResource} - Response will be converted to a {@link StreamResource}. 460 * <li>Any type that takes in an {@link HttpResponse} object. 461 * </ul> 462 * <li class='note'> 463 * If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with 464 * other methods that retrieve the content of the response. Otherwise a {@link RestCallException} 465 * with an inner {@link IllegalStateException} will be thrown. 466 * <li class='note'> 467 * The input stream is automatically closed after this call. 468 * </ul> 469 * 470 * @param <T> The class type of the object to create. 471 * @param type 472 * The object type to create. 473 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 474 * @param args 475 * The type arguments of the class if it's a collection or map. 476 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 477 * <br>Ignored if the main type is not a map or collection. 478 * @return The parsed object. 479 * @throws RestCallException 480 * <ul> 481 * <li>If the input contains a syntax error or is malformed, or is not valid for the specified type. 482 * <li>If a connection error occurred. 483 * </ul> 484 * @see MarshallingSession#getClassMeta(Class) for argument syntax for maps and collections. 485 */ 486 public <T> T as(Type type, Type...args) throws RestCallException { 487 return as(getClassMeta(type, args)); 488 } 489 490 /** 491 * Same as {@link #asString()} but truncates the string to the specified length. 492 * 493 * <p> 494 * If truncation occurs, the string will be suffixed with <js>"..."</js>. 495 * 496 * @param length The max length of the returned string. 497 * @return The truncated string. 498 * @throws RestCallException If a problem occurred trying to read from the reader. 499 */ 500 public String asAbbreviatedString(int length) throws RestCallException { 501 return StringUtils.abbreviate(asString(), length); 502 } 503 504 /** 505 * Returns the HTTP response message body as a byte array. 506 * 507 * The HTTP response message body as a byte array, never <jk>null</jk>. 508 * <br>For responses without a body(e.g. HTTP 204), returns an empty array. 509 * 510 * @return The HTTP response body as a byte array. 511 * @throws RestCallException If an exception occurred. 512 */ 513 public byte[] asBytes() throws RestCallException { 514 if (body == null) { 515 try { 516 if (entity instanceof BasicHttpEntity<?> entity2) { 517 body = entity2.asBytes(); 518 } else { 519 body = readBytes(entity.getContent()); 520 } 521 } catch (IOException e) { 522 throw new RestCallException(response, e, "Could not read response body."); 523 } finally { 524 response.close(); 525 } 526 } 527 return body; 528 } 529 530 /** 531 * Same as {@link #as(Class)} but allows you to run the call asynchronously. 532 * 533 * <h5 class='section'>Notes:</h5><ul> 534 * <li class='note'> 535 * If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with 536 * other methods that retrieve the content of the response. Otherwise a {@link RestCallException} 537 * with an inner {@link IllegalStateException} will be thrown. 538 * <li class='note'> 539 * The input stream is automatically closed after the execution of the future. 540 * </ul> 541 * 542 * @param <T> The class type of the object being created. 543 * @param type The object type to create. 544 * @return The future object. 545 * @throws RestCallException If the executor service was not defined. 546 * @see 547 * RestClient.Builder#executorService(ExecutorService, boolean) for defining the executor service for creating 548 * Future instances. 549 */ 550 public <T> Future<T> asFuture(Class<T> type) throws RestCallException { 551 return client.getExecutorService().submit(() -> as(type)); 552 } 553 554 /** 555 * Same as {@link #as(ClassMeta)} but allows you to run the call asynchronously. 556 * 557 * <h5 class='section'>Notes:</h5><ul> 558 * <li class='note'> 559 * If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with 560 * other methods that retrieve the content of the response. Otherwise a {@link RestCallException} 561 * with an inner {@link IllegalStateException} will be thrown. 562 * <li class='note'> 563 * The input stream is automatically closed after the execution of the future. 564 * </ul> 565 * 566 * @param <T> 567 * The class type of the object being created. 568 * See {@link #as(Type, Type...)} for details. 569 * @param type The object type to create. 570 * @return The future object. 571 * @throws RestCallException If the executor service was not defined. 572 * @see 573 * RestClient.Builder#executorService(ExecutorService, boolean) for defining the executor service for creating 574 * Future instances. 575 */ 576 public <T> Future<T> asFuture(ClassMeta<T> type) throws RestCallException { 577 return client.getExecutorService().submit(() -> as(type)); 578 } 579 580 /** 581 * Same as {@link #as(Type,Type...)} but allows you to run the call asynchronously. 582 * 583 * <h5 class='section'>Notes:</h5><ul> 584 * <li class='note'> 585 * If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with 586 * other methods that retrieve the content of the response. Otherwise a {@link RestCallException} 587 * with an inner {@link IllegalStateException} will be thrown. 588 * <li class='note'> 589 * The input stream is automatically closed after the execution of the future. 590 * </ul> 591 * 592 * @param <T> 593 * The class type of the object being created. 594 * See {@link #as(Type, Type...)} for details. 595 * @param type 596 * The object type to create. 597 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 598 * @param args 599 * The type arguments of the class if it's a collection or map. 600 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 601 * <br>Ignored if the main type is not a map or collection. 602 * @return The future object. 603 * @throws RestCallException If the executor service was not defined. 604 * @see 605 * RestClient.Builder#executorService(ExecutorService, boolean) for defining the executor service for creating 606 * Future instances. 607 */ 608 public <T> Future<T> asFuture(Type type, Type...args) throws RestCallException { 609 return client.getExecutorService().submit(() -> as(type, args)); 610 } 611 612 /** 613 * Returns the HTTP body content as a simple hexadecimal character string. 614 * 615 * <h5 class='section'>Example:</h5> 616 * <p class='bcode'> 617 * 0123456789ABCDEF 618 * </p> 619 * 620 * @return The incoming input from the connection as a plain string. 621 * @throws RestCallException If a problem occurred trying to read from the reader. 622 */ 623 public String asHex() throws RestCallException { 624 return toHex(asBytes()); 625 } 626 627 /** 628 * Returns the HTTP response message body as an input stream. 629 * 630 * <h5 class='section'>Notes:</h5><ul> 631 * <li class='note'> 632 * Once this input stream is exhausted, it will automatically be closed. 633 * <li class='note'> 634 * This method can be called multiple times if {@link #cache()} has been called. 635 * <li class='note'> 636 * Calling this method multiple times without caching enabled will cause a {@link RestCallException} 637 * with an inner {@link IllegalStateException} to be thrown. 638 * </ul> 639 * 640 * @return 641 * The HTTP response message body input stream, never <jk>null</jk>. 642 * <br>For responses without a body(e.g. HTTP 204), returns an empty stream. 643 * @throws IOException If a stream or illegal state exception was thrown. 644 */ 645 public InputStream asInputStream() throws IOException { 646 try { 647 if (nn(body)) 648 return new ByteArrayInputStream(body); 649 650 if (cached) { 651 body = readBytes(entity.getContent()); 652 response.close(); 653 return new ByteArrayInputStream(body); 654 } 655 656 if (isConsumed && ! entity.isRepeatable()) 657 throw new IllegalStateException("Method cannot be called. Response has already been consumed. Consider using the RestResponse.cacheBody() method."); 658 659 HttpEntity e = response.asHttpResponse().getEntity(); 660 InputStream is = e == null ? new ByteArrayInputStream(new byte[0]) : e.getContent(); 661 662 is = new EofSensorInputStream(is, new EofSensorWatcher() { 663 @Override 664 public boolean eofDetected(InputStream wrapped) throws IOException { 665 response.close(); 666 return true; 667 } 668 669 @Override 670 public boolean streamAbort(InputStream wrapped) throws IOException { 671 response.close(); 672 return true; 673 } 674 675 @Override 676 public boolean streamClosed(InputStream wrapped) throws IOException { 677 response.close(); 678 return true; 679 } 680 }); 681 682 isConsumed = true; 683 684 return is; 685 } catch (UnsupportedOperationException e) { 686 throw ioex(e); 687 } 688 } 689 690 /** 691 * Converts the contents of the response body to a string and then matches the specified pattern against it. 692 * 693 * <h5 class='section'>Example:</h5> 694 * <p class='bjava'> 695 * <jc>// Parse response using a regular expression.</jc> 696 * Matcher <jv>matcher</jv> = <jv>client</jv> 697 * .get(<jsf>URI</jsf>) 698 * .run() 699 * .getContent().asMatcher(Pattern.<jsm>compile</jsm>(<js>"foo=(.*)"</js>)); 700 * 701 * <jk>if</jk> (<jv>matcher</jv>.matches()) { 702 * String <jv>foo</jv> = <jv>matcher</jv>.group(1); 703 * } 704 * </p> 705 * 706 * <h5 class='section'>Notes:</h5><ul> 707 * <li class='note'> 708 * If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed. 709 * <li class='note'> 710 * This method automatically calls {@link #cache()} so that the body can be retrieved multiple times. 711 * <li class='note'> 712 * The input stream is automatically closed after this call. 713 * </ul> 714 * 715 * @param pattern The regular expression pattern to match. 716 * @return The matcher. 717 * @throws RestCallException If a connection error occurred. 718 */ 719 public Matcher asMatcher(Pattern pattern) throws RestCallException { 720 return pattern.matcher(asString()); 721 } 722 723 /** 724 * Converts the contents of the response body to a string and then matches the specified pattern against it. 725 * 726 * <h5 class='section'>Example:</h5> 727 * <p class='bjava'> 728 * <jc>// Parse response using a regular expression.</jc> 729 * Matcher <jv>matcher</jv> = <jv>client</jv> 730 * .get(<jsf>URI</jsf>) 731 * .run() 732 * .getContent().asMatcher(<js>"foo=(.*)"</js>); 733 * 734 * <jk>if</jk> (<jv>matcher</jv>.matches()) { 735 * String <jv>foo</jv> = <jv>matcher</jv>.group(1); 736 * } 737 * </p> 738 * 739 * 740 * <h5 class='section'>Notes:</h5><ul> 741 * <li class='note'> 742 * If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed. 743 * <li class='note'> 744 * If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with 745 * other methods that retrieve the content of the response. Otherwise a {@link RestCallException} 746 * with an inner {@link IllegalStateException} will be thrown. 747 * <li class='note'> 748 * The input stream is automatically closed after this call. 749 * </ul> 750 * 751 * @param regex The regular expression pattern to match. 752 * @return The matcher. 753 * @throws RestCallException If a connection error occurred. 754 */ 755 public Matcher asMatcher(String regex) throws RestCallException { 756 return asMatcher(regex, 0); 757 } 758 759 /** 760 * Converts the contents of the response body to a string and then matches the specified pattern against it. 761 * 762 * <h5 class='section'>Example:</h5> 763 * <p class='bjava'> 764 * <jc>// Parse response using a regular expression.</jc> 765 * Matcher <jv>matcher</jv> = <jv>client</jv> 766 * .get(<jsf>URI</jsf>) 767 * .run() 768 * .getContent().asMatcher(<js>"foo=(.*)"</js>, <jsf>MULTILINE</jsf> & <jsf>CASE_INSENSITIVE</jsf>); 769 * 770 * <jk>if</jk> (<jv>matcher</jv>.matches()) { 771 * String <jv>foo</jv> = <jv>matcher</jv>.group(1); 772 * } 773 * </p> 774 * 775 * 776 * <h5 class='section'>Notes:</h5><ul> 777 * <li class='note'> 778 * If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed. 779 * <li class='note'> 780 * If {@link #cache()} or {@link RestResponse#cacheContent()} has been called, this method can be can be called multiple times and/or combined with 781 * other methods that retrieve the content of the response. Otherwise a {@link RestCallException} 782 * with an inner {@link IllegalStateException} will be thrown. 783 * <li class='note'> 784 * The input stream is automatically closed after this call. 785 * </ul> 786 * 787 * @param regex The regular expression pattern to match. 788 * @param flags Pattern match flags. See {@link Pattern#compile(String, int)}. 789 * @return The matcher. 790 * @throws RestCallException If a connection error occurred. 791 */ 792 public Matcher asMatcher(String regex, int flags) throws RestCallException { 793 return asMatcher(Pattern.compile(regex, flags)); 794 } 795 796 /** 797 * Returns the HTTP response message body as a reader based on the charset on the <code>Content-Type</code> response header. 798 * 799 * <h5 class='section'>Notes:</h5><ul> 800 * <li class='note'> 801 * If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed. 802 * <li class='note'> 803 * Once this input stream is exhausted, it will automatically be closed. 804 * <li class='note'> 805 * This method can be called multiple times if {@link #cache()} has been called. 806 * <li class='note'> 807 * Calling this method multiple times without caching enabled will cause a {@link RestCallException} 808 * with an inner {@link IllegalStateException} to be thrown. 809 * </ul> 810 * 811 * @return 812 * The HTTP response message body reader, never <jk>null</jk>. 813 * <br>For responses without a body(e.g. HTTP 204), returns an empty reader. 814 * @throws IOException If an exception occurred. 815 */ 816 public Reader asReader() throws IOException { 817 818 // Figure out what the charset of the response is. 819 String cs = null; 820 var ct = getContentType().orElse(null); 821 822 // First look for "charset=" in Content-Type header of response. 823 if (nn(ct) && ct.contains("charset=")) 824 cs = ct.substring(ct.indexOf("charset=") + 8).trim(); 825 826 return asReader(cs == null ? UTF8 : Charset.forName(cs)); 827 } 828 829 /** 830 * Returns the HTTP response message body as a reader using the specified charset. 831 * 832 * <h5 class='section'>Notes:</h5><ul> 833 * <li class='note'> 834 * Once this input stream is exhausted, it will automatically be closed. 835 * <li class='note'> 836 * This method can be called multiple times if {@link #cache()} has been called. 837 * <li class='note'> 838 * Calling this method multiple times without caching enabled will cause a {@link RestCallException} 839 * with an inner {@link IllegalStateException} to be thrown. 840 * </ul> 841 * 842 * @param charset 843 * The charset to use for the reader. 844 * <br>If <jk>null</jk>, <js>"UTF-8"</js> is used. 845 * @return 846 * The HTTP response message body reader, never <jk>null</jk>. 847 * <br>For responses without a body(e.g. HTTP 204), returns an empty reader. 848 * @throws IOException If an exception occurred. 849 */ 850 public Reader asReader(Charset charset) throws IOException { 851 return new InputStreamReader(asInputStream(), charset == null ? UTF8 : charset); 852 } 853 854 /** 855 * Shortcut for calling <c>assertValue().asBytes()</c>. 856 * 857 * @return A new fluent assertion. 858 */ 859 public FluentByteArrayAssertion<ResponseContent> assertBytes() { 860 return new FluentResponseBodyAssertion<>(this, this).asBytes(); 861 } 862 863 /** 864 * Shortcut for calling <c>assertValue().as(<jv>type</jv>)</c>. 865 * 866 * @param <T> The object type to create. 867 * @param type The object type to create. 868 * @return A new fluent assertion. 869 */ 870 public <T> FluentAnyAssertion<T,ResponseContent> assertObject(Class<T> type) { 871 return new FluentResponseBodyAssertion<>(this, this).as(type); 872 } 873 874 /** 875 * Shortcut for calling <c>assertValue().as(<jv>type</jv>, <jv>args</jv>)</c>. 876 * 877 * @param type The object type to create. 878 * @param args Optional type arguments. 879 * @return A new fluent assertion. 880 */ 881 public FluentAnyAssertion<Object,ResponseContent> assertObject(Type type, Type...args) { 882 return new FluentResponseBodyAssertion<>(this, this).as(type, args); 883 } 884 885 /** 886 * Shortcut for calling <c>assertValue().asString()</c>. 887 * 888 * @return A new fluent assertion. 889 */ 890 public FluentStringAssertion<ResponseContent> assertString() { 891 return new FluentResponseBodyAssertion<>(this, this).asString(); 892 } 893 894 /** 895 * Provides the ability to perform fluent-style assertions on this response body. 896 * 897 * <p> 898 * This method is called directly from the {@link RestResponse#assertContent()} method to instantiate a fluent assertions object. 899 * 900 * <h5 class='section'>Examples:</h5> 901 * <p class='bjava'> 902 * <jc>// Validates the response body equals the text "OK".</jc> 903 * <jv>client</jv> 904 * .get(<jsf>URI</jsf>) 905 * .run() 906 * .getContent().assertValue().equals(<js>"OK"</js>); 907 * 908 * <jc>// Validates the response body contains the text "OK".</jc> 909 * <jv>client</jv> 910 * .get(<jsf>URI</jsf>) 911 * .run() 912 * .getContent().assertValue().contains(<js>"OK"</js>); 913 * 914 * <jc>// Validates the response body passes a predicate test.</jc> 915 * <jv>client</jv> 916 * .get(<jsf>URI</jsf>) 917 * .run() 918 * .getContent().assertValue().is(<jv>x</jv> -> <jv>x</jv>.contains(<js>"OK"</js>)); 919 * 920 * <jc>// Validates the response body matches a regular expression.</jc> 921 * <jv>client</jv> 922 * .get(<jsf>URI</jsf>) 923 * .run() 924 * .getContent().assertValue().isPattern(<js>".*OK.*"</js>); 925 * 926 * <jc>// Validates the response body matches a regular expression using regex flags.</jc> 927 * <jv>client</jv> 928 * .get(<jsf>URI</jsf>) 929 * .run() 930 * .getContent().assertValue().isPattern(<js>".*OK.*"</js>, <jsf>MULTILINE</jsf> & <jsf>CASE_INSENSITIVE</jsf>); 931 * 932 * <jc>// Validates the response body matches a regular expression in the form of an existing Pattern.</jc> 933 * Pattern <jv>pattern</jv> = Pattern.<jsm>compile</jsm>(<js>".*OK.*"</js>); 934 * <jv>client</jv> 935 * .get(<jsf>URI</jsf>) 936 * .run() 937 * .getContent().assertValue().isPattern(<jv>pattern</jv>); 938 * </p> 939 * 940 * <p> 941 * The assertion test returns the original response object allowing you to chain multiple requests like so: 942 * <p class='bjava'> 943 * <jc>// Validates the response body matches a regular expression.</jc> 944 * MyBean <jv>bean</jv> = <jv>client</jv> 945 * .get(<jsf>URI</jsf>) 946 * .run() 947 * .getContent().assertValue().isPattern(<js>".*OK.*"</js>); 948 * .getContent().assertValue().isNotPattern(<js>".*ERROR.*"</js>) 949 * .getContent().as(MyBean.<jk>class</jk>); 950 * </p> 951 * 952 * <h5 class='section'>Notes:</h5><ul> 953 * <li class='note'> 954 * If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed. 955 * <li class='note'> 956 * This method automatically calls {@link #cache()} so that the body can be retrieved multiple times. 957 * <li class='note'> 958 * The input stream is automatically closed after this call. 959 * </ul> 960 * 961 * @return A new fluent assertion object. 962 */ 963 public FluentResponseBodyAssertion<ResponseContent> assertValue() { 964 return new FluentResponseBodyAssertion<>(this, this); 965 } 966 967 /** 968 * Returns the HTTP body content as a simple space-delimited hexadecimal character string. 969 * 970 * <h5 class='section'>Example:</h5> 971 * <p class='bcode'> 972 * 01 23 45 67 89 AB CD EF 973 * </p> 974 * 975 * @return The incoming input from the connection as a plain string. 976 * @throws RestCallException If a problem occurred trying to read from the reader. 977 */ 978 public String asSpacedHex() throws RestCallException { 979 return toSpacedHex(asBytes()); 980 } 981 982 /** 983 * Returns the contents of this body as a string. 984 * 985 * <h5 class='section'>Notes:</h5><ul> 986 * <li class='note'> 987 * If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed. 988 * <li class='note'> 989 * This method automatically calls {@link #cache()} so that the body can be retrieved multiple times. 990 * <li class='note'> 991 * The input stream is automatically closed after this call. 992 * </ul> 993 * 994 * @return The response as a string. 995 * @throws RestCallException 996 * <ul> 997 * <li>If the input contains a syntax error or is malformed, or is not valid for the specified type. 998 * <li>If a connection error occurred. 999 * </ul> 1000 */ 1001 public String asString() throws RestCallException { 1002 cache(); 1003 try (Reader r = asReader()) { 1004 return read(r); 1005 } catch (IOException e) { 1006 response.close(); 1007 throw new RestCallException(response, e, "Could not read response body."); 1008 } 1009 } 1010 1011 /** 1012 * Same as {@link #asString()} but allows you to run the call asynchronously. 1013 * 1014 * <h5 class='section'>Notes:</h5><ul> 1015 * <li class='note'> 1016 * If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed. 1017 * <li class='note'> 1018 * This method automatically calls {@link #cache()} so that the body can be retrieved multiple times. 1019 * <li class='note'> 1020 * The input stream is automatically closed after this call. 1021 * </ul> 1022 * 1023 * @return The future object. 1024 * @throws RestCallException If the executor service was not defined. 1025 * @see 1026 * RestClient.Builder#executorService(ExecutorService, boolean) for defining the executor service for creating 1027 * Future instances. 1028 */ 1029 public Future<String> asStringFuture() throws RestCallException { 1030 return client.getExecutorService().submit(this::asString); 1031 } 1032 1033 /** 1034 * Causes the contents of the response body to be stored so that it can be repeatedly read. 1035 * 1036 * <p> 1037 * Calling this method allows methods that read the response body to be called multiple times. 1038 * 1039 * <h5 class='section'>Notes:</h5><ul> 1040 * <li class='note'> 1041 * Multiple calls to this method are ignored. 1042 * </ul> 1043 * 1044 * @return This object. 1045 */ 1046 public ResponseContent cache() { 1047 this.cached = true; 1048 return this; 1049 } 1050 1051 /** 1052 * This method is called to indicate that the content of this entity is no longer required. 1053 * 1054 * <p> 1055 * This method is of particular importance for entities being received from a connection. 1056 * <br>The entity needs to be consumed completely in order to re-use the connection with keep-alive. 1057 * 1058 * @throws IOException If an I/O error occurs. 1059 * @deprecated Use standard java convention to ensure resource deallocation by calling {@link InputStream#close()} on 1060 * the input stream returned by {@link #getContent()} 1061 */ 1062 @Override /* Overridden from HttpEntity */ 1063 @Deprecated(since = "10.0", forRemoval = true) 1064 @SuppressWarnings({ 1065 "java:S1133" // Intentional deprecation retained for backward compatibility until the documented removal; the reminder is not actionable now. 1066 }) 1067 public void consumeContent() throws IOException { 1068 entity.consumeContent(); 1069 } 1070 1071 /** 1072 * Returns a content stream of the entity. 1073 * 1074 * <h5 class='section'>Notes:</h5><ul> 1075 * <li class='note'>This method is equivalent to {@link #asInputStream()} which is the preferred method for fluent-style coding. 1076 * <li class='note'>This input stream will auto-close once the end of stream has been reached. 1077 * <li class='note'>It is up to the caller to properly close this stream if not fully consumed. 1078 * <li class='note'>This method can be called multiple times if the entity is repeatable or the cache flag is set on this object. 1079 * <li class='note'>Calling this method multiple times on a non-repeatable or cached body will throw a {@link IllegalStateException}. 1080 * Note that this is different from the HttpClient specs for this method. 1081 * </ul> 1082 * 1083 * @return Content stream of the entity. 1084 */ 1085 @Override /* Overridden from HttpEntity */ 1086 public InputStream getContent() throws IOException, UnsupportedOperationException { return asInputStream(); } 1087 1088 /** 1089 * Obtains the Content-Encoding header, if known. 1090 * 1091 * <p> 1092 * This is the header that should be used when sending the entity, or the one that was received with the entity. 1093 * <br>Wrapping entities that modify the content encoding should adjust this header accordingly. 1094 * 1095 * @return The <c>Content-Encoding</c> header for this entity. Never <jk>null</jk>, but wraps a <jk>null</jk> value if the content encoding is unknown. 1096 */ 1097 @Override /* Overridden from HttpEntity */ 1098 public ResponseHeader getContentEncoding() { return new ResponseHeader("Content-Encoding", request, response, entity.getContentEncoding()); } 1099 1100 /** 1101 * Tells the length of the content, if known. 1102 * 1103 * @return 1104 * The number of bytes of the content, or a negative number if unknown. 1105 * <br>If the content length is known but exceeds {@link Long#MAX_VALUE}, a negative number is returned. 1106 */ 1107 @Override /* Overridden from HttpEntity */ 1108 public long getContentLength() { return nn(body) ? body.length : entity.getContentLength(); } 1109 1110 /** 1111 * Obtains the <c>Content-Type</c> header, if known. 1112 * 1113 * <p> 1114 * This is the header that should be used when sending the entity, or the one that was received with the entity. 1115 * It can include a charset attribute. 1116 * 1117 * @return The <c>Content-Type</c> header for this entity. Never <jk>null</jk>, but wraps a <jk>null</jk> value if the content type is unknown. 1118 */ 1119 @Override /* Overridden from HttpEntity */ 1120 public ResponseHeader getContentType() { return new ResponseHeader(HEADER_ContentType, request, response, entity.getContentType()); } 1121 1122 /** 1123 * Tells about chunked encoding for this entity. 1124 * 1125 * <p> 1126 * The primary purpose of this method is to indicate whether chunked encoding should be used when the entity is sent. 1127 * <br>For entities that are received, it can also indicate whether the entity was received with chunked encoding. 1128 * 1129 * <p> 1130 * The behavior of wrapping entities is implementation dependent, but should respect the primary purpose. 1131 * 1132 * @return <jk>true</jk> if chunked encoding is preferred for this entity, or <jk>false</jk> if it is not. 1133 */ 1134 @Override /* Overridden from HttpEntity */ 1135 public boolean isChunked() { return entity.isChunked(); } 1136 1137 /** 1138 * Tells if the entity is capable of producing its data more than once. 1139 * 1140 * <p> 1141 * A repeatable entity's {@link #getContent()} and {@link #writeTo(OutputStream)} methods can be called more than 1142 * once whereas a non-repeatable entity's can not. 1143 * 1144 * <h5 class='section'>Notes:</h5><ul> 1145 * <li class='note'>This method always returns <jk>true</jk> if the response body is cached (see {@link #cache()}). 1146 * </ul> 1147 * 1148 * @return <jk>true</jk> if the entity is repeatable, <jk>false</jk> otherwise. 1149 */ 1150 @Override /* Overridden from HttpEntity */ 1151 public boolean isRepeatable() { return cached || entity.isRepeatable(); } 1152 1153 /** 1154 * Tells whether this entity depends on an underlying stream. 1155 * 1156 * <h5 class='section'>Notes:</h5><ul> 1157 * <li class='note'>This method always returns <jk>false</jk> if the response body is cached (see {@link #cache()}. 1158 * </ul> 1159 * 1160 * @return <jk>true</jk> if the entity content is streamed, <jk>false</jk> otherwise. 1161 */ 1162 @Override /* Overridden from HttpEntity */ 1163 public boolean isStreaming() { return !cached && entity.isStreaming(); } 1164 1165 /** 1166 * Specifies the parser to use for this body. 1167 * 1168 * <p> 1169 * If not specified, uses the parser defined on the client set via {@link RestClient.Builder#parser(Class)}. 1170 * 1171 * @param value 1172 * The new parser to use for this body. 1173 * @return This object. 1174 */ 1175 public ResponseContent parser(Parser value) { 1176 parser = value; 1177 return this; 1178 } 1179 1180 /** 1181 * Pipes the contents of the response to the specified output stream. 1182 * 1183 * <h5 class='section'>Notes:</h5><ul> 1184 * <li class='note'> 1185 * The output stream is not automatically closed. 1186 * <li class='note'> 1187 * Once the input stream is exhausted, it will automatically be closed. 1188 * <li class='note'> 1189 * This method can be called multiple times if {@link #cache()} has been called. 1190 * <li class='note'> 1191 * Calling this method multiple times without caching enabled will cause a {@link RestCallException} 1192 * with an inner {@link IllegalStateException} to be thrown. 1193 * </ul> 1194 * 1195 * @param os The output stream to pipe the output to. 1196 * @return This object. 1197 * @throws IOException If an IO exception occurred. 1198 */ 1199 public RestResponse pipeTo(OutputStream os) throws IOException { 1200 pipe(asInputStream(), os); 1201 return response; 1202 } 1203 1204 /** 1205 * Pipes the contents of the response to the specified writer. 1206 * 1207 * <h5 class='section'>Notes:</h5><ul> 1208 * <li class='note'> 1209 * The writer is not automatically closed. 1210 * <li class='note'> 1211 * Once the reader is exhausted, it will automatically be closed. 1212 * <li class='note'> 1213 * If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed. 1214 * <li class='note'> 1215 * This method can be called multiple times if {@link #cache()} has been called. 1216 * <li class='note'> 1217 * Calling this method multiple times without caching enabled will cause a {@link RestCallException} 1218 * with an inner {@link IllegalStateException} to be thrown. 1219 * </ul> 1220 * 1221 * @param w The writer to pipe the output to. 1222 * @return This object. 1223 * @throws IOException If an IO exception occurred. 1224 */ 1225 public RestResponse pipeTo(Writer w) throws IOException { 1226 return pipeTo(w, false); 1227 } 1228 1229 /** 1230 * Pipes the contents of the response to the specified writer. 1231 * 1232 * <h5 class='section'>Notes:</h5><ul> 1233 * <li class='note'> 1234 * The writer is not automatically closed. 1235 * <li class='note'> 1236 * Once the reader is exhausted, it will automatically be closed. 1237 * <li class='note'> 1238 * If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed. 1239 * <li class='note'> 1240 * This method can be called multiple times if {@link #cache()} has been called. 1241 * <li class='note'> 1242 * Calling this method multiple times without caching enabled will cause a {@link RestCallException} 1243 * with an inner {@link IllegalStateException} to be thrown. 1244 * </ul> 1245 * 1246 * @param w The writer to write the output to. 1247 * @param byLines Flush the writers after every line of output. 1248 * @return This object. 1249 * @throws IOException If an IO exception occurred. 1250 */ 1251 public RestResponse pipeTo(Writer w, boolean byLines) throws IOException { 1252 return pipeTo(w, null, byLines); 1253 } 1254 1255 /** 1256 * Pipes the contents of the response to the specified writer. 1257 * 1258 * <h5 class='section'>Notes:</h5><ul> 1259 * <li class='note'> 1260 * The writer is not automatically closed. 1261 * <li class='note'> 1262 * Once the reader is exhausted, it will automatically be closed. 1263 * <li class='note'> 1264 * This method can be called multiple times if {@link #cache()} has been called. 1265 * <li class='note'> 1266 * Calling this method multiple times without caching enabled will cause a {@link RestCallException} 1267 * with an inner {@link IllegalStateException} to be thrown. 1268 * </ul> 1269 * 1270 * @param w The writer to pipe the output to. 1271 * @param charset 1272 * The charset to use for the reader. 1273 * <br>If <jk>null</jk>, <js>"UTF-8"</js> is used. 1274 * @return This object. 1275 * @throws IOException If an IO exception occurred. 1276 */ 1277 public RestResponse pipeTo(Writer w, Charset charset) throws IOException { 1278 return pipeTo(w, charset, false); 1279 } 1280 1281 /** 1282 * Pipes the contents of the response to the specified writer. 1283 * 1284 * <h5 class='section'>Notes:</h5><ul> 1285 * <li class='note'> 1286 * The writer is not automatically closed. 1287 * <li class='note'> 1288 * Once the reader is exhausted, it will automatically be closed. 1289 * <li class='note'> 1290 * This method can be called multiple times if {@link #cache()} has been called. 1291 * <li class='note'> 1292 * Calling this method multiple times without caching enabled will cause a {@link RestCallException} 1293 * with an inner {@link IllegalStateException} to be thrown. 1294 * </ul> 1295 * 1296 * @param w The writer to pipe the output to. 1297 * @param byLines Flush the writers after every line of output. 1298 * @param charset 1299 * The charset to use for the reader. 1300 * <br>If <jk>null</jk>, <js>"UTF-8"</js> is used. 1301 * @return This object. 1302 * @throws IOException If an IO exception occurred. 1303 */ 1304 public RestResponse pipeTo(Writer w, Charset charset, boolean byLines) throws IOException { 1305 if (byLines) 1306 pipeLines(asReader(charset), w); 1307 else 1308 pipe(asReader(charset), w); 1309 return response; 1310 } 1311 1312 /** 1313 * Returns the response that created this object. 1314 * 1315 * @return The response that created this object. 1316 */ 1317 public RestResponse response() { 1318 return response; 1319 } 1320 1321 /** 1322 * Specifies the schema for this body. 1323 * 1324 * <p> 1325 * Used by schema-based parsers such as {@link OpenApiParser}. 1326 * 1327 * @param value The schema. 1328 * @return This object. 1329 */ 1330 public ResponseContent schema(HttpPartSchema value) { 1331 schema = value; 1332 return this; 1333 } 1334 1335 @Override 1336 public String toString() { 1337 try { 1338 return asString(); 1339 } catch (RestCallException e) { 1340 return localizedMessage(e); 1341 } 1342 } 1343 1344 /** 1345 * Writes the entity content out to the output stream. 1346 * 1347 * <h5 class='section'>Notes:</h5><ul> 1348 * <li class='note'>This method is equivalent to {@link #pipeTo(OutputStream)} which is the preferred method for fluent-style coding. 1349 * </ul> 1350 * 1351 * @param outstream The output stream to write entity content to. 1352 */ 1353 @Override /* Overridden from HttpEntity */ 1354 public void writeTo(OutputStream outstream) throws IOException { 1355 pipeTo(outstream); 1356 } 1357 1358 private MarshallingContext getMarshallingContext() { return parser == null ? MarshallingContext.DEFAULT : parser.getMarshallingContext(); } 1359 1360 private <T> ClassMeta<T> getClassMeta(Class<T> c) { 1361 return getMarshallingContext().getClassMeta(c); 1362 } 1363 1364 private <T> ClassMeta<T> getClassMeta(Type type, Type...args) { 1365 return getMarshallingContext().getClassMeta(type, args); 1366 } 1367}