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 1613 request.setURI(uriBuilder.build()); 1614 1615 if (hasInput || formData != null) { 1616 1617 if (hasInput && formData != null) 1618 throw new RestCallException("Both input and form data found on same request."); 1619 1620 if (! (request instanceof HttpEntityEnclosingRequestBase)) 1621 throw new RestCallException(0, "Method does not support content entity.", request.getMethod(), request.getURI(), null); 1622 1623 HttpEntity entity = null; 1624 if (formData != null) 1625 entity = new UrlEncodedFormEntity(formData); 1626 else if (input instanceof NameValuePairs) 1627 entity = new UrlEncodedFormEntity((NameValuePairs)input); 1628 else if (input instanceof HttpEntity) 1629 entity = (HttpEntity)input; 1630 else if (input instanceof Reader) 1631 entity = new StringEntity(IOUtils.read((Reader)input), getRequestContentType(TEXT_PLAIN)); 1632 else if (input instanceof InputStream) 1633 entity = new InputStreamEntity((InputStream)input, getRequestContentType(ContentType.APPLICATION_OCTET_STREAM)); 1634 else if (serializer != null) 1635 entity = new RestRequestEntity(input, serializer, requestBodySchema); 1636 else if (partSerializer != null) 1637 entity = new StringEntity(partSerializer.serialize((HttpPartSchema)null, input), getRequestContentType(TEXT_PLAIN)); 1638 else 1639 entity = new StringEntity(getBeanContext().getClassMetaForObject(input).toString(input), getRequestContentType(TEXT_PLAIN)); 1640 1641 if (retries > 1 && ! entity.isRepeatable()) 1642 throw new RestCallException("Rest call set to retryable, but entity is not repeatable."); 1643 1644 ((HttpEntityEnclosingRequestBase)request).setEntity(entity); 1645 } 1646 1647 int sc = 0; 1648 while (retries > 0) { 1649 retries--; 1650 Exception ex = null; 1651 try { 1652 response = client.execute(request); 1653 sc = (response == null || response.getStatusLine() == null) ? -1 : response.getStatusLine().getStatusCode(); 1654 } catch (Exception e) { 1655 ex = e; 1656 sc = -1; 1657 if (response != null) 1658 EntityUtils.consumeQuietly(response.getEntity()); 1659 } 1660 if (! retryOn.onResponse(response)) 1661 retries = 0; 1662 if (retries > 0) { 1663 for (RestCallInterceptor rci : interceptors) 1664 rci.onRetry(this, sc, request, response, ex); 1665 request.reset(); 1666 long w = retryInterval; 1667 synchronized(this) { 1668 wait(w); 1669 } 1670 } else if (ex != null) { 1671 throw ex; 1672 } 1673 } 1674 for (RestCallInterceptor rci : interceptors) 1675 rci.onConnect(this, sc, request, response); 1676 if (response == null) 1677 throw new RestCallException("HttpClient returned a null response"); 1678 StatusLine sl = response.getStatusLine(); 1679 String method = request.getMethod(); 1680 sc = sl.getStatusCode(); // Read it again in case it was changed by one of the interceptors. 1681 1682 int[] expected = new int[0]; 1683 if (bodyType != null && bodyType.hasAnnotation(Response.class)) 1684 expected = bodyType.getAnnotation(Response.class).code(); 1685 1686 if (sc >= 400 && ! ignoreErrors && ! ArrayUtils.contains(sc, expected)) { 1687 throw new RestCallException(sc, sl.getReasonPhrase(), method, request.getURI(), getResponseAsString()) 1688 .setServerException(response.getFirstHeader("Exception-Name"), response.getFirstHeader("Exception-Message"), response.getFirstHeader("Exception-Trace")) 1689 .setHttpResponse(response); 1690 } 1691 if ((sc == 307 || sc == 302) && allowRedirectsOnPosts && method.equalsIgnoreCase("POST") && ! ArrayUtils.contains(sc, expected)) { 1692 if (redirectOnPostsTries-- < 1) 1693 throw new RestCallException(sc, "Maximum number of redirects occurred. Location header: " + response.getFirstHeader("Location"), method, request.getURI(), getResponseAsString()); 1694 Header h = response.getFirstHeader("Location"); 1695 if (h != null) { 1696 reset(); 1697 request.setURI(URI.create(h.getValue())); 1698 retries++; // Redirects should affect retries. 1699 connect(); 1700 } 1701 } 1702 1703 } catch (RestCallException e) { 1704 isFailed = true; 1705 close(); 1706 throw e; 1707 } catch (Exception e) { 1708 isFailed = true; 1709 close(); 1710 throw new RestCallException(e).setHttpResponse(response); 1711 } 1712 1713 return this; 1714 } 1715 1716 private ContentType getRequestContentType(ContentType def) { 1717 Header h = request.getFirstHeader("Content-Type"); 1718 if (h != null) { 1719 String s = h.getValue(); 1720 if (! isEmpty(s)) 1721 return ContentType.create(s); 1722 } 1723 return def; 1724 } 1725 1726 private void reset() { 1727 if (response != null) 1728 EntityUtils.consumeQuietly(response.getEntity()); 1729 request.reset(); 1730 isConnected = false; 1731 isClosed = false; 1732 isFailed = false; 1733 if (capturedResponseWriter != null) 1734 capturedResponseWriter.getBuffer().setLength(0); 1735 } 1736 1737 /** 1738 * Connects to the remote resource (if <c>connect()</c> hasn't already been called) and returns the HTTP 1739 * response message body as a reader. 1740 * 1741 * <p> 1742 * If an {@link Encoder} has been registered with the {@link RestClient}, then the underlying input stream will be 1743 * wrapped in the encoded stream (e.g. a <c>GZIPInputStream</c>). 1744 * 1745 * <p> 1746 * If present, automatically handles the <c>charset</c> value in the <c>Content-Type</c> response header. 1747 * 1748 * <p> 1749 * <b>IMPORTANT:</b> It is your responsibility to close this reader once you have finished with it. 1750 * 1751 * @return 1752 * The HTTP response message body reader. 1753 * <jk>null</jk> if response was successful but didn't contain a body (e.g. HTTP 204). 1754 * @throws IOException If an exception occurred while streaming was already occurring. 1755 */ 1756 public Reader getReader() throws IOException { 1757 InputStream is = getInputStream(); 1758 if (is == null) 1759 return null; 1760 1761 // Figure out what the charset of the response is. 1762 String cs = null; 1763 Header contentType = response.getLastHeader("Content-Type"); 1764 String ct = contentType == null ? null : contentType.getValue(); 1765 1766 // First look for "charset=" in Content-Type header of response. 1767 if (ct != null && ct.contains("charset=")) 1768 cs = ct.substring(ct.indexOf("charset=")+8).trim(); 1769 1770 if (cs == null) 1771 cs = "UTF-8"; 1772 1773 if (writers.size() > 0) { 1774 try (Reader isr = new InputStreamReader(is, cs)) { 1775 StringWriter sw = new StringWriter(); 1776 writers.add(sw, true); 1777 IOPipe.create(isr, writers).byLines(byLines).run(); 1778 return new StringReader(sw.toString()); 1779 } 1780 } 1781 1782 return new InputStreamReader(is, cs); 1783 } 1784 1785 /** 1786 * Returns the response text as a string if {@link #captureResponse()} was called on this object. 1787 * 1788 * <p> 1789 * Note that while similar to {@link #getResponseAsString()}, this method can be called multiple times to retrieve 1790 * the response text multiple times. 1791 * 1792 * <p> 1793 * Note that this method returns <jk>null</jk> if you have not called one of the methods that cause the response to 1794 * be processed. (e.g. {@link #run()}, {@link #getResponse()}, {@link #getResponseAsString()}. 1795 * 1796 * @return The captured response, or <jk>null</jk> if {@link #captureResponse()} has not been called. 1797 * @throws IllegalStateException If trying to call this method before the response is consumed. 1798 */ 1799 public String getCapturedResponse() { 1800 if (! isClosed) 1801 throw new IllegalStateException("This method cannot be called until the response has been consumed."); 1802 if (capturedResponse == null && capturedResponseWriter != null && capturedResponseWriter.getBuffer().length() > 0) 1803 capturedResponse = capturedResponseWriter.toString(); 1804 return capturedResponse; 1805 } 1806 1807 /** 1808 * Returns the value of the <c>Content-Length</c> header. 1809 * 1810 * @return The value of the <c>Content-Length</c> header, or <c>-1</c> if header is not present. 1811 * @throws IOException Thrown by underlying stream. 1812 */ 1813 public int getContentLength() throws IOException { 1814 connect(); 1815 Header h = response.getLastHeader("Content-Length"); 1816 if (h == null) 1817 return -1; 1818 long l = Long.parseLong(h.getValue()); 1819 if (l > Integer.MAX_VALUE) 1820 return Integer.MAX_VALUE; 1821 return (int)l; 1822 } 1823 1824 /** 1825 * Connects to the remote resource (if <c>connect()</c> hasn't already been called) and returns the HTTP 1826 * response message body as an input stream. 1827 * 1828 * <p> 1829 * If an {@link Encoder} has been registered with the {@link RestClient}, then the underlying input stream will be 1830 * wrapped in the encoded stream (e.g. a <c>GZIPInputStream</c>). 1831 * 1832 * <p> 1833 * <b>IMPORTANT:</b> It is your responsibility to close this reader once you have finished with it. 1834 * 1835 * @return 1836 * The HTTP response message body input stream. <jk>null</jk> if response was successful but didn't contain 1837 * a body (e.g. HTTP 204). 1838 * @throws IOException If an exception occurred while streaming was already occurring. 1839 * @throws IllegalStateException If an attempt is made to read the response more than once. 1840 */ 1841 @SuppressWarnings("resource") 1842 public InputStream getInputStream() throws IOException { 1843 if (isClosed) 1844 throw new IllegalStateException("Method cannot be called. Response has already been consumed."); 1845 connect(); 1846 if (response == null) 1847 throw new RestCallException("Response was null"); 1848 if (response.getEntity() == null) // HTTP 204 results in no content. 1849 return null; 1850 1851 softClose(); 1852 1853 InputStream is = new EofSensorInputStream(response.getEntity().getContent(), new EofSensorWatcher() { 1854 @Override 1855 public boolean eofDetected(InputStream wrapped) throws IOException { 1856 RestCall.this.forceClose(); 1857 return true; 1858 } 1859 @Override 1860 public boolean streamClosed(InputStream wrapped) throws IOException { 1861 RestCall.this.forceClose(); 1862 return true; 1863 } 1864 @Override 1865 public boolean streamAbort(InputStream wrapped) throws IOException { 1866 RestCall.this.forceClose(); 1867 return true; 1868 } 1869 }); 1870 1871 if (outputStreams.size() > 0) { 1872 ByteArrayInOutStream baios = new ByteArrayInOutStream(); 1873 outputStreams.add(baios, true); 1874 IOPipe.create(is, baios).run(); 1875 is.close(); 1876 return baios.getInputStream(); 1877 } 1878 return is; 1879 } 1880 1881 /** 1882 * Connects to the remote resource (if {@code connect()} hasn't already been called) and returns the HTTP response 1883 * message body as plain text. 1884 * 1885 * <p> 1886 * The response entity is discarded unless one of the pipe methods have been specified to pipe the output to an 1887 * output stream or writer. 1888 * 1889 * @return The response as a string. 1890 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 1891 * @throws IOException If an exception occurred while streaming was already occurring. 1892 */ 1893 public String getResponseAsString() throws IOException { 1894 try (Reader r = getReader()) { 1895 return read(r).toString(); 1896 } catch (IOException e) { 1897 isFailed = true; 1898 close(); 1899 throw e; 1900 } 1901 } 1902 1903 /** 1904 * Connects to the remote resource (if {@code connect()} hasn't already been called) and returns the value of 1905 * an HTTP header on the response. 1906 * 1907 * <p> 1908 * Useful if you're only interested in a particular header value from the response and not the body of the response. 1909 * 1910 * <p> 1911 * The response entity is discarded unless one of the pipe methods have been specified to pipe the output to an 1912 * output stream or writer. 1913 * 1914 * @param name The header name. 1915 * @return The response header as a string, or <jk>null</jk> if the header was not found. 1916 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 1917 * @throws IOException If an exception occurred while streaming was already occurring. 1918 */ 1919 public String getResponseHeader(String name) throws IOException { 1920 try { 1921 HttpResponse r = getResponse(); 1922 Header h = r.getFirstHeader(name); 1923 return h == null ? null : h.getValue(); 1924 } catch (IOException e) { 1925 isFailed = true; 1926 close(); 1927 throw e; 1928 } 1929 } 1930 1931 /** 1932 * Same as {@link #getResponseHeader(String)} except parses the header value using the specified part parser and schema. 1933 * 1934 * @param name The header name. 1935 * @param partParser The part parser to use for parsing the header. 1936 * @param schema The part schema. Can be <jk>null</jk>. 1937 * @param c The type to convert the part into. 1938 * @return The parsed part. 1939 * @throws IOException Thrown by underlying stream. 1940 * @throws ParseException Header value could not be parsed into the specified type. 1941 */ 1942 public <T> T getResponseHeader(HttpPartParser partParser, HttpPartSchema schema, String name, Class<T> c) throws IOException, ParseException { 1943 return getResponseHeader(partParser, schema, name, (Type)c); 1944 } 1945 1946 /** 1947 * Same as {@link #getResponseHeader(String)} except parses the header value using the specified part parser and schema. 1948 * 1949 * @param name The header name. 1950 * @param partParser The part parser to use for parsing the header. 1951 * @param schema The part schema. Can be <jk>null</jk>. 1952 * @param type The type to convert the part into. 1953 * @param args The type arguments to convert the part into. 1954 * @return The parsed part. 1955 * @throws IOException Thrown by underlying stream. 1956 * @throws ParseException Header value could not be parsed into the specified type. 1957 */ 1958 public <T> T getResponseHeader(HttpPartParser partParser, HttpPartSchema schema, String name, Type type, Type...args) throws IOException, ParseException { 1959 try { 1960 HttpResponse r = getResponse(); 1961 Header h = r.getFirstHeader(name); 1962 if (h == null) 1963 return null; 1964 String hs = h.getValue(); 1965 if (partParser == null) 1966 partParser = client.getPartParser(); 1967 return partParser.parse(schema, hs, type, args); 1968 } catch (IOException e) { 1969 isFailed = true; 1970 close(); 1971 throw e; 1972 } 1973 } 1974 1975 /** 1976 * Connects to the remote resource (if {@code connect()} hasn't already been called) and returns the HTTP response code. 1977 * 1978 * <p> 1979 * Useful if you're only interested in the status code and not the body of the response. 1980 * 1981 * <p> 1982 * The response entity is discarded unless one of the pipe methods have been specified to pipe the output to an 1983 * output stream or writer. 1984 * 1985 * @return The response code. 1986 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 1987 * @throws IOException If an exception occurred while streaming was already occurring. 1988 */ 1989 public int getResponseCode() throws IOException { 1990 return run(); 1991 } 1992 1993 /** 1994 * Same as {@link #getResponse(Class)} but allows you to run the call asynchronously. 1995 * 1996 * @return The response as a string. 1997 * @throws RestCallException If the executor service was not defined. 1998 * @see 1999 * RestClientBuilder#executorService(ExecutorService, boolean) for defining the executor service for creating 2000 * {@link Future Futures}. 2001 */ 2002 public Future<String> getResponseAsStringFuture() throws RestCallException { 2003 return client.getExecutorService(true).submit( 2004 new Callable<String>() { 2005 @Override /* Callable */ 2006 public String call() throws Exception { 2007 return getResponseAsString(); 2008 } 2009 } 2010 ); 2011 } 2012 2013 /** 2014 * Same as {@link #getResponse(Type, Type...)} except optimized for a non-parameterized class. 2015 * 2016 * <p> 2017 * This is the preferred parse method for simple types since you don't need to cast the results. 2018 * 2019 * <h5 class='section'>Examples:</h5> 2020 * <p class='bcode w800'> 2021 * <jc>// Parse into a string.</jc> 2022 * String s = restClient.doGet(url).getResponse(String.<jk>class</jk>); 2023 * 2024 * <jc>// Parse into a bean.</jc> 2025 * MyBean b = restClient.doGet(url).getResponse(MyBean.<jk>class</jk>); 2026 * 2027 * <jc>// Parse into a bean array.</jc> 2028 * MyBean[] ba = restClient.doGet(url).getResponse(MyBean[].<jk>class</jk>); 2029 * 2030 * <jc>// Parse into a linked-list of objects.</jc> 2031 * List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>); 2032 * 2033 * <jc>// Parse into a map of object keys/values.</jc> 2034 * Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>); 2035 * </p> 2036 * 2037 * <ul class='notes'> 2038 * <li> 2039 * You can also specify any of the following types: 2040 * <ul> 2041 * <li>{@link HttpResponse} - Returns the raw <c>HttpResponse</c> returned by the inner <c>HttpClient</c>. 2042 * <li>{@link Reader} - Returns access to the raw reader of the response. 2043 * <li>{@link InputStream} - Returns access to the raw input stream of the response. 2044 * </ul> 2045 * </ul> 2046 * 2047 * @param <T> 2048 * The class type of the object being created. 2049 * See {@link #getResponse(Type, Type...)} for details. 2050 * @param type The object type to create. 2051 * @return The parsed object. 2052 * @throws ParseException 2053 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 2054 * @throws IOException If a connection error occurred. 2055 */ 2056 public <T> T getResponse(Class<T> type) throws IOException, ParseException { 2057 BeanContext bc = parser; 2058 if (bc == null) 2059 bc = BeanContext.DEFAULT; 2060 return getResponseInner(bc.getClassMeta(type)); 2061 } 2062 2063 /** 2064 * Same as {@link #getResponse(Class)} but allows you to run the call asynchronously. 2065 * 2066 * @param <T> 2067 * The class type of the object being created. 2068 * See {@link #getResponse(Type, Type...)} for details. 2069 * @param type The object type to create. 2070 * @return The parsed object. 2071 * @throws RestCallException If the executor service was not defined. 2072 * @see 2073 * RestClientBuilder#executorService(ExecutorService, boolean) for defining the executor service for creating 2074 * {@link Future Futures}. 2075 */ 2076 public <T> Future<T> getResponseFuture(final Class<T> type) throws RestCallException { 2077 return client.getExecutorService(true).submit( 2078 new Callable<T>() { 2079 @Override /* Callable */ 2080 public T call() throws Exception { 2081 return getResponse(type); 2082 } 2083 } 2084 ); 2085 } 2086 2087 /** 2088 * Parses HTTP body into the specified object type. 2089 * 2090 * <p> 2091 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). 2092 * 2093 * <h5 class='section'>Examples:</h5> 2094 * <p class='bcode w800'> 2095 * <jc>// Parse into a linked-list of strings.</jc> 2096 * List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, String.<jk>class</jk>); 2097 * 2098 * <jc>// Parse into a linked-list of beans.</jc> 2099 * List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); 2100 * 2101 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 2102 * List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 2103 * 2104 * <jc>// Parse into a map of string keys/values.</jc> 2105 * Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 2106 * 2107 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 2108 * Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 2109 * </p> 2110 * 2111 * <p> 2112 * <c>Collection</c> classes are assumed to be followed by zero or one objects indicating the element type. 2113 * 2114 * <p> 2115 * <c>Map</c> classes are assumed to be followed by zero or two meta objects indicating the key and value types. 2116 * 2117 * <p> 2118 * The array can be arbitrarily long to indicate arbitrarily complex data structures. 2119 * 2120 * <ul class='notes'> 2121 * <li> 2122 * Use the {@link #getResponse(Class)} method instead if you don't need a parameterized map/collection. 2123 * <li> 2124 * You can also specify any of the following types: 2125 * <ul> 2126 * <li>{@link HttpResponse} - Returns the raw <c>HttpResponse</c> returned by the inner <c>HttpClient</c>. 2127 * <li>{@link Reader} - Returns access to the raw reader of the response. 2128 * <li>{@link InputStream} - Returns access to the raw input stream of the response. 2129 * </ul> 2130 * </ul> 2131 * 2132 * @param <T> The class type of the object to create. 2133 * @param type 2134 * The object type to create. 2135 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2136 * @param args 2137 * The type arguments of the class if it's a collection or map. 2138 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2139 * <br>Ignored if the main type is not a map or collection. 2140 * @return The parsed object. 2141 * @throws ParseException 2142 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 2143 * @throws IOException If a connection error occurred. 2144 * @see BeanSession#getClassMeta(Class) for argument syntax for maps and collections. 2145 */ 2146 public <T> T getResponse(Type type, Type...args) throws IOException, ParseException { 2147 BeanContext bc = parser; 2148 if (bc == null) 2149 bc = BeanContext.DEFAULT; 2150 return (T)getResponseInner(bc.getClassMeta(type, args)); 2151 } 2152 2153 /** 2154 * Same as {@link #getResponse(Type, Type...)} but allows you to specify a part parser to use for parsing the response. 2155 * 2156 * @param <T> The class type of the object to create. 2157 * @param type 2158 * The object type to create. 2159 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2160 * @param args 2161 * The type arguments of the class if it's a collection or map. 2162 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2163 * <br>Ignored if the main type is not a map or collection. 2164 * @return The parsed object. 2165 * @throws ParseException 2166 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 2167 * @throws IOException If a connection error occurred. 2168 * @see BeanSession#getClassMeta(Class) for argument syntax for maps and collections. 2169 */ 2170 public <T> T getResponseBody(Type type, Type...args) throws IOException, ParseException { 2171 BeanContext bc = parser; 2172 if (bc == null) 2173 bc = BeanContext.DEFAULT; 2174 return (T)getResponseInner(bc.getClassMeta(type, args)); 2175 } 2176 2177 /** 2178 * Same as {@link #getResponse(Class)} but allows you to run the call asynchronously. 2179 * 2180 * @param <T> 2181 * The class type of the object being created. 2182 * See {@link #getResponse(Type, Type...)} for details. 2183 * @param type 2184 * The object type to create. 2185 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2186 * @param args 2187 * The type arguments of the class if it's a collection or map. 2188 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2189 * <br>Ignored if the main type is not a map or collection. 2190 * @return The parsed object. 2191 * @throws RestCallException If the executor service was not defined. 2192 * @see 2193 * RestClientBuilder#executorService(ExecutorService, boolean) for defining the executor service for creating 2194 * {@link Future Futures}. 2195 */ 2196 public <T> Future<T> getResponseFuture(final Type type, final Type...args) throws RestCallException { 2197 return client.getExecutorService(true).submit( 2198 new Callable<T>() { 2199 @Override /* Callable */ 2200 public T call() throws Exception { 2201 return getResponse(type, args); 2202 } 2203 } 2204 ); 2205 } 2206 2207 /** 2208 * Parses the output from the connection into the specified type and then wraps that in a {@link PojoRest}. 2209 * 2210 * <p> 2211 * Useful if you want to quickly retrieve a single value from inside of a larger JSON document. 2212 * 2213 * @param innerType The class type of the POJO being wrapped. 2214 * @return The parsed output wrapped in a {@link PojoRest}. 2215 * @throws IOException If a connection error occurred. 2216 * @throws ParseException 2217 * If the input contains a syntax error or is malformed for the <c>Content-Type</c> header. 2218 */ 2219 public PojoRest getResponsePojoRest(Class<?> innerType) throws IOException, ParseException { 2220 return new PojoRest(getResponse(innerType)); 2221 } 2222 2223 /** 2224 * Converts the output from the connection into an {@link ObjectMap} and then wraps that in a {@link PojoRest}. 2225 * 2226 * <p> 2227 * Useful if you want to quickly retrieve a single value from inside of a larger JSON document. 2228 * 2229 * @return The parsed output wrapped in a {@link PojoRest}. 2230 * @throws IOException If a connection error occurred. 2231 * @throws ParseException 2232 * If the input contains a syntax error or is malformed for the <c>Content-Type</c> header. 2233 */ 2234 public PojoRest getResponsePojoRest() throws IOException, ParseException { 2235 return getResponsePojoRest(ObjectMap.class); 2236 } 2237 2238 <T> T getResponseInner(ClassMeta<T> type) throws IOException, ParseException { 2239 try { 2240 if (response == null) 2241 connect(type); 2242 2243 Class<?> ic = type.getInnerClass(); 2244 2245 if (ic.equals(HttpResponse.class)) { 2246 softClose(); 2247 return (T)response; 2248 } 2249 if (ic.equals(Reader.class)) 2250 return (T)getReader(); 2251 if (ic.equals(InputStream.class)) 2252 return (T)getInputStream(); 2253 if (type.isType(ReaderResource.class) || type.isType(StreamResource.class)) { 2254 String mediaType = null; 2255 ObjectMap headers = new ObjectMap(); 2256 for (Header h : response.getAllHeaders()) { 2257 if (h.getName().equalsIgnoreCase("Content-Type")) 2258 mediaType = h.getValue(); 2259 else 2260 headers.put(h.getName(), h.getValue()); 2261 } 2262 if (type.isType(ReaderResource.class)) 2263 return (T)ReaderResource.create().headers(headers).mediaType(mediaType).contents(getReader()).build(); 2264 return (T)StreamResource.create().headers(headers).mediaType(mediaType).contents(getInputStream()).build(); 2265 } 2266 2267 connect(type); 2268 Header h = response.getFirstHeader("Content-Type"); 2269 int sc = response.getStatusLine().getStatusCode(); 2270 String ct = firstNonEmpty(h == null ? null : h.getValue(), "text/plain"); 2271 2272 MediaType mt = MediaType.forString(ct); 2273 2274 if (parser == null || (mt.toString().equals("text/plain") && ! parser.canHandle(ct))) { 2275 if (type.hasStringMutater()) 2276 return type.getStringMutater().mutate(getResponseAsString()); 2277 } 2278 2279 if (parser != null) { 2280 try (Closeable in = parser.isReaderParser() ? getReader() : getInputStream()) { 2281 2282 // HttpClient automatically ignores the content body for certain HTTP status codes. 2283 // So instantiate the object anyway if it has a no-arg constructor. 2284 // This allows a remote resource method to return a NoContent object for example. 2285 if (in == null && (sc < SC_OK || sc == SC_NO_CONTENT || sc == SC_NOT_MODIFIED || sc == SC_RESET_CONTENT)) { 2286 ConstructorInfo c = type.getInfo().getPublicConstructor(); 2287 if (c != null) { 2288 try { 2289 return c.<T>invoke(); 2290 } catch (ExecutableException e) { 2291 throw new ParseException(e); 2292 } 2293 } 2294 } 2295 2296 ParserSessionArgs pArgs = 2297 ParserSessionArgs 2298 .create() 2299 .properties(new ObjectMap().setInner(getProperties())) 2300 .locale(response.getLocale()) 2301 .mediaType(mt) 2302 .schema(responseBodySchema); 2303 return parser.createSession(pArgs).parse(in, type); 2304 } 2305 } 2306 2307 if (type.hasReaderMutater()) 2308 return type.getReaderMutater().mutate(getReader()); 2309 2310 if (type.hasInputStreamMutater()) 2311 return type.getInputStreamMutater().mutate(getInputStream()); 2312 2313 throw new ParseException( 2314 "Unsupported media-type in request header ''Content-Type'': ''{0}''\n\tSupported media-types: {1}", 2315 getResponseHeader("Content-Type"), parser == null ? null : parser.getMediaTypes() 2316 ); 2317 2318 } catch (ParseException | IOException e) { 2319 isFailed = true; 2320 close(); 2321 throw e; 2322 } 2323 } 2324 2325 BeanContext getBeanContext() { 2326 BeanContext bc = parser; 2327 if (bc == null) 2328 bc = BeanContext.DEFAULT; 2329 return bc; 2330 } 2331 2332 /** 2333 * Converts the response from this call into a response bean. 2334 * 2335 * @param rbm The metadata used to construct the response bean. 2336 * @return A new response bean. 2337 */ 2338 public <T> T getResponse(final ResponseBeanMeta rbm) { 2339 try { 2340 softClose(); 2341 Class<T> c = (Class<T>)rbm.getClassMeta().getInnerClass(); 2342 final RestClient rc = this.client; 2343 final HttpPartParser p = ObjectUtils.firstNonNull(partParser, rc.getPartParser()); 2344 return (T)Proxy.newProxyInstance( 2345 c.getClassLoader(), 2346 new Class[] { c }, 2347 new InvocationHandler() { 2348 @Override /* InvocationHandler */ 2349 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 2350 ResponseBeanPropertyMeta pm = rbm.getProperty(method.getName()); 2351 if (pm != null) { 2352 HttpPartParser pp = pm.getParser(p); 2353 HttpPartSchema schema = pm.getSchema(); 2354 String name = pm.getPartName(); 2355 ClassMeta<?> type = rc.getClassMeta(method.getGenericReturnType()); 2356 HttpPartType pt = pm.getPartType(); 2357 if (pt == RESPONSE_BODY) { 2358 responseBodySchema(schema); 2359 return getResponseBody(type); 2360 } 2361 if (pt == RESPONSE_HEADER) 2362 return getResponseHeader(pp, schema, name, type); 2363 if (pt == RESPONSE_STATUS) 2364 return getResponseCode(); 2365 } 2366 return null; 2367 } 2368 2369 }); 2370 } catch (Exception e) { 2371 throw new RuntimeException(e); 2372 } 2373 } 2374 2375 /** 2376 * Returns access to the {@link HttpUriRequest} passed to {@link HttpClient#execute(HttpUriRequest)}. 2377 * 2378 * @return The {@link HttpUriRequest} object. 2379 */ 2380 public HttpUriRequest getRequest() { 2381 return request; 2382 } 2383 2384 /** 2385 * Returns access to the {@link HttpResponse} returned by {@link HttpClient#execute(HttpUriRequest)}. 2386 * 2387 * <p> 2388 * Returns <jk>null</jk> if {@link #connect()} has not yet been called. 2389 * 2390 * @return The HTTP response object. 2391 * @throws IOException Thrown by underlying stream. 2392 */ 2393 public HttpResponse getResponse() throws IOException { 2394 connect(); 2395 return response; 2396 } 2397 2398 /** 2399 * Shortcut for calling <c>getRequest().setHeader(header)</c> 2400 * 2401 * @param header The header to set on the request. 2402 * @return This object (for method chaining). 2403 */ 2404 public RestCall header(Header header) { 2405 request.setHeader(header); 2406 return this; 2407 } 2408 2409 /** 2410 * Cleans up this HTTP call. 2411 * 2412 * @throws RestCallException Can be thrown by one of the {@link RestCallInterceptor#onClose(RestCall)} calls. 2413 */ 2414 @Override /* Closeable */ 2415 public void close() throws RestCallException { 2416 if (response != null && ! softClose) 2417 EntityUtils.consumeQuietly(response.getEntity()); 2418 if (! softClose) 2419 isClosed = true; 2420 if (! isFailed) 2421 for (RestCallInterceptor r : interceptors) 2422 r.onClose(this); 2423 } 2424 2425 void forceClose() throws RestCallException { 2426 softClose = false; 2427 close(); 2428 } 2429 2430 /** 2431 * Adds a {@link RestCallLogger} to the list of interceptors on this class. 2432 * 2433 * @param level The log level to log events at. 2434 * @param log The logger. 2435 * @return This object (for method chaining). 2436 */ 2437 public RestCall logTo(Level level, Logger log) { 2438 interceptor(new RestCallLogger(level, log)); 2439 return this; 2440 } 2441 2442 /** 2443 * Sets <c>Debug: value</c> header on this request. 2444 * 2445 * @return This object (for method chaining). 2446 * @throws RestCallException Invalid input. 2447 */ 2448 public RestCall debug() throws RestCallException { 2449 header("Debug", true); 2450 return this; 2451 } 2452 2453 /** 2454 * If called, the underlying response stream will not be closed when you call {@link #close()}. 2455 * 2456 * <p> 2457 * This is useful in cases where you want access to that stream after you've already cleaned up this object. 2458 * However, it is your responsibility to close that stream yourself. 2459 * 2460 * @return This object (for method chaining). 2461 */ 2462 public RestCall softClose() { 2463 this.softClose = true; 2464 return this; 2465 } 2466 2467 //----------------------------------------------------------------------------------------------------------------- 2468 // Utility methods 2469 //----------------------------------------------------------------------------------------------------------------- 2470 2471 /** 2472 * Specifies that the following value can be added as an HTTP part. 2473 */ 2474 private boolean canAdd(Object value, HttpPartSchema schema, boolean skipIfEmpty) { 2475 if (value != null) { 2476 if (ObjectUtils.isEmpty(value) && skipIfEmpty) 2477 return false; 2478 return true; 2479 } 2480 if (schema == null) 2481 return false; 2482 if (schema.isRequired()) 2483 return true; 2484 String def = schema.getDefault(); 2485 if (def == null) 2486 return false; 2487 if (StringUtils.isEmpty(def) && skipIfEmpty) 2488 return false; 2489 return true; 2490 } 2491 2492 private static String className(Object o) { 2493 return ClassInfo.of(o).getFullName(); 2494 } 2495 2496 //----------------------------------------------------------------------------------------------------------------- 2497 // Other methods 2498 //----------------------------------------------------------------------------------------------------------------- 2499 2500 @Override /* Session */ 2501 public ObjectMap toMap() { 2502 return super.toMap() 2503 .append("RestCall", new DefaultFilteringObjectMap() 2504 .append("allowRedirectsOnPosts", allowRedirectsOnPosts) 2505 .append("byLines", byLines) 2506 .append("capturedResponse", capturedResponse) 2507 .append("client", client) 2508 .append("hasInput", hasInput) 2509 .append("ignoreErrors", ignoreErrors) 2510 .append("interceptors", interceptors) 2511 .append("isClosed", isClosed) 2512 .append("isConnected", isConnected) 2513 .append("isFailed", isFailed) 2514 .append("parser", parser) 2515 .append("partParser", partParser) 2516 .append("partSerializer", partSerializer) 2517 .append("redirectOnPostsTries", redirectOnPostsTries) 2518 .append("requestBodySchema", requestBodySchema) 2519 .append("response", response) 2520 .append("responseBodySchema", responseBodySchema) 2521 .append("retries", retries) 2522 .append("retryInterval", retryInterval) 2523 .append("retryOn", retryOn) 2524 .append("serializer", serializer) 2525 .append("softClose", softClose) 2526 .append("uriBuilder", uriBuilder) 2527 ); 2528 } 2529}