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