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.rest.client; 014 015import static org.apache.juneau.internal.ClassUtils.*; 016import static org.apache.juneau.internal.IOUtils.*; 017import static org.apache.juneau.internal.StringUtils.*; 018import static org.apache.juneau.httppart.HttpPartType.*; 019 020import java.io.*; 021import java.lang.reflect.*; 022import java.lang.reflect.Proxy; 023import java.net.*; 024import java.util.*; 025import java.util.concurrent.*; 026import java.util.logging.*; 027import java.util.regex.*; 028 029import org.apache.http.*; 030import org.apache.http.client.*; 031import org.apache.http.client.config.*; 032import org.apache.http.client.entity.*; 033import org.apache.http.client.methods.*; 034import org.apache.http.client.utils.*; 035import org.apache.http.conn.*; 036import org.apache.http.entity.*; 037import org.apache.http.entity.ContentType; 038import org.apache.http.impl.client.*; 039import org.apache.http.util.*; 040import org.apache.juneau.*; 041import org.apache.juneau.encoders.*; 042import org.apache.juneau.http.*; 043import org.apache.juneau.httppart.*; 044import org.apache.juneau.httppart.bean.*; 045import org.apache.juneau.internal.*; 046import org.apache.juneau.oapi.*; 047import org.apache.juneau.parser.*; 048import org.apache.juneau.parser.ParseException; 049import org.apache.juneau.serializer.*; 050import org.apache.juneau.utils.*; 051 052/** 053 * Represents a connection to a remote REST resource. 054 * 055 * <p> 056 * Instances of this class are created by the various {@code doX()} methods on the {@link RestClient} class. 057 * 058 * <p> 059 * This class uses only Java standard APIs. Requests can be built up using a fluent interface with method chaining, 060 * like so... 061 * <p class='bcode w800'> 062 * RestClient client = <jk>new</jk> RestClient(); 063 * RestCall c = client.doPost(<jsf>URL</jsf>).setInput(o).setHeader(x,y); 064 * MyBean b = c.getResponse(MyBean.<jk>class</jk>); 065 * </p> 066 * 067 * <p> 068 * The actual connection and request/response transaction occurs when calling one of the <code>getResponseXXX()</code> 069 * methods. 070 * 071 * <h5 class='section'>See Also:</h5> 072 * <ul> 073 * <li class='link'>{@doc juneau-rest-client} 074 * </ul> 075 */ 076@SuppressWarnings({ "unchecked" }) 077public final class RestCall extends BeanSession implements Closeable { 078 079 private static final ContentType TEXT_PLAIN = ContentType.create("text/plain"); 080 081 private final RestClient client; // The client that created this call. 082 private final HttpRequestBase request; // The request. 083 private HttpResponse response; // The response. 084 private List<RestCallInterceptor> interceptors = new ArrayList<>(); // Used for intercepting and altering requests. 085 086 private boolean isConnected = false; // connect() has been called. 087 private boolean allowRedirectsOnPosts; 088 private int retries = 1; 089 private int redirectOnPostsTries = 5; 090 private long retryInterval = -1; 091 private RetryOn retryOn; 092 private boolean ignoreErrors; 093 private boolean byLines = false; 094 private TeeWriter writers = new TeeWriter(); 095 private StringWriter capturedResponseWriter; 096 private String capturedResponse; 097 private TeeOutputStream outputStreams = new TeeOutputStream(); 098 private boolean isClosed = false; 099 private boolean isFailed = false; 100 private Object input; 101 private boolean hasInput; // input() was called, even if it's setting 'null'. 102 private Serializer serializer; 103 private Parser parser; 104 private HttpPartSerializer partSerializer; 105 private HttpPartParser partParser; 106 private HttpPartSchema requestBodySchema, responseBodySchema; 107 private URIBuilder uriBuilder; 108 private NameValuePairs formData; 109 private boolean softClose = false; // If true, don't consume response and set isClosed flag, but do call listeners. 110 111 /** 112 * Constructs a REST call with the specified method name. 113 * 114 * @param client The client that created this request. 115 * @param request The wrapped Apache HTTP client request object. 116 * @param uri The URI for this call. 117 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 118 */ 119 protected RestCall(RestClient client, HttpRequestBase request, URI uri) throws RestCallException { 120 super(client, BeanSessionArgs.DEFAULT); 121 this.client = client; 122 this.request = request; 123 for (RestCallInterceptor i : this.client.interceptors) 124 interceptor(i); 125 this.retryOn = client.retryOn; 126 this.retries = client.retries; 127 this.retryInterval = client.retryInterval; 128 this.serializer = client.serializer; 129 this.parser = client.parser; 130 this.partSerializer = client.getPartSerializer(); 131 this.partParser = client.getPartParser(); 132 uriBuilder = new URIBuilder(uri); 133 } 134 135 /** 136 * Sets the URI for this call. 137 * 138 * <p> 139 * Can be any of the following types: 140 * <ul> 141 * <li>{@link URI} 142 * <li>{@link URL} 143 * <li>{@link URIBuilder} 144 * <li>Anything else converted to a string using {@link Object#toString()}. 145 * </ul> 146 * 147 * <p> 148 * Relative URL strings will be interpreted as relative to the root URL defined on the client. 149 * 150 * @param uri 151 * The URI to use for this call. 152 * This overrides the URI passed in from the client. 153 * @return This object (for method chaining). 154 * @throws RestCallException 155 */ 156 public RestCall uri(Object uri) throws RestCallException { 157 try { 158 if (uri != null) 159 uriBuilder = new URIBuilder(client.toURI(uri)); 160 return this; 161 } catch (URISyntaxException e) { 162 throw new RestCallException(e); 163 } 164 } 165 166 /** 167 * Sets the URI scheme. 168 * 169 * @param scheme The new URI host. 170 * @return This object (for method chaining). 171 */ 172 public RestCall scheme(String scheme) { 173 uriBuilder.setScheme(scheme); 174 return this; 175 } 176 177 /** 178 * Sets the URI host. 179 * 180 * @param host The new URI host. 181 * @return This object (for method chaining). 182 */ 183 public RestCall host(String host) { 184 uriBuilder.setHost(host); 185 return this; 186 } 187 188 /** 189 * Sets the URI port. 190 * 191 * @param port The new URI port. 192 * @return This object (for method chaining). 193 */ 194 public RestCall port(int port) { 195 uriBuilder.setPort(port); 196 return this; 197 } 198 199 /** 200 * Adds a query parameter to the URI query. 201 * 202 * @param name 203 * The parameter name. 204 * Can be null/blank/* if the value is a {@link Map}, {@link String}, {@link NameValuePairs}, or bean. 205 * @param value 206 * The parameter value converted to a string using UON notation. 207 * Can also be {@link Map}, {@link String}, {@link NameValuePairs}, or bean if the name is null/blank/*. 208 * If a {@link String} and the name is null/blank/*, then calls {@link URIBuilder#setCustomQuery(String)}. 209 * @param skipIfEmpty Don't add the pair if the value is empty. 210 * @param serializer 211 * The serializer to use for serializing the value to a string value. 212 * If <jk>null</jk>, then the URL-encoding serializer defined on the client is used. 213 * @param schema 214 * The schema object that defines the format of the output. 215 * <br>If <jk>null</jk>, defaults to the schema defined on the serializer. 216 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 217 * <br>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}). 218 * @return This object (for method chaining). 219 * @throws RestCallException 220 */ 221 public RestCall query(String name, Object value, boolean skipIfEmpty, HttpPartSerializer serializer, HttpPartSchema schema) throws RestCallException { 222 if (serializer == null) 223 serializer = client.getPartSerializer(); 224 if (schema == null) 225 schema = HttpPartSchema.DEFAULT; 226 boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof NameValuePairs; 227 if (! isMulti) { 228 if (canAdd(value, schema, skipIfEmpty)) 229 try { 230 uriBuilder.addParameter(name, serializer.serialize(QUERY, schema, value)); 231 } catch (SchemaValidationException e) { 232 throw new RestCallException(e, "Validation error on request query parameter ''{0}''=''{1}''", name, value); 233 } catch (SerializeException e) { 234 throw new RestCallException(e, "Serialization error on request query parameter ''{0}''", name); 235 } 236 } else if (value instanceof NameValuePairs) { 237 for (NameValuePair p : (NameValuePairs)value) { 238 String n = p.getName(); 239 String v = p.getValue(); 240 HttpPartSchema s = schema.getProperty(n); 241 if (canAdd(v, s, skipIfEmpty)) 242 query(n, v, skipIfEmpty, serializer, s); 243 } 244 } else if (value instanceof Map) { 245 for (Map.Entry<String,Object> p : ((Map<String,Object>) value).entrySet()) { 246 String n = p.getKey(); 247 Object v = p.getValue(); 248 HttpPartSchema s = schema.getProperty(n); 249 if (canAdd(v, s, skipIfEmpty)) 250 query(n, v, skipIfEmpty, serializer, s); 251 } 252 } else if (isBean(value)) { 253 return query(name, toBeanMap(value), skipIfEmpty, serializer, schema); 254 } else if (value instanceof Reader || value instanceof InputStream) { 255 try { 256 uriBuilder.setCustomQuery(read(value)); 257 } catch (IOException e) { 258 throw new RestCallException(e); 259 } 260 } else if (value instanceof CharSequence) { 261 String s = value.toString(); 262 if (isNotEmpty(s)) 263 uriBuilder.setCustomQuery(s); 264 } else { 265 throw new RestCallException("Invalid name ''{0}'' passed to query(name,value,skipIfEmpty) for data type ''{1}''", name, getReadableClassNameForObject(value)); 266 } 267 return this; 268 } 269 270 /** 271 * Adds a query parameter to the URI query. 272 * 273 * @param name The parameter name. 274 * @param value The parameter value converted to a string using UON notation. 275 * @return This object (for method chaining). 276 * @throws RestCallException 277 */ 278 public RestCall query(String name, Object value) throws RestCallException { 279 return query(name, value, false, null, null); 280 } 281 282 /** 283 * Adds query parameters to the URI query. 284 * 285 * @param params The parameters. Values are converted to a string using UON notation. 286 * @return This object (for method chaining). 287 * @throws RestCallException 288 */ 289 public RestCall query(Map<String,Object> params) throws RestCallException { 290 return query(null, params); 291 } 292 293 /** 294 * Adds a query parameter to the URI query if the parameter value is not <jk>null</jk> or an empty string. 295 * 296 * <p> 297 * NE = "not empty" 298 * 299 * @param name The parameter name. 300 * @param value The parameter value converted to a string using UON notation. 301 * @return This object (for method chaining). 302 * @throws RestCallException 303 */ 304 public RestCall queryIfNE(String name, Object value) throws RestCallException { 305 return query(name, value, true, null, null); 306 } 307 308 /** 309 * Adds query parameters to the URI for any parameters that aren't null/empty. 310 * 311 * <p> 312 * NE = "not empty" 313 * 314 * @param params The parameters. Values are converted to a string using UON notation. 315 * @return This object (for method chaining). 316 * @throws RestCallException 317 */ 318 public RestCall queryIfNE(Map<String,Object> params) throws RestCallException { 319 return query(null, params, true, null, null); 320 } 321 322 /** 323 * Sets a custom URI query. 324 * 325 * @param query The new URI query string. 326 * @return This object (for method chaining). 327 */ 328 public RestCall query(String query) { 329 uriBuilder.setCustomQuery(query); 330 return this; 331 } 332 333 /** 334 * Adds a form data pair to this request to perform a URL-encoded form post. 335 * 336 * @param name 337 * The parameter name. 338 * Can be null/blank/* if the value is a {@link Map}, {@link NameValuePairs}, or bean. 339 * @param value 340 * The parameter value converted to a string using UON notation. 341 * Can also be {@link Map}, {@link NameValuePairs}, or bean if the name is null/blank/*. 342 * @param skipIfEmpty Don't add the pair if the value is empty. 343 * @param serializer 344 * The serializer to use for serializing the value to a string value. 345 * If <jk>null</jk>, then the URL-encoding serializer defined on the client is used. 346 * @param schema 347 * The schema object that defines the format of the output. 348 * <br>If <jk>null</jk>, defaults to the schema defined on the serializer. 349 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 350 * <br>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}). 351 * @return This object (for method chaining). 352 * @throws RestCallException 353 */ 354 public RestCall formData(String name, Object value, boolean skipIfEmpty, HttpPartSerializer serializer, HttpPartSchema schema) throws RestCallException { 355 if (formData == null) 356 formData = new NameValuePairs(); 357 if (serializer == null) 358 serializer = client.getPartSerializer(); 359 if (schema == null) 360 schema = HttpPartSchema.DEFAULT; 361 boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof NameValuePairs; 362 if (! isMulti) { 363 if (canAdd(value, schema, skipIfEmpty)) 364 formData.add(new SerializedNameValuePair(name, value, serializer, schema)); 365 } else if (value instanceof NameValuePairs) { 366 for (NameValuePair p : (NameValuePairs)value) { 367 String n = p.getName(); 368 String v = p.getValue(); 369 HttpPartSchema s = schema.getProperty(n); 370 if (canAdd(v, s, skipIfEmpty)) 371 formData.add(p); 372 } 373 } else if (value instanceof Map) { 374 for (Map.Entry<String,Object> p : ((Map<String,Object>) value).entrySet()) { 375 String n = p.getKey(); 376 Object v = p.getValue(); 377 HttpPartSchema s = schema.getProperty(n); 378 if (canAdd(v, s, skipIfEmpty)) 379 formData(n, v, skipIfEmpty, serializer, s); 380 } 381 } else if (isBean(value)) { 382 return formData(name, toBeanMap(value), skipIfEmpty, serializer, schema); 383 } else if (value instanceof Reader || value instanceof InputStream) { 384 contentType("application/x-www-form-urlencoded"); 385 body(value); 386 } else if (value instanceof CharSequence) { 387 try { 388 contentType("application/x-www-form-urlencoded"); 389 body(new StringEntity(value.toString())); 390 } catch (UnsupportedEncodingException e) {} 391 } else { 392 throw new FormattedRuntimeException("Invalid name ''{0}'' passed to formData(name,value,skipIfEmpty) for data type ''{1}''", name, getReadableClassNameForObject(value)); 393 } 394 return this; 395 } 396 397 /** 398 * Adds a form data pair to this request to perform a URL-encoded form post. 399 * 400 * @param name 401 * The parameter name. 402 * Can be null/blank if the value is a {@link Map} or {@link NameValuePairs}. 403 * @param value 404 * The parameter value converted to a string using UON notation. 405 * Can also be a {@link Map} or {@link NameValuePairs}. 406 * @return This object (for method chaining). 407 * @throws RestCallException If name was null/blank and value wasn't a {@link Map} or {@link NameValuePairs}. 408 */ 409 public RestCall formData(String name, Object value) throws RestCallException { 410 return formData(name, value, false, null, null); 411 } 412 413 /** 414 * Adds form data pairs to this request to perform a URL-encoded form post. 415 * 416 * @param nameValuePairs The name-value pairs of the request. 417 * @return This object (for method chaining). 418 * @throws RestCallException 419 */ 420 public RestCall formData(NameValuePairs nameValuePairs) throws RestCallException { 421 return formData(null, nameValuePairs); 422 } 423 424 /** 425 * Adds form data pairs to this request to perform a URL-encoded form post. 426 * 427 * @param params The parameters. Values are converted to a string using UON notation. 428 * @return This object (for method chaining). 429 * @throws RestCallException If name was null/blank and value wasn't a {@link Map} or {@link NameValuePairs}. 430 */ 431 public RestCall formData(Map<String,Object> params) throws RestCallException { 432 return formData(null, params); 433 } 434 435 /** 436 * Adds a form data pair to the request if the parameter value is not <jk>null</jk> or an empty string. 437 * 438 * <p> 439 * NE = "not empty" 440 * 441 * @param name The parameter name. 442 * @param value The parameter value converted to a string using UON notation. 443 * @return This object (for method chaining). 444 * @throws RestCallException 445 */ 446 public RestCall formDataIfNE(String name, Object value) throws RestCallException { 447 return formData(name, value, true, null, null); 448 } 449 450 /** 451 * Adds form data parameters to the request for any parameters that aren't null/empty. 452 * 453 * <p> 454 * NE = "not empty" 455 * 456 * @param params The parameters. Values are converted to a string using UON notation. 457 * @return This object (for method chaining). 458 * @throws RestCallException 459 */ 460 public RestCall formDataIfNE(Map<String,Object> params) throws RestCallException { 461 return formData(null, params, true, null, null); 462 } 463 464 /** 465 * Replaces a variable of the form <js>"{name}"</js> in the URL path with the specified value. 466 * 467 * @param name The path variable name. 468 * @param value The replacement value. 469 * @param serializer 470 * The serializer to use for serializing the value to a string value. 471 * If <jk>null</jk>, then the URL-encoding serializer defined on the client is used. 472 * @param schema 473 * The schema object that defines the format of the output. 474 * <br>If <jk>null</jk>, defaults to the schema defined on the serializer. 475 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 476 * <br>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}). 477 * @return This object (for method chaining). 478 * @throws RestCallException If variable could not be found in path. 479 */ 480 public RestCall path(String name, Object value, HttpPartSerializer serializer, HttpPartSchema schema) throws RestCallException { 481 String path = uriBuilder.getPath(); 482 if (serializer == null) 483 serializer = client.getPartSerializer(); 484 if (schema == null) 485 schema = HttpPartSchema.DEFAULT; 486 boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof NameValuePairs; 487 if (! isMulti) { 488 String var = "{" + name + "}"; 489 if (path.indexOf(var) == -1 && ! name.equals("/*")) 490 throw new RestCallException("Path variable {"+name+"} was not found in path."); 491 try { 492 String p = null; 493 if (name.equals("/*")) 494 p = path.replaceAll("\\/\\*$", serializer.serialize(PATH, schema, value)); 495 else 496 p = path.replace(var, serializer.serialize(PATH, schema, value)); 497 uriBuilder.setPath(p); 498 } catch (SchemaValidationException e) { 499 throw new RestCallException(e, "Validation error on request path parameter ''{0}''=''{1}''", name, value); 500 } catch (SerializeException e) { 501 throw new RestCallException(e, "Serialization error on request path parameter ''{0}''", name); 502 } 503 } else if (value instanceof NameValuePairs) { 504 for (NameValuePair p : (NameValuePairs)value) { 505 String n = p.getName(); 506 String v = p.getValue(); 507 HttpPartSchema s = schema.getProperty(n); 508 path(n, v, serializer, s); 509 } 510 } else if (value instanceof Map) { 511 for (Map.Entry<String,Object> p : ((Map<String,Object>) value).entrySet()) { 512 String n = p.getKey(); 513 Object v = p.getValue(); 514 HttpPartSchema s = schema.getProperty(n); 515 path(n, v, serializer, s); 516 } 517 } else if (isBean(value)) { 518 return path(name, toBeanMap(value), serializer, schema); 519 } else if (value != null) { 520 throw new RestCallException("Invalid name ''{0}'' passed to path(name,value) for data type ''{1}''", name, getReadableClassNameForObject(value)); 521 } 522 return this; 523 } 524 525 /** 526 * Replaces a variable of the form <js>"{name}"</js> in the URL path with the specified value. 527 * 528 * @param name The path variable name. 529 * @param value The replacement value. 530 * @return This object (for method chaining). 531 * @throws RestCallException If variable could not be found in path. 532 */ 533 public RestCall path(String name, Object value) throws RestCallException { 534 return path(name, value, null, null); 535 } 536 537 /** 538 * Sets the URI user info. 539 * 540 * @param userInfo The new URI user info. 541 * @return This object (for method chaining). 542 */ 543 public RestCall userInfo(String userInfo) { 544 uriBuilder.setUserInfo(userInfo); 545 return this; 546 } 547 548 /** 549 * Sets the URI user info. 550 * 551 * @param username The new URI username. 552 * @param password The new URI password. 553 * @return This object (for method chaining). 554 */ 555 public RestCall userInfo(String username, String password) { 556 uriBuilder.setUserInfo(username, password); 557 return this; 558 } 559 560 /** 561 * Specifies the part schema for the request body. 562 * 563 * <p> 564 * This is only useful for schema-aware serializers such as {@link OpenApiSerializer}. 565 * 566 * @param value 567 * The new part schema for the request body. 568 * <br>Can be <jk>null</jk>. 569 * @return This object (for method chaining). 570 */ 571 public RestCall requestBodySchema(HttpPartSchema value) { 572 this.requestBodySchema = value; 573 return this; 574 } 575 576 /** 577 * Specifies the part schema for the response body. 578 * 579 * <p> 580 * This is only useful for schema-aware parsers such as {@link OpenApiParser}. 581 * 582 * @param value 583 * The new part schema for the response body. 584 * <br>Can be <jk>null</jk>. 585 * @return This object (for method chaining). 586 */ 587 public RestCall responseBodySchema(HttpPartSchema value) { 588 this.responseBodySchema = value; 589 return this; 590 } 591 592 /** 593 * Sets the input for this REST call. 594 * 595 * @param input 596 * The input to be sent to the REST resource (only valid for PUT and POST) requests. 597 * <br>Can be of the following types: 598 * <ul class='spaced-list'> 599 * <li> 600 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 601 * <li> 602 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 603 * <li> 604 * {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the 605 * {@link RestClient}. 606 * <li> 607 * {@link HttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. 608 * <li> 609 * {@link NameValuePairs} - Converted to a URL-encoded FORM post. 610 * </ul> 611 * @return This object (for method chaining). 612 * @throws RestCallException If a retry was attempted, but the entity was not repeatable. 613 */ 614 public RestCall body(Object input) throws RestCallException { 615 this.input = input; 616 this.hasInput = true; 617 this.formData = null; 618 return this; 619 } 620 621 /** 622 * Specifies the serializer to use on this call. 623 * 624 * <p> 625 * Overrides the serializer specified on the {@link RestClient}. 626 * 627 * @param serializer The serializer used to serialize POJOs to the body of the HTTP request. 628 * @return This object (for method chaining). 629 */ 630 public RestCall serializer(Serializer serializer) { 631 this.serializer = serializer; 632 return this; 633 } 634 635 /** 636 * Specifies the parser to use on this call. 637 * 638 * <p> 639 * Overrides the parser specified on the {@link RestClient}. 640 * 641 * @param parser The parser used to parse POJOs from the body of the HTTP response. 642 * @return This object (for method chaining). 643 */ 644 public RestCall parser(Parser parser) { 645 this.parser = parser; 646 return this; 647 } 648 649 //----------------------------------------------------------------------------------------------------------------- 650 // HTTP headers 651 //----------------------------------------------------------------------------------------------------------------- 652 653 /** 654 * Sets a header on the request. 655 * 656 * @param name 657 * The header name. 658 * The name can be null/empty if the value is a {@link Map}. 659 * @param value The header value. 660 * @param skipIfEmpty Don't add the header if the name is null/empty. 661 * @param serializer 662 * The serializer to use for serializing the value to a string value. 663 * If <jk>null</jk>, then the URL-encoding serializer defined on the client is used. 664 * @param schema 665 * The schema object that defines the format of the output. 666 * <br>If <jk>null</jk>, defaults to the schema defined on the serializer. 667 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 668 * <br>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}). 669 * @return This object (for method chaining). 670 * @throws RestCallException 671 */ 672 public RestCall header(String name, Object value, boolean skipIfEmpty, HttpPartSerializer serializer, HttpPartSchema schema) throws RestCallException { 673 if (serializer == null) 674 serializer = client.getPartSerializer(); 675 if (schema == null) 676 schema = HttpPartSchema.DEFAULT; 677 boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof NameValuePairs; 678 if (! isMulti) { 679 if (canAdd(value, schema, skipIfEmpty)) 680 try { 681 request.setHeader(name, serializer.serialize(HEADER, schema, value)); 682 } catch (SchemaValidationException e) { 683 throw new RestCallException(e, "Validation error on request header parameter ''{0}''=''{1}''", name, value); 684 } catch (SerializeException e) { 685 throw new RestCallException(e, "Serialization error on request header parameter ''{0}''", name); 686 } 687 } else if (value instanceof NameValuePairs) { 688 for (NameValuePair p : (NameValuePairs)value) { 689 String n = p.getName(); 690 String v = p.getValue(); 691 HttpPartSchema s = schema.getProperty(n); 692 if (canAdd(v, s, skipIfEmpty)) 693 header(n, v, skipIfEmpty, serializer, s); 694 } 695 } else if (value instanceof Map) { 696 for (Map.Entry<String,Object> p : ((Map<String,Object>) value).entrySet()) { 697 String n = p.getKey(); 698 Object v = p.getValue(); 699 HttpPartSchema s = schema.getProperty(n); 700 if (canAdd(v, s, skipIfEmpty)) 701 header(n, v, skipIfEmpty, serializer, s); 702 } 703 } else if (isBean(value)) { 704 return header(name, toBeanMap(value), skipIfEmpty, serializer, schema); 705 } else { 706 throw new RestCallException("Invalid name ''{0}'' passed to header(name,value,skipIfEmpty) for data type ''{1}''", name, getReadableClassNameForObject(value)); 707 } 708 return this; 709 } 710 711 712 /** 713 * Sets a header on the request. 714 * 715 * @param name 716 * The header name. 717 * The name can be null/empty if the value is a {@link Map}. 718 * @param value The header value. 719 * @return This object (for method chaining). 720 * @throws RestCallException 721 */ 722 public RestCall header(String name, Object value) throws RestCallException { 723 return header(name, value, false, null, null); 724 } 725 726 /** 727 * Sets headers on the request. 728 * 729 * @param values The header values. 730 * @return This object (for method chaining). 731 * @throws RestCallException 732 */ 733 public RestCall headers(Map<String,Object> values) throws RestCallException { 734 return header(null, values, false, null, null); 735 } 736 737 /** 738 * Sets a header on the request if the value is not null/empty. 739 * 740 * <p> 741 * NE = "not empty" 742 * 743 * @param name 744 * The header name. 745 * The name can be null/empty if the value is a {@link Map}. 746 * @param value The header value. 747 * @return This object (for method chaining). 748 * @throws RestCallException 749 */ 750 public RestCall headerIfNE(String name, Object value) throws RestCallException { 751 return header(name, value, true, null, null); 752 } 753 754 /** 755 * Sets headers on the request if the values are not null/empty. 756 * 757 * <p> 758 * NE = "not empty" 759 * 760 * @param values The header values. 761 * @return This object (for method chaining). 762 * @throws RestCallException 763 */ 764 public RestCall headersIfNE(Map<String,Object> values) throws RestCallException { 765 return header(null, values, true, null, null); 766 } 767 768 /** 769 * Sets the value for the <code>Accept</code> request header. 770 * 771 * <p> 772 * This overrides the media type specified on the parser, but is overridden by calling 773 * <code>header(<js>"Accept"</js>, value);</code> 774 * 775 * @param value The new header value. 776 * @return This object (for method chaining). 777 * @throws RestCallException 778 */ 779 public RestCall accept(Object value) throws RestCallException { 780 return header("Accept", value); 781 } 782 783 /** 784 * Sets the value for the <code>Accept-Charset</code> request header. 785 * 786 * <p> 787 * This is a shortcut for calling <code>header(<js>"Accept-Charset"</js>, value);</code> 788 * 789 * @param value The new header value. 790 * @return This object (for method chaining). 791 * @throws RestCallException 792 */ 793 public RestCall acceptCharset(Object value) throws RestCallException { 794 return header("Accept-Charset", value); 795 } 796 797 /** 798 * Sets the value for the <code>Accept-Encoding</code> request header. 799 * 800 * <p> 801 * This is a shortcut for calling <code>header(<js>"Accept-Encoding"</js>, value);</code> 802 * 803 * @param value The new header value. 804 * @return This object (for method chaining). 805 * @throws RestCallException 806 */ 807 public RestCall acceptEncoding(Object value) throws RestCallException { 808 return header("Accept-Encoding", value); 809 } 810 811 /** 812 * Sets the value for the <code>Accept-Language</code> request header. 813 * 814 * <p> 815 * This is a shortcut for calling <code>header(<js>"Accept-Language"</js>, value);</code> 816 * 817 * @param value The new header value. 818 * @return This object (for method chaining). 819 * @throws RestCallException 820 */ 821 public RestCall acceptLanguage(Object value) throws RestCallException { 822 return header("Accept-Language", value); 823 } 824 825 /** 826 * Sets the value for the <code>Authorization</code> request header. 827 * 828 * <p> 829 * This is a shortcut for calling <code>header(<js>"Authorization"</js>, value);</code> 830 * 831 * @param value The new header value. 832 * @return This object (for method chaining). 833 * @throws RestCallException 834 */ 835 public RestCall authorization(Object value) throws RestCallException { 836 return header("Authorization", value); 837 } 838 839 /** 840 * Sets the value for the <code>Cache-Control</code> request header. 841 * 842 * <p> 843 * This is a shortcut for calling <code>header(<js>"Cache-Control"</js>, value);</code> 844 * 845 * @param value The new header value. 846 * @return This object (for method chaining). 847 * @throws RestCallException 848 */ 849 public RestCall cacheControl(Object value) throws RestCallException { 850 return header("Cache-Control", value); 851 } 852 853 /** 854 * Sets the value for the <code>Connection</code> request header. 855 * 856 * <p> 857 * This is a shortcut for calling <code>header(<js>"Connection"</js>, value);</code> 858 * 859 * @param value The new header value. 860 * @return This object (for method chaining). 861 * @throws RestCallException 862 */ 863 public RestCall connection(Object value) throws RestCallException { 864 return header("Connection", value); 865 } 866 867 /** 868 * Sets the value for the <code>Content-Length</code> request header. 869 * 870 * <p> 871 * This is a shortcut for calling <code>header(<js>"Content-Length"</js>, value);</code> 872 * 873 * @param value The new header value. 874 * @return This object (for method chaining). 875 * @throws RestCallException 876 */ 877 public RestCall contentLength(Object value) throws RestCallException { 878 return header("Content-Length", value); 879 } 880 881 /** 882 * Sets the value for the <code>Content-Type</code> request header. 883 * 884 * <p> 885 * This overrides the media type specified on the serializer, but is overridden by calling 886 * <code>header(<js>"Content-Type"</js>, value);</code> 887 * 888 * @param value The new header value. 889 * @return This object (for method chaining). 890 * @throws RestCallException 891 */ 892 public RestCall contentType(Object value) throws RestCallException { 893 return header("Content-Type", value); 894 } 895 896 /** 897 * Sets the value for the <code>Date</code> request header. 898 * 899 * <p> 900 * This is a shortcut for calling <code>header(<js>"Date"</js>, value);</code> 901 * 902 * @param value The new header value. 903 * @return This object (for method chaining). 904 * @throws RestCallException 905 */ 906 public RestCall date(Object value) throws RestCallException { 907 return header("Date", value); 908 } 909 910 /** 911 * Sets the value for the <code>Expect</code> request header. 912 * 913 * <p> 914 * This is a shortcut for calling <code>header(<js>"Expect"</js>, value);</code> 915 * 916 * @param value The new header value. 917 * @return This object (for method chaining). 918 * @throws RestCallException 919 */ 920 public RestCall expect(Object value) throws RestCallException { 921 return header("Expect", value); 922 } 923 924 /** 925 * Sets the value for the <code>Forwarded</code> request header. 926 * 927 * <p> 928 * This is a shortcut for calling <code>header(<js>"Forwarded"</js>, value);</code> 929 * 930 * @param value The new header value. 931 * @return This object (for method chaining). 932 * @throws RestCallException 933 */ 934 public RestCall forwarded(Object value) throws RestCallException { 935 return header("Forwarded", value); 936 } 937 938 /** 939 * Sets the value for the <code>From</code> request header. 940 * 941 * <p> 942 * This is a shortcut for calling <code>header(<js>"From"</js>, value);</code> 943 * 944 * @param value The new header value. 945 * @return This object (for method chaining). 946 * @throws RestCallException 947 */ 948 public RestCall from(Object value) throws RestCallException { 949 return header("From", value); 950 } 951 952 /** 953 * Sets the value for the <code>Host</code> request header. 954 * 955 * <p> 956 * This is a shortcut for calling <code>header(<js>"Host"</js>, value);</code> 957 * 958 * @param value The new header value. 959 * @return This object (for method chaining). 960 * @throws RestCallException 961 */ 962 public RestCall host(Object value) throws RestCallException { 963 return header("Host", value); 964 } 965 966 /** 967 * Sets the value for the <code>If-Match</code> request header. 968 * 969 * <p> 970 * This is a shortcut for calling <code>header(<js>"If-Match"</js>, value);</code> 971 * 972 * @param value The new header value. 973 * @return This object (for method chaining). 974 * @throws RestCallException 975 */ 976 public RestCall ifMatch(Object value) throws RestCallException { 977 return header("If-Match", value); 978 } 979 980 /** 981 * Sets the value for the <code>If-Modified-Since</code> request header. 982 * 983 * <p> 984 * This is a shortcut for calling <code>header(<js>"If-Modified-Since"</js>, value);</code> 985 * 986 * @param value The new header value. 987 * @return This object (for method chaining). 988 * @throws RestCallException 989 */ 990 public RestCall ifModifiedSince(Object value) throws RestCallException { 991 return header("If-Modified-Since", value); 992 } 993 994 /** 995 * Sets the value for the <code>If-None-Match</code> request header. 996 * 997 * <p> 998 * This is a shortcut for calling <code>header(<js>"If-None-Match"</js>, value);</code> 999 * 1000 * @param value The new header value. 1001 * @return This object (for method chaining). 1002 * @throws RestCallException 1003 */ 1004 public RestCall ifNoneMatch(Object value) throws RestCallException { 1005 return header("If-None-Match", value); 1006 } 1007 1008 /** 1009 * Sets the value for the <code>If-Range</code> request header. 1010 * 1011 * <p> 1012 * This is a shortcut for calling <code>header(<js>"If-Range"</js>, value);</code> 1013 * 1014 * @param value The new header value. 1015 * @return This object (for method chaining). 1016 * @throws RestCallException 1017 */ 1018 public RestCall ifRange(Object value) throws RestCallException { 1019 return header("If-Range", value); 1020 } 1021 1022 /** 1023 * Sets the value for the <code>If-Unmodified-Since</code> request header. 1024 * 1025 * <p> 1026 * This is a shortcut for calling <code>header(<js>"If-Unmodified-Since"</js>, value);</code> 1027 * 1028 * @param value The new header value. 1029 * @return This object (for method chaining). 1030 * @throws RestCallException 1031 */ 1032 public RestCall ifUnmodifiedSince(Object value) throws RestCallException { 1033 return header("If-Unmodified-Since", value); 1034 } 1035 1036 /** 1037 * Sets the value for the <code>Max-Forwards</code> request header. 1038 * 1039 * <p> 1040 * This is a shortcut for calling <code>header(<js>"Max-Forwards"</js>, value);</code> 1041 * 1042 * @param value The new header value. 1043 * @return This object (for method chaining). 1044 * @throws RestCallException 1045 */ 1046 public RestCall maxForwards(Object value) throws RestCallException { 1047 return header("Max-Forwards", value); 1048 } 1049 1050 /** 1051 * Sets the value for the <code>Origin</code> request header. 1052 * 1053 * <p> 1054 * This is a shortcut for calling <code>header(<js>"Origin"</js>, value);</code> 1055 * 1056 * @param value The new header value. 1057 * @return This object (for method chaining). 1058 * @throws RestCallException 1059 */ 1060 public RestCall origin(Object value) throws RestCallException { 1061 return header("Origin", value); 1062 } 1063 1064 /** 1065 * Sets the value for the <code>Pragma</code> request header. 1066 * 1067 * <p> 1068 * This is a shortcut for calling <code>header(<js>"Pragma"</js>, value);</code> 1069 * 1070 * @param value The new header value. 1071 * @return This object (for method chaining). 1072 * @throws RestCallException 1073 */ 1074 public RestCall pragma(Object value) throws RestCallException { 1075 return header("Pragma", value); 1076 } 1077 1078 /** 1079 * Sets the value for the <code>Proxy-Authorization</code> request header. 1080 * 1081 * <p> 1082 * This is a shortcut for calling <code>header(<js>"Proxy-Authorization"</js>, value);</code> 1083 * 1084 * @param value The new header value. 1085 * @return This object (for method chaining). 1086 * @throws RestCallException 1087 */ 1088 public RestCall proxyAuthorization(Object value) throws RestCallException { 1089 return header("Proxy-Authorization", value); 1090 } 1091 1092 /** 1093 * Sets the value for the <code>Range</code> request header. 1094 * 1095 * <p> 1096 * This is a shortcut for calling <code>header(<js>"Range"</js>, value);</code> 1097 * 1098 * @param value The new header value. 1099 * @return This object (for method chaining). 1100 * @throws RestCallException 1101 */ 1102 public RestCall range(Object value) throws RestCallException { 1103 return header("Range", value); 1104 } 1105 1106 /** 1107 * Sets the value for the <code>Referer</code> request header. 1108 * 1109 * <p> 1110 * This is a shortcut for calling <code>header(<js>"Referer"</js>, value);</code> 1111 * 1112 * @param value The new header value. 1113 * @return This object (for method chaining). 1114 * @throws RestCallException 1115 */ 1116 public RestCall referer(Object value) throws RestCallException { 1117 return header("Referer", value); 1118 } 1119 1120 /** 1121 * Sets the value for the <code>TE</code> request header. 1122 * 1123 * <p> 1124 * This is a shortcut for calling <code>header(<js>"TE"</js>, value);</code> 1125 * 1126 * @param value The new header value. 1127 * @return This object (for method chaining). 1128 * @throws RestCallException 1129 */ 1130 public RestCall te(Object value) throws RestCallException { 1131 return header("TE", value); 1132 } 1133 1134 /** 1135 * Sets the value for the <code>User-Agent</code> request header. 1136 * 1137 * <p> 1138 * This is a shortcut for calling <code>header(<js>"User-Agent"</js>, value);</code> 1139 * 1140 * @param value The new header value. 1141 * @return This object (for method chaining). 1142 * @throws RestCallException 1143 */ 1144 public RestCall userAgent(Object value) throws RestCallException { 1145 return header("User-Agent", value); 1146 } 1147 1148 /** 1149 * Sets the value for the <code>Upgrade</code> request header. 1150 * 1151 * <p> 1152 * This is a shortcut for calling <code>header(<js>"Upgrade"</js>, value);</code> 1153 * 1154 * @param value The new header value. 1155 * @return This object (for method chaining). 1156 * @throws RestCallException 1157 */ 1158 public RestCall upgrade(Object value) throws RestCallException { 1159 return header("Upgrade", value); 1160 } 1161 1162 /** 1163 * Sets the value for the <code>Via</code> request header. 1164 * 1165 * <p> 1166 * This is a shortcut for calling <code>header(<js>"Via"</js>, value);</code> 1167 * 1168 * @param value The new header value. 1169 * @return This object (for method chaining). 1170 * @throws RestCallException 1171 */ 1172 public RestCall via(Object value) throws RestCallException { 1173 return header("Via", value); 1174 } 1175 1176 /** 1177 * Sets the value for the <code>Warning</code> request header. 1178 * 1179 * <p> 1180 * This is a shortcut for calling <code>header(<js>"Warning"</js>, value);</code> 1181 * 1182 * @param value The new header value. 1183 * @return This object (for method chaining). 1184 * @throws RestCallException 1185 */ 1186 public RestCall warning(Object value) throws RestCallException { 1187 return header("Warning", value); 1188 } 1189 1190 /** 1191 * Sets the client version by setting the value for the <js>"X-Client-Version"</js> header. 1192 * 1193 * @param version The version string (e.g. <js>"1.2.3"</js>) 1194 * @return This object (for method chaining). 1195 * @throws RestCallException 1196 */ 1197 public RestCall clientVersion(String version) throws RestCallException { 1198 return header("X-Client-Version", version); 1199 } 1200 1201 /** 1202 * Make this call retryable if an error response (>=400) is received. 1203 * 1204 * @param retries The number of retries to attempt. 1205 * @param interval The time in milliseconds between attempts. 1206 * @param retryOn 1207 * Optional object used for determining whether a retry should be attempted. 1208 * If <jk>null</jk>, uses {@link RetryOn#DEFAULT}. 1209 * @return This object (for method chaining). 1210 * @throws RestCallException If current entity is not repeatable. 1211 */ 1212 public RestCall retryable(int retries, long interval, RetryOn retryOn) throws RestCallException { 1213 if (request instanceof HttpEntityEnclosingRequestBase) { 1214 if (input != null && input instanceof HttpEntity) { 1215 HttpEntity e = (HttpEntity)input; 1216 if (e != null && ! e.isRepeatable()) 1217 throw new RestCallException("Attempt to make call retryable, but entity is not repeatable."); 1218 } 1219 } 1220 this.retries = retries; 1221 this.retryInterval = interval; 1222 this.retryOn = (retryOn == null ? RetryOn.DEFAULT : retryOn); 1223 return this; 1224 1225 } 1226 1227 /** 1228 * For this call, allow automatic redirects when a 302 or 307 occurs when performing a POST. 1229 * 1230 * <p> 1231 * Note that this can be inefficient since the POST body needs to be serialized twice. 1232 * The preferred approach if possible is to use the {@link LaxRedirectStrategy} strategy on the underlying HTTP 1233 * client. 1234 * However, this method is provided if you don't have access to the underlying client. 1235 * 1236 * @param b Redirect flag. 1237 * @return This object (for method chaining). 1238 */ 1239 public RestCall allowRedirectsOnPosts(boolean b) { 1240 this.allowRedirectsOnPosts = b; 1241 return this; 1242 } 1243 1244 /** 1245 * Specify the number of redirects to follow before throwing an exception. 1246 * 1247 * @param maxAttempts Allow a redirect to occur this number of times. 1248 * @return This object (for method chaining). 1249 */ 1250 public RestCall redirectMaxAttempts(int maxAttempts) { 1251 this.redirectOnPostsTries = maxAttempts; 1252 return this; 1253 } 1254 1255 /** 1256 * Add an interceptor for this call only. 1257 * 1258 * @param interceptor The interceptor to add to this call. 1259 * @return This object (for method chaining). 1260 */ 1261 public RestCall interceptor(RestCallInterceptor interceptor) { 1262 interceptors.add(interceptor); 1263 interceptor.onInit(this); 1264 return this; 1265 } 1266 1267 /** 1268 * Pipes the request output to the specified writer when {@link #run()} is called. 1269 * 1270 * <p> 1271 * The writer is not closed. 1272 * 1273 * <p> 1274 * This method can be called multiple times to pipe to multiple writers. 1275 * 1276 * @param w The writer to pipe the output to. 1277 * @return This object (for method chaining). 1278 */ 1279 public RestCall pipeTo(Writer w) { 1280 return pipeTo(w, false); 1281 } 1282 1283 /** 1284 * Pipe output from response to the specified writer when {@link #run()} is called. 1285 * 1286 * <p> 1287 * This method can be called multiple times to pipe to multiple writers. 1288 * 1289 * @param w The writer to write the output to. 1290 * @param close Close the writer when {@link #close()} is called. 1291 * @return This object (for method chaining). 1292 */ 1293 public RestCall pipeTo(Writer w, boolean close) { 1294 return pipeTo(null, w, close); 1295 } 1296 1297 /** 1298 * Pipe output from response to the specified writer when {@link #run()} is called and associate that writer with an 1299 * ID so it can be retrieved through {@link #getWriter(String)}. 1300 * 1301 * <p> 1302 * This method can be called multiple times to pipe to multiple writers. 1303 * 1304 * @param id A string identifier that can be used to retrieve the writer using {@link #getWriter(String)} 1305 * @param w The writer to write the output to. 1306 * @param close Close the writer when {@link #close()} is called. 1307 * @return This object (for method chaining). 1308 */ 1309 public RestCall pipeTo(String id, Writer w, boolean close) { 1310 writers.add(id, w, close); 1311 return this; 1312 } 1313 1314 /** 1315 * Retrieves a writer associated with an ID via {@link #pipeTo(String, Writer, boolean)} 1316 * 1317 * @param id A string identifier that can be used to retrieve the writer using {@link #getWriter(String)} 1318 * @return The writer, or <jk>null</jk> if no writer is associated with that ID. 1319 */ 1320 public Writer getWriter(String id) { 1321 return writers.getWriter(id); 1322 } 1323 1324 /** 1325 * When output is piped to writers, flush the writers after every line of output. 1326 * 1327 * @return This object (for method chaining). 1328 */ 1329 public RestCall byLines() { 1330 this.byLines = true; 1331 return this; 1332 } 1333 1334 /** 1335 * Pipes the request output to the specified output stream when {@link #run()} is called. 1336 * 1337 * <p> 1338 * The output stream is not closed. 1339 * 1340 * <p> 1341 * This method can be called multiple times to pipe to multiple output streams. 1342 * 1343 * @param os The output stream to pipe the output to. 1344 * @return This object (for method chaining). 1345 */ 1346 public RestCall pipeTo(OutputStream os) { 1347 return pipeTo(os, false); 1348 } 1349 1350 /** 1351 * Pipe output from response to the specified output stream when {@link #run()} is called. 1352 * 1353 * <p> 1354 * This method can be called multiple times to pipe to multiple output stream. 1355 * 1356 * @param os The output stream to write the output to. 1357 * @param close Close the output stream when {@link #close()} is called. 1358 * @return This object (for method chaining). 1359 */ 1360 public RestCall pipeTo(OutputStream os, boolean close) { 1361 return pipeTo(null, os, close); 1362 } 1363 1364 /** 1365 * Pipe output from response to the specified output stream when {@link #run()} is called and associate 1366 * that output stream with an ID so it can be retrieved through {@link #getOutputStream(String)}. 1367 * 1368 * <p> 1369 * This method can be called multiple times to pipe to multiple output stream. 1370 * 1371 * @param id A string identifier that can be used to retrieve the output stream using {@link #getOutputStream(String)} 1372 * @param os The output stream to write the output to. 1373 * @param close Close the output stream when {@link #close()} is called. 1374 * @return This object (for method chaining). 1375 */ 1376 public RestCall pipeTo(String id, OutputStream os, boolean close) { 1377 outputStreams.add(id, os, close); 1378 return this; 1379 } 1380 1381 /** 1382 * Retrieves an output stream associated with an ID via {@link #pipeTo(String, OutputStream, boolean)} 1383 * 1384 * @param id A string identifier that can be used to retrieve the writer using {@link #getWriter(String)} 1385 * @return The writer, or <jk>null</jk> if no writer is associated with that ID. 1386 */ 1387 public OutputStream getOutputStream(String id) { 1388 return outputStreams.getOutputStream(id); 1389 } 1390 1391 /** 1392 * Prevent {@link RestCallException RestCallExceptions} from being thrown when HTTP status 400+ is encountered. 1393 * 1394 * @return This object (for method chaining). 1395 */ 1396 public RestCall ignoreErrors() { 1397 this.ignoreErrors = true; 1398 return this; 1399 } 1400 1401 /** 1402 * Stores the response text so that it can later be captured using {@link #getCapturedResponse()}. 1403 * 1404 * <p> 1405 * This method should only be called once. Multiple calls to this method are ignored. 1406 * 1407 * @return This object (for method chaining). 1408 */ 1409 public RestCall captureResponse() { 1410 if (capturedResponseWriter == null) { 1411 capturedResponseWriter = new StringWriter(); 1412 writers.add(capturedResponseWriter, false); 1413 } 1414 return this; 1415 } 1416 1417 1418 /** 1419 * Look for the specified regular expression pattern in the response output. 1420 * 1421 * <p> 1422 * Causes a {@link RestCallException} to be thrown if the specified pattern is found in the output. 1423 * 1424 * <p> 1425 * This method uses {@link #getCapturedResponse()} to read the response text and so does not affect the other output 1426 * methods such as {@link #getResponseAsString()}. 1427 * 1428 * <h5 class='section'>Example:</h5> 1429 * <p class='bcode w800'> 1430 * <jc>// Throw a RestCallException if FAILURE or ERROR is found in the output.</jc> 1431 * restClient.doGet(<jsf>URL</jsf>) 1432 * .failurePattern(<js>"FAILURE|ERROR"</js>) 1433 * .run(); 1434 * </p> 1435 * 1436 * @param errorPattern A regular expression to look for in the response output. 1437 * @return This object (for method chaining). 1438 */ 1439 public RestCall failurePattern(final String errorPattern) { 1440 responsePattern( 1441 new ResponsePattern(errorPattern) { 1442 @Override 1443 public void onMatch(RestCall rc, Matcher m) throws RestCallException { 1444 throw new RestCallException("Failure pattern detected."); 1445 } 1446 } 1447 ); 1448 return this; 1449 } 1450 1451 /** 1452 * Look for the specified regular expression pattern in the response output. 1453 * 1454 * <p> 1455 * Causes a {@link RestCallException} to be thrown if the specified pattern is not found in the output. 1456 * 1457 * <p> 1458 * This method uses {@link #getCapturedResponse()} to read the response text and so does not affect the other output 1459 * methods such as {@link #getResponseAsString()}. 1460 * 1461 * <h5 class='section'>Example:</h5> 1462 * <p class='bcode w800'> 1463 * <jc>// Throw a RestCallException if SUCCESS is not found in the output.</jc> 1464 * restClient.doGet(<jsf>URL</jsf>) 1465 * .successPattern(<js>"SUCCESS"</js>) 1466 * .run(); 1467 * </p> 1468 * 1469 * @param successPattern A regular expression to look for in the response output. 1470 * @return This object (for method chaining). 1471 */ 1472 public RestCall successPattern(String successPattern) { 1473 responsePattern( 1474 new ResponsePattern(successPattern) { 1475 @Override 1476 public void onNoMatch(RestCall rc) throws RestCallException { 1477 throw new RestCallException("Success pattern not detected."); 1478 } 1479 } 1480 ); 1481 return this; 1482 } 1483 1484 /** 1485 * Adds a response pattern finder to look for regular expression matches in the response output. 1486 * 1487 * <p> 1488 * This method can be called multiple times to add multiple response pattern finders. 1489 * 1490 * <p> 1491 * {@link ResponsePattern ResponsePatterns} use the {@link #getCapturedResponse()} to read the response text and so 1492 * does not affect the other output methods such as {@link #getResponseAsString()}. 1493 * 1494 * @param responsePattern The response pattern finder. 1495 * @return This object (for method chaining). 1496 */ 1497 public RestCall responsePattern(final ResponsePattern responsePattern) { 1498 captureResponse(); 1499 interceptor( 1500 new RestCallInterceptor() { 1501 @Override 1502 public void onClose(RestCall restCall) throws RestCallException { 1503 responsePattern.match(RestCall.this); 1504 } 1505 } 1506 ); 1507 return this; 1508 } 1509 1510 /** 1511 * Set configuration settings on this request. 1512 * 1513 * <p> 1514 * Use {@link RequestConfig#custom()} to create configuration parameters for the request. 1515 * 1516 * @param config The new configuration settings for this request. 1517 * @return This object (for method chaining). 1518 */ 1519 public RestCall setConfig(RequestConfig config) { 1520 this.request.setConfig(config); 1521 return this; 1522 } 1523 1524 /** 1525 * Method used to execute an HTTP response where you're only interested in the HTTP response code. 1526 * 1527 * <p> 1528 * The response entity is discarded unless one of the pipe methods have been specified to pipe the output to an 1529 * output stream or writer. 1530 * 1531 * <h5 class='section'>Example:</h5> 1532 * <p class='bcode w800'> 1533 * <jk>try</jk> { 1534 * RestClient client = <jk>new</jk> RestClient(); 1535 * <jk>int</jk> rc = client.doGet(url).execute(); 1536 * <jc>// Succeeded!</jc> 1537 * } <jk>catch</jk> (RestCallException e) { 1538 * <jc>// Failed!</jc> 1539 * } 1540 * </p> 1541 * 1542 * @return The HTTP status code. 1543 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 1544 */ 1545 public int run() throws RestCallException { 1546 connect(); 1547 try { 1548 StatusLine status = response.getStatusLine(); 1549 int sc = status.getStatusCode(); 1550 if (sc >= 400 && ! ignoreErrors) 1551 throw new RestCallException(sc, status.getReasonPhrase(), request.getMethod(), request.getURI(), getResponseAsString()).setHttpResponse(response); 1552 if (outputStreams.size() > 0 || writers.size() > 0) 1553 getReader(); 1554 return sc; 1555 } catch (RestCallException e) { 1556 isFailed = true; 1557 throw e; 1558 } catch (IOException e) { 1559 isFailed = true; 1560 throw new RestCallException(e).setHttpResponse(response); 1561 } finally { 1562 close(); 1563 } 1564 } 1565 1566 /** 1567 * Same as {@link #run()} but allows you to run the call asynchronously. 1568 * 1569 * @return The HTTP status code. 1570 * @throws RestCallException If the executor service was not defined. 1571 * @see RestClientBuilder#executorService(ExecutorService, boolean) for defining the executor service for creating 1572 * {@link Future Futures}. 1573 */ 1574 public Future<Integer> runFuture() throws RestCallException { 1575 return client.getExecutorService(true).submit( 1576 new Callable<Integer>() { 1577 @Override /* Callable */ 1578 public Integer call() throws Exception { 1579 return run(); 1580 } 1581 } 1582 ); 1583 } 1584 1585 /** 1586 * Connects to the REST resource. 1587 * 1588 * <p> 1589 * If this is a <code>PUT</code> or <code>POST</code>, also sends the input to the remote resource.<br> 1590 * 1591 * <p> 1592 * Typically, you would only call this method if you're not interested in retrieving the body of the HTTP response. 1593 * Otherwise, you're better off just calling one of the {@link #getReader()}/{@link #getResponse(Class)}/{@link #pipeTo(Writer)} 1594 * methods directly which automatically call this method already. 1595 * 1596 * @return This object (for method chaining). 1597 * @throws RestCallException If an exception or <code>400+</code> HTTP status code occurred during the connection attempt. 1598 */ 1599 public RestCall connect() throws RestCallException { 1600 1601 if (isConnected) 1602 return this; 1603 isConnected = true; 1604 1605 try { 1606 1607 request.setURI(uriBuilder.build()); 1608 1609 if (hasInput || formData != null) { 1610 1611 if (hasInput && formData != null) 1612 throw new RestCallException("Both input and form data found on same request."); 1613 1614 if (! (request instanceof HttpEntityEnclosingRequestBase)) 1615 throw new RestCallException(0, "Method does not support content entity.", request.getMethod(), request.getURI(), null); 1616 1617 HttpEntity entity = null; 1618 if (formData != null) 1619 entity = new UrlEncodedFormEntity(formData); 1620 else if (input instanceof NameValuePairs) 1621 entity = new UrlEncodedFormEntity((NameValuePairs)input); 1622 else if (input instanceof HttpEntity) 1623 entity = (HttpEntity)input; 1624 else if (input instanceof Reader) 1625 entity = new StringEntity(IOUtils.read((Reader)input), getRequestContentType(TEXT_PLAIN)); 1626 else if (input instanceof InputStream) 1627 entity = new InputStreamEntity((InputStream)input, getRequestContentType(ContentType.APPLICATION_OCTET_STREAM)); 1628 else if (serializer != null) 1629 entity = new RestRequestEntity(input, serializer, requestBodySchema); 1630 else if (partSerializer != null) 1631 entity = new StringEntity(partSerializer.serialize(null, input), getRequestContentType(TEXT_PLAIN)); 1632 else 1633 entity = new StringEntity(getBeanContext().getClassMetaForObject(input).toString(input), getRequestContentType(TEXT_PLAIN)); 1634 1635 if (retries > 1 && ! entity.isRepeatable()) 1636 throw new RestCallException("Rest call set to retryable, but entity is not repeatable."); 1637 1638 ((HttpEntityEnclosingRequestBase)request).setEntity(entity); 1639 } 1640 1641 int sc = 0; 1642 while (retries > 0) { 1643 retries--; 1644 Exception ex = null; 1645 try { 1646 response = client.execute(request); 1647 sc = (response == null || response.getStatusLine() == null) ? -1 : response.getStatusLine().getStatusCode(); 1648 } catch (Exception e) { 1649 ex = e; 1650 sc = -1; 1651 if (response != null) 1652 EntityUtils.consumeQuietly(response.getEntity()); 1653 } 1654 if (! retryOn.onResponse(response)) 1655 retries = 0; 1656 if (retries > 0) { 1657 for (RestCallInterceptor rci : interceptors) 1658 rci.onRetry(this, sc, request, response, ex); 1659 request.reset(); 1660 long w = retryInterval; 1661 synchronized(this) { 1662 wait(w); 1663 } 1664 } else if (ex != null) { 1665 throw ex; 1666 } 1667 } 1668 for (RestCallInterceptor rci : interceptors) 1669 rci.onConnect(this, sc, request, response); 1670 if (response == null) 1671 throw new RestCallException("HttpClient returned a null response"); 1672 StatusLine sl = response.getStatusLine(); 1673 String method = request.getMethod(); 1674 sc = sl.getStatusCode(); // Read it again in case it was changed by one of the interceptors. 1675 if (sc >= 400 && ! ignoreErrors) 1676 throw new RestCallException(sc, sl.getReasonPhrase(), method, request.getURI(), getResponseAsString()) 1677 .setServerException(response.getFirstHeader("Exception-Name"), response.getFirstHeader("Exception-Message"), response.getFirstHeader("Exception-Trace")) 1678 .setHttpResponse(response); 1679 if ((sc == 307 || sc == 302) && allowRedirectsOnPosts && method.equalsIgnoreCase("POST")) { 1680 if (redirectOnPostsTries-- < 1) 1681 throw new RestCallException(sc, "Maximum number of redirects occurred. Location header: " + response.getFirstHeader("Location"), method, request.getURI(), getResponseAsString()); 1682 Header h = response.getFirstHeader("Location"); 1683 if (h != null) { 1684 reset(); 1685 request.setURI(URI.create(h.getValue())); 1686 retries++; // Redirects should affect retries. 1687 connect(); 1688 } 1689 } 1690 1691 } catch (RestCallException e) { 1692 isFailed = true; 1693 close(); 1694 throw e; 1695 } catch (Exception e) { 1696 isFailed = true; 1697 close(); 1698 throw new RestCallException(e).setHttpResponse(response); 1699 } 1700 1701 return this; 1702 } 1703 1704 private ContentType getRequestContentType(ContentType def) { 1705 Header h = request.getFirstHeader("Content-Type"); 1706 if (h != null) { 1707 String s = h.getValue(); 1708 if (! isEmpty(s)) 1709 return ContentType.create(s); 1710 } 1711 return def; 1712 } 1713 1714 private void reset() { 1715 if (response != null) 1716 EntityUtils.consumeQuietly(response.getEntity()); 1717 request.reset(); 1718 isConnected = false; 1719 isClosed = false; 1720 isFailed = false; 1721 if (capturedResponseWriter != null) 1722 capturedResponseWriter.getBuffer().setLength(0); 1723 } 1724 1725 /** 1726 * Connects to the remote resource (if <code>connect()</code> hasn't already been called) and returns the HTTP 1727 * response message body as a reader. 1728 * 1729 * <p> 1730 * If an {@link Encoder} has been registered with the {@link RestClient}, then the underlying input stream will be 1731 * wrapped in the encoded stream (e.g. a <code>GZIPInputStream</code>). 1732 * 1733 * <p> 1734 * If present, automatically handles the <code>charset</code> value in the <code>Content-Type</code> response header. 1735 * 1736 * <p> 1737 * <b>IMPORTANT:</b> It is your responsibility to close this reader once you have finished with it. 1738 * 1739 * @return 1740 * The HTTP response message body reader. 1741 * <jk>null</jk> if response was successful but didn't contain a body (e.g. HTTP 204). 1742 * @throws IOException If an exception occurred while streaming was already occurring. 1743 */ 1744 public Reader getReader() throws IOException { 1745 InputStream is = getInputStream(); 1746 if (is == null) 1747 return null; 1748 1749 // Figure out what the charset of the response is. 1750 String cs = null; 1751 Header contentType = response.getLastHeader("Content-Type"); 1752 String ct = contentType == null ? null : contentType.getValue(); 1753 1754 // First look for "charset=" in Content-Type header of response. 1755 if (ct != null && ct.contains("charset=")) 1756 cs = ct.substring(ct.indexOf("charset=")+8).trim(); 1757 1758 if (cs == null) 1759 cs = "UTF-8"; 1760 1761 if (writers.size() > 0) { 1762 try (Reader isr = new InputStreamReader(is, cs)) { 1763 StringWriter sw = new StringWriter(); 1764 writers.add(sw, true); 1765 IOPipe.create(isr, writers).byLines(byLines).run(); 1766 return new StringReader(sw.toString()); 1767 } 1768 } 1769 1770 return new InputStreamReader(is, cs); 1771 } 1772 1773 /** 1774 * Returns the response text as a string if {@link #captureResponse()} was called on this object. 1775 * 1776 * <p> 1777 * Note that while similar to {@link #getResponseAsString()}, this method can be called multiple times to retrieve 1778 * the response text multiple times. 1779 * 1780 * <p> 1781 * Note that this method returns <jk>null</jk> if you have not called one of the methods that cause the response to 1782 * be processed. (e.g. {@link #run()}, {@link #getResponse()}, {@link #getResponseAsString()}. 1783 * 1784 * @return The captured response, or <jk>null</jk> if {@link #captureResponse()} has not been called. 1785 * @throws IllegalStateException If trying to call this method before the response is consumed. 1786 */ 1787 public String getCapturedResponse() { 1788 if (! isClosed) 1789 throw new IllegalStateException("This method cannot be called until the response has been consumed."); 1790 if (capturedResponse == null && capturedResponseWriter != null && capturedResponseWriter.getBuffer().length() > 0) 1791 capturedResponse = capturedResponseWriter.toString(); 1792 return capturedResponse; 1793 } 1794 1795 /** 1796 * Returns the value of the <code>Content-Length</code> header. 1797 * 1798 * @return The value of the <code>Content-Length</code> header, or <code>-1</code> if header is not present. 1799 * @throws IOException 1800 */ 1801 public int getContentLength() throws IOException { 1802 connect(); 1803 Header h = response.getLastHeader("Content-Length"); 1804 if (h == null) 1805 return -1; 1806 long l = Long.parseLong(h.getValue()); 1807 if (l > Integer.MAX_VALUE) 1808 return Integer.MAX_VALUE; 1809 return (int)l; 1810 } 1811 1812 /** 1813 * Connects to the remote resource (if <code>connect()</code> hasn't already been called) and returns the HTTP 1814 * response message body as an input stream. 1815 * 1816 * <p> 1817 * If an {@link Encoder} has been registered with the {@link RestClient}, then the underlying input stream will be 1818 * wrapped in the encoded stream (e.g. a <code>GZIPInputStream</code>). 1819 * 1820 * <p> 1821 * <b>IMPORTANT:</b> It is your responsibility to close this reader once you have finished with it. 1822 * 1823 * @return 1824 * The HTTP response message body input stream. <jk>null</jk> if response was successful but didn't contain 1825 * a body (e.g. HTTP 204). 1826 * @throws IOException If an exception occurred while streaming was already occurring. 1827 * @throws IllegalStateException If an attempt is made to read the response more than once. 1828 */ 1829 @SuppressWarnings("resource") 1830 public InputStream getInputStream() throws IOException { 1831 if (isClosed) 1832 throw new IllegalStateException("Method cannot be called. Response has already been consumed."); 1833 connect(); 1834 if (response == null) 1835 throw new RestCallException("Response was null"); 1836 if (response.getEntity() == null) // HTTP 204 results in no content. 1837 return null; 1838 1839 softClose(); 1840 1841 InputStream is = new EofSensorInputStream(response.getEntity().getContent(), new EofSensorWatcher() { 1842 @Override 1843 public boolean eofDetected(InputStream wrapped) throws IOException { 1844 RestCall.this.forceClose(); 1845 return true; 1846 } 1847 @Override 1848 public boolean streamClosed(InputStream wrapped) throws IOException { 1849 RestCall.this.forceClose(); 1850 return true; 1851 } 1852 @Override 1853 public boolean streamAbort(InputStream wrapped) throws IOException { 1854 RestCall.this.forceClose(); 1855 return true; 1856 } 1857 }); 1858 1859 if (outputStreams.size() > 0) { 1860 ByteArrayInOutStream baios = new ByteArrayInOutStream(); 1861 outputStreams.add(baios, true); 1862 IOPipe.create(is, baios).run(); 1863 is.close(); 1864 return baios.getInputStream(); 1865 } 1866 return is; 1867 } 1868 1869 /** 1870 * Connects to the remote resource (if {@code connect()} hasn't already been called) and returns the HTTP response 1871 * message body as plain text. 1872 * 1873 * <p> 1874 * The response entity is discarded unless one of the pipe methods have been specified to pipe the output to an 1875 * output stream or writer. 1876 * 1877 * @return The response as a string. 1878 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 1879 * @throws IOException If an exception occurred while streaming was already occurring. 1880 */ 1881 public String getResponseAsString() throws IOException { 1882 try (Reader r = getReader()) { 1883 return read(r).toString(); 1884 } catch (IOException e) { 1885 isFailed = true; 1886 close(); 1887 throw e; 1888 } 1889 } 1890 1891 /** 1892 * Connects to the remote resource (if {@code connect()} hasn't already been called) and returns the value of 1893 * an HTTP header on the response. 1894 * 1895 * <p> 1896 * Useful if you're only interested in a particular header value from the response and not the body of the response. 1897 * 1898 * <p> 1899 * The response entity is discarded unless one of the pipe methods have been specified to pipe the output to an 1900 * output stream or writer. 1901 * 1902 * @param name The header name. 1903 * @return The response header as a string, or <jk>null</jk> if the header was not found. 1904 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 1905 * @throws IOException If an exception occurred while streaming was already occurring. 1906 */ 1907 public String getResponseHeader(String name) throws IOException { 1908 try { 1909 HttpResponse r = getResponse(); 1910 Header h = r.getFirstHeader(name); 1911 return h == null ? null : h.getValue(); 1912 } catch (IOException e) { 1913 isFailed = true; 1914 close(); 1915 throw e; 1916 } 1917 } 1918 1919 /** 1920 * Same as {@link #getResponseHeader(String)} except parses the header value using the specified part parser and schema. 1921 * 1922 * @param name The header name. 1923 * @param partParser The part parser to use for parsing the header. 1924 * @param schema The part schema. Can be <jk>null</jk>. 1925 * @param c The type to convert the part into. 1926 * @return The parsed part. 1927 * @throws IOException 1928 * @throws ParseException 1929 */ 1930 public <T> T getResponseHeader(HttpPartParser partParser, HttpPartSchema schema, String name, Class<T> c) throws IOException, ParseException { 1931 return getResponseHeader(partParser, schema, name, (Type)c); 1932 } 1933 1934 /** 1935 * Same as {@link #getResponseHeader(String)} except parses the header value using the specified part parser and schema. 1936 * 1937 * @param name The header name. 1938 * @param partParser The part parser to use for parsing the header. 1939 * @param schema The part schema. Can be <jk>null</jk>. 1940 * @param type The type to convert the part into. 1941 * @param args The type arguments to convert the part into. 1942 * @return The parsed part. 1943 * @throws IOException 1944 * @throws ParseException 1945 */ 1946 public <T> T getResponseHeader(HttpPartParser partParser, HttpPartSchema schema, String name, Type type, Type...args) throws IOException, ParseException { 1947 try { 1948 HttpResponse r = getResponse(); 1949 Header h = r.getFirstHeader(name); 1950 if (h == null) 1951 return null; 1952 String hs = h.getValue(); 1953 if (partParser == null) 1954 partParser = client.getPartParser(); 1955 return partParser.parse(schema, hs, type, args); 1956 } catch (IOException e) { 1957 isFailed = true; 1958 close(); 1959 throw e; 1960 } 1961 } 1962 1963 /** 1964 * Connects to the remote resource (if {@code connect()} hasn't already been called) and returns the HTTP response code. 1965 * 1966 * <p> 1967 * Useful if you're only interested in the status code and not the body of the response. 1968 * 1969 * <p> 1970 * The response entity is discarded unless one of the pipe methods have been specified to pipe the output to an 1971 * output stream or writer. 1972 * 1973 * @return The response code. 1974 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 1975 * @throws IOException If an exception occurred while streaming was already occurring. 1976 */ 1977 public int getResponseCode() throws IOException { 1978 return run(); 1979 } 1980 1981 /** 1982 * Same as {@link #getResponse(Class)} but allows you to run the call asynchronously. 1983 * 1984 * @return The response as a string. 1985 * @throws RestCallException If the executor service was not defined. 1986 * @see 1987 * RestClientBuilder#executorService(ExecutorService, boolean) for defining the executor service for creating 1988 * {@link Future Futures}. 1989 */ 1990 public Future<String> getResponseAsStringFuture() throws RestCallException { 1991 return client.getExecutorService(true).submit( 1992 new Callable<String>() { 1993 @Override /* Callable */ 1994 public String call() throws Exception { 1995 return getResponseAsString(); 1996 } 1997 } 1998 ); 1999 } 2000 2001 /** 2002 * Same as {@link #getResponse(Type, Type...)} except optimized for a non-parameterized class. 2003 * 2004 * <p> 2005 * This is the preferred parse method for simple types since you don't need to cast the results. 2006 * 2007 * <h5 class='section'>Examples:</h5> 2008 * <p class='bcode w800'> 2009 * <jc>// Parse into a string.</jc> 2010 * String s = restClient.doGet(url).getResponse(String.<jk>class</jk>); 2011 * 2012 * <jc>// Parse into a bean.</jc> 2013 * MyBean b = restClient.doGet(url).getResponse(MyBean.<jk>class</jk>); 2014 * 2015 * <jc>// Parse into a bean array.</jc> 2016 * MyBean[] ba = restClient.doGet(url).getResponse(MyBean[].<jk>class</jk>); 2017 * 2018 * <jc>// Parse into a linked-list of objects.</jc> 2019 * List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>); 2020 * 2021 * <jc>// Parse into a map of object keys/values.</jc> 2022 * Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>); 2023 * </p> 2024 * 2025 * <h5 class='section'>Notes:</h5> 2026 * <ul class='spaced-list'> 2027 * <li> 2028 * You can also specify any of the following types: 2029 * <ul> 2030 * <li>{@link HttpResponse} - Returns the raw <code>HttpResponse</code> returned by the inner <code>HttpClient</code>. 2031 * <li>{@link Reader} - Returns access to the raw reader of the response. 2032 * <li>{@link InputStream} - Returns access to the raw input stream of the response. 2033 * </ul> 2034 * </ul> 2035 * 2036 * @param <T> 2037 * The class type of the object being created. 2038 * See {@link #getResponse(Type, Type...)} for details. 2039 * @param type The object type to create. 2040 * @return The parsed object. 2041 * @throws ParseException 2042 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 2043 * @throws IOException If a connection error occurred. 2044 */ 2045 public <T> T getResponse(Class<T> type) throws IOException, ParseException { 2046 BeanContext bc = parser; 2047 if (bc == null) 2048 bc = BeanContext.DEFAULT; 2049 return getResponseInner(bc.getClassMeta(type)); 2050 } 2051 2052 /** 2053 * Same as {@link #getResponse(Class)} but allows you to run the call asynchronously. 2054 * 2055 * @param <T> 2056 * The class type of the object being created. 2057 * See {@link #getResponse(Type, Type...)} for details. 2058 * @param type The object type to create. 2059 * @return The parsed object. 2060 * @throws RestCallException If the executor service was not defined. 2061 * @see 2062 * RestClientBuilder#executorService(ExecutorService, boolean) for defining the executor service for creating 2063 * {@link Future Futures}. 2064 */ 2065 public <T> Future<T> getResponseFuture(final Class<T> type) throws RestCallException { 2066 return client.getExecutorService(true).submit( 2067 new Callable<T>() { 2068 @Override /* Callable */ 2069 public T call() throws Exception { 2070 return getResponse(type); 2071 } 2072 } 2073 ); 2074 } 2075 2076 /** 2077 * Parses HTTP body into the specified object type. 2078 * 2079 * <p> 2080 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). 2081 * 2082 * <h5 class='section'>Examples:</h5> 2083 * <p class='bcode w800'> 2084 * <jc>// Parse into a linked-list of strings.</jc> 2085 * List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, String.<jk>class</jk>); 2086 * 2087 * <jc>// Parse into a linked-list of beans.</jc> 2088 * List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); 2089 * 2090 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 2091 * List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 2092 * 2093 * <jc>// Parse into a map of string keys/values.</jc> 2094 * Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 2095 * 2096 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 2097 * Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 2098 * </p> 2099 * 2100 * <p> 2101 * <code>Collection</code> classes are assumed to be followed by zero or one objects indicating the element type. 2102 * 2103 * <p> 2104 * <code>Map</code> classes are assumed to be followed by zero or two meta objects indicating the key and value types. 2105 * 2106 * <p> 2107 * The array can be arbitrarily long to indicate arbitrarily complex data structures. 2108 * 2109 * <h5 class='section'>Notes:</h5> 2110 * <ul class='spaced-list'> 2111 * <li> 2112 * Use the {@link #getResponse(Class)} method instead if you don't need a parameterized map/collection. 2113 * <li> 2114 * You can also specify any of the following types: 2115 * <ul> 2116 * <li>{@link HttpResponse} - Returns the raw <code>HttpResponse</code> returned by the inner <code>HttpClient</code>. 2117 * <li>{@link Reader} - Returns access to the raw reader of the response. 2118 * <li>{@link InputStream} - Returns access to the raw input stream of the response. 2119 * </ul> 2120 * </ul> 2121 * 2122 * @param <T> The class type of the object to create. 2123 * @param type 2124 * The object type to create. 2125 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2126 * @param args 2127 * The type arguments of the class if it's a collection or map. 2128 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2129 * <br>Ignored if the main type is not a map or collection. 2130 * @return The parsed object. 2131 * @throws ParseException 2132 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 2133 * @throws IOException If a connection error occurred. 2134 * @see BeanSession#getClassMeta(Class) for argument syntax for maps and collections. 2135 */ 2136 public <T> T getResponse(Type type, Type...args) throws IOException, ParseException { 2137 BeanContext bc = parser; 2138 if (bc == null) 2139 bc = BeanContext.DEFAULT; 2140 return (T)getResponseInner(bc.getClassMeta(type, args)); 2141 } 2142 2143 /** 2144 * Same as {@link #getResponse(Type, Type...)} but allows you to specify a part parser to use for parsing the response. 2145 * 2146 * @param <T> The class type of the object to create. 2147 * @param type 2148 * The object type to create. 2149 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2150 * @param args 2151 * The type arguments of the class if it's a collection or map. 2152 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2153 * <br>Ignored if the main type is not a map or collection. 2154 * @return The parsed object. 2155 * @throws ParseException 2156 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 2157 * @throws IOException If a connection error occurred. 2158 * @see BeanSession#getClassMeta(Class) for argument syntax for maps and collections. 2159 */ 2160 public <T> T getResponseBody(Type type, Type...args) throws IOException, ParseException { 2161 BeanContext bc = parser; 2162 if (bc == null) 2163 bc = BeanContext.DEFAULT; 2164 return (T)getResponseInner(bc.getClassMeta(type, args)); 2165 } 2166 2167 /** 2168 * Same as {@link #getResponse(Class)} but allows you to run the call asynchronously. 2169 * 2170 * @param <T> 2171 * The class type of the object being created. 2172 * See {@link #getResponse(Type, Type...)} for details. 2173 * @param type 2174 * The object type to create. 2175 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2176 * @param args 2177 * The type arguments of the class if it's a collection or map. 2178 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2179 * <br>Ignored if the main type is not a map or collection. 2180 * @return The parsed object. 2181 * @throws RestCallException If the executor service was not defined. 2182 * @see 2183 * RestClientBuilder#executorService(ExecutorService, boolean) for defining the executor service for creating 2184 * {@link Future Futures}. 2185 */ 2186 public <T> Future<T> getResponseFuture(final Type type, final Type...args) throws RestCallException { 2187 return client.getExecutorService(true).submit( 2188 new Callable<T>() { 2189 @Override /* Callable */ 2190 public T call() throws Exception { 2191 return getResponse(type, args); 2192 } 2193 } 2194 ); 2195 } 2196 2197 /** 2198 * Parses the output from the connection into the specified type and then wraps that in a {@link PojoRest}. 2199 * 2200 * <p> 2201 * Useful if you want to quickly retrieve a single value from inside of a larger JSON document. 2202 * 2203 * @param innerType The class type of the POJO being wrapped. 2204 * @return The parsed output wrapped in a {@link PojoRest}. 2205 * @throws IOException If a connection error occurred. 2206 * @throws ParseException 2207 * If the input contains a syntax error or is malformed for the <code>Content-Type</code> header. 2208 */ 2209 public PojoRest getResponsePojoRest(Class<?> innerType) throws IOException, ParseException { 2210 return new PojoRest(getResponse(innerType)); 2211 } 2212 2213 /** 2214 * Converts the output from the connection into an {@link ObjectMap} and then wraps that in a {@link PojoRest}. 2215 * 2216 * <p> 2217 * Useful if you want to quickly retrieve a single value from inside of a larger JSON document. 2218 * 2219 * @return The parsed output wrapped in a {@link PojoRest}. 2220 * @throws IOException If a connection error occurred. 2221 * @throws ParseException 2222 * If the input contains a syntax error or is malformed for the <code>Content-Type</code> header. 2223 */ 2224 public PojoRest getResponsePojoRest() throws IOException, ParseException { 2225 return getResponsePojoRest(ObjectMap.class); 2226 } 2227 2228 <T> T getResponseInner(ClassMeta<T> type) throws IOException, ParseException { 2229 try { 2230 if (response == null) 2231 connect(); 2232 2233 Class<?> ic = type.getInnerClass(); 2234 2235 if (ic.equals(HttpResponse.class)) { 2236 softClose(); 2237 return (T)response; 2238 } 2239 if (ic.equals(Reader.class)) 2240 return (T)getReader(); 2241 if (ic.equals(InputStream.class)) 2242 return (T)getInputStream(); 2243 2244 connect(); 2245 Header h = response.getFirstHeader("Content-Type"); 2246 String ct = firstNonEmpty(h == null ? null : h.getValue(), "text/plain"); 2247 2248 MediaType mt = MediaType.forString(ct); 2249 2250 if (parser == null || (mt.toString().equals("text/plain") && ! parser.canHandle(ct))) { 2251 if (type.hasStringTransform()) 2252 return type.getStringTransform().transform(getResponseAsString()); 2253 } 2254 2255 if (parser != null) { 2256 try (Closeable in = parser.isReaderParser() ? getReader() : getInputStream()) { 2257 ParserSessionArgs pArgs = new ParserSessionArgs(this.getProperties(), null, response.getLocale(), null, mt, responseBodySchema, false, null); 2258 return parser.createSession(pArgs).parse(in, type); 2259 } 2260 } 2261 2262 if (type.hasReaderTransform()) 2263 return type.getReaderTransform().transform(getReader()); 2264 2265 if (type.hasInputStreamTransform()) 2266 return type.getInputStreamTransform().transform(getInputStream()); 2267 2268 throw new ParseException( 2269 "Unsupported media-type in request header ''Content-Type'': ''{0}''\n\tSupported media-types: {1}", 2270 getResponseHeader("Content-Type"), parser == null ? null : parser.getMediaTypes() 2271 ); 2272 2273 } catch (ParseException | IOException e) { 2274 isFailed = true; 2275 close(); 2276 throw e; 2277 } 2278 } 2279 2280 BeanContext getBeanContext() { 2281 BeanContext bc = parser; 2282 if (bc == null) 2283 bc = BeanContext.DEFAULT; 2284 return bc; 2285 } 2286 2287 /** 2288 * Converts the response from this call into a response bean. 2289 * 2290 * @param rbm The metadata used to construct the response bean. 2291 * @return A new response bean. 2292 */ 2293 public <T> T getResponse(final ResponseBeanMeta rbm) { 2294 try { 2295 softClose(); 2296 Class<T> c = (Class<T>)rbm.getClassMeta().getInnerClass(); 2297 final RestClient rc = this.client; 2298 final HttpPartParser p = ObjectUtils.firstNonNull(partParser, rc.getPartParser()); 2299 return (T)Proxy.newProxyInstance( 2300 c.getClassLoader(), 2301 new Class[] { c }, 2302 new InvocationHandler() { 2303 @Override /* InvocationHandler */ 2304 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 2305 ResponseBeanPropertyMeta pm = rbm.getProperty(method.getName()); 2306 if (pm != null) { 2307 HttpPartParser pp = pm.getParser(p); 2308 HttpPartSchema schema = pm.getSchema(); 2309 String name = pm.getPartName(); 2310 ClassMeta<?> type = rc.getClassMeta(method.getGenericReturnType()); 2311 HttpPartType pt = pm.getPartType(); 2312 if (pt == RESPONSE_BODY) { 2313 responseBodySchema(schema); 2314 return getResponseBody(type); 2315 } 2316 if (pt == RESPONSE_HEADER) 2317 return getResponseHeader(pp, schema, name, type); 2318 if (pt == RESPONSE_STATUS) 2319 return getResponseCode(); 2320 } 2321 return null; 2322 } 2323 2324 }); 2325 } catch (Exception e) { 2326 throw new RuntimeException(e); 2327 } 2328 } 2329 2330 /** 2331 * Returns access to the {@link HttpUriRequest} passed to {@link HttpClient#execute(HttpUriRequest)}. 2332 * 2333 * @return The {@link HttpUriRequest} object. 2334 */ 2335 public HttpUriRequest getRequest() { 2336 return request; 2337 } 2338 2339 /** 2340 * Returns access to the {@link HttpResponse} returned by {@link HttpClient#execute(HttpUriRequest)}. 2341 * 2342 * <p> 2343 * Returns <jk>null</jk> if {@link #connect()} has not yet been called. 2344 * 2345 * @return The HTTP response object. 2346 * @throws IOException 2347 */ 2348 public HttpResponse getResponse() throws IOException { 2349 connect(); 2350 return response; 2351 } 2352 2353 /** 2354 * Shortcut for calling <code>getRequest().setHeader(header)</code> 2355 * 2356 * @param header The header to set on the request. 2357 * @return This object (for method chaining). 2358 */ 2359 public RestCall header(Header header) { 2360 request.setHeader(header); 2361 return this; 2362 } 2363 2364 /** Use close() */ 2365 @Deprecated 2366 public void consumeResponse() { 2367 if (response != null) 2368 EntityUtils.consumeQuietly(response.getEntity()); 2369 } 2370 2371 /** 2372 * Cleans up this HTTP call. 2373 * 2374 * @throws RestCallException Can be thrown by one of the {@link RestCallInterceptor#onClose(RestCall)} calls. 2375 */ 2376 @Override /* Closeable */ 2377 public void close() throws RestCallException { 2378 if (response != null && ! softClose) 2379 EntityUtils.consumeQuietly(response.getEntity()); 2380 if (! softClose) 2381 isClosed = true; 2382 if (! isFailed) 2383 for (RestCallInterceptor r : interceptors) 2384 r.onClose(this); 2385 } 2386 2387 void forceClose() throws RestCallException { 2388 softClose = false; 2389 close(); 2390 } 2391 2392 /** 2393 * Adds a {@link RestCallLogger} to the list of interceptors on this class. 2394 * 2395 * @param level The log level to log events at. 2396 * @param log The logger. 2397 * @return This object (for method chaining). 2398 */ 2399 public RestCall logTo(Level level, Logger log) { 2400 interceptor(new RestCallLogger(level, log)); 2401 return this; 2402 } 2403 2404 /** 2405 * Sets <code>Debug: value</code> header on this request. 2406 * 2407 * @return This object (for method chaining). 2408 * @throws RestCallException 2409 */ 2410 public RestCall debug() throws RestCallException { 2411 header("Debug", true); 2412 return this; 2413 } 2414 2415 /** 2416 * If called, the underlying response stream will not be closed when you call {@link #close()}. 2417 * 2418 * <p> 2419 * This is useful in cases where you want access to that stream after you've already cleaned up this object. 2420 * However, it is your responsibility to close that stream yourself. 2421 * 2422 * @return This object (for method chaining). 2423 */ 2424 public RestCall softClose() { 2425 this.softClose = true; 2426 return this; 2427 } 2428 2429 //----------------------------------------------------------------------------------------------------------------- 2430 // Utility methods 2431 //----------------------------------------------------------------------------------------------------------------- 2432 2433 /** 2434 * Specifies that the following value can be added as an HTTP part. 2435 */ 2436 private boolean canAdd(Object value, HttpPartSchema schema, boolean skipIfEmpty) { 2437 if (value != null) { 2438 if (ObjectUtils.isEmpty(value) && skipIfEmpty) 2439 return false; 2440 return true; 2441 } 2442 if (schema == null) 2443 return false; 2444 if (schema.isRequired()) 2445 return true; 2446 String def = schema.getDefault(); 2447 if (def == null) 2448 return false; 2449 if (StringUtils.isEmpty(def) && skipIfEmpty) 2450 return false; 2451 return true; 2452 } 2453}