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