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