001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.client; 018 019import static java.util.stream.Collectors.toList; 020import static org.apache.juneau.collections.JsonMap.*; 021import static org.apache.juneau.common.utils.IOUtils.*; 022import static org.apache.juneau.common.utils.Utils.*; 023import static org.apache.juneau.http.HttpEntities.*; 024import static org.apache.juneau.http.HttpHeaders.*; 025import static org.apache.juneau.httppart.HttpPartType.*; 026import static org.apache.juneau.internal.ClassUtils.*; 027import static org.apache.juneau.rest.client.RestOperation.*; 028 029import java.io.*; 030import java.lang.reflect.*; 031import java.net.*; 032import java.text.*; 033import java.util.*; 034import java.util.concurrent.*; 035import java.util.function.*; 036import java.util.logging.*; 037 038import org.apache.http.*; 039import org.apache.http.ParseException; 040import org.apache.http.client.config.*; 041import org.apache.http.client.entity.*; 042import org.apache.http.client.methods.*; 043import org.apache.http.client.utils.*; 044import org.apache.http.concurrent.*; 045import org.apache.http.entity.BasicHttpEntity; 046import org.apache.http.params.*; 047import org.apache.http.protocol.*; 048import org.apache.juneau.*; 049import org.apache.juneau.collections.*; 050import org.apache.juneau.common.utils.*; 051import org.apache.juneau.html.*; 052import org.apache.juneau.http.*; 053import org.apache.juneau.http.HttpHeaders; 054import org.apache.juneau.http.entity.*; 055import org.apache.juneau.http.header.*; 056import org.apache.juneau.http.header.ContentType; 057import org.apache.juneau.http.part.*; 058import org.apache.juneau.http.resource.*; 059import org.apache.juneau.httppart.*; 060import org.apache.juneau.internal.*; 061import org.apache.juneau.json.*; 062import org.apache.juneau.msgpack.*; 063import org.apache.juneau.oapi.*; 064import org.apache.juneau.parser.*; 065import org.apache.juneau.plaintext.*; 066import org.apache.juneau.reflect.*; 067import org.apache.juneau.serializer.*; 068import org.apache.juneau.uon.*; 069import org.apache.juneau.urlencoding.*; 070import org.apache.juneau.xml.*; 071 072/** 073 * Represents a request to a remote REST resource. 074 * 075 * <p> 076 * Instances of this class are created by the various creator methods on the {@link RestClient} class. 077 * 078 * <h5 class='section'>Example:</h5> 079 * <p class='bjava'> 080 * <jc>// Create a request and automatically close it.</jc> 081 * <jk>try</jk> (<jv>RestRequest</jv> <jv>req</jv> = <jv>client</jv>.get(<js>"/myResource"</js>)) { 082 * <jv>req</jv> 083 * .queryData(<js>"foo"</js>, <js>"bar"</js>) 084 * .run() 085 * .assertStatus().asCode().is(200); 086 * } 087 * </p> 088 * 089 * <p> 090 * The {@link #close()} method will automatically close any associated {@link RestResponse} if one was created via {@link #run()}. 091 * 092 * <h5 class='section'>Notes:</h5><ul> 093 * <li class='warn'>This class is not thread safe. 094 * <li class='note'>This class implements {@link AutoCloseable} and can be used in try-with-resources blocks. 095 * The {@link #close()} method allows unchecked exceptions to propagate for debuggability, 096 * while catching and logging checked exceptions to follow AutoCloseable best practices. 097 * </ul> 098 * 099 * <h5 class='section'>See Also:</h5><ul> 100 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestClientBasics">juneau-rest-client Basics</a> 101 * </ul> 102 */ 103public class RestRequest extends BeanSession implements HttpUriRequest, Configurable, AutoCloseable { 104 105 private static final ContentType TEXT_PLAIN = ContentType.TEXT_PLAIN; 106 107 final RestClient client; // The client that created this call. 108 private final HttpRequestBase request; // The request. 109 private RestResponse response; // The response. 110 List<RestCallInterceptor> interceptors = list(); // Used for intercepting and altering requests. 111 112 private HeaderList headerData; 113 private PartList queryData, formData, pathData; 114 115 private boolean ignoreErrors, suppressLogging; 116 117 private Object content; 118 private Serializer serializer; 119 private Parser parser; 120 private HttpPartSchema contentSchema; 121 private URIBuilder uriBuilder; 122 private Predicate<Integer> errorCodes; 123 private HttpHost target; 124 private HttpContext context; 125 private List<Class<? extends Throwable>> rethrow; 126 private HttpPartSerializerSession partSerializerSession; 127 128 private final Map<HttpPartSerializer,HttpPartSerializerSession> partSerializerSessions = new IdentityHashMap<>(); 129 130 /** 131 * Constructs a REST call with the specified method name. 132 * 133 * @param client The client that created this request. 134 * @param uri The target URI. 135 * @param method The HTTP method name (uppercase). 136 * @param hasBody Whether this method has a body. 137 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 138 */ 139 protected RestRequest(RestClient client, URI uri, String method, boolean hasBody) throws RestCallException { 140 super(client.getBeanContext().createSession()); 141 this.client = client; 142 this.request = createInnerRequest(method, uri, hasBody); 143 this.errorCodes = client.errorCodes; 144 this.uriBuilder = new URIBuilder(request.getURI()); 145 this.ignoreErrors = client.ignoreErrors; 146 this.headerData = client.createHeaderData(); 147 this.queryData = client.createQueryData(); 148 this.formData = client.createFormData(); 149 this.pathData = client.createPathData(); 150 } 151 152 /** 153 * Constructs the {@link HttpRequestBase} object that ends up being passed to the client execute method. 154 * 155 * <p> 156 * Subclasses can override this method to create their own request base objects. 157 * 158 * @param method The HTTP method. 159 * @param uri The HTTP URI. 160 * @param hasBody Whether the HTTP request has a body. 161 * @return A new {@link HttpRequestBase} object. 162 */ 163 protected HttpRequestBase createInnerRequest(String method, URI uri, boolean hasBody) { 164 HttpRequestBase req = hasBody ? new BasicHttpEntityRequestBase(this, method) : new BasicHttpRequestBase(this, method); 165 req.setURI(uri); 166 return req; 167 } 168 169 170 //------------------------------------------------------------------------------------------------------------------ 171 // Configuration 172 //------------------------------------------------------------------------------------------------------------------ 173 174 /** 175 * Convenience method for specifying JSON as the marshalling transmission media type for this request only. 176 * 177 * <p> 178 * {@link JsonSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 179 * <ul> 180 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 181 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 182 * </ul> 183 * <p> 184 * {@link JsonParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 185 * <ul> 186 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 187 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 188 * </ul> 189 * <p> 190 * <c>Accept</c> request header will be set to <js>"application/json"</js> unless overridden 191 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 192 * <p> 193 * <c>Content-Type</c> request header will be set to <js>"application/json"</js> unless overridden 194 * {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 195 * <p> 196 * Identical to calling <c>serializer(JsonSerializer.<jk>class</jk>).parser(JsonParser.<jk>class</jk>)</c>. 197 * 198 * @return This object. 199 */ 200 public RestRequest json() { 201 return serializer(JsonSerializer.class).parser(JsonParser.class); 202 } 203 204 /** 205 * Convenience method for specifying Simplified JSON as the marshalling transmission media type for this request only. 206 * 207 * <p> 208 * Simplified JSON is typically useful for automated tests because you can do simple string comparison of results 209 * without having to escape lots of quotes. 210 * 211 * <p> 212 * {@link Json5Serializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 213 * <ul> 214 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 215 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 216 * </ul> 217 * <p> 218 * {@link Json5Parser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 219 * <ul> 220 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 221 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 222 * </ul> 223 * <p> 224 * <c>Accept</c> request header will be set to <js>"application/json"</js> unless overridden 225 * by {@link #header(String,Object)} or {@link #accept(String)}, or per-request via {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 226 * <p> 227 * <c>Content-Type</c> request header will be set to <js>"application/json+simple"</js> unless overridden 228 * by {@link #header(String,Object)} or {@link #contentType(String)}, or per-request via {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 229 * <p> 230 * Can be combined with other marshaller setters such as {@link #xml()} to provide support for multiple languages. 231 * <ul> 232 * <li>When multiple languages are supported, the <c>Accept</c> and <c>Content-Type</c> headers control which marshallers are used, or uses the 233 * last-enabled language if the headers are not set. 234 * </ul> 235 * <p> 236 * Identical to calling <c>serializer(Json5Serializer.<jk>class</jk>).parser(Json5Parser.<jk>class</jk>)</c>. 237 * 238 * @return This object. 239 */ 240 public RestRequest json5() { 241 return serializer(Json5Serializer.class).parser(Json5Parser.class); 242 } 243 244 /** 245 * Convenience method for specifying XML as the marshalling transmission media type for this request only. 246 * 247 * <p> 248 * {@link XmlSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 249 * <ul> 250 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 251 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 252 * </ul> 253 * <p> 254 * {@link XmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 255 * <ul> 256 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 257 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 258 * </ul> 259 * <p> 260 * <c>Accept</c> request header will be set to <js>"text/xml"</js> unless overridden 261 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 262 * <p> 263 * <c>Content-Type</c> request header will be set to <js>"text/xml"</js> unless overridden 264 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 265 * <p> 266 * Identical to calling <c>serializer(XmlSerializer.<jk>class</jk>).parser(XmlParser.<jk>class</jk>)</c>. 267 * 268 * @return This object. 269 */ 270 public RestRequest xml() { 271 return serializer(XmlSerializer.class).parser(XmlParser.class); 272 } 273 274 /** 275 * Convenience method for specifying HTML as the marshalling transmission media type for this request only. 276 * 277 * <p> 278 * POJOs are converted to HTML without any sort of doc wrappers. 279 * 280 * <p> 281 * {@link HtmlSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 282 * <ul> 283 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 284 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 285 * </ul> 286 * <p> 287 * {@link HtmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 288 * <ul> 289 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 290 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 291 * </ul> 292 * <p> 293 * <c>Accept</c> request header will be set to <js>"text/html"</js> unless overridden 294 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 295 * <p> 296 * <c>Content-Type</c> request header will be set to <js>"text/html"</js> unless overridden 297 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 298 * <p> 299 * Identical to calling <c>serializer(HtmlSerializer.<jk>class</jk>).parser(HtmlParser.<jk>class</jk>)</c>. 300 * 301 * @return This object. 302 */ 303 public RestRequest html() { 304 return serializer(HtmlSerializer.class).parser(HtmlParser.class); 305 } 306 307 /** 308 * Convenience method for specifying HTML DOC as the marshalling transmission media type for this request only. 309 * 310 * <p> 311 * POJOs are converted to fully renderable HTML pages. 312 * 313 * <p> 314 * {@link HtmlDocSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 315 * <ul> 316 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 317 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 318 * </ul> 319 * <p> 320 * {@link HtmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 321 * <ul> 322 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 323 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 324 * </ul> 325 * <p> 326 * <c>Accept</c> request header will be set to <js>"text/html"</js> unless overridden 327 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 328 * <p> 329 * <c>Content-Type</c> request header will be set to <js>"text/html"</js> unless overridden 330 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 331 * <p> 332 * Identical to calling <c>serializer(HtmlDocSerializer.<jk>class</jk>).parser(HtmlParser.<jk>class</jk>)</c>. 333 * 334 * @return This object. 335 */ 336 public RestRequest htmlDoc() { 337 return serializer(HtmlDocSerializer.class).parser(HtmlParser.class); 338 } 339 340 /** 341 * Convenience method for specifying Stripped HTML DOC as the marshalling transmission media type for this request only. 342 * 343 * <p> 344 * Same as {@link #htmlDoc()} but without the header and body tags and page title and description. 345 * 346 * <p> 347 * {@link HtmlStrippedDocSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 348 * <ul> 349 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 350 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 351 * </ul> 352 * <p> 353 * {@link HtmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 354 * <ul> 355 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 356 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 357 * </ul> 358 * <p> 359 * <c>Accept</c> request header will be set to <js>"text/html+stripped"</js> unless overridden 360 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 361 * <p> 362 * <c>Content-Type</c> request header will be set to <js>"text/html+stripped"</js> unless overridden 363 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 364 * <p> 365 * Identical to calling <c>serializer(HtmlStrippedDocSerializer.<jk>class</jk>).parser(HtmlParser.<jk>class</jk>)</c>. 366 * 367 * @return This object. 368 */ 369 public RestRequest htmlStrippedDoc() { 370 return serializer(HtmlStrippedDocSerializer.class).parser(HtmlParser.class); 371 } 372 373 /** 374 * Convenience method for specifying Plain Text as the marshalling transmission media type for this request only. 375 * 376 * <p> 377 * Plain text marshalling typically only works on simple POJOs that can be converted to and from strings using 378 * swaps, swap methods, etc... 379 * 380 * <p> 381 * {@link PlainTextSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 382 * <ul> 383 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 384 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 385 * </ul> 386 * <p> 387 * {@link PlainTextParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 388 * <ul> 389 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 390 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 391 * </ul> 392 * <p> 393 * <c>Accept</c> request header will be set to <js>"text/plain"</js> unless overridden 394 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 395 * <p> 396 * <c>Content-Type</c> request header will be set to <js>"text/plain"</js> unless overridden 397 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 398 * <p> 399 * Identical to calling <c>serializer(PlainTextSerializer.<jk>class</jk>).parser(PlainTextParser.<jk>class</jk>)</c>. 400 * 401 * @return This object. 402 */ 403 public RestRequest plainText() { 404 return serializer(PlainTextSerializer.class).parser(PlainTextParser.class); 405 } 406 407 /** 408 * Convenience method for specifying MessagePack as the marshalling transmission media type for this request only. 409 * 410 * <p> 411 * MessagePack is a binary equivalent to JSON that takes up considerably less space than JSON. 412 * 413 * <p> 414 * {@link MsgPackSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 415 * <ul> 416 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 417 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 418 * </ul> 419 * <p> 420 * {@link MsgPackParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 421 * <ul> 422 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 423 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 424 * </ul> 425 * <p> 426 * <c>Accept</c> request header will be set to <js>"octal/msgpack"</js> unless overridden 427 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 428 * <p> 429 * <c>Content-Type</c> request header will be set to <js>"octal/msgpack"</js> unless overridden 430 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 431 * <p> 432 * Identical to calling <c>serializer(MsgPackSerializer.<jk>class</jk>).parser(MsgPackParser.<jk>class</jk>)</c>. 433 * 434 * @return This object. 435 */ 436 public RestRequest msgPack() { 437 return serializer(MsgPackSerializer.class).parser(MsgPackParser.class); 438 } 439 440 /** 441 * Convenience method for specifying UON as the marshalling transmission media type for this request only. 442 * 443 * <p> 444 * UON is Url-Encoding Object notation that is equivalent to JSON but suitable for transmission as URL-encoded 445 * query and form post values. 446 * 447 * <p> 448 * {@link UonSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 449 * <ul> 450 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 451 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 452 * </ul> 453 * <p> 454 * {@link UonParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 455 * <ul> 456 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 457 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 458 * </ul> 459 * <p> 460 * <c>Accept</c> request header will be set to <js>"text/uon"</js> unless overridden 461 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 462 * <p> 463 * <c>Content-Type</c> request header will be set to <js>"text/uon"</js> unless overridden 464 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 465 * <p> 466 * Identical to calling <c>serializer(UonSerializer.<jk>class</jk>).parser(UonParser.<jk>class</jk>)</c>. 467 * 468 * @return This object. 469 */ 470 public RestRequest uon() { 471 return serializer(UonSerializer.class).parser(UonParser.class); 472 } 473 474 /** 475 * Convenience method for specifying URL-Encoding as the marshalling transmission media type for this request only. 476 * 477 * <p> 478 * {@link UrlEncodingSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 479 * <ul> 480 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 481 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 482 * <li>This serializer is NOT used when using the {@link RestRequest#formData(String, Object)} (and related) methods for constructing 483 * the request body. Instead, the part serializer specified via {@link RestClient.Builder#partSerializer(Class)} is used. 484 * </ul> 485 * <p> 486 * {@link UrlEncodingParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 487 * <ul> 488 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 489 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 490 * </ul> 491 * <p> 492 * <c>Accept</c> request header will be set to <js>"application/x-www-form-urlencoded"</js> unless overridden 493 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 494 * <p> 495 * <c>Content-Type</c> request header will be set to <js>"application/x-www-form-urlencoded"</js> unless overridden 496 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 497 * <p> 498 * Identical to calling <c>serializer(UrlEncodingSerializer.<jk>class</jk>).parser(UrlEncodingParser.<jk>class</jk>)</c>. 499 * 500 * @return This object. 501 */ 502 public RestRequest urlEnc() { 503 return serializer(UrlEncodingSerializer.class).parser(UrlEncodingParser.class); 504 } 505 506 /** 507 * Convenience method for specifying OpenAPI as the marshalling transmission media type for this request only. 508 * 509 * <p> 510 * OpenAPI is a language that allows serialization to formats that use {@link HttpPartSchema} objects to describe their structure. 511 * 512 * <p> 513 * {@link OpenApiSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 514 * <ul> 515 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 516 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 517 * <li>Typically the {@link RestRequest#content(Object, HttpPartSchema)} method will be used to specify the body of the request with the 518 * schema describing it's structure. 519 * </ul> 520 * <p> 521 * {@link OpenApiParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 522 * <ul> 523 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 524 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 525 * <li>Typically the {@link ResponseContent#schema(HttpPartSchema)} method will be used to specify the structure of the response body. 526 * </ul> 527 * <p> 528 * <c>Accept</c> request header will be set to <js>"text/openapi"</js> unless overridden 529 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 530 * <p> 531 * <c>Content-Type</c> request header will be set to <js>"text/openapi"</js> unless overridden 532 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 533 * <p> 534 * Identical to calling <c>serializer(OpenApiSerializer.<jk>class</jk>).parser(OpenApiParser.<jk>class</jk>)</c>. 535 * 536 * @return This object. 537 */ 538 public RestRequest openApi() { 539 return serializer(OpenApiSerializer.class).parser(OpenApiParser.class); 540 } 541 542 /** 543 * Specifies the serializer to use on the request body. 544 * 545 * <p> 546 * Overrides the serializers specified on the {@link RestClient}. 547 * 548 * <p> 549 * The serializer is not modified by an of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 550 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 551 * 552 * <p> 553 * If the <c>Content-Type</c> header is not set on the request, it will be set to the media type of this serializer. 554 * 555 * @param serializer The serializer used to serialize POJOs to the body of the HTTP request. 556 * @return This object. 557 */ 558 public RestRequest serializer(Serializer serializer) { 559 this.serializer = serializer; 560 return this; 561 } 562 563 /** 564 * Specifies the serializer to use on the request body. 565 * 566 * <p> 567 * Overrides the serializers specified on the {@link RestClient}. 568 * 569 * <p> 570 * The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 571 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 572 * 573 * <p> 574 * If the <c>Content-Type</c> header is not set on the request, it will be set to the media type of this serializer. 575 * 576 * @param serializer The serializer used to serialize POJOs to the body of the HTTP request. 577 * @return This object. 578 */ 579 public RestRequest serializer(Class<? extends Serializer> serializer) { 580 this.serializer = client.getInstance(serializer); 581 return this; 582 } 583 584 /** 585 * Specifies the parser to use on the response body. 586 * 587 * <p> 588 * Overrides the parsers specified on the {@link RestClient}. 589 * 590 * <p> 591 * The parser is not modified by any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 592 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 593 * 594 * <p> 595 * If the <c>Accept</c> header is not set on the request, it will be set to the media type of this parser. 596 * 597 * @param parser The parser used to parse POJOs from the body of the HTTP response. 598 * @return This object. 599 */ 600 public RestRequest parser(Parser parser) { 601 this.parser = parser; 602 return this; 603 } 604 605 /** 606 * Specifies the parser to use on the response body. 607 * 608 * <p> 609 * Overrides the parsers specified on the {@link RestClient}. 610 * 611 * <p> 612 * The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#strict()}) or 613 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 614 * 615 * <p> 616 * If the <c>Accept</c> header is not set on the request, it will be set to the media type of this parser. 617 * 618 * @param parser The parser used to parse POJOs from the body of the HTTP response. 619 * @return This object. 620 */ 621 public RestRequest parser(Class<? extends Parser> parser) { 622 this.parser = client.getInstance(parser); 623 return this; 624 } 625 626 /** 627 * Allows you to override what status codes are considered error codes that would result in a {@link RestCallException}. 628 * 629 * <p> 630 * The default error code predicate is: <code>x -> x >= 400</code>. 631 * 632 * @param value The new predicate for calculating error codes. 633 * @return This object. 634 */ 635 public RestRequest errorCodes(Predicate<Integer> value) { 636 this.errorCodes = value; 637 return this; 638 } 639 640 /** 641 * Add one or more interceptors for this call only. 642 * 643 * @param interceptors The interceptors to add to this call. 644 * @return This object. 645 * @throws RestCallException If init method on interceptor threw an exception. 646 */ 647 public RestRequest interceptors(RestCallInterceptor...interceptors) throws RestCallException { 648 try { 649 for (RestCallInterceptor i : interceptors) { 650 this.interceptors.add(i); 651 i.onInit(this); 652 } 653 } catch (RuntimeException | RestCallException e) { 654 throw e; 655 } catch (Exception e) { 656 throw new RestCallException(null, e, "Interceptor threw an exception on init."); 657 } 658 659 return this; 660 } 661 662 /** 663 * Prevent {@link RestCallException RestCallExceptions} from being thrown when HTTP status 400+ is encountered. 664 * 665 * <p> 666 * This overrides the <l>ignoreErrors</l> property on the client. 667 * 668 * @return This object. 669 */ 670 public RestRequest ignoreErrors() { 671 this.ignoreErrors = true; 672 return this; 673 } 674 675 /** 676 * Rethrow any of the specified exception types if a matching <c>Exception-Name</c> header is found. 677 * 678 * <p> 679 * Rethrown exceptions will be set on the caused-by exception of {@link RestCallException} when 680 * thrown from the {@link #run()} method. 681 * 682 * <p> 683 * Can be called multiple times to append multiple rethrows. 684 * 685 * @param values The classes to rethrow. 686 * @return This object. 687 */ 688 @SuppressWarnings("unchecked") 689 public RestRequest rethrow(Class<?>...values) { 690 if (rethrow == null) 691 rethrow = list(); 692 for (Class<?> v : values) { 693 if (v != null && Throwable.class.isAssignableFrom(v)) 694 rethrow.add((Class<? extends Throwable>)v); 695 } 696 return this; 697 } 698 699 /** 700 * Sets <c>Debug: value</c> header on this request. 701 * 702 * @return This object. 703 * @throws RestCallException Invalid input. 704 */ 705 public RestRequest debug() throws RestCallException { 706 header("Debug", true); 707 return this; 708 } 709 710 /** 711 * Returns <jk>true</jk> if debug mode is currently enabled. 712 */ 713 @Override 714 public boolean isDebug() { 715 return headerData.get("Debug", Boolean.class).orElse(false); 716 } 717 718 /** 719 * Causes logging to be suppressed for the duration of this request. 720 * 721 * <p> 722 * Overrides the {@link #debug()} setting on this request or client. 723 * 724 * @return This object. 725 */ 726 public RestRequest suppressLogging() { 727 this.suppressLogging = true; 728 return this; 729 } 730 731 boolean isLoggingSuppressed() { 732 return suppressLogging; 733 } 734 735 /** 736 * Specifies the target host for the request. 737 * 738 * @param target The target host for the request. 739 * Implementations may accept <jk>null</jk> if they can still determine a route, for example to a default 740 * target or by inspecting the request. 741 * @return This object. 742 */ 743 public RestRequest target(HttpHost target) { 744 this.target = target; 745 return this; 746 } 747 748 /** 749 * Override the context to use for the execution. 750 * 751 * @param context The context to use for the execution, or <jk>null</jk> to use the default context. 752 * @return This object. 753 */ 754 public RestRequest context(HttpContext context) { 755 this.context = context; 756 return this; 757 } 758 759 //------------------------------------------------------------------------------------------------------------------ 760 // Part builders 761 //------------------------------------------------------------------------------------------------------------------ 762 763 /** 764 * Returns the header data for the request. 765 * 766 * @return An immutable list of headers to send on the request. 767 */ 768 public HeaderList getHeaders() { 769 return headerData; 770 } 771 772 /** 773 * Returns the query data for the request. 774 * 775 * @return An immutable list of query data to send on the request. 776 */ 777 public PartList getQueryData() { 778 return queryData; 779 } 780 781 /** 782 * Returns the form data for the request. 783 * 784 * @return An immutable list of form data to send on the request. 785 */ 786 public PartList getFormData() { 787 return formData; 788 } 789 790 /** 791 * Returns the path data for the request. 792 * 793 * @return An immutable list of path data to send on the request. 794 */ 795 public PartList getPathData() { 796 return pathData; 797 } 798 799 //----------------------------------------------------------------------------------------------------------------- 800 // Parts 801 //----------------------------------------------------------------------------------------------------------------- 802 803 /** 804 * Appends a header to the request. 805 * 806 * <h5 class='section'>Example:</h5> 807 * <p class='bjava'> 808 * <jc>// Adds header "Foo: bar".</jc> 809 * <jv>client</jv> 810 * .get(<jsf>URI</jsf>) 811 * .header(Accept.<jsf>TEXT_XML</jsf>) 812 * .run(); 813 * </p> 814 * 815 * @param part 816 * The parameter to set. 817 * <jk>null</jk> values are ignored. 818 * @return This object. 819 */ 820 public RestRequest header(Header part) { 821 return headers(part); 822 } 823 824 /** 825 * Appends multiple headers to the request. 826 * 827 * <h5 class='section'>Example:</h5> 828 * <p class='bjava'> 829 * <jc>// Appends two headers to the request.</jc> 830 * <jv>client</jv> 831 * .get(<jsf>URI</jsf>) 832 * .headers( 833 * BasicHeader.<jsm>of</jsm>(<js>"Foo"</js>, <js>"bar"</js>), 834 * Accept.<jsf>TEXT_XML</jsf> 835 * ) 836 * .run(); 837 * </p> 838 * 839 * @param parts 840 * The parameters to set. 841 * <jk>null</jk> values are ignored. 842 * @return This object. 843 */ 844 public RestRequest headers(Header...parts) { 845 headerData.append(parts); 846 return this; 847 } 848 849 /** 850 * Appends multiple query parameters to the request. 851 * 852 * <h5 class='section'>Example:</h5> 853 * <p class='bjava'> 854 * <jc>// Appends two query parameters to the request.</jc> 855 * <jv>client</jv> 856 * .get(<jsf>URI</jsf>) 857 * .queryData( 858 * BasicStringPart.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>), 859 * BasicBooleanPart.<jsm>of</jsm>(<js>"baz"</js>, <jk>true</jk>) 860 * ) 861 * .run(); 862 * </p> 863 * 864 * @param parts 865 * The parameters to set. 866 * <jk>null</jk> values are ignored. 867 * @return This object. 868 */ 869 public RestRequest queryData(NameValuePair...parts) { 870 queryData.append(parts); 871 return this; 872 } 873 874 /** 875 * Appends multiple form-data parameters to the request. 876 * 877 * <h5 class='section'>Example:</h5> 878 * <p class='bjava'> 879 * <jc>// Appends two form-data parameters to the request.</jc> 880 * <jv>client</jv> 881 * .get(<jsf>URI</jsf>) 882 * .formData( 883 * BasicStringPart.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>), 884 * BasicBooleanPart.<jsm>of</jsm>(<js>"baz"</js>, <jk>true</jk>) 885 * ) 886 * .run(); 887 * </p> 888 * 889 * @param parts 890 * The parameters to set. 891 * <jk>null</jk> values are ignored. 892 * @return This object. 893 */ 894 public RestRequest formData(NameValuePair...parts) { 895 formData.append(parts); 896 return this; 897 } 898 899 /** 900 * Sets or replaces multiple path parameters on the request. 901 * 902 * <h5 class='section'>Example:</h5> 903 * <p class='bjava'> 904 * <jc>// Appends two path parameters to the request.</jc> 905 * <jv>client</jv> 906 * .get(<jsf>URI</jsf>) 907 * .pathData( 908 * BasicStringPart.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>), 909 * BasicBooleanPart.<jsm>of</jsm>(<js>"baz"</js>, <jk>true</jk>) 910 * ) 911 * .run(); 912 * </p> 913 * 914 * @param parts 915 * The parameters to set. 916 * <jk>null</jk> values are ignored. 917 * @return This object. 918 */ 919 public RestRequest pathData(NameValuePair...parts) { 920 pathData.set(parts); 921 return this; 922 } 923 924 /** 925 * Appends a header to the request. 926 * 927 * <h5 class='section'>Example:</h5> 928 * <p class='bjava'> 929 * <jc>// Adds header "Foo: bar".</jc> 930 * <jv>client</jv> 931 * .get(<jsf>URI</jsf>) 932 * .header(<js>"Foo"</js>, <js>"bar"</js>) 933 * .run(); 934 * </p> 935 * 936 * @param name 937 * The parameter name. 938 * @param value 939 * The parameter value. 940 * <br>Non-string values are converted to strings using the {@link HttpPartSerializer} defined on the client. 941 * @return This object. 942 */ 943 public RestRequest header(String name, Object value) { 944 headerData.append(createHeader(name, value)); 945 return this; 946 } 947 948 /** 949 * Appends a query parameter to the URI of the request. 950 * 951 * <h5 class='section'>Example:</h5> 952 * <p class='bjava'> 953 * <jc>// Adds query parameter "foo=bar".</jc> 954 * <jv>client</jv> 955 * .get(<jsf>URI</jsf>) 956 * .queryData(<js>"foo"</js>, <js>"bar"</js>) 957 * .run(); 958 * </p> 959 * 960 * @param name 961 * The parameter name. 962 * @param value 963 * The parameter value. 964 * <br>Non-string values are converted to strings using the {@link HttpPartSerializer} defined on the client. 965 * @return This object. 966 */ 967 public RestRequest queryData(String name, Object value) { 968 queryData.append(createPart(QUERY, name, value)); 969 return this; 970 } 971 972 /** 973 * Adds a form-data parameter to the request body. 974 * 975 * <h5 class='section'>Example:</h5> 976 * <p class='bjava'> 977 * <jc>// Adds form data parameter "foo=bar".</jc> 978 * <jv>client</jv> 979 * .formPost(<jsf>URI</jsf>) 980 * .formData(<js>"foo"</js>, <js>"bar"</js>) 981 * .run(); 982 * </p> 983 * 984 * @param name 985 * The parameter name. 986 * @param value 987 * The parameter value. 988 * <br>Non-string values are converted to strings using the {@link HttpPartSerializer} defined on the client. 989 * @return This object. 990 */ 991 public RestRequest formData(String name, Object value) { 992 formData.append(createPart(FORMDATA, name, value)); 993 return this; 994 } 995 996 /** 997 * Sets or replaces a path parameter of the form <js>"{name}"</js> in the URI. 998 * 999 * <h5 class='section'>Example:</h5> 1000 * <p class='bjava'> 1001 * <jc>// Sets path to "/bar".</jc> 1002 * <jv>client</jv> 1003 * .get(<js>"/{foo}"</js>) 1004 * .pathData(<js>"foo"</js>, <js>"bar"</js>) 1005 * .run(); 1006 * </p> 1007 * 1008 * @param name 1009 * The parameter name. 1010 * @param value 1011 * The parameter value. 1012 * <br>Non-string values are converted to strings using the {@link HttpPartSerializer} defined on the client. 1013 * @return This object. 1014 */ 1015 public RestRequest pathData(String name, Object value) { 1016 pathData.set(createPart(PATH, name, value)); 1017 return this; 1018 } 1019 1020 /** 1021 * Appends multiple headers to the request using freeform key/value pairs. 1022 * 1023 * <h5 class='section'>Example:</h5> 1024 * <p class='bjava'> 1025 * <jc>// Adds headers "Foo: bar" and "Baz: qux".</jc> 1026 * <jv>client</jv> 1027 * .get(<jsf>URI</jsf>) 1028 * .headerPairs(<js>"Foo"</js>,<js>"bar"</js>,<js>"Baz"</js>,<js>"qux"</js>) 1029 * .run(); 1030 * </p> 1031 * 1032 * @param pairs The form-data key/value pairs. 1033 * @return This object. 1034 */ 1035 public RestRequest headerPairs(String...pairs) { 1036 if (pairs.length % 2 != 0) 1037 throw new IllegalArgumentException("Odd number of parameters passed into headerPairs(String...)"); 1038 HeaderList b = headerData; 1039 for (int i = 0; i < pairs.length; i+=2) 1040 b.append(pairs[i], pairs[i+1]); 1041 return this; 1042 } 1043 1044 /** 1045 * Adds query parameters to the URI query using free-form key/value pairs.. 1046 * 1047 * <h5 class='section'>Example:</h5> 1048 * <p class='bjava'> 1049 * <jc>// Adds query parameters "foo=bar&baz=qux".</jc> 1050 * <jv>client</jv> 1051 * .get(<jsf>URI</jsf>) 1052 * .queryDataPairs(<js>"foo"</js>,<js>"bar"</js>,<js>"baz"</js>,<js>"qux"</js>) 1053 * .run(); 1054 * </p> 1055 * 1056 * @param pairs The query key/value pairs. 1057 * <ul> 1058 * <li>Values can be any POJO. 1059 * <li>Values converted to a string using the configured part serializer. 1060 * </ul> 1061 * @return This object. 1062 * @throws RestCallException Invalid input. 1063 */ 1064 public RestRequest queryDataPairs(String...pairs) throws RestCallException { 1065 if (pairs.length % 2 != 0) 1066 throw new IllegalArgumentException("Odd number of parameters passed into queryDataPairs(String...)"); 1067 PartList b = queryData; 1068 for (int i = 0; i < pairs.length; i+=2) 1069 b.append(pairs[i], pairs[i+1]); 1070 return this; 1071 } 1072 1073 /** 1074 * Adds form-data parameters to the request body using free-form key/value pairs. 1075 * 1076 * <h5 class='section'>Example:</h5> 1077 * <p class='bjava'> 1078 * <jc>// Creates form data "key1=val1&key2=val2".</jc> 1079 * <jv>client</jv> 1080 * .formPost(<jsf>URI</jsf>) 1081 * .formDataPairs(<js>"key1"</js>,<js>"val1"</js>,<js>"key2"</js>,<js>"val2"</js>) 1082 * .run(); 1083 * </p> 1084 * 1085 * @param pairs The form-data key/value pairs. 1086 * <ul> 1087 * <li>Values can be any POJO. 1088 * <li>Values converted to a string using the configured part serializer. 1089 * </ul> 1090 * @return This object. 1091 * @throws RestCallException Invalid input. 1092 */ 1093 public RestRequest formDataPairs(String...pairs) throws RestCallException { 1094 if (pairs.length % 2 != 0) 1095 throw new IllegalArgumentException("Odd number of parameters passed into formDataPairs(String...)"); 1096 PartList b = formData; 1097 for (int i = 0; i < pairs.length; i+=2) 1098 b.append(pairs[i], pairs[i+1]); 1099 return this; 1100 } 1101 1102 /** 1103 * Replaces path parameters of the form <js>"{name}"</js> in the URI using free-form key/value pairs. 1104 * 1105 * <h5 class='section'>Example:</h5> 1106 * <p class='bjava'> 1107 * <jc>// Sets path to "/baz/qux".</jc> 1108 * <jv>client</jv> 1109 * .get(<js>"/{foo}/{bar}"</js>) 1110 * .pathDataPairs( 1111 * <js>"foo"</js>,<js>"baz"</js>, 1112 * <js>"bar"</js>,<js>"qux"</js> 1113 * ) 1114 * .run(); 1115 * </p> 1116 * 1117 * @param pairs The path key/value pairs. 1118 * <ul> 1119 * <li>Values can be any POJO. 1120 * <li>Values converted to a string using the configured part serializer. 1121 * </ul> 1122 * @return This object. 1123 */ 1124 public RestRequest pathDataPairs(String...pairs) { 1125 if (pairs.length % 2 != 0) 1126 throw new IllegalArgumentException("Odd number of parameters passed into pathDataPairs(String...)"); 1127 PartList b = pathData; 1128 for (int i = 0; i < pairs.length; i+=2) 1129 b.set(pairs[i], pairs[i+1]); 1130 return this; 1131 } 1132 1133 /** 1134 * Appends multiple headers to the request from properties defined on a Java bean. 1135 * 1136 * <p> 1137 * Uses {@link PropertyNamerDUCS} for resolving the header names from property names. 1138 * 1139 * <h5 class='section'>Example:</h5> 1140 * <p class='bjava'> 1141 * <ja>@Bean</ja> 1142 * <jk>public class</jk> MyHeaders { 1143 * <jk>public</jk> String getFooBar() { <jk>return</jk> <js>"baz"</js>; } 1144 * <jk>public</jk> Integer getQux() { <jk>return</jk> 123; } 1145 * } 1146 * 1147 * <jc>// Appends headers "Foo-Bar: baz" and "Qux: 123".</jc> 1148 * <jv>client</jv> 1149 * .get(<jsf>URI</jsf>) 1150 * .headersBean(<jk>new</jk> MyHeaders()) 1151 * .run(); 1152 * </p> 1153 * 1154 * @param value The bean containing the properties to set as header values. 1155 * @return This object. 1156 */ 1157 public RestRequest headersBean(Object value) { 1158 if (! isBean(value)) 1159 throw new IllegalArgumentException("Object passed into headersBean(Object) is not a bean."); 1160 HeaderList b = headerData; 1161 toBeanMap(value, PropertyNamerDUCS.INSTANCE).forEach((k,v) -> b.append(createHeader(k, v))); 1162 return this; 1163 } 1164 1165 /** 1166 * Appends multiple query parameters to the request from properties defined on a Java bean. 1167 * 1168 * <h5 class='section'>Example:</h5> 1169 * <p class='bjava'> 1170 * <jk>public class</jk> MyQuery { 1171 * <jk>public</jk> String getFooBar() { <jk>return</jk> <js>"baz"</js>; } 1172 * <jk>public</jk> Integer getQux() { <jk>return</jk> 123; } 1173 * } 1174 * 1175 * <jc>// Appends query "fooBar=baz&qux=123".</jc> 1176 * <jv>client</jv> 1177 * .get(<jsf>URI</jsf>) 1178 * .queryDataBean(<jk>new</jk> MyQuery()) 1179 * .run(); 1180 * </p> 1181 * 1182 * @param value The bean containing the properties to set as query parameter values. 1183 * @return This object. 1184 */ 1185 public RestRequest queryDataBean(Object value) { 1186 if (! isBean(value)) 1187 throw new IllegalArgumentException("Object passed into queryDataBean(Object) is not a bean."); 1188 PartList b = queryData; 1189 toBeanMap(value).forEach((k,v) -> b.append(createPart(QUERY, k, v))); 1190 return this; 1191 } 1192 1193 /** 1194 * Appends multiple form-data parameters to the request from properties defined on a Java bean. 1195 * 1196 * <h5 class='section'>Example:</h5> 1197 * <p class='bjava'> 1198 * <jk>public class</jk> MyFormData { 1199 * <jk>public</jk> String getFooBar() { <jk>return</jk> <js>"baz"</js>; } 1200 * <jk>public</jk> Integer getQux() { <jk>return</jk> 123; } 1201 * } 1202 * 1203 * <jc>// Appends form data "fooBar=baz&qux=123".</jc> 1204 * <jv>client</jv> 1205 * .get(<jsf>URI</jsf>) 1206 * .formDataBean(<jk>new</jk> MyFormData()) 1207 * .run(); 1208 * </p> 1209 * 1210 * @param value The bean containing the properties to set as form-data parameter values. 1211 * @return This object. 1212 */ 1213 public RestRequest formDataBean(Object value) { 1214 if (! isBean(value)) 1215 throw new IllegalArgumentException("Object passed into formDataBean(Object) is not a bean."); 1216 PartList b = formData; 1217 toBeanMap(value).forEach((k,v) -> b.append(createPart(FORMDATA, k, v))); 1218 return this; 1219 } 1220 1221 /** 1222 * Sets multiple path parameters to the request from properties defined on a Java bean. 1223 * 1224 * <h5 class='section'>Example:</h5> 1225 * <p class='bjava'> 1226 * <jk>public class</jk> MyPathVars { 1227 * <jk>public</jk> String getFooBar() { <jk>return</jk> <js>"baz"</js>; } 1228 * <jk>public</jk> Integer getQux() { <jk>return</jk> 123; } 1229 * } 1230 * 1231 * <jc>// Given path "/{fooBar}/{qux}/", gets converted to "/baz/123/".</jc> 1232 * <jv>client</jv> 1233 * .get(<jsf>URI</jsf>) 1234 * .pathDataBean(<jk>new</jk> MyPathVars()) 1235 * .run(); 1236 * </p> 1237 * 1238 * @param value The bean containing the properties to set as path parameter values. 1239 * @return This object. 1240 */ 1241 public RestRequest pathDataBean(Object value) { 1242 if (! isBean(value)) 1243 throw new IllegalArgumentException("Object passed into pathDataBean(Object) is not a bean."); 1244 PartList b = pathData; 1245 toBeanMap(value).forEach((k,v) -> b.set(createPart(PATH, k, v))); 1246 return this; 1247 } 1248 1249 //------------------------------------------------------------------------------------------------------------------ 1250 // URI 1251 //------------------------------------------------------------------------------------------------------------------ 1252 1253 /** 1254 * Sets the URI for this request. 1255 * 1256 * <p> 1257 * Can be any of the following types: 1258 * <ul> 1259 * <li>{@link URI} 1260 * <li>{@link URL} 1261 * <li>{@link URIBuilder} 1262 * <li>Anything else converted to a string using {@link Object#toString()}. 1263 * </ul> 1264 * 1265 * <p> 1266 * Relative URI strings will be interpreted as relative to the root URI defined on the client. 1267 * 1268 * @param uri 1269 * The URI of the remote REST resource. 1270 * <br>This overrides the URI passed in from the client. 1271 * <br>Can be any of the following types: 1272 * <ul> 1273 * <li>{@link URIBuilder} 1274 * <li>{@link URI} 1275 * <li>{@link URL} 1276 * <li>{@link String} 1277 * <li>{@link Object} - Converted to <c>String</c> using <c>toString()</c> 1278 * </ul> 1279 * @return This object. 1280 * @throws RestCallException Invalid URI syntax detected. 1281 */ 1282 public RestRequest uri(Object uri) throws RestCallException { 1283 URI x = client.toURI(uri, null); 1284 if (x.getScheme() != null) 1285 uriBuilder.setScheme(x.getScheme()); 1286 if (x.getHost() != null) 1287 uriBuilder.setHost(x.getHost()); 1288 if (x.getPort() != -1) 1289 uriBuilder.setPort(x.getPort()); 1290 if (x.getUserInfo() != null) 1291 uriBuilder.setUserInfo(x.getUserInfo()); 1292 if (x.getFragment() != null) 1293 uriBuilder.setFragment(x.getFragment()); 1294 if (x.getQuery() != null) 1295 uriBuilder.setCustomQuery(x.getQuery()); 1296 uriBuilder.setPath(x.getPath()); 1297 return this; 1298 } 1299 1300 /** 1301 * Sets the URI scheme. 1302 * 1303 * @param scheme The new URI host. 1304 * @return This object. 1305 */ 1306 public RestRequest uriScheme(String scheme) { 1307 uriBuilder.setScheme(scheme); 1308 return this; 1309 } 1310 1311 /** 1312 * Sets the URI host. 1313 * 1314 * @param host The new URI host. 1315 * @return This object. 1316 */ 1317 public RestRequest uriHost(String host) { 1318 uriBuilder.setHost(host); 1319 return this; 1320 } 1321 1322 /** 1323 * Sets the URI port. 1324 * 1325 * @param port The new URI port. 1326 * @return This object. 1327 */ 1328 public RestRequest uriPort(int port) { 1329 uriBuilder.setPort(port); 1330 return this; 1331 } 1332 1333 /** 1334 * Sets the URI user info. 1335 * 1336 * @param userInfo The new URI user info. 1337 * @return This object. 1338 */ 1339 public RestRequest uriUserInfo(String userInfo) { 1340 uriBuilder.setUserInfo(userInfo); 1341 return this; 1342 } 1343 1344 /** 1345 * Sets the URI user info. 1346 * 1347 * @param username The new URI username. 1348 * @param password The new URI password. 1349 * @return This object. 1350 */ 1351 public RestRequest uriUserInfo(String username, String password) { 1352 uriBuilder.setUserInfo(username, password); 1353 return this; 1354 } 1355 1356 /** 1357 * Sets the URI fragment. 1358 * 1359 * @param fragment The URI fragment. The value is expected to be unescaped and may contain non ASCII characters. 1360 * @return This object. 1361 */ 1362 public RestRequest uriFragment(String fragment) { 1363 uriBuilder.setFragment(fragment); 1364 return this; 1365 } 1366 1367 /** 1368 * Adds a free-form custom query. 1369 * 1370 * <h5 class='section'>Example:</h5> 1371 * <p class='bjava'> 1372 * <jc>// Adds query parameter "foo=bar&baz=qux".</jc> 1373 * <jv>client</jv> 1374 * .get(<jsf>URI</jsf>) 1375 * .queryCustom(<js>"foo=bar&baz=qux"</js>) 1376 * .run(); 1377 * </p> 1378 * 1379 * @param value The parameter value. 1380 * <br>Can be any of the following types: 1381 * <ul> 1382 * <li> 1383 * {@link CharSequence} 1384 * <li> 1385 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 1386 * <li> 1387 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 1388 * <li> 1389 * {@link PartList} - Converted to a URL-encoded query. 1390 * </ul> 1391 * @return This object. 1392 */ 1393 public RestRequest queryCustom(Object value) { 1394 try { 1395 String q = null; 1396 if (value instanceof Reader) 1397 q = read((Reader)value); 1398 else if (value instanceof InputStream) 1399 q = read((InputStream)value); 1400 else 1401 q = Utils.s(value); // Works for NameValuePairs. 1402 uriBuilder.setCustomQuery(q); 1403 } catch (IOException e) { 1404 throw new BasicRuntimeException(e, "Could not read custom query."); 1405 } 1406 return this; 1407 } 1408 1409 /** 1410 * Adds form-data parameters as the entire body of the request. 1411 * 1412 * <h5 class='section'>Example:</h5> 1413 * <p class='bjava'> 1414 * <jc>// Creates form data "foo=bar&baz=qux".</jc> 1415 * <jv>client</jv> 1416 * .formPost(<jsf>URI</jsf>) 1417 * .formDataCustom(<js>"foo=bar&baz=qux"</js>) 1418 * .run(); 1419 * 1420 * <jc>// Creates form data "foo=bar&baz=qux" using StringEntity.</jc> 1421 * <jv>client</jv> 1422 * .formPost(<jsf>URI</jsf>) 1423 * .formDataCustom(<jk>new</jk> StringEntity(<js>"foo=bar&baz=qux"</js>,<js>"application/x-www-form-urlencoded"</js>)) 1424 * .run(); 1425 * 1426 * <jc>// Creates form data "foo=bar&baz=qux" using StringEntity and body().</jc> 1427 * <jv>client</jv> 1428 * .formPost(<jsf>URI</jsf>) 1429 * .content(<jk>new</jk> StringEntity(<js>"foo=bar&baz=qux"</js>,<js>"application/x-www-form-urlencoded"</js>)) 1430 * .run(); 1431 * </p> 1432 * 1433 * @param value The parameter value. 1434 * <br>Can be any of the following types: 1435 * <ul class='spaced-list'> 1436 * <li> 1437 * {@link CharSequence} 1438 * <li> 1439 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 1440 * <li> 1441 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 1442 * <li> 1443 * {@link HttpResource} - Raw contents will be serialized to remote resource. Additional headers and media type will be set on request. 1444 * <li> 1445 * {@link HttpEntity}/{@link BasicHttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. 1446 * <li> 1447 * {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the 1448 * {@link RestClient}. 1449 * <li> 1450 * {@link PartList} - Converted to a URL-encoded FORM post. 1451 * </ul> 1452 * @return This object. 1453 */ 1454 public RestRequest formDataCustom(Object value) { 1455 header(ContentType.APPLICATION_FORM_URLENCODED); 1456 content(value instanceof CharSequence ? new StringReader(value.toString()) : value); 1457 return this; 1458 } 1459 1460 //------------------------------------------------------------------------------------------------------------------ 1461 // Args 1462 //------------------------------------------------------------------------------------------------------------------ 1463 1464 RestRequest headerArg(String name, Object value, HttpPartSchema schema, HttpPartSerializer serializer, boolean skipIfEmpty) { 1465 boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof HeaderList || isHeaderArray(value); 1466 1467 if (! isMulti) { 1468 if (! (skipIfEmpty && isEmpty(Utils.s(value)))) 1469 return header(createHeader(name, value, serializer, schema, skipIfEmpty)); 1470 return this; 1471 } 1472 1473 List<Header> l = list(); 1474 1475 if (HttpHeaders.canCast(value)) { 1476 l.add(HttpHeaders.cast(value)); 1477 } else if (value instanceof HeaderList) { 1478 ((HeaderList)value).forEach(x->l.add(x)); 1479 } else if (value instanceof Collection) { 1480 ((Collection<?>)value).forEach(x -> l.add(HttpHeaders.cast(x))); 1481 } else if (isArray(value)) { 1482 for (int i = 0; i < Array.getLength(value); i++) 1483 l.add(HttpHeaders.cast(Array.get(value, i))); 1484 } else if (value instanceof Map) { 1485 toMap(value).forEach((k,v) -> l.add(createHeader(Utils.s(k), v, serializer, schema, skipIfEmpty))); 1486 } else if (isBean(value)) { 1487 toBeanMap(value).forEach((k,v) -> l.add(createHeader(k, v, serializer, schema, skipIfEmpty))); 1488 } else if (value != null) { 1489 throw new BasicRuntimeException("Invalid value type for header arg ''{0}'': {1}", name, className(value)); 1490 } 1491 1492 if (skipIfEmpty) 1493 l.removeIf(x -> isEmpty(x.getValue())); 1494 1495 headerData.append(l); 1496 1497 return this; 1498 } 1499 1500 RestRequest queryArg(String name, Object value, HttpPartSchema schema, HttpPartSerializer serializer, boolean skipIfEmpty) { 1501 boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof PartList || isNameValuePairArray(value); 1502 1503 if (! isMulti) { 1504 if (! (skipIfEmpty && isEmpty(Utils.s(value)))) 1505 return queryData(createPart(name, value, QUERY, serializer, schema, skipIfEmpty)); 1506 return this; 1507 } 1508 1509 List<NameValuePair> l = list(); 1510 1511 if (HttpParts.canCast(value)) { 1512 l.add(HttpParts.cast(value)); 1513 } else if (value instanceof PartList) { 1514 ((PartList)value).forEach(x->l.add(x)); 1515 } else if (value instanceof Collection) { 1516 ((Collection<?>)value).forEach(x -> l.add(HttpParts.cast(x))); 1517 } else if (isArray(value)) { 1518 for (int i = 0; i < Array.getLength(value); i++) 1519 l.add(HttpParts.cast(Array.get(value, i))); 1520 } else if (value instanceof Map) { 1521 toMap(value).forEach((k,v) -> l.add(createPart(Utils.s(k), v, QUERY, serializer, schema, skipIfEmpty))); 1522 } else if (isBean(value)) { 1523 toBeanMap(value).forEach((k,v) -> l.add(createPart(k, v, QUERY, serializer, schema, skipIfEmpty))); 1524 } else if (value != null) { 1525 queryCustom(value); 1526 return this; 1527 } 1528 1529 if (skipIfEmpty) 1530 l.removeIf(x -> isEmpty(x.getValue())); 1531 1532 queryData.append(l); 1533 1534 return this; 1535 } 1536 1537 RestRequest formDataArg(String name, Object value, HttpPartSchema schema, HttpPartSerializer serializer, boolean skipIfEmpty) { 1538 boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof PartList || isNameValuePairArray(value); 1539 1540 if (! isMulti) { 1541 if (! (skipIfEmpty && isEmpty(Utils.s(value)))) 1542 return formData(createPart(name, value, FORMDATA, serializer, schema, skipIfEmpty)); 1543 return this; 1544 } 1545 1546 List<NameValuePair> l = list(); 1547 1548 if (HttpParts.canCast(value)) { 1549 l.add(HttpParts.cast(value)); 1550 } else if (value instanceof PartList) { 1551 ((PartList)value).forEach(x->l.add(x)); 1552 } else if (value instanceof Collection) { 1553 ((Collection<?>)value).forEach(x -> l.add(HttpParts.cast(x))); 1554 } else if (isArray(value)) { 1555 for (int i = 0; i < Array.getLength(value); i++) 1556 l.add(HttpParts.cast(Array.get(value, i))); 1557 } else if (value instanceof Map) { 1558 toMap(value).forEach((k,v) -> l.add(createPart(Utils.s(k), v, FORMDATA, serializer, schema, skipIfEmpty))); 1559 } else if (isBean(value)) { 1560 toBeanMap(value).forEach((k,v) -> l.add(createPart(k, v, FORMDATA, serializer, schema, skipIfEmpty))); 1561 } else if (value != null) { 1562 formDataCustom(value); 1563 return this; 1564 } 1565 1566 if (skipIfEmpty) 1567 l.removeIf(x -> isEmpty(x.getValue())); 1568 1569 formData.append(l); 1570 1571 return this; 1572 } 1573 1574 RestRequest pathArg(String name, Object value, HttpPartSchema schema, HttpPartSerializer serializer) { 1575 boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof PartList || isNameValuePairArray(value); 1576 1577 if (! isMulti) 1578 return pathData(createPart(name, value, PATH, serializer, schema, false)); 1579 1580 List<NameValuePair> l = list(); 1581 1582 if (HttpParts.canCast(value)) { 1583 l.add(HttpParts.cast(value)); 1584 } else if (value instanceof PartList) { 1585 ((PartList)value).forEach(x->l.add(x)); 1586 } else if (value instanceof Collection) { 1587 ((Collection<?>)value).forEach(x -> l.add(HttpParts.cast(x))); 1588 } else if (isArray(value)) { 1589 for (int i = 0; i < Array.getLength(value); i++) 1590 l.add(HttpParts.cast(Array.get(value, i))); 1591 } else if (value instanceof Map) { 1592 toMap(value).forEach((k,v) -> l.add(createPart(Utils.s(k), v, PATH, serializer, schema, false))); 1593 } else if (isBean(value)) { 1594 toBeanMap(value).forEach((k,v) -> l.add(createPart(k, v, PATH, serializer, schema, false))); 1595 } else if (value != null) { 1596 throw new BasicRuntimeException("Invalid value type for path arg ''{0}'': {1}", name, className(value)); 1597 } 1598 1599 pathData.append(l); 1600 1601 return this; 1602 } 1603 1604 //------------------------------------------------------------------------------------------------------------------ 1605 // Request body 1606 //------------------------------------------------------------------------------------------------------------------ 1607 1608 /** 1609 * Sets the body of this request. 1610 * 1611 * @param value 1612 * The input to be sent to the REST resource (only valid for PUT/POST/PATCH) requests. 1613 * <br>Can be of the following types: 1614 * <ul class='spaced-list'> 1615 * <li> 1616 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 1617 * <li> 1618 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 1619 * <li> 1620 * {@link HttpResource} - Raw contents will be serialized to remote resource. Additional headers and media type will be set on request. 1621 * <li> 1622 * {@link HttpEntity}/{@link BasicHttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. 1623 * <li> 1624 * {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the 1625 * {@link RestClient}. 1626 * <li> 1627 * {@link PartList} - Converted to a URL-encoded FORM post. 1628 * <li> 1629 * A {@link Supplier} of anything on this list. 1630 * </ul> 1631 * @return This object. 1632 */ 1633 public RestRequest content(Object value) { 1634 this.content = value; 1635 return this; 1636 } 1637 1638 /** 1639 * Sets the body of this request as straight text bypassing the serializer. 1640 * 1641 * <p class='bjava'> 1642 * <jv>client</jv> 1643 * .put(<js>"/foo"</js>) 1644 * .content(<jk>new</jk> StringReader(<js>"foo"</js>)) 1645 * .contentType(<js>"text/foo"</js>) 1646 * .run(); 1647 * 1648 * <jv>client</jv> 1649 * .put(<js>"/foo"</js>) 1650 * .bodyString(<js>"foo"</js>) 1651 * .run(); 1652 * </p> 1653 * 1654 * <p> 1655 * Note that this is different than the following which will serialize <l>foo</l> as a JSON string <l>"foo"</l>. 1656 * <p class='bjava'> 1657 * <jv>client</jv> 1658 * .put(<js>"/foo"</js>) 1659 * .json() 1660 * .content(<js>"foo"</js>) 1661 * .run(); 1662 * </p> 1663 * 1664 * @param input 1665 * The input to be sent to the REST resource (only valid for PUT/POST/PATCH) requests. 1666 * @return This object. 1667 * @throws RestCallException If a retry was attempted, but the entity was not repeatable. 1668 */ 1669 public RestRequest contentString(Object input) throws RestCallException { 1670 return content(input == null ? null : new StringReader(Utils.s(input))); 1671 } 1672 1673 /** 1674 * Sets the body of this request. 1675 * 1676 * @param input 1677 * The input to be sent to the REST resource (only valid for PUT/POST/PATCH) requests. 1678 * <br>Can be of the following types: 1679 * <ul class='spaced-list'> 1680 * <li> 1681 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 1682 * <li> 1683 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 1684 * <li> 1685 * {@link HttpResource} - Raw contents will be serialized to remote resource. Additional headers and media type will be set on request. 1686 * <li> 1687 * {@link HttpEntity}/{@link BasicHttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. 1688 * <li> 1689 * {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the 1690 * {@link RestClient}. 1691 * <li> 1692 * {@link PartList} - Converted to a URL-encoded FORM post. 1693 * <li> 1694 * A {@link Supplier} of anything on this list. 1695 * </ul> 1696 * @param schema The schema object that defines the format of the output. 1697 * <ul> 1698 * <li>If <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 1699 * <li>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}). 1700 * </ul> 1701 * @return This object. 1702 */ 1703 public RestRequest content(Object input, HttpPartSchema schema) { 1704 this.content = input; 1705 this.contentSchema = schema; 1706 return this; 1707 } 1708 1709 //------------------------------------------------------------------------------------------------------------------ 1710 // Specialized headers. 1711 //------------------------------------------------------------------------------------------------------------------ 1712 1713 /** 1714 * Sets the value for the <c>Accept</c> request header. 1715 * 1716 * <p> 1717 * This overrides the media type specified on the parser, but is overridden by calling 1718 * <code>header(<js>"Accept"</js>, value);</code> 1719 * 1720 * @param value The new header value. 1721 * @return This object. 1722 * @throws RestCallException Invalid input. 1723 */ 1724 public RestRequest accept(String value) throws RestCallException { 1725 return header(Accept.of(value)); 1726 } 1727 1728 /** 1729 * Sets the value for the <c>Accept-Charset</c> request header. 1730 * 1731 * <p> 1732 * This is a shortcut for calling <code>header(<js>"Accept-Charset"</js>, value);</code> 1733 * 1734 * @param value The new header value. 1735 * @return This object. 1736 * @throws RestCallException Invalid input. 1737 */ 1738 public RestRequest acceptCharset(String value) throws RestCallException { 1739 return header(AcceptCharset.of(value)); 1740 } 1741 1742 /** 1743 * Sets the value for the <c>Content-Type</c> request header. 1744 * 1745 * <p> 1746 * This overrides the media type specified on the serializer, but is overridden by calling 1747 * <code>header(<js>"Content-Type"</js>, value);</code> 1748 * 1749 * @param value The new header value. 1750 * @return This object. 1751 * @throws RestCallException Invalid input. 1752 */ 1753 public RestRequest contentType(String value) throws RestCallException { 1754 return header(ContentType.of(value)); 1755 } 1756 1757 /** 1758 * Shortcut for setting the <c>Accept</c> and <c>Content-Type</c> headers on a request. 1759 * 1760 * @param value The new header values. 1761 * @return This object. 1762 * @throws RestCallException Invalid input. 1763 */ 1764 public RestRequest mediaType(String value) throws RestCallException { 1765 return header(Accept.of(value)).header(ContentType.of(value)); 1766 } 1767 1768 /** 1769 * When called, <c>No-Trace: true</c> is added to requests. 1770 * 1771 * <p> 1772 * This gives the opportunity for the servlet to not log errors on invalid requests. 1773 * This is useful for testing purposes when you don't want your log file to show lots of errors that are simply the 1774 * results of testing. 1775 * 1776 * @return This object. 1777 * @throws RestCallException Invalid input. 1778 */ 1779 public RestRequest noTrace() throws RestCallException { 1780 return header(NoTrace.TRUE); 1781 } 1782 1783 //------------------------------------------------------------------------------------------------------------------ 1784 // Execution methods. 1785 //------------------------------------------------------------------------------------------------------------------ 1786 1787 /** 1788 * Runs this request and returns the resulting response object. 1789 * 1790 * <h5 class='section'>Example:</h5> 1791 * <p class='bjava'> 1792 * <jk>try</jk> { 1793 * <jk>int</jk> <jv>rc</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).execute().getResponseStatus(); 1794 * <jc>// Succeeded!</jc> 1795 * } <jk>catch</jk> (RestCallException <jv>e</jv>) { 1796 * <jc>// Failed!</jc> 1797 * } 1798 * </p> 1799 * 1800 * <h5 class='section'>Notes:</h5><ul> 1801 * <li class='note'>Calling this method multiple times will return the same original response object. 1802 * <li class='note'>You must close the returned object if you do not consume the response or execute a method that consumes 1803 * the response. 1804 * <li class='note'>If you are only interested in the response code, use the {@link #complete()} method which will automatically 1805 * consume the response so that you don't need to call {@link InputStream#close()} on the response body. 1806 * </ul> 1807 * 1808 * @return The response object. 1809 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 1810 */ 1811 public RestResponse run() throws RestCallException { 1812 if (response != null) 1813 throw new RestCallException(response, null, "run() already called."); 1814 1815 try { 1816 1817 queryData.stream().map(SimpleQuery::new).filter(SimplePart::isValid).forEach( 1818 x -> uriBuilder.addParameter(x.name, x.value) 1819 ); 1820 1821 pathData.stream().map(SimplePath::new).forEach(x -> 1822 { 1823 String path = uriBuilder.getPath(); 1824 String name = x.name, value = x.value; 1825 String var = "{" + name + "}"; 1826 if (path.indexOf(var) == -1 && ! name.equals("/*")) 1827 throw new IllegalStateException("Path variable {" + name + "} was not found in path."); 1828 if (name.equals("/*")) 1829 path = path.replaceAll("\\/\\*$", "/" + value); 1830 else 1831 path = path.replace(var, String.valueOf(value)); 1832 uriBuilder.setPath(path); 1833 } 1834 ); 1835 1836 HttpEntityEnclosingRequestBase request2 = request instanceof HttpEntityEnclosingRequestBase ? (HttpEntityEnclosingRequestBase)request : null; 1837 request.setURI(uriBuilder.build()); 1838 1839 // Pick the serializer if it hasn't been overridden. 1840 HeaderList hl = headerData; 1841 Optional<Header> h = hl.getLast("Content-Type"); 1842 String contentType = h.isPresent() ? h.get().getValue() : null; 1843 Serializer serializer = this.serializer; 1844 if (serializer == null) 1845 serializer = client.getMatchingSerializer(contentType); 1846 if (contentType == null && serializer != null) 1847 contentType = serializer.getPrimaryMediaType().toString(); 1848 1849 // Pick the parser if it hasn't been overridden. 1850 h = hl.getLast("Accept"); 1851 String accept = h.isPresent() ? h.get().getValue() : null; 1852 Parser parser = this.parser; 1853 if (parser == null) 1854 parser = client.getMatchingParser(accept); 1855 if (accept == null && parser != null) 1856 hl.set(Accept.of( parser.getPrimaryMediaType())); 1857 1858 headerData.stream().map(SimpleHeader::new).filter(SimplePart::isValid).forEach(x -> request.addHeader(x)); 1859 1860 if (request2 == null && content != NO_BODY) 1861 throw new RestCallException(null, null, "Method does not support content entity. Method={0}, URI={1}", getMethod(), getURI()); 1862 1863 if (request2 != null) { 1864 1865 Object input2 = null; 1866 if (content != NO_BODY) { 1867 input2 = content; 1868 } else { 1869 input2 = new UrlEncodedFormEntity(formData.stream().map(SimpleFormData::new).filter(SimplePart::isValid).collect(toList())); 1870 } 1871 1872 if (input2 instanceof Supplier) 1873 input2 = ((Supplier<?>)input2).get(); 1874 1875 HttpEntity entity = null; 1876 if (input2 instanceof PartList) 1877 entity = new UrlEncodedFormEntity(((PartList)input2).stream().map(SimpleFormData::new).filter(SimplePart::isValid).collect(toList())); 1878 else if (input2 instanceof HttpResource) { 1879 HttpResource r = (HttpResource)input2; 1880 r.getHeaders().forEach(x -> request.addHeader(x)); 1881 entity = (HttpEntity)input2; 1882 } 1883 else if (input2 instanceof HttpEntity) { 1884 if (input2 instanceof SerializedEntity) { 1885 entity = ((SerializedEntity)input2).copyWith(serializer, contentSchema); 1886 } else { 1887 entity = (HttpEntity)input2; 1888 } 1889 } 1890 else if (input2 instanceof Reader) 1891 entity = readerEntity((Reader)input2, getRequestContentType(TEXT_PLAIN)); 1892 else if (input2 instanceof InputStream) 1893 entity = streamEntity((InputStream)input2, -1, getRequestContentType(ContentType.APPLICATION_OCTET_STREAM)); 1894 else if (serializer != null) 1895 entity = serializedEntity(input2, serializer, contentSchema).setContentType(contentType); 1896 else { 1897 if (client.hasSerializers()) { 1898 if (contentType == null) 1899 throw new RestCallException(null, null, "Content-Type not specified on request. Cannot match correct serializer. Use contentType(String) or mediaType(String) to specify transport language."); 1900 throw new RestCallException(null, null, "No matching serializer for media type ''{0}''", contentType); 1901 } 1902 entity = stringEntity(input2 == null ? "" : BeanContext.DEFAULT.getClassMetaForObject(input2).toString(input2), getRequestContentType(TEXT_PLAIN)); 1903 } 1904 1905 request2.setEntity(entity); 1906 } 1907 1908 try { 1909 response = client.createResponse(this, client.run(target, request, context), parser); 1910 } catch (Exception e) { 1911 throw e; 1912 } 1913 1914 if (isDebug() || client.logRequests == DetailLevel.FULL) 1915 response.cacheContent(); 1916 1917 for (RestCallInterceptor rci : interceptors) 1918 rci.onConnect(this, response); 1919 client.onCallConnect(this, response); 1920 1921 String method = getMethod(); 1922 int sc = response.getStatusCode(); 1923 1924 Thrown thrown = response.getHeader("Thrown").asHeader(Thrown.class); 1925 if (thrown.isPresent() && rethrow != null) { 1926 Thrown.Part thrownPart = thrown.asParts().get().get(0); 1927 String className = thrownPart.getClassName(); 1928 String message = thrownPart.getMessage(); 1929 for (Class<? extends Throwable> t : rethrow) { 1930 if (t.getName().equals(className)) { 1931 ConstructorInfo c = null; 1932 ClassInfo ci = ClassInfo.of(t); 1933 c = ci.getPublicConstructor(x -> x.hasParamTypes(HttpResponse.class)); 1934 if (c != null) 1935 throw c.<Throwable>invoke(response); 1936 c = ci.getPublicConstructor(x -> x.hasParamTypes(String.class)); 1937 if (c != null) 1938 throw c.<Throwable>invoke(message != null ? message : response.getContent().asString()); 1939 c = ci.getPublicConstructor(x -> x.hasParamTypes(String.class,Throwable.class)); 1940 if (c != null) 1941 throw c.<Throwable>invoke(message != null ? message : response.getContent().asString(), null); 1942 c = ci.getPublicConstructor(ConstructorInfo::hasNoParams); 1943 if (c != null) 1944 throw c.<Throwable>invoke(); 1945 } 1946 } 1947 } 1948 1949 if (errorCodes.test(sc) && ! ignoreErrors) { 1950 throw new RestCallException(response, null, "HTTP method ''{0}'' call to ''{1}'' caused response code ''{2}, {3}''.\nResponse: \n{4}", 1951 method, getURI(), sc, response.getReasonPhrase(), response.getContent().asAbbreviatedString(1000)); 1952 } 1953 1954 } catch (RuntimeException | RestCallException e) { 1955 if (response != null) 1956 response.close(); 1957 throw e; 1958 } catch (Throwable e) { 1959 if (response != null) 1960 response.close(); 1961 throw new RestCallException(response, e, "Call failed."); 1962 } 1963 1964 return this.response; 1965 } 1966 1967 /** 1968 * Same as {@link #run()} but allows you to run the call asynchronously. 1969 * 1970 * <h5 class='section'>Example:</h5> 1971 * <p class='bjava'> 1972 * Future<RestResponse> <jv>future</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).runFuture(); 1973 * 1974 * <jc>// Do some other stuff</jc> 1975 * 1976 * <jk>try</jk> { 1977 * String <jv>body</jv> = <jv>future</jv>.get().getContent().asString(); 1978 * <jc>// Succeeded!</jc> 1979 * } <jk>catch</jk> (RestCallException <jv>e</jv>) { 1980 * <jc>// Failed!</jc> 1981 * } 1982 * </p> 1983 * 1984 * <h5 class='section'>Notes:</h5><ul> 1985 * <li class='note'>Use the {@link RestClient.Builder#executorService(ExecutorService, boolean)} method to customize the 1986 * executor service used for creating {@link Future Futures}. 1987 * </ul> 1988 * 1989 * @return The HTTP status code. 1990 * @throws RestCallException If the executor service was not defined. 1991 */ 1992 public Future<RestResponse> runFuture() throws RestCallException { 1993 return client.getExecutorService().submit(this::run); 1994 } 1995 1996 /** 1997 * Same as {@link #run()} but immediately calls {@link RestResponse#consume()} to clean up the response. 1998 * 1999 * <p> 2000 * Use this method if you're only interested in the status line of the response and not the response entity. 2001 * Attempts to call any of the methods on the response object that retrieve the body (e.g. {@link ResponseContent#asReader()} 2002 * will cause a {@link RestCallException} to be thrown. 2003 * 2004 * <h5 class='section'>Notes:</h5><ul> 2005 * <li class='note'>You do not need to execute {@link InputStream#close()} on the response body to consume the response. 2006 * </ul> 2007 * 2008 * <h5 class='section'>Example:</h5> 2009 * <p class='bjava'> 2010 * <jc>// Get the response code. 2011 * // No need to call close() on the RestResponse object.</jc> 2012 * <jk>int</jk> <jv>rc</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).complete().getResponseCode(); 2013 * </p> 2014 * 2015 * @return The response object. 2016 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 2017 */ 2018 public RestResponse complete() throws RestCallException { 2019 return run().consume(); 2020 } 2021 2022 /** 2023 * Same as {@link #complete()} but allows you to run the call asynchronously. 2024 * 2025 * <h5 class='section'>Example:</h5> 2026 * <p class='bjava'> 2027 * Future<RestResponse> <jv>future</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).completeFuture(); 2028 * 2029 * <jc>// Do some other stuff</jc> 2030 * 2031 * <jk>int</jk> <jv>rc</jv> = <jv>future</jv>.get().getResponseStatus(); 2032 * </p> 2033 * 2034 * <h5 class='section'>Notes:</h5><ul> 2035 * <li class='note'>Use the {@link RestClient.Builder#executorService(ExecutorService, boolean)} method to customize the 2036 * executor service used for creating {@link Future Futures}. 2037 * <li class='note'>You do not need to execute {@link InputStream#close()} on the response body to consume the response. 2038 * </ul> 2039 * 2040 * @return The HTTP status code. 2041 * @throws RestCallException If the executor service was not defined. 2042 */ 2043 public Future<RestResponse> completeFuture() throws RestCallException { 2044 return client.getExecutorService().submit( 2045 this::complete 2046 ); 2047 } 2048 2049 /** 2050 * A shortcut for calling <c>run().getContent().asString()</c>. 2051 * 2052 * @return The response content as a simple string. 2053 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 2054 */ 2055 public String getResponseAsString() throws RestCallException { 2056 return run().getContent().asString(); 2057 } 2058 2059 /** 2060 * A shortcut for calling <c>run().getContent().as(<js>type</js>)</c>. 2061 * 2062 * @param type The object type to create. 2063 * @param <T> The object type to create. 2064 * @see ResponseContent#as(Class) 2065 * @return The response content as a simple string. 2066 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 2067 */ 2068 public <T> T getResponse(Class<T> type) throws RestCallException { 2069 return run().getContent().as(type); 2070 } 2071 2072 /** 2073 * A shortcut for calling <c>run().getContent().as(<js>type</js>,<js>args</js>)</c>. 2074 * 2075 * @param <T> 2076 * The object type to create. 2077 * @param type 2078 * The object type to create. 2079 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2080 * @param args 2081 * The type arguments of the class if it's a collection or map. 2082 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 2083 * <br>Ignored if the main type is not a map or collection. 2084 * @see ResponseContent#as(Type,Type...) 2085 * @return The response content as a simple string. 2086 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 2087 */ 2088 public <T> T getResponse(Type type, Type...args) throws RestCallException { 2089 return run().getContent().as(type, args); 2090 } 2091 2092 /** 2093 * Returns <jk>true</jk> if this request has a body. 2094 * 2095 * @return <jk>true</jk> if this request has a body. 2096 */ 2097 public boolean hasHttpEntity() { 2098 return request instanceof HttpEntityEnclosingRequestBase; 2099 } 2100 2101 /** 2102 * Returns the body of this request. 2103 * 2104 * @return The body of this request, or <jk>null</jk> if it doesn't have a body. 2105 */ 2106 public HttpEntity getHttpEntity() { 2107 return hasHttpEntity() ? ((HttpEntityEnclosingRequestBase)request).getEntity() : null; 2108 } 2109 2110 /** 2111 * Logs a message. 2112 * 2113 * @param level The log level. 2114 * @param t The throwable cause. 2115 * @param msg The message with {@link MessageFormat}-style arguments. 2116 * @param args The arguments. 2117 * @return This object. 2118 */ 2119 public RestRequest log(Level level, Throwable t, String msg, Object...args) { 2120 client.log(level, t, msg, args); 2121 return this; 2122 } 2123 2124 /** 2125 * Logs a message. 2126 * 2127 * @param level The log level. 2128 * @param msg The message with {@link MessageFormat}-style arguments. 2129 * @param args The arguments. 2130 * @return This object. 2131 */ 2132 public RestRequest log(Level level, String msg, Object...args) { 2133 client.log(level, msg, args); 2134 return this; 2135 } 2136 2137 //----------------------------------------------------------------------------------------------------------------- 2138 // HttpRequestBase pass-through methods. 2139 //----------------------------------------------------------------------------------------------------------------- 2140 2141 /** 2142 * Sets the actual request configuration. 2143 * 2144 * @param value The new value. 2145 * @return This object. 2146 */ 2147 public RestRequest config(RequestConfig value) { 2148 request.setConfig(value); 2149 return this; 2150 } 2151 2152 /** 2153 * Sets {@link Cancellable} for the ongoing operation. 2154 * 2155 * @param cancellable The cancellable object. 2156 * @return This object. 2157 */ 2158 public RestRequest cancellable(Cancellable cancellable) { 2159 request.setCancellable(cancellable); 2160 return this; 2161 } 2162 2163 /** 2164 * Sets the protocol version for this request. 2165 * 2166 * @param version The protocol version for this request. 2167 * @return This object. 2168 */ 2169 public RestRequest protocolVersion(ProtocolVersion version) { 2170 request.setProtocolVersion(version); 2171 return this; 2172 } 2173 2174 /** 2175 * Used in combination with {@link #cancellable(Cancellable)}. 2176 * 2177 * @return This object. 2178 */ 2179 @Deprecated 2180 public RestRequest completed() { 2181 request.completed(); 2182 return this; 2183 } 2184 2185 // ----------------------------------------------------------------------------------------------------------------- 2186 // HttpUriRequest pass-through methods. 2187 // ----------------------------------------------------------------------------------------------------------------- 2188 2189 /** 2190 * Returns the HTTP method this request uses, such as GET, PUT, POST, or other. 2191 * 2192 * @return The HTTP method this request uses, such as GET, PUT, POST, or other. 2193 */ 2194 @Override /* HttpUriRequest */ 2195 public String getMethod() { 2196 return request.getMethod(); 2197 } 2198 2199 /** 2200 * Returns the original request URI. 2201 * 2202 * <h5 class='section'>Notes:</h5><ul> 2203 * <li class='note'>URI remains unchanged in the course of request execution and is not updated if the request is redirected to another location. 2204 * </ul> 2205 * 2206 * @return The original request URI. 2207 */ 2208 @Override /* HttpUriRequest */ 2209 public URI getURI() { 2210 return request.getURI(); 2211 } 2212 2213 /** 2214 * Aborts this http request. Any active execution of this method should return immediately. 2215 * 2216 * If the request has not started, it will abort after the next execution. 2217 * <br>Aborting this request will cause all subsequent executions with this request to fail. 2218 */ 2219 @Override /* HttpUriRequest */ 2220 public void abort() throws UnsupportedOperationException { 2221 request.abort(); 2222 } 2223 2224 @Override /* HttpUriRequest */ 2225 public boolean isAborted() { 2226 return request.isAborted(); 2227 } 2228 2229 /** 2230 * Returns the request line of this request. 2231 * 2232 * @return The request line. 2233 */ 2234 @Override /* HttpRequest */ 2235 public RequestLine getRequestLine() { 2236 return request.getRequestLine(); 2237 } 2238 2239 /** 2240 * Returns the protocol version this message is compatible with. 2241 * 2242 * @return The protocol version. 2243 */ 2244 @Override /* HttpMessage */ 2245 public ProtocolVersion getProtocolVersion() { 2246 return request.getProtocolVersion(); 2247 } 2248 2249 /** 2250 * Checks if a certain header is present in this message. 2251 * 2252 * Header values are ignored. 2253 * 2254 * @param name The header name to check for. 2255 * @return <jk>true</jk> if at least one header with this name is present. 2256 */ 2257 @Override /* HttpMessage */ 2258 public boolean containsHeader(String name) { 2259 return headerData.contains(name); 2260 } 2261 2262 /** 2263 * Returns all the headers with a specified name of this message. 2264 * 2265 * Header values are ignored. 2266 * <br>Headers are ordered in the sequence they will be sent over a connection. 2267 * 2268 * @param name The name of the headers to return. 2269 * @return The headers whose name property equals name. 2270 */ 2271 @Override /* HttpMessage */ 2272 public Header[] getHeaders(String name) { 2273 return headerData.getAll(name); 2274 } 2275 2276 /** 2277 * Returns the first header with a specified name of this message. 2278 * 2279 * Header values are ignored. 2280 * <br>If there is more than one matching header in the message the first element of {@link #getHeaders(String)} is returned. 2281 * <br>If there is no matching header in the message <jk>null</jk> is returned. 2282 * 2283 * @param name The name of the header to return. 2284 * @return The first header whose name property equals name or <jk>null</jk> if no such header could be found. 2285 */ 2286 @Override /* HttpMessage */ 2287 public Header getFirstHeader(String name) { 2288 return headerData.getFirst(name).orElse(null); 2289 } 2290 2291 /** 2292 * Returns the last header with a specified name of this message. 2293 * 2294 * Header values are ignored. 2295 * <br>If there is more than one matching header in the message the last element of {@link #getHeaders(String)} is returned. 2296 * <br>If there is no matching header in the message null is returned. 2297 * 2298 * @param name The name of the header to return. 2299 * @return The last header whose name property equals name or <jk>null</jk> if no such header could be found. 2300 */ 2301 @Override /* HttpMessage */ 2302 public Header getLastHeader(String name) { 2303 return headerData.getLast(name).orElse(null); 2304 } 2305 2306 /** 2307 * Returns all the headers of this message. 2308 * 2309 * Headers are ordered in the sequence they will be sent over a connection. 2310 * 2311 * @return All the headers of this message 2312 */ 2313 @Override /* HttpMessage */ 2314 public Header[] getAllHeaders() { 2315 return headerData.getAll(); 2316 } 2317 2318 /** 2319 * Adds a header to this message. 2320 * 2321 * The header will be appended to the end of the list. 2322 * 2323 * <h5 class='section'>Notes:</h5><ul> 2324 * <li class='note'>{@link #header(Header)} is an equivalent method and the preferred method for fluent-style coding. 2325 * </ul> 2326 * 2327 * @param header The header to append. 2328 */ 2329 @Override /* HttpMessage */ 2330 public void addHeader(Header header) { 2331 headerData.append(header); 2332 } 2333 2334 /** 2335 * Adds a header to this message. 2336 * 2337 * The header will be appended to the end of the list. 2338 * 2339 * <h5 class='section'>Notes:</h5><ul> 2340 * <li class='note'>{@link #header(String,Object)} is an equivalent method and the preferred method for fluent-style coding. 2341 * </ul> 2342 * 2343 * @param name The name of the header. 2344 * @param value The value of the header. 2345 */ 2346 @Override /* HttpMessage */ 2347 public void addHeader(String name, String value) { 2348 headerData.append(stringHeader(name, value)); 2349 } 2350 2351 /** 2352 * Overwrites the first header with the same name. 2353 * 2354 * The new header will be appended to the end of the list, if no header with the given name can be found. 2355 * 2356 * @param header The header to set. 2357 */ 2358 @Override /* HttpMessage */ 2359 public void setHeader(Header header) { 2360 headerData.set(header); 2361 } 2362 2363 /** 2364 * Overwrites the first header with the same name. 2365 * 2366 * The new header will be appended to the end of the list, if no header with the given name can be found. 2367 * 2368 * @param name The name of the header. 2369 * @param value The value of the header. 2370 */ 2371 @Override /* HttpMessage */ 2372 public void setHeader(String name, String value) { 2373 headerData.set(stringHeader(name, value)); 2374 } 2375 2376 /** 2377 * Overwrites all the headers in the message. 2378 * 2379 * @param headers The array of headers to set. 2380 */ 2381 @Override /* HttpMessage */ 2382 public void setHeaders(Header[] headers) { 2383 headerData.set(headers); 2384 } 2385 2386 /** 2387 * Removes a header from this message. 2388 * 2389 * @param header The header to remove. 2390 */ 2391 @Override /* HttpMessage */ 2392 public void removeHeader(Header header) { 2393 headerData.remove(header); 2394 } 2395 2396 /** 2397 * Removes all headers with a certain name from this message. 2398 * 2399 * @param name The name of the headers to remove. 2400 */ 2401 @Override /* HttpMessage */ 2402 public void removeHeaders(String name) { 2403 headerData.remove(name); 2404 } 2405 2406 /** 2407 * Returns an iterator of all the headers. 2408 * 2409 * @return Iterator that returns {@link Header} objects in the sequence they are sent over a connection. 2410 */ 2411 @Override /* HttpMessage */ 2412 public HeaderIterator headerIterator() { 2413 return headerData.headerIterator(); 2414 } 2415 2416 /** 2417 * Returns an iterator of the headers with a given name. 2418 * 2419 * @param name the name of the headers over which to iterate, or <jk>null</jk> for all headers. 2420 * @return Iterator that returns {@link Header} objects with the argument name in the sequence they are sent over a connection. 2421 */ 2422 @Override /* HttpMessage */ 2423 public HeaderIterator headerIterator(String name) { 2424 return headerData.headerIterator(name); 2425 } 2426 2427 /** 2428 * Returns the parameters effective for this message as set by {@link #setParams(HttpParams)}. 2429 * 2430 * @return The parameters effective for this message as set by {@link #setParams(HttpParams)}. 2431 * @deprecated Use constructor parameters of configuration API provided by HttpClient. 2432 */ 2433 @Override /* HttpMessage */ 2434 @Deprecated 2435 public HttpParams getParams() { 2436 return request.getParams(); 2437 } 2438 2439 /** 2440 * Provides parameters to be used for the processing of this message. 2441 * 2442 * @param params The parameters. 2443 * @deprecated Use constructor parameters of configuration API provided by HttpClient. 2444 */ 2445 @Override /* HttpMessage */ 2446 @Deprecated 2447 public void setParams(HttpParams params) { 2448 request.setParams(params); 2449 } 2450 2451 /** 2452 * Returns the actual request configuration. 2453 * 2454 * @return The actual request configuration. 2455 */ 2456 @Override /* Configurable */ 2457 public RequestConfig getConfig() { 2458 return request.getConfig(); 2459 } 2460 2461 // ----------------------------------------------------------------------------------------------------------------- 2462 // Utility methods 2463 // ----------------------------------------------------------------------------------------------------------------- 2464 2465 private ContentType getRequestContentType(ContentType def) { 2466 Header h = request.getFirstHeader("Content-Type"); 2467 if (h != null) { 2468 String s = h.getValue(); 2469 if (! isEmpty(s)) 2470 return ContentType.of(s); 2471 } 2472 return def; 2473 } 2474 2475 @SuppressWarnings("unchecked") 2476 private static Map<Object,Object> toMap(Object o) { 2477 return (Map<Object,Object>)o; 2478 } 2479 2480 /** 2481 * Creates a new header. 2482 * 2483 * @param name The header name. 2484 * @param value The header value. 2485 * @param serializer The part serializer to use, or <jk>null</jk> to use the part serializer defined on the client. 2486 * @param schema Optional HTTP part schema to provide to the part serializer. 2487 * @param skipIfEmpty If <jk>true</jk>, empty string values will be ignored on the request. 2488 * @return A new header. 2489 */ 2490 protected Header createHeader(String name, Object value, HttpPartSerializer serializer, HttpPartSchema schema, Boolean skipIfEmpty) { 2491 if (isEmpty(name)) 2492 return null; 2493 if (skipIfEmpty == null) 2494 skipIfEmpty = client.isSkipEmptyHeaderData(); 2495 if (serializer == null) 2496 serializer = client.getPartSerializer(); 2497 return new SerializedHeader(name, value, getPartSerializerSession(serializer), schema, skipIfEmpty); 2498 } 2499 2500 private Header createHeader(String name, Object value) { 2501 return createHeader(name, value, null, null, null); 2502 } 2503 2504 /** 2505 * Creates a new query/form-data/path part. 2506 * 2507 * @param name The part name. 2508 * @param value The part value. 2509 * @param type The HTTP part type. 2510 * @param serializer The part serializer to use, or <jk>null</jk> to use the part serializer defined on the client. 2511 * @param schema Optional HTTP part schema to provide to the part serializer. 2512 * @param skipIfEmpty If <jk>true</jk>, empty string values will be ignored on the request. 2513 * @return A new part. 2514 */ 2515 protected NameValuePair createPart(String name, Object value, HttpPartType type, HttpPartSerializer serializer, HttpPartSchema schema, Boolean skipIfEmpty) { 2516 if (isEmpty(name)) 2517 return null; 2518 if (skipIfEmpty == null) { 2519 if (type == QUERY) 2520 skipIfEmpty = client.isSkipEmptyQueryData(); 2521 else if (type == FORMDATA) 2522 skipIfEmpty = client.isSkipEmptyFormData(); 2523 else 2524 skipIfEmpty = false; 2525 } 2526 if (serializer == null) 2527 serializer = client.getPartSerializer(); 2528 return new SerializedPart(name, value, type, getPartSerializerSession(serializer), schema, skipIfEmpty); 2529 } 2530 2531 private NameValuePair createPart(HttpPartType type, String name, Object value) { 2532 return createPart(name, value, type, null, null, null); 2533 } 2534 2535 private HttpPartSerializerSession getPartSerializerSession(HttpPartSerializer serializer) { 2536 if (serializer == null) 2537 serializer = client.getPartSerializer(); 2538 HttpPartSerializerSession s = partSerializerSessions.get(serializer); 2539 if (s == null) { 2540 s = serializer.getPartSession(); 2541 partSerializerSessions.put(serializer, s); 2542 } 2543 return s; 2544 } 2545 2546 HttpPartSerializerSession getPartSerializerSession() { 2547 if (partSerializerSession == null) 2548 partSerializerSession = getPartSerializerSession(null); 2549 return partSerializerSession; 2550 } 2551 2552 private static boolean isNameValuePairArray(Object o) { 2553 if (! isArray(o)) 2554 return false; 2555 if (NameValuePair.class.isAssignableFrom(o.getClass().getComponentType())) 2556 return true; 2557 return false; 2558 } 2559 2560 private static boolean isHeaderArray(Object o) { 2561 if (! isArray(o)) 2562 return false; 2563 if (Header.class.isAssignableFrom(o.getClass().getComponentType())) 2564 return true; 2565 return false; 2566 } 2567 2568 //----------------------------------------------------------------------------------------------------------------- 2569 // Simple parts 2570 //----------------------------------------------------------------------------------------------------------------- 2571 2572 private class SimplePart implements NameValuePair { 2573 final String name; 2574 final String value; 2575 2576 SimplePart(NameValuePair x, boolean skipIfEmpty) { 2577 name = x.getName(); 2578 if (x instanceof SerializedHeader) { 2579 value = ((SerializedHeader)x).copyWith(getPartSerializerSession(), null).getValue(); 2580 } else if (x instanceof SerializedPart) { 2581 value = ((SerializedPart)x).copyWith(getPartSerializerSession(), null).getValue(); 2582 } else { 2583 String v = x.getValue(); 2584 value = (isEmpty(v) && skipIfEmpty) ? null : v; 2585 } 2586 } 2587 2588 boolean isValid() { 2589 if (isEmpty(name) || value == null) 2590 return false; 2591 return true; 2592 } 2593 2594 @Override 2595 public String getName() { 2596 return name; 2597 } 2598 2599 @Override 2600 public String getValue() { 2601 return value; 2602 } 2603 } 2604 2605 private class SimpleQuery extends SimplePart { 2606 SimpleQuery(NameValuePair x) { 2607 super(x, client.isSkipEmptyQueryData()); 2608 } 2609 } 2610 2611 private class SimpleFormData extends SimplePart { 2612 SimpleFormData(NameValuePair x) { 2613 super(x, client.isSkipEmptyFormData()); 2614 } 2615 } 2616 2617 private class SimplePath extends SimplePart { 2618 SimplePath(NameValuePair x) { 2619 super(x, false); 2620 } 2621 } 2622 2623 private class SimpleHeader extends SimplePart implements Header { 2624 2625 SimpleHeader(NameValuePair x) { 2626 super(x, client.isSkipEmptyHeaderData()); 2627 } 2628 2629 @Override 2630 public HeaderElement[] getElements() throws ParseException { 2631 return null; 2632 } 2633 } 2634 2635 //----------------------------------------------------------------------------------------------------------------- 2636 // Fluent setters 2637 //----------------------------------------------------------------------------------------------------------------- 2638 //----------------------------------------------------------------------------------------------------------------- 2639 // Other methods 2640 //----------------------------------------------------------------------------------------------------------------- 2641 2642 /** 2643 * Closes this request and its associated response (if one was created). 2644 * 2645 * <p> 2646 * This method is idempotent and can be called multiple times without side effects. 2647 * 2648 * <h5 class='section'>Implementation Notes:</h5> 2649 * <p> 2650 * This implementation represents a compromise between strict AutoCloseable compliance and debuggability: 2651 * <ul> 2652 * <li>Unchecked exceptions ({@link RuntimeException} and {@link Error}) from the response close are allowed to propagate. 2653 * This ensures programming errors and serious issues are visible during development and testing. 2654 * <li>Checked exceptions (including {@link RestCallException}) are caught and logged but not thrown. 2655 * This follows AutoCloseable best practices and prevents close exceptions from interfering with 2656 * try-with-resources cleanup or masking the original exception. 2657 * </ul> 2658 */ 2659 @Override /* AutoCloseable */ 2660 public void close() { 2661 try { 2662 if (response != null) { 2663 response.close(); 2664 } 2665 } catch (RuntimeException | Error e) { 2666 // Let unchecked exceptions propagate for debuggability 2667 throw e; 2668 } catch (Exception e) { 2669 // Log checked exceptions but don't throw - follows AutoCloseable best practices 2670 client.log(Level.WARNING, e, "Error closing RestResponse"); 2671 } 2672 } 2673 2674 @Override /* ContextSession */ 2675 protected JsonMap properties() { 2676 return filteredMap() 2677 .append("client", client.properties()) 2678 .append("ignoreErrors", ignoreErrors) 2679 .append("interceptors", interceptors) 2680 .append("requestBodySchema", contentSchema) 2681 .append("response", response) 2682 .append("serializer", serializer); 2683 } 2684}