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.classic;
018
019import static org.apache.juneau.commons.httppart.HttpPartType.*;
020import static org.apache.juneau.commons.utils.ClassUtils.*;
021import static org.apache.juneau.commons.utils.CollectionUtils.*;
022import static org.apache.juneau.commons.utils.IoUtils.*;
023import static org.apache.juneau.commons.utils.Shorts.*;
024import static org.apache.juneau.http.classic.HttpEntities.*;
025import static org.apache.juneau.http.classic.HttpHeaders.*;
026import static org.apache.juneau.rest.RestSharedConstants.*;
027import static org.apache.juneau.rest.client.classic.RestOperation.*;
028
029import java.io.*;
030import java.lang.reflect.*;
031import java.net.*;
032import java.util.*;
033import java.util.concurrent.*;
034import java.util.function.*;
035import java.util.logging.*;
036
037import org.apache.http.*;
038import org.apache.http.ParseException;
039import org.apache.http.client.*;
040import org.apache.http.client.config.*;
041import org.apache.http.client.entity.*;
042import org.apache.http.client.methods.*;
043import org.apache.http.client.protocol.HttpClientContext;
044import org.apache.http.client.utils.*;
045import org.apache.http.concurrent.*;
046import org.apache.http.entity.BasicHttpEntity;
047import org.apache.http.params.*;
048import org.apache.http.protocol.*;
049import org.apache.http.util.*;
050import org.apache.juneau.commons.bean.*;
051import org.apache.juneau.commons.collections.*;
052import org.apache.juneau.commons.httppart.*;
053import org.apache.juneau.commons.reflect.*;
054import org.apache.juneau.http.classic.*;
055import org.apache.juneau.http.classic.HttpHeaders;
056import org.apache.juneau.http.classic.entity.*;
057import org.apache.juneau.http.classic.header.*;
058import org.apache.juneau.http.classic.header.ContentType;
059import org.apache.juneau.http.classic.part.*;
060import org.apache.juneau.http.classic.resource.*;
061import org.apache.juneau.http.remote.*;
062import org.apache.juneau.rest.client.RedirectSecurity;
063import org.apache.juneau.rest.client.classic.remote.*;
064import org.apache.juneau.marshall.*;
065import org.apache.juneau.marshall.bson.*;
066import org.apache.juneau.marshall.html.*;
067import org.apache.juneau.marshall.httppart.*;
068import org.apache.juneau.marshall.jcs.*;
069import org.apache.juneau.marshall.json.*;
070import org.apache.juneau.marshall.json5.*;
071import org.apache.juneau.marshall.marshaller.*;
072import org.apache.juneau.marshall.marshaller.Uon;
073import org.apache.juneau.marshall.msgpack.*;
074import org.apache.juneau.marshall.oapi.*;
075import org.apache.juneau.marshall.parser.*;
076import org.apache.juneau.marshall.plaintext.*;
077import org.apache.juneau.marshall.serializer.*;
078import org.apache.juneau.marshall.stream.*;
079import org.apache.juneau.marshall.uon.*;
080import org.apache.juneau.marshall.urlencoding.*;
081import org.apache.juneau.marshall.xml.*;
082
083/**
084 * Represents a request to a remote REST resource.
085 *
086 * <p>
087 * Instances of this class are created by the various creator methods on the {@link RestClient} class.
088 *
089 * <h5 class='section'>Example:</h5>
090 * <p class='bjava'>
091 *    <jc>// Create a request and automatically close it.</jc>
092 *    <jk>try</jk> (<jv>RestRequest</jv> <jv>req</jv> = <jv>client</jv>.get(<js>"/myResource"</js>)) {
093 *       <jv>req</jv>
094 *          .queryData(<js>"foo"</js>, <js>"bar"</js>)
095 *          .run()
096 *          .assertStatus().asCode().is(200);
097 *    }
098 * </p>
099 *
100 * <p>
101 * The {@link #close()} method will automatically close any associated {@link RestResponse} if one was created via {@link #run()}.
102 *
103 * <h5 class='section'>Notes:</h5><ul>
104 *    <li class='warn'>This class is not thread safe.
105 *    <li class='note'>This class implements {@link AutoCloseable} and can be used in try-with-resources blocks.
106 *       The {@link #close()} method allows unchecked exceptions to propagate for debuggability,
107 *       while catching and logging checked exceptions to follow AutoCloseable best practices.
108 * </ul>
109 *
110 * <h5 class='section'>See Also:</h5><ul>
111 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestClient">juneau-rest-client-classic</a>
112 * </ul>
113 */
114@SuppressWarnings({
115   "java:S1133", // Intentional deprecation retained for backward compatibility until the documented removal; the reminder is not actionable now.
116   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
117   "java:S3776", // Cognitive complexity acceptable for request-building and dispatch logic.
118   "java:S3740", // Raw Class/Supplier types used for fluent REST request building where response type is unknown at construction time
119   "java:S6539", // Monster class; RestRequest is intentionally a single per-request fluent builder aggregating headers/query/form/body/serialization config
120   "resource", // Resource management handled externally
121   "unchecked" // toMap() casts a value already verified to be a Map to Map<Object,Object>.
122})
123public class RestRequest extends MarshallingSession implements HttpUriRequest, Configurable, AutoCloseable {
124
125   private static final String HEADER_ACCEPT = org.apache.juneau.http.header.Accept.NAME;
126   private static final String HEADER_CONTENT_TYPE = org.apache.juneau.http.header.ContentType.NAME;
127
128   // URI path separator (always "/" per RFC 3986)
129   private static final String URI_PATH_SEPARATOR = "/";
130
131   private class SimpleFormData extends SimplePart {
132      SimpleFormData(NameValuePair x) {
133         super(x, client.isSkipEmptyFormData());
134      }
135   }
136
137   private class SimpleHeader extends SimplePart implements Header {
138
139      SimpleHeader(NameValuePair x) {
140         super(x, client.isSkipEmptyHeaderData());
141      }
142
143      @Override
144      public HeaderElement[] getElements() throws ParseException { return new HeaderElement[0]; }
145   }
146
147   private class SimplePart implements NameValuePair {
148      final String name;
149      final String value;
150
151      SimplePart(NameValuePair x, boolean skipIfEmpty) {
152         name = x.getName();
153         if (x instanceof SerializedHeader x2) {
154            value = x2.copyWith(getPartSerializerSession(), null).getValue();
155         } else if (x instanceof SerializedPart x2) {
156            value = x2.copyWith(getPartSerializerSession(), null).getValue();
157         } else {
158            var v = x.getValue();
159            value = (ie(v) && skipIfEmpty) ? null : v;
160         }
161      }
162
163      @Override
164      public String getName() { return name; }
165
166      @Override
167      public String getValue() { return value; }
168
169      boolean isValid() {
170         return !(ie(name) || value == null);
171      }
172   }
173
174   private class SimplePath extends SimplePart {
175      SimplePath(NameValuePair x) {
176         super(x, false);
177      }
178   }
179
180   private class SimpleQuery extends SimplePart {
181      SimpleQuery(NameValuePair x) {
182         super(x, client.isSkipEmptyQueryData());
183      }
184   }
185
186   private static final ContentType TEXT_PLAIN = ContentType.TEXT_PLAIN;
187
188   private static boolean isHeaderArray(Object o) {
189      if (! isArray(o))
190         return false;
191      return Header.class.isAssignableFrom(o.getClass().getComponentType());
192   }
193
194   private static boolean isNameValuePairArray(Object o) {
195      if (! isArray(o))
196         return false;
197      return NameValuePair.class.isAssignableFrom(o.getClass().getComponentType());
198   }
199
200   private static Map<Object,Object> toMap(Object o) {
201      return (Map<Object,Object>)o;
202   }
203
204   final RestClient client;                               // The client that created this call.
205   private final HttpRequestBase request;                 // The request.
206   private boolean ignoreErrors;
207   private boolean suppressLogging;
208   private HttpContext context;
209   // Set for the request of an SSRF-guard-active @Remote call (by RestClient, or by run() while the calling thread runs
210   // a guarded call); it then sends every hop with the SsrfGuardMarker on its context.  Volatile: the request's
211   // HttpClient may run on another thread.
212   private volatile boolean ssrfGuarded;
213   private HttpHost target;
214   private HttpPartSchema contentSchema;
215   private HttpPartSerializerSession partSerializerSession;
216   private final HeaderList headerData;
217   private List<Class<? extends Throwable>> rethrow;
218   List<RestCallInterceptor> interceptors = list();   // Used for intercepting and altering requests.
219   private Object content;
220   private Parser parser;
221   private final PartList formData;
222   private final PartList pathData;
223   private final PartList queryData;
224   private Predicate<Integer> errorCodes;
225   private RestResponse response;                         // The response.
226   private Serializer serializer;
227   private final URIBuilder uriBuilder;
228
229   private final Map<HttpPartSerializer,HttpPartSerializerSession> partSerializerSessions = new IdentityHashMap<>();
230
231   /**
232    * Constructs a REST call with the specified method name.
233    *
234    * @param client The client that created this request.
235    *    <br>Cannot be <jk>null</jk>.
236    * @param uri The target URI.
237    *    <br>Cannot be <jk>null</jk>.
238    * @param method The HTTP method name (uppercase).
239    *    <br>Cannot be <jk>null</jk>.
240    * @param hasBody Whether this method has a body.
241    * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt.
242    */
243   protected RestRequest(RestClient client, URI uri, String method, boolean hasBody) throws RestCallException {
244      super(reqnn("client", client).getMarshallingContext().createSession());
245      this.client = client;
246      this.request = createInnerRequest(reqnn("method", method), reqnn("uri", uri), hasBody);
247      this.errorCodes = client.errorCodes;
248      this.formData = client.createFormData();
249      this.headerData = client.createHeaderData();
250      this.ignoreErrors = client.ignoreErrors;
251      this.pathData = client.createPathData();
252      this.queryData = client.createQueryData();
253      this.uriBuilder = new URIBuilder(request.getURI());
254   }
255
256   /**
257    * Aborts this http request. Any active execution of this method should return immediately.
258    *
259    * If the request has not started, it will abort after the next execution.
260    * <br>Aborting this request will cause all subsequent executions with this request to fail.
261    */
262   @Override /* Overridden from HttpUriRequest */
263   public void abort() throws UnsupportedOperationException {
264      request.abort();
265   }
266
267   /**
268    * Sets the value for the <c>Accept</c> request header.
269    *
270    * <p>
271    * This overrides the media type specified on the parser, but is overridden by calling
272    * <code>header(<js>"Accept"</js>, value);</code>
273    *
274    * @param value The new header value.
275    *    <br>Can be <jk>null</jk> (header will not be set).
276    * @return This object.
277    * @throws RestCallException Invalid input.
278    */
279   public RestRequest accept(String value) throws RestCallException {
280      return header(Accept.of(value));
281   }
282
283   /**
284    * Sets the value for the <c>Accept-Charset</c> request header.
285    *
286    * <p>
287    * This is a shortcut for calling <code>header(<js>"Accept-Charset"</js>, value);</code>
288    *
289    * @param value The new header value.
290    *    <br>Can be <jk>null</jk> (header will not be set).
291    * @return This object.
292    * @throws RestCallException Invalid input.
293    */
294   public RestRequest acceptCharset(String value) throws RestCallException {
295      return header(AcceptCharset.of(value));
296   }
297
298   /**
299    * Adds a header to this message.
300    *
301    * The header will be appended to the end of the list.
302    *
303    * <h5 class='section'>Notes:</h5><ul>
304    *    <li class='note'>{@link #header(Header)} is an equivalent method and the preferred method for fluent-style coding.
305    * </ul>
306    *
307    * @param header The header to append.
308    *    <br>Can be <jk>null</jk> (ignored).
309    */
310   @Override /* Overridden from HttpMessage */
311   public void addHeader(Header header) {
312      headerData.append(header);
313   }
314
315   /**
316    * Adds a header to this message.
317    *
318    * The header will be appended to the end of the list.
319    *
320    * <h5 class='section'>Notes:</h5><ul>
321    *    <li class='note'>{@link #header(String,Object)} is an equivalent method and the preferred method for fluent-style coding.
322    * </ul>
323    *
324    * @param name The name of the header.
325    *    <br>Cannot be <jk>null</jk> or empty (header will not be created).
326    * @param value The value of the header.
327    *    <br>Can be <jk>null</jk>.
328    */
329   @Override /* Overridden from HttpMessage */
330   public void addHeader(String name, String value) {
331      headerData.append(stringHeader(name, value));
332   }
333
334   /**
335    * Sets {@link Cancellable} for the ongoing operation.
336    *
337    * @param cancellable The cancellable object.
338    *    <br>Can be <jk>null</jk> (no cancellation will be available).
339    * @return This object.
340    */
341   public RestRequest cancellable(Cancellable cancellable) {
342      request.setCancellable(cancellable);
343      return this;
344   }
345
346   /**
347    * Closes this request and its associated response (if one was created).
348    *
349    * <p>
350    * This method is idempotent and can be called multiple times without side effects.
351    *
352    * <h5 class='section'>Implementation Notes:</h5>
353    * <p>
354    * This implementation represents a compromise between strict AutoCloseable compliance and debuggability:
355    * <ul>
356    *    <li>Unchecked exceptions ({@link RuntimeException} and {@link Error}) from the response close are allowed to propagate.
357    *       This ensures programming errors and serious issues are visible during development and testing.
358    *    <li>Checked exceptions (including {@link RestCallException}) are caught and logged but not thrown.
359    *       This follows AutoCloseable best practices and prevents close exceptions from interfering with
360    *       try-with-resources cleanup or masking the original exception.
361    * </ul>
362    */
363   @Override /* Overridden from AutoCloseable */
364   public void close() {
365      try {
366         if (nn(response)) {
367            response.close();
368         }
369      } catch (RuntimeException e) {
370         // Let unchecked exceptions propagate for debuggability
371         throw e;
372      } catch (Exception e) {
373         // Log checked exceptions but don't throw - follows AutoCloseable best practices
374         client.log(Level.WARNING, e, "Error closing RestResponse");
375      }
376   }
377
378   /**
379    * Same as {@link #run()} but immediately calls {@link RestResponse#consume()} to clean up the response.
380    *
381    * <p>
382    * Use this method if you're only interested in the status line of the response and not the response entity.
383    * Attempts to call any of the methods on the response object that retrieve the body (e.g. {@link ResponseContent#asReader()}
384    * will cause a {@link RestCallException} to be thrown.
385    *
386    * <h5 class='section'>Notes:</h5><ul>
387    *    <li class='note'>You do not need to execute {@link InputStream#close()} on the response body to consume the response.
388    * </ul>
389    *
390    * <h5 class='section'>Example:</h5>
391    * <p class='bjava'>
392    *  <jc>// Get the response code.
393    *  // No need to call close() on the RestResponse object.</jc>
394    *  <jk>int</jk> <jv>rc</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).complete().getResponseCode();
395    * </p>
396    *
397    * @return The response object.
398    * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt.
399    */
400   public RestResponse complete() throws RestCallException {
401      return run().consume();
402   }
403
404   /**
405    * Used in combination with {@link #cancellable(Cancellable)}.
406    *
407    * @deprecated Since 10.0, for removal.
408    * @return This object.
409    */
410   @Deprecated(since = "10.0", forRemoval = true)
411   public RestRequest completed() {
412      request.completed();
413      return this;
414   }
415
416   /**
417    * Same as {@link #complete()} but allows you to run the call asynchronously.
418    *
419    * <h5 class='section'>Example:</h5>
420    * <p class='bjava'>
421    *    Future&lt;RestResponse&gt; <jv>future</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).completeFuture();
422    *
423    *    <jc>// Do some other stuff</jc>
424    *
425    *    <jk>int</jk> <jv>rc</jv> = <jv>future</jv>.get().getResponseStatus();
426    * </p>
427    *
428    * <h5 class='section'>Notes:</h5><ul>
429    *    <li class='note'>Use the {@link RestClient.Builder#executorService(ExecutorService, boolean)} method to customize the
430    *       executor service used for creating {@link Future Futures}.
431    *    <li class='note'>You do not need to execute {@link InputStream#close()} on the response body to consume the response.
432    * </ul>
433    *
434    * @return The HTTP status code.
435    * @throws RestCallException If the executor service was not defined.
436    */
437   public Future<RestResponse> completeFuture() throws RestCallException {
438      return client.getExecutorService().submit(this::complete);
439   }
440
441   /**
442    * Sets the actual request configuration.
443    *
444    * @param value The new value.
445    *    <br>Can be <jk>null</jk> (default configuration will be used).
446    * @return This object.
447    */
448   public RestRequest config(RequestConfig value) {
449      request.setConfig(value);
450      return this;
451   }
452
453   /**
454    * Checks if a certain header is present in this message.
455    *
456    * Header values are ignored.
457    *
458    * @param name The header name to check for.
459    *    <br>Cannot be <jk>null</jk>.
460    * @return <jk>true</jk> if at least one header with this name is present.
461    */
462   @Override /* Overridden from HttpMessage */
463   public boolean containsHeader(String name) {
464      return headerData.contains(name);
465   }
466
467   /**
468    * Sets the body of this request.
469    *
470    * @param value
471    *    The input to be sent to the REST resource (only valid for PUT/POST/PATCH) requests.
472    *    <br>Can be of the following types:
473    *    <ul class='spaced-list'>
474    *       <li>
475    *          {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource.
476    *       <li>
477    *          {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource.
478    *       <li>
479    *          {@link HttpResource} - Raw contents will be serialized to remote resource.  Additional headers and media type will be set on request.
480    *       <li>
481    *          {@link HttpEntity}/{@link BasicHttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
482    *       <li>
483    *          {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the
484    *          {@link RestClient}.
485    *       <li>
486    *          {@link PartList} - Converted to a URL-encoded FORM post.
487    *       <li>
488    *          A {@link Supplier} of anything on this list.
489    *    </ul>
490    *    <br>Can be <jk>null</jk> (no body will be sent).
491    * @return This object.
492    */
493   public RestRequest content(Object value) {
494      content = value;
495      return this;
496   }
497
498   /**
499    * Sets the body of this request.
500    *
501    * @param input
502    *    The input to be sent to the REST resource (only valid for PUT/POST/PATCH) requests.
503    *    <br>Can be of the following types:
504    *    <ul class='spaced-list'>
505    *       <li>
506    *          {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource.
507    *       <li>
508    *          {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource.
509    *       <li>
510    *          {@link HttpResource} - Raw contents will be serialized to remote resource.  Additional headers and media type will be set on request.
511    *       <li>
512    *          {@link HttpEntity}/{@link BasicHttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
513    *       <li>
514    *          {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the
515    *          {@link RestClient}.
516    *       <li>
517    *          {@link PartList} - Converted to a URL-encoded FORM post.
518    *       <li>
519    *          A {@link Supplier} of anything on this list.
520    *    </ul>
521    * @param schema The schema object that defines the format of the output.
522    *    <ul>
523    *       <li>If <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
524    *       <li>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}).
525    *    </ul>
526    * @return This object.
527    */
528   public RestRequest content(Object input, HttpPartSchema schema) {
529      this.content = input;
530      this.contentSchema = schema;
531      return this;
532   }
533
534   /**
535    * Sets the body of this request as straight text bypassing the serializer.
536    *
537    * <p class='bjava'>
538    *    <jv>client</jv>
539    *       .put(<js>"/foo"</js>)
540    *       .content(<jk>new</jk> StringReader(<js>"foo"</js>))
541    *       .contentType(<js>"text/foo"</js>)
542    *       .run();
543    *
544    * <jv>client</jv>
545    *       .put(<js>"/foo"</js>)
546    *       .bodyString(<js>"foo"</js>)
547    *       .run();
548    * </p>
549    *
550    * <p>
551    * Note that this is different than the following which will serialize <l>foo</l> as a JSON string <l>"foo"</l>.
552    * <p class='bjava'>
553    *    <jv>client</jv>
554    *       .put(<js>"/foo"</js>)
555    *       .json()
556    *       .content(<js>"foo"</js>)
557    *       .run();
558    * </p>
559    *
560    * @param input
561    *    The input to be sent to the REST resource (only valid for PUT/POST/PATCH) requests.
562    * @return This object.
563    * @throws RestCallException If a retry was attempted, but the entity was not repeatable.
564    */
565   public RestRequest contentString(Object input) throws RestCallException {
566      return content(input == null ? null : new StringReader(s(input)));
567   }
568
569   /**
570    * Sets the value for the <c>Content-Type</c> request header.
571    *
572    * <p>
573    * This overrides the media type specified on the serializer, but is overridden by calling
574    * <code>header(<js>"Content-Type"</js>, value);</code>
575    *
576    * @param value The new header value.
577    *    <br>Can be <jk>null</jk> (header will not be set).
578    * @return This object.
579    * @throws RestCallException Invalid input.
580    */
581   public RestRequest contentType(String value) throws RestCallException {
582      return header(ContentType.of(value));
583   }
584
585   /**
586    * Override the context to use for the execution.
587    *
588    * @param context The context to use for the execution, or <jk>null</jk> to use the default context.
589    * @return This object.
590    */
591   public RestRequest context(HttpContext context) {
592      this.context = context;
593      return this;
594   }
595
596   /**
597    * Sets <c>Debug: value</c> header on this request.
598    *
599    * @return This object.
600    * @throws RestCallException Invalid input.
601    */
602   public RestRequest debug() throws RestCallException {
603      header("Debug", true);
604      return this;
605   }
606
607   /**
608    * Allows you to override what status codes are considered error codes that would result in a {@link RestCallException}.
609    *
610    * <p>
611    * The default error code predicate is: <code>x -&gt; x &gt;= 400</code>.
612    *
613    * @param value The new predicate for calculating error codes.
614    *    <br>Cannot be <jk>null</jk>.
615    * @return This object.
616    */
617   public RestRequest errorCodes(Predicate<Integer> value) {
618      errorCodes = reqnn("value", value);
619      return this;
620   }
621
622   /**
623    * Appends multiple form-data parameters to the request.
624    *
625    * <h5 class='section'>Example:</h5>
626    * <p class='bjava'>
627    *    <jc>// Appends two form-data parameters to the request.</jc>
628    *    <jv>client</jv>
629    *       .get(<jsf>URI</jsf>)
630    *       .formData(
631    *          BasicStringPart.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>),
632    *          BasicBooleanPart.<jsm>of</jsm>(<js>"baz"</js>, <jk>true</jk>)
633    *       )
634    *       .run();
635    * </p>
636    *
637    * @param parts
638    *    The parameters to set.
639    *    <jk>null</jk> values are ignored.
640    * @return This object.
641    */
642   public RestRequest formData(NameValuePair...parts) {
643      formData.append(parts);
644      return this;
645   }
646
647   /**
648    * Adds a form-data parameter to the request body.
649    *
650    * <h5 class='section'>Example:</h5>
651    * <p class='bjava'>
652    *    <jc>// Adds form data parameter "foo=bar".</jc>
653    *    <jv>client</jv>
654    *       .formPost(<jsf>URI</jsf>)
655    *       .formData(<js>"foo"</js>, <js>"bar"</js>)
656    *       .run();
657    * </p>
658    *
659    * @param name
660    *    The parameter name.
661    *    <br>Cannot be <jk>null</jk> or empty (parameter will not be created).
662    * @param value
663    *    The parameter value.
664    *    <br>Non-string values are converted to strings using the {@link HttpPartSerializer} defined on the client.
665    *    <br>Can be <jk>null</jk>.
666    * @return This object.
667    */
668   public RestRequest formData(String name, Object value) {
669      formData.append(createPart(FORMDATA, name, value));
670      return this;
671   }
672
673   /**
674    * Appends multiple form-data parameters to the request from properties defined on a Java bean.
675    *
676    * <h5 class='section'>Example:</h5>
677    * <p class='bjava'>
678    *    <jk>public class</jk> MyFormData {
679    *       <jk>public</jk> String getFooBar() { <jk>return</jk> <js>"baz"</js>; }
680    *       <jk>public</jk> Integer getQux() { <jk>return</jk> 123; }
681    *    }
682    *
683    *    <jc>// Appends form data "fooBar=baz&amp;qux=123".</jc>
684    *    <jv>client</jv>
685    *       .get(<jsf>URI</jsf>)
686    *       .formDataBean(<jk>new</jk> MyFormData())
687    *       .run();
688    * </p>
689    *
690    * @param value The bean containing the properties to set as form-data parameter values.
691    *    <br>Cannot be <jk>null</jk>.
692    * @return This object.
693    */
694   public RestRequest formDataBean(Object value) {
695      req(isBean(reqnn("value", value)), "Object passed into formDataBean(Object) is not a bean.");
696      var b = formData;
697      toBeanMap(value).forEach((k, v) -> b.append(createPart(FORMDATA, k, v)));
698      return this;
699   }
700
701   /**
702    * Adds form-data parameters as the entire body of the request.
703    *
704    * <h5 class='section'>Example:</h5>
705    * <p class='bjava'>
706    *    <jc>// Creates form data "foo=bar&amp;baz=qux".</jc>
707    *    <jv>client</jv>
708    *       .formPost(<jsf>URI</jsf>)
709    *       .formDataCustom(<js>"foo=bar&amp;baz=qux"</js>)
710    *       .run();
711    *
712    *    <jc>// Creates form data "foo=bar&amp;baz=qux" using StringEntity.</jc>
713    *    <jv>client</jv>
714    *       .formPost(<jsf>URI</jsf>)
715    *       .formDataCustom(<jk>new</jk> StringEntity(<js>"foo=bar&amp;baz=qux"</js>,<js>"application/x-www-form-urlencoded"</js>))
716    *       .run();
717    *
718    *    <jc>// Creates form data "foo=bar&amp;baz=qux" using StringEntity and body().</jc>
719    *    <jv>client</jv>
720    *       .formPost(<jsf>URI</jsf>)
721    *       .content(<jk>new</jk> StringEntity(<js>"foo=bar&amp;baz=qux"</js>,<js>"application/x-www-form-urlencoded"</js>))
722    *       .run();
723    * </p>
724    *
725    * @param value The parameter value.
726    *    <br>Can be any of the following types:
727    *    <ul class='spaced-list'>
728    *       <li>
729    *          {@link CharSequence}
730    *       <li>
731    *          {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource.
732    *       <li>
733    *          {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource.
734    *       <li>
735    *          {@link HttpResource} - Raw contents will be serialized to remote resource.  Additional headers and media type will be set on request.
736    *       <li>
737    *          {@link HttpEntity}/{@link BasicHttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
738    *       <li>
739    *          {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the
740    *          {@link RestClient}.
741    *       <li>
742    *          {@link PartList} - Converted to a URL-encoded FORM post.
743    *    </ul>
744    *    <br>Can be <jk>null</jk> (no body will be sent).
745    * @return This object.
746    */
747   public RestRequest formDataCustom(Object value) {
748      header(ContentType.APPLICATION_FORM_URLENCODED);
749      content(value instanceof CharSequence ? new StringReader(value.toString()) : value);
750      return this;
751   }
752
753   /**
754    * Adds form-data parameters to the request body using free-form key/value pairs.
755    *
756    * <h5 class='section'>Example:</h5>
757    * <p class='bjava'>
758    *    <jc>// Creates form data "key1=val1&amp;key2=val2".</jc>
759    *    <jv>client</jv>
760    *       .formPost(<jsf>URI</jsf>)
761    *       .formDataPairs(<js>"key1"</js>,<js>"val1"</js>,<js>"key2"</js>,<js>"val2"</js>)
762    *       .run();
763    * </p>
764    *
765    * @param pairs The form-data key/value pairs.
766    *    <ul>
767    *       <li>Values can be any POJO.
768    *       <li>Values converted to a string using the configured part serializer.
769    *    </ul>
770    *    <br>Cannot be <jk>null</jk>.
771    *    <br>Must have an even number of elements (key/value pairs).
772    *    <br>Null keys are ignored (parameter will not be created).
773    *    <br>Null values are allowed.
774    * @return This object.
775    * @throws RestCallException Invalid input.
776    */
777   public RestRequest formDataPairs(String...pairs) throws RestCallException {
778      req(pairs.length % 2 == 0, "Odd number of parameters passed into formDataPairs(String...)");
779      var b = formData;
780      for (var i = 0; i < pairs.length; i += 2)
781         b.append(pairs[i], pairs[i + 1]);
782      return this;
783   }
784
785   /**
786    * Returns all the headers of this message.
787    *
788    * Headers are ordered in the sequence they will be sent over a connection.
789    *
790    * @return All the headers of this message
791    */
792   @Override /* Overridden from HttpMessage */
793   public Header[] getAllHeaders() { return headerData.getAll(); }
794
795   /**
796    * Returns the actual request configuration.
797    *
798    * @return The actual request configuration.
799    */
800   @Override /* Overridden from Configurable */
801   public RequestConfig getConfig() { return request.getConfig(); }
802
803   /**
804    * Returns the first header with a specified name of this message.
805    *
806    * Header values are ignored.
807    * <br>If there is more than one matching header in the message the first element of {@link #getHeaders(String)} is returned.
808    * <br>If there is no matching header in the message <jk>null</jk> is returned.
809    *
810    * @param name The name of the header to return.
811    *    <br>Cannot be <jk>null</jk>.
812    * @return The first header whose name property equals name or <jk>null</jk> if no such header could be found.
813    */
814   @Override /* Overridden from HttpMessage */
815   public Header getFirstHeader(String name) {
816      return headerData.getFirst(name).orElse(null);
817   }
818
819   /**
820    * Returns the form data for the request.
821    *
822    * @return An immutable list of form data to send on the request.
823    */
824   public PartList getFormData() { return formData; }
825
826   /**
827    * Returns the header data for the request.
828    *
829    * @return An immutable list of headers to send on the request.
830    */
831   public HeaderList getHeaders() { return headerData; }
832
833   /**
834    * Returns all the headers with a specified name of this message.
835    *
836    * Header values are ignored.
837    * <br>Headers are ordered in the sequence they will be sent over a connection.
838    *
839    * @param name The name of the headers to return.
840    *    <br>Cannot be <jk>null</jk>.
841    * @return The headers whose name property equals name.
842    */
843   @Override /* Overridden from HttpMessage */
844   public Header[] getHeaders(String name) {
845      return headerData.getAll(name);
846   }
847
848   /**
849    * Returns the body of this request.
850    *
851    * @return The body of this request, or <jk>null</jk> if it doesn't have a body.
852    */
853   public HttpEntity getHttpEntity() { return hasHttpEntity() ? ((HttpEntityEnclosingRequestBase)request).getEntity() : null; }
854
855   /**
856    * Returns the last header with a specified name of this message.
857    *
858    * Header values are ignored.
859    * <br>If there is more than one matching header in the message the last element of {@link #getHeaders(String)} is returned.
860    * <br>If there is no matching header in the message, <jk>null</jk> is returned.
861    *
862    * @param name The name of the header to return.
863    *    <br>Cannot be <jk>null</jk>.
864    * @return The last header whose name property equals name or <jk>null</jk> if no such header could be found.
865    */
866   @Override /* Overridden from HttpMessage */
867   public Header getLastHeader(String name) {
868      return headerData.getLast(name).orElse(null);
869   }
870
871   /**
872    * Returns the HTTP method this request uses, such as GET, PUT, POST, or other.
873    *
874    * @return The HTTP method this request uses, such as GET, PUT, POST, or other.
875    */
876   @Override /* Overridden from HttpUriRequest */
877   public String getMethod() { return request.getMethod(); }
878
879   /**
880    * Returns the parameters effective for this message as set by {@link #setParams(HttpParams)}.
881    *
882    * @return The parameters effective for this message as set by {@link #setParams(HttpParams)}.
883    * @deprecated Use constructor parameters of configuration API provided by HttpClient.
884    */
885   @Override /* Overridden from HttpMessage */
886   @Deprecated(since = "10.0", forRemoval = true)
887   public HttpParams getParams() { return request.getParams(); }
888
889   /**
890    * Returns the path data for the request.
891    *
892    * @return An immutable list of path data to send on the request.
893    */
894   public PartList getPathData() { return pathData; }
895
896   /**
897    * Returns the protocol version this message is compatible with.
898    *
899    * @return The protocol version.
900    */
901   @Override /* Overridden from HttpMessage */
902   public ProtocolVersion getProtocolVersion() { return request.getProtocolVersion(); }
903
904   /**
905    * Returns the query data for the request.
906    *
907    * @return An immutable list of query data to send on the request.
908    */
909   public PartList getQueryData() { return queryData; }
910
911   /**
912    * Returns the request line of this request.
913    *
914    * @return The request line.
915    */
916   @Override /* Overridden from HttpRequest */
917   public RequestLine getRequestLine() { return request.getRequestLine(); }
918
919   /**
920    * A shortcut for calling <c>run().getContent().as(<js>type</js>)</c>.
921    *
922    * @param type The object type to create.
923    *    <br>Cannot be <jk>null</jk>.
924    * @param <T> The object type to create.
925    * @see ResponseContent#as(Class)
926    * @return The response content as a simple string.
927    * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt.
928    */
929   public <T> T getResponse(Class<T> type) throws RestCallException {
930      return run().getContent().as(type);
931   }
932
933   /**
934    * A shortcut for calling <c>run().getContent().as(<js>type</js>,<js>args</js>)</c>.
935    *
936    * @param <T>
937    *    The object type to create.
938    * @param type
939    *    The object type to create.
940    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
941    *    <br>Cannot be <jk>null</jk>.
942    * @param args
943    *    The type arguments of the class if it's a collection or map.
944    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
945    *    <br>Ignored if the main type is not a map or collection.
946    *    <br>Can contain <jk>null</jk> values (ignored).
947    * @see ResponseContent#as(Type,Type...)
948    * @return The response content as a simple string.
949    * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt.
950    */
951   public <T> T getResponse(Type type, Type...args) throws RestCallException {
952      return run().getContent().as(type, args);
953   }
954
955   /**
956    * A shortcut for calling <c>run().getContent().asString()</c>.
957    *
958    * @return The response content as a simple string.
959    * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt.
960    */
961   public String getResponseAsString() throws RestCallException { return run().getContent().asString(); }
962
963   /**
964    * Returns the original request URI.
965    *
966    * <h5 class='section'>Notes:</h5><ul>
967    *    <li class='note'>URI remains unchanged in the course of request execution and is not updated if the request is redirected to another location.
968    * </ul>
969    *
970    * @return The original request URI.
971    */
972   @Override /* Overridden from HttpUriRequest */
973   public URI getURI() { return request.getURI(); }
974
975   /**
976    * Returns <jk>true</jk> if this request has a body.
977    *
978    * @return <jk>true</jk> if this request has a body.
979    */
980   public boolean hasHttpEntity() {
981      return request instanceof HttpEntityEnclosingRequestBase;
982   }
983
984   /**
985    * Appends a header to the request.
986    *
987    * <h5 class='section'>Example:</h5>
988    * <p class='bjava'>
989    *    <jc>// Adds header "Foo: bar".</jc>
990    *    <jv>client</jv>
991    *       .get(<jsf>URI</jsf>)
992    *       .header(Accept.<jsf>TEXT_XML</jsf>)
993    *       .run();
994    * </p>
995    *
996    * @param part
997    *    The parameter to set.
998    *    <jk>null</jk> values are ignored.
999    * @return This object.
1000    */
1001   public RestRequest header(Header part) {
1002      return headers(part);
1003   }
1004
1005   /**
1006    * Appends a header to the request.
1007    *
1008    * <h5 class='section'>Example:</h5>
1009    * <p class='bjava'>
1010    *    <jc>// Adds header "Foo: bar".</jc>
1011    *    <jv>client</jv>
1012    *       .get(<jsf>URI</jsf>)
1013    *       .header(<js>"Foo"</js>, <js>"bar"</js>)
1014    *       .run();
1015    * </p>
1016    *
1017    * @param name
1018    *    The parameter name.
1019    * @param value
1020    *    The parameter value.
1021    *    <br>Non-string values are converted to strings using the {@link HttpPartSerializer} defined on the client.
1022    * @return This object.
1023    */
1024   public RestRequest header(String name, Object value) {
1025      headerData.append(createHeader(name, value));
1026      return this;
1027   }
1028
1029   /**
1030    * Returns an iterator of all the headers.
1031    *
1032    * @return Iterator that returns {@link Header} objects in the sequence they are sent over a connection.
1033    */
1034   @Override /* Overridden from HttpMessage */
1035   public HeaderIterator headerIterator() {
1036      return headerData.headerIterator();
1037   }
1038
1039   /**
1040    * Returns an iterator of the headers with a given name.
1041    *
1042    * @param name the name of the headers over which to iterate, or <jk>null</jk> for all headers.
1043    * @return Iterator that returns {@link Header} objects with the argument name in the sequence they are sent over a connection.
1044    */
1045   @Override /* Overridden from HttpMessage */
1046   public HeaderIterator headerIterator(String name) {
1047      return headerData.headerIterator(name);
1048   }
1049
1050   /**
1051    * Appends multiple headers to the request using freeform key/value pairs.
1052    *
1053    * <h5 class='section'>Example:</h5>
1054    * <p class='bjava'>
1055    *    <jc>// Adds headers "Foo: bar" and "Baz: qux".</jc>
1056    *    <jv>client</jv>
1057    *       .get(<jsf>URI</jsf>)
1058    *       .headerPairs(<js>"Foo"</js>,<js>"bar"</js>,<js>"Baz"</js>,<js>"qux"</js>)
1059    *       .run();
1060    * </p>
1061    *
1062    * @param pairs The header key/value pairs.
1063    *    <br>Cannot be <jk>null</jk>.
1064    *    <br>Must have an even number of elements (key/value pairs).
1065    *    <br>Null keys are ignored (header will not be created).
1066    *    <br>Null values are allowed.
1067    * @return This object.
1068    */
1069   public RestRequest headerPairs(String...pairs) {
1070      req(pairs.length % 2 == 0, "Odd number of parameters passed into headerPairs(String...)");
1071      var b = headerData;
1072      for (var i = 0; i < pairs.length; i += 2)
1073         b.append(pairs[i], pairs[i + 1]);
1074      return this;
1075   }
1076
1077   /**
1078    * Appends multiple headers to the request.
1079    *
1080    * <h5 class='section'>Example:</h5>
1081    * <p class='bjava'>
1082    *    <jc>// Appends two headers to the request.</jc>
1083    *    <jv>client</jv>
1084    *       .get(<jsf>URI</jsf>)
1085    *       .headers(
1086    *          BasicHeader.<jsm>of</jsm>(<js>"Foo"</js>, <js>"bar"</js>),
1087    *          Accept.<jsf>TEXT_XML</jsf>
1088    *       )
1089    *       .run();
1090    * </p>
1091    *
1092    * @param parts
1093    *    The parameters to set.
1094    *    <jk>null</jk> values are ignored.
1095    * @return This object.
1096    */
1097   public RestRequest headers(Header...parts) {
1098      headerData.append(parts);
1099      return this;
1100   }
1101
1102   /**
1103    * Appends multiple headers to the request from properties defined on a Java bean.
1104    *
1105    * <p>
1106    * Uses {@link PropertyNamerDUCS} for resolving the header names from property names.
1107    *
1108    * <h5 class='section'>Example:</h5>
1109    * <p class='bjava'>
1110    *    <ja>@Marshalled</ja>
1111    *    <jk>public class</jk> MyHeaders {
1112    *       <jk>public</jk> String getFooBar() { <jk>return</jk> <js>"baz"</js>; }
1113    *       <jk>public</jk> Integer getQux() { <jk>return</jk> 123; }
1114    *    }
1115    *
1116    *    <jc>// Appends headers "Foo-Bar: baz" and "Qux: 123".</jc>
1117    *    <jv>client</jv>
1118    *       .get(<jsf>URI</jsf>)
1119    *       .headersBean(<jk>new</jk> MyHeaders())
1120    *       .run();
1121    * </p>
1122    *
1123    * @param value The bean containing the properties to set as header values.
1124    *    <br>Cannot be <jk>null</jk>.
1125    * @return This object.
1126    */
1127   public RestRequest headersBean(Object value) {
1128      req(isBean(reqnn("value", value)), "Object passed into headersBean(Object) is not a bean.");
1129      var b = headerData;
1130      toBeanMap(value, PropertyNamerDUCS.INSTANCE).forEach((k, v) -> b.append(createHeader(k, v)));
1131      return this;
1132   }
1133
1134   /**
1135    * Convenience method for specifying HTML as the marshalling transmission media type for this request only.
1136    *
1137    * <p>
1138    * POJOs are converted to HTML without any sort of doc wrappers.
1139    *
1140    * <p>
1141    *    {@link HtmlSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
1142    *    <ul>
1143    *       <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
1144    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1145    *    </ul>
1146    * <p>
1147    *    {@link HtmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
1148    *    <ul>
1149    *       <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
1150    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1151    *    </ul>
1152    * <p>
1153    *    <c>Accept</c> request header will be set to <js>"text/html"</js> unless overridden
1154    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}.
1155    * <p>
1156    *    <c>Content-Type</c> request header will be set to <js>"text/html"</js> unless overridden
1157    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}.
1158    * <p>
1159    *    Identical to calling <c>serializer(HtmlSerializer.<jk>class</jk>).parser(HtmlParser.<jk>class</jk>)</c>.
1160    *
1161    * @return This object.
1162    */
1163   public RestRequest html() {
1164      return serializer(HtmlSerializer.class).parser(HtmlParser.class);
1165   }
1166
1167   /**
1168    * Convenience method for specifying HTML DOC as the marshalling transmission media type for this request only.
1169    *
1170    * <p>
1171    * POJOs are converted to fully renderable HTML pages.
1172    *
1173    * <p>
1174    *    {@link HtmlDocSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
1175    *    <ul>
1176    *       <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
1177    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1178    *    </ul>
1179    * <p>
1180    *    {@link HtmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
1181    *    <ul>
1182    *       <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
1183    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1184    *    </ul>
1185    * <p>
1186    *    <c>Accept</c> request header will be set to <js>"text/html"</js> unless overridden
1187    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}.
1188    * <p>
1189    *    <c>Content-Type</c> request header will be set to <js>"text/html"</js> unless overridden
1190    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}.
1191    * <p>
1192    *    Identical to calling <c>serializer(HtmlDocSerializer.<jk>class</jk>).parser(HtmlParser.<jk>class</jk>)</c>.
1193    *
1194    * @return This object.
1195    */
1196   public RestRequest htmlDoc() {
1197      return serializer(HtmlDocSerializer.class).parser(HtmlParser.class);
1198   }
1199
1200   /**
1201    * Convenience method for specifying Stripped HTML DOC as the marshalling transmission media type for this request only.
1202    *
1203    * <p>
1204    * Same as {@link #htmlDoc()} but without the header and body tags and page title and description.
1205    *
1206    * <p>
1207    *    {@link HtmlStrippedDocSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
1208    *    <ul>
1209    *       <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
1210    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1211    *    </ul>
1212    * <p>
1213    *    {@link HtmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
1214    *    <ul>
1215    *       <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
1216    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1217    *    </ul>
1218    * <p>
1219    *    <c>Accept</c> request header will be set to <js>"text/html+stripped"</js> unless overridden
1220    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}.
1221    * <p>
1222    *    <c>Content-Type</c> request header will be set to <js>"text/html+stripped"</js> unless overridden
1223    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}.
1224    * <p>
1225    *    Identical to calling <c>serializer(HtmlStrippedDocSerializer.<jk>class</jk>).parser(HtmlParser.<jk>class</jk>)</c>.
1226    *
1227    * @return This object.
1228    */
1229   public RestRequest htmlStrippedDoc() {
1230      return serializer(HtmlStrippedDocSerializer.class).parser(HtmlParser.class);
1231   }
1232
1233   /**
1234    * Prevent {@link RestCallException RestCallExceptions} from being thrown when HTTP status 400+ is encountered.
1235    *
1236    * <p>
1237    * This overrides the <l>ignoreErrors</l> property on the client.
1238    *
1239    * @return This object.
1240    */
1241   public RestRequest ignoreErrors() {
1242      this.ignoreErrors = true;
1243      return this;
1244   }
1245
1246   /**
1247    * Add one or more interceptors for this call only.
1248    *
1249    * @param interceptors The interceptors to add to this call.
1250    *    <br>Cannot contain <jk>null</jk> values.
1251    * @return This object.
1252    * @throws RestCallException If init method on interceptor threw an exception.
1253    */
1254   public RestRequest interceptors(RestCallInterceptor...interceptors) throws RestCallException {
1255      reqnns("interceptors", interceptors);
1256      try {
1257         for (var i : interceptors) {
1258            this.interceptors.add(i);
1259            i.onInit(this);
1260         }
1261      } catch (RuntimeException | RestCallException e) {
1262         throw e;
1263      } catch (Exception e) {
1264         throw new RestCallException(null, e, "Interceptor threw an exception on init.");
1265      }
1266
1267      return this;
1268   }
1269
1270   @Override /* Overridden from HttpUriRequest */
1271   public boolean isAborted() { return request.isAborted(); }
1272
1273   /**
1274    * Returns <jk>true</jk> if debug mode is currently enabled.
1275    */
1276   @Override
1277   public boolean isDebug() { return headerData.get("Debug", Boolean.class).orElse(false); }
1278
1279   /**
1280    * Convenience method for specifying JSON as the marshalling transmission media type for this request only.
1281    *
1282    * <p>
1283    * {@link JsonSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
1284    *    <ul>
1285    *       <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
1286    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1287    *    </ul>
1288    * <p>
1289    *    {@link JsonParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
1290    *    <ul>
1291    *       <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
1292    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1293    *    </ul>
1294    * <p>
1295    *    <c>Accept</c> request header will be set to <js>"application/json"</js> unless overridden
1296    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}.
1297    * <p>
1298    *    <c>Content-Type</c> request header will be set to <js>"application/json"</js> unless overridden
1299    *       {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}.
1300    * <p>
1301    *    Identical to calling <c>serializer(JsonSerializer.<jk>class</jk>).parser(JsonParser.<jk>class</jk>)</c>.
1302    *
1303    * @return This object.
1304    */
1305   public RestRequest json() {
1306      return serializer(JsonSerializer.class).parser(JsonParser.class);
1307   }
1308
1309   /**
1310    * Convenience method for specifying Simplified JSON as the marshalling transmission media type for this request only.
1311    *
1312    * <p>
1313    * Simplified JSON is typically useful for automated tests because you can do simple string comparison of results
1314    * without having to escape lots of quotes.
1315    *
1316    * <p>
1317    *    {@link Json5Serializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
1318    *    <ul>
1319    *       <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
1320    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1321    *    </ul>
1322    * <p>
1323    *    {@link Json5Parser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
1324    *    <ul>
1325    *       <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
1326    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1327    *    </ul>
1328    * <p>
1329    *    <c>Accept</c> request header will be set to <js>"application/json5"</js> unless overridden
1330    *       by {@link #header(String,Object)} or {@link #accept(String)}, or per-request via {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}.
1331    * <p>
1332    *    <c>Content-Type</c> request header will be set to <js>"application/json5"</js> unless overridden
1333    *       by {@link #header(String,Object)} or {@link #contentType(String)}, or per-request via {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}.
1334    * <p>
1335    *    A server that only handles JSON may reply <c>406 Not Acceptable</c> or <c>415 Unsupported Media Type</c> to these
1336    *    JSON5 media types.
1337    * <p>
1338    *    Can be combined with other marshaller setters such as {@link #xml()} to provide support for multiple languages.
1339    *    <ul>
1340    *       <li>When multiple languages are supported, the <c>Accept</c> and <c>Content-Type</c> headers control which marshallers are used, or uses the
1341    *       last-enabled language if the headers are not set.
1342    *    </ul>
1343    * <p>
1344    *    Identical to calling <c>serializer(Json5Serializer.<jk>class</jk>).parser(Json5Parser.<jk>class</jk>)</c>.
1345    *
1346    * @return This object.
1347    */
1348   public RestRequest json5() {
1349      return serializer(Json5Serializer.class).parser(Json5Parser.class);
1350   }
1351
1352   /**
1353    * Convenience method for specifying JCS (JSON Canonicalization Scheme, RFC 8785) as the marshalling transmission media type for this request only.
1354    *
1355    * <p>
1356    *    {@link JcsSerializer} will be used to serialize POJOs to request bodies.
1357    *    {@link JsonParser} will be used to parse POJOs from response bodies (JCS output is valid JSON).
1358    * <p>
1359    *    <c>Accept</c> and <c>Content-Type</c> will be set to <js>"application/jcs+json"</js>.
1360    * <p>
1361    *    Identical to calling <c>serializer(JcsSerializer.<jk>class</jk>).parser(JsonParser.<jk>class</jk>)</c>.
1362    *
1363    * @return This object.
1364    */
1365   public RestRequest jcs() {
1366      return serializer(JcsSerializer.class).parser(JsonParser.class);
1367   }
1368
1369   /**
1370    * Logs a message.
1371    *
1372    * @param level The log level.
1373    *    <br>Cannot be <jk>null</jk>.
1374    * @param msg The message with {@link String#format(String, Object...) String.format}-style (<c>%s</c>) arguments.
1375    *    <br>Cannot be <jk>null</jk>.
1376    * @param args The arguments.
1377    *    <br>Can contain <jk>null</jk> values.
1378    * @return This object.
1379    */
1380   public RestRequest log(Level level, String msg, Object...args) {
1381      client.log(level, msg, args);
1382      return this;
1383   }
1384
1385   /**
1386    * Logs a message.
1387    *
1388    * @param level The log level.
1389    *    <br>Cannot be <jk>null</jk>.
1390    * @param t The throwable cause.
1391    *    <br>Can be <jk>null</jk> (no exception details are logged).
1392    * @param msg The message with {@link String#format(String, Object...) String.format}-style (<c>%s</c>) arguments.
1393    *    <br>Cannot be <jk>null</jk>.
1394    * @param args The arguments.
1395    *    <br>Can contain <jk>null</jk> values.
1396    * @return This object.
1397    */
1398   public RestRequest log(Level level, Throwable t, String msg, Object...args) {
1399      client.log(level, t, msg, args);
1400      return this;
1401   }
1402
1403   /**
1404    * Shortcut for setting the <c>Accept</c> and <c>Content-Type</c> headers on a request.
1405    *
1406    * @param value The new header values.
1407    *    <br>Can be <jk>null</jk> (headers will not be set).
1408    * @return This object.
1409    * @throws RestCallException Invalid input.
1410    */
1411   public RestRequest mediaType(String value) throws RestCallException {
1412      return header(Accept.of(value)).header(ContentType.of(value));
1413   }
1414
1415   /**
1416    * Convenience method for specifying BSON as the marshalling transmission media type for this request only.
1417    *
1418    * <p>
1419    * Identical to calling <c>serializer(BsonSerializer.<jk>class</jk>).parser(BsonParser.<jk>class</jk>)</c>.
1420    *
1421    * @return This object.
1422    */
1423   public RestRequest bson() {
1424      return serializer(BsonSerializer.class).parser(BsonParser.class);
1425   }
1426
1427   /**
1428    * Convenience method for specifying MessagePack as the marshalling transmission media type for this request only.
1429    *
1430    * <p>
1431    * MessagePack is a binary equivalent to JSON that takes up considerably less space than JSON.
1432    *
1433    * <p>
1434    *    {@link MsgPackSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
1435    *    <ul>
1436    *       <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
1437    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1438    *    </ul>
1439    * <p>
1440    *    {@link MsgPackParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
1441    *    <ul>
1442    *       <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
1443    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1444    *    </ul>
1445    * <p>
1446    *    <c>Accept</c> request header will be set to <js>"application/msgpack"</js> unless overridden
1447    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}.
1448    * <p>
1449    *    <c>Content-Type</c> request header will be set to <js>"application/msgpack"</js> unless overridden
1450    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}.
1451    * <p>
1452    *    Identical to calling <c>serializer(MsgPackSerializer.<jk>class</jk>).parser(MsgPackParser.<jk>class</jk>)</c>.
1453    *
1454    * @return This object.
1455    */
1456   public RestRequest msgPack() {
1457      return serializer(MsgPackSerializer.class).parser(MsgPackParser.class);
1458   }
1459
1460   /**
1461    * When called, <c>No-Trace: true</c> is added to requests.
1462    *
1463    * <p>
1464    * This gives the opportunity for the servlet to not log errors on invalid requests.
1465    * This is useful for testing purposes when you don't want your log file to show lots of errors that are simply the
1466    * results of testing.
1467    *
1468    * @return This object.
1469    * @throws RestCallException Invalid input.
1470    */
1471   public RestRequest noTrace() throws RestCallException {
1472      return header(NoTrace.TRUE);
1473   }
1474
1475   /**
1476    * Convenience method for specifying OpenAPI as the marshalling transmission media type for this request only.
1477    *
1478    * <p>
1479    * OpenAPI is a language that allows serialization to formats that use {@link HttpPartSchema} objects to describe their structure.
1480    *
1481    * <p>
1482    *    {@link OpenApiSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
1483    *    <ul>
1484    *       <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
1485    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1486    *       <li>Typically the {@link RestRequest#content(Object, HttpPartSchema)} method will be used to specify the body of the request with the
1487    *          schema describing it's structure.
1488    *    </ul>
1489    * <p>
1490    *    {@link OpenApiParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
1491    *    <ul>
1492    *       <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
1493    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1494    *       <li>Typically the {@link ResponseContent#schema(HttpPartSchema)} method will be used to specify the structure of the response body.
1495    *    </ul>
1496    * <p>
1497    *    <c>Accept</c> request header will be set to <js>"text/openapi"</js> unless overridden
1498    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}.
1499    * <p>
1500    *    <c>Content-Type</c> request header will be set to <js>"text/openapi"</js> unless overridden
1501    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}.
1502    * <p>
1503    *    Identical to calling <c>serializer(OpenApiSerializer.<jk>class</jk>).parser(OpenApiParser.<jk>class</jk>)</c>.
1504    *
1505    * @return This object.
1506    */
1507   public RestRequest openApi() {
1508      return serializer(OpenApiSerializer.class).parser(OpenApiParser.class);
1509   }
1510
1511   /**
1512    * Specifies the parser to use on the response body.
1513    *
1514    * <p>
1515    * Overrides the parsers specified on the {@link RestClient}.
1516    *
1517    * <p>
1518    *    The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
1519    *    bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1520    *
1521    * <p>
1522    * If the <c>Accept</c> header is not set on the request, it will be set to the media type of this parser.
1523    *
1524    * @param parser The parser used to parse POJOs from the body of the HTTP response.
1525    *    <br>Cannot be <jk>null</jk>.
1526    * @return This object.
1527    */
1528   public RestRequest parser(Class<? extends Parser> parser) {
1529      this.parser = client.getInstance(reqnn("parser", parser));
1530      return this;
1531   }
1532
1533   /**
1534    * Specifies the parser to use on the response body.
1535    *
1536    * <p>
1537    * Overrides the parsers specified on the {@link RestClient}.
1538    *
1539    * <p>
1540    *    The parser is not modified by any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
1541    *    bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1542    *
1543    * <p>
1544    * If the <c>Accept</c> header is not set on the request, it will be set to the media type of this parser.
1545    *
1546    * @param parser The parser used to parse POJOs from the body of the HTTP response.
1547    *    <br>Cannot be <jk>null</jk>.
1548    * @return This object.
1549    */
1550   public RestRequest parser(Parser parser) {
1551      this.parser = reqnn("parser", parser);
1552      return this;
1553   }
1554
1555   /**
1556    * Sets or replaces multiple path parameters on the request.
1557    *
1558    * <h5 class='section'>Example:</h5>
1559    * <p class='bjava'>
1560    *    <jc>// Appends two path parameters to the request.</jc>
1561    *    <jv>client</jv>
1562    *       .get(<jsf>URI</jsf>)
1563    *       .pathData(
1564    *          BasicStringPart.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>),
1565    *          BasicBooleanPart.<jsm>of</jsm>(<js>"baz"</js>, <jk>true</jk>)
1566    *       )
1567    *       .run();
1568    * </p>
1569    *
1570    * @param parts
1571    *    The parameters to set.
1572    *    <jk>null</jk> values are ignored.
1573    * @return This object.
1574    */
1575   public RestRequest pathData(NameValuePair...parts) {
1576      pathData.set(parts);
1577      return this;
1578   }
1579
1580   /**
1581    * Sets or replaces a path parameter of the form <js>"{name}"</js> in the URI.
1582    *
1583    * <h5 class='section'>Example:</h5>
1584    * <p class='bjava'>
1585    *    <jc>// Sets path to "/bar".</jc>
1586    *    <jv>client</jv>
1587    *       .get(<js>"/{foo}"</js>)
1588    *       .pathData(<js>"foo"</js>, <js>"bar"</js>)
1589    *       .run();
1590    * </p>
1591    *
1592    * @param name
1593    *    The parameter name.
1594    *    <br>Cannot be <jk>null</jk> or empty (parameter will not be created).
1595    * @param value
1596    *    The parameter value.
1597    *    <br>Non-string values are converted to strings using the {@link HttpPartSerializer} defined on the client.
1598    *    <br>Can be <jk>null</jk>.
1599    * @return This object.
1600    */
1601   public RestRequest pathData(String name, Object value) {
1602      pathData.set(createPart(PATH, name, value));
1603      return this;
1604   }
1605
1606   /**
1607    * Sets multiple path parameters to the request from properties defined on a Java bean.
1608    *
1609    * <h5 class='section'>Example:</h5>
1610    * <p class='bjava'>
1611    *    <jk>public class</jk> MyPathVars {
1612    *       <jk>public</jk> String getFooBar() { <jk>return</jk> <js>"baz"</js>; }
1613    *       <jk>public</jk> Integer getQux() { <jk>return</jk> 123; }
1614    *    }
1615    *
1616    *    <jc>// Given path "/{fooBar}/{qux}/", gets converted to "/baz/123/".</jc>
1617    *    <jv>client</jv>
1618    *       .get(<jsf>URI</jsf>)
1619    *       .pathDataBean(<jk>new</jk> MyPathVars())
1620    *       .run();
1621    * </p>
1622    *
1623    * @param value The bean containing the properties to set as path parameter values.
1624    *    <br>Cannot be <jk>null</jk>.
1625    * @return This object.
1626    */
1627   public RestRequest pathDataBean(Object value) {
1628      req(isBean(reqnn("value", value)), "Object passed into pathDataBean(Object) is not a bean.");
1629      var b = pathData;
1630      toBeanMap(value).forEach((k, v) -> b.set(createPart(PATH, k, v)));
1631      return this;
1632   }
1633
1634   /**
1635    * Replaces path parameters of the form <js>"{name}"</js> in the URI using free-form key/value pairs.
1636    *
1637    * <h5 class='section'>Example:</h5>
1638    * <p class='bjava'>
1639    *    <jc>// Sets path to "/baz/qux".</jc>
1640    *    <jv>client</jv>
1641    *       .get(<js>"/{foo}/{bar}"</js>)
1642    *       .pathDataPairs(
1643    *          <js>"foo"</js>,<js>"baz"</js>,
1644    *          <js>"bar"</js>,<js>"qux"</js>
1645    *       )
1646    *       .run();
1647    * </p>
1648    *
1649    * @param pairs The path key/value pairs.
1650    *    <ul>
1651    *       <li>Values can be any POJO.
1652    *       <li>Values converted to a string using the configured part serializer.
1653    *    </ul>
1654    *    <br>Cannot be <jk>null</jk>.
1655    *    <br>Must have an even number of elements (key/value pairs).
1656    *    <br>Null keys are ignored (parameter will not be created).
1657    *    <br>Null values are allowed.
1658    * @return This object.
1659    */
1660   public RestRequest pathDataPairs(String...pairs) {
1661      req(pairs.length % 2 == 0, "Odd number of parameters passed into pathDataPairs(String...)");
1662      var b = pathData;
1663      for (var i = 0; i < pairs.length; i += 2)
1664         b.set(pairs[i], pairs[i + 1]);
1665      return this;
1666   }
1667
1668   /**
1669    * Convenience method for specifying Plain Text as the marshalling transmission media type for this request only.
1670    *
1671    * <p>
1672    * Plain text marshalling typically only works on simple POJOs that can be converted to and from strings using
1673    * swaps, swap methods, etc...
1674    *
1675    * <p>
1676    *    {@link PlainTextSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
1677    *    <ul>
1678    *       <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
1679    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1680    *    </ul>
1681    * <p>
1682    *    {@link PlainTextParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
1683    *    <ul>
1684    *       <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
1685    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
1686    *    </ul>
1687    * <p>
1688    *    <c>Accept</c> request header will be set to <js>"text/plain"</js> unless overridden
1689    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}.
1690    * <p>
1691    *    <c>Content-Type</c> request header will be set to <js>"text/plain"</js> unless overridden
1692    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}.
1693    * <p>
1694    *    Identical to calling <c>serializer(PlainTextSerializer.<jk>class</jk>).parser(PlainTextParser.<jk>class</jk>)</c>.
1695    *
1696    * @return This object.
1697    */
1698   public RestRequest plainText() {
1699      return serializer(PlainTextSerializer.class).parser(PlainTextParser.class);
1700   }
1701
1702   /**
1703    * Sets the {@code X-Juneau-Serializer-Options} header for this request.
1704    *
1705    * <p>
1706    * The value is a JSON5 object whose keys are serializer session property names and values are the overrides.
1707    * Example: <js>"{escapeSolidus:true,maxIndent:4}"</js>.
1708    *
1709    * @param json5
1710    *    Serializer session options as JSON5. Can be <jk>null</jk> (header is not set).
1711    * @return This object.
1712    */
1713   public RestRequest serializerSessionOptionsHeader(String json5) {
1714      return header(HEADER_JuneauSerializerOptions, json5);
1715   }
1716
1717   /**
1718    * Sets the {@code X-Juneau-Parser-Options} header for this request.
1719    *
1720    * <p>
1721    * The value is a JSON5 object whose keys are parser session property names and values are the overrides.
1722    *
1723    * @param json5
1724    *    Parser session options as JSON5. Can be <jk>null</jk> (header is not set).
1725    * @return This object.
1726    */
1727   public RestRequest parserSessionOptionsHeader(String json5) {
1728      return header(HEADER_JuneauParserOptions, json5);
1729   }
1730
1731   /**
1732    * Convenience for {@link #serializerSessionOptionsHeader(String)} using a map serialized with {@link Json5#of(Object)}.
1733    *
1734    * @param properties
1735    *    Property map for serializer session options. Can be <jk>null</jk> (treated as empty).
1736    * @return This object.
1737    * @throws SerializeException If serializing {@code properties} to JSON5 fails.
1738    */
1739   public RestRequest serializerSessionOptionsHeader(Map<String,?> properties) {
1740      if (isEmpty(properties))
1741         return this;
1742      return header(HEADER_JuneauSerializerOptions, Json5.of(properties));
1743   }
1744
1745   /**
1746    * Convenience for {@link #parserSessionOptionsHeader(String)} using a map serialized with {@link Json5#of(Object)}.
1747    *
1748    * @param properties
1749    *    Property map for parser session options. Can be <jk>null</jk> (treated as empty).
1750    * @return This object.
1751    * @throws SerializeException If serializing {@code properties} to JSON5 fails.
1752    */
1753   public RestRequest parserSessionOptionsHeader(Map<String,?> properties) {
1754      if (isEmpty(properties))
1755         return this;
1756      return header(HEADER_JuneauParserOptions, Json5.of(properties));
1757   }
1758
1759   /**
1760    * Sets the {@code juneauSerializerOptions} query parameter for this request (UON-encoded map).
1761    *
1762    * <p>
1763    * Example: <js>"(escapeSolidus=true,maxIndent=4)"</js>.
1764    *
1765    * @param uon
1766    *    Serializer session options in UON form. Can be <jk>null</jk> (parameter is not set).
1767    * @return This object.
1768    */
1769   public RestRequest serializerSessionOptionsQuery(String uon) {
1770      return queryData(QUERY_juneauSerializerOptions, uon);
1771   }
1772
1773   /**
1774    * Sets the {@code juneauParserOptions} query parameter for this request (UON-encoded map).
1775    *
1776    * @param uon
1777    *    Parser session options in UON form. Can be <jk>null</jk> (parameter is not set).
1778    * @return This object.
1779    */
1780   public RestRequest parserSessionOptionsQuery(String uon) {
1781      return queryData(QUERY_juneauParserOptions, uon);
1782   }
1783
1784   /**
1785    * Convenience for {@link #serializerSessionOptionsQuery(String)} using {@link Uon#of(Object)}.
1786    *
1787    * @param properties
1788    *    Property map for serializer session options. Can be <jk>null</jk> (treated as empty).
1789    * @return This object.
1790    * @throws SerializeException If serializing {@code properties} to UON fails.
1791    */
1792   public RestRequest serializerSessionOptionsQuery(Map<String,?> properties) {
1793      if (isEmpty(properties))
1794         return this;
1795      return serializerSessionOptionsQuery(Uon.of(properties));
1796   }
1797
1798   /**
1799    * Convenience for {@link #parserSessionOptionsQuery(String)} using {@link Uon#of(Object)}.
1800    *
1801    * @param properties
1802    *    Property map for parser session options. Can be <jk>null</jk> (treated as empty).
1803    * @return This object.
1804    * @throws SerializeException If serializing {@code properties} to UON fails.
1805    */
1806   public RestRequest parserSessionOptionsQuery(Map<String,?> properties) {
1807      if (isEmpty(properties))
1808         return this;
1809      return parserSessionOptionsQuery(Uon.of(properties));
1810   }
1811
1812   /**
1813    * Sets the protocol version for this request.
1814    *
1815    * @param version The protocol version for this request.
1816    *    <br>Can be <jk>null</jk> (default protocol version will be used).
1817    * @return This object.
1818    */
1819   public RestRequest protocolVersion(ProtocolVersion version) {
1820      request.setProtocolVersion(version);
1821      return this;
1822   }
1823
1824   /**
1825    * Adds a free-form custom query.
1826    *
1827    * <h5 class='section'>Example:</h5>
1828    * <p class='bjava'>
1829    *    <jc>// Adds query parameter "foo=bar&amp;baz=qux".</jc>
1830    *    <jv>client</jv>
1831    *       .get(<jsf>URI</jsf>)
1832    *       .queryCustom(<js>"foo=bar&amp;baz=qux"</js>)
1833    *       .run();
1834    * </p>
1835    *
1836    * @param value The parameter value.
1837    *    <br>Can be any of the following types:
1838    *    <ul>
1839    *       <li>
1840    *          {@link CharSequence}
1841    *       <li>
1842    *          {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource.
1843    *       <li>
1844    *          {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource.
1845    *       <li>
1846    *          {@link PartList} - Converted to a URL-encoded query.
1847    *    </ul>
1848    *    <br>Can be <jk>null</jk> (no custom query will be set).
1849    * @return This object.
1850    */
1851   public RestRequest queryCustom(Object value) {
1852      try {
1853         String q = null;
1854         if (value instanceof Reader value2)
1855            q = read(value2);
1856         else if (value instanceof InputStream value2)
1857            q = read(value2);
1858         else
1859            q = s(value);  // Works for NameValuePairs.
1860         uriBuilder.setCustomQuery(q);
1861      } catch (IOException e) {
1862         throw rex(e, "Could not read custom query.");
1863      }
1864      return this;
1865   }
1866
1867   /**
1868    * Appends multiple query parameters to the request.
1869    *
1870    * <h5 class='section'>Example:</h5>
1871    * <p class='bjava'>
1872    *    <jc>// Appends two query parameters to the request.</jc>
1873    *    <jv>client</jv>
1874    *       .get(<jsf>URI</jsf>)
1875    *       .queryData(
1876    *          BasicStringPart.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>),
1877    *          BasicBooleanPart.<jsm>of</jsm>(<js>"baz"</js>, <jk>true</jk>)
1878    *       )
1879    *       .run();
1880    * </p>
1881    *
1882    * @param parts
1883    *    The parameters to set.
1884    *    <jk>null</jk> values are ignored.
1885    * @return This object.
1886    */
1887   public RestRequest queryData(NameValuePair...parts) {
1888      queryData.append(parts);
1889      return this;
1890   }
1891
1892   /**
1893    * Appends a query parameter to the URI of the request.
1894    *
1895    * <h5 class='section'>Example:</h5>
1896    * <p class='bjava'>
1897    *    <jc>// Adds query parameter "foo=bar".</jc>
1898    *    <jv>client</jv>
1899    *       .get(<jsf>URI</jsf>)
1900    *       .queryData(<js>"foo"</js>, <js>"bar"</js>)
1901    *       .run();
1902    * </p>
1903    *
1904    * @param name
1905    *    The parameter name.
1906    *    <br>Cannot be <jk>null</jk> or empty (parameter will not be created).
1907    * @param value
1908    *    The parameter value.
1909    *    <br>Non-string values are converted to strings using the {@link HttpPartSerializer} defined on the client.
1910    *    <br>Can be <jk>null</jk>.
1911    * @return This object.
1912    */
1913   public RestRequest queryData(String name, Object value) {
1914      queryData.append(createPart(QUERY, name, value));
1915      return this;
1916   }
1917
1918   /**
1919    * Appends multiple query parameters to the request from properties defined on a Java bean.
1920    *
1921    * <h5 class='section'>Example:</h5>
1922    * <p class='bjava'>
1923    *    <jk>public class</jk> MyQuery {
1924    *       <jk>public</jk> String getFooBar() { <jk>return</jk> <js>"baz"</js>; }
1925    *       <jk>public</jk> Integer getQux() { <jk>return</jk> 123; }
1926    *    }
1927    *
1928    *    <jc>// Appends query "fooBar=baz&amp;qux=123".</jc>
1929    *    <jv>client</jv>
1930    *       .get(<jsf>URI</jsf>)
1931    *       .queryDataBean(<jk>new</jk> MyQuery())
1932    *       .run();
1933    * </p>
1934    *
1935    * @param value The bean containing the properties to set as query parameter values.
1936    *    <br>Cannot be <jk>null</jk>.
1937    * @return This object.
1938    */
1939   public RestRequest queryDataBean(Object value) {
1940      req(isBean(reqnn("value", value)), "Object passed into queryDataBean(Object) is not a bean.");
1941      var b = queryData;
1942      toBeanMap(value).forEach((k, v) -> b.append(createPart(QUERY, k, v)));
1943      return this;
1944   }
1945
1946   /**
1947    * Adds query parameters to the URI query using free-form key/value pairs..
1948    *
1949    * <h5 class='section'>Example:</h5>
1950    * <p class='bjava'>
1951    *    <jc>// Adds query parameters "foo=bar&amp;baz=qux".</jc>
1952    *    <jv>client</jv>
1953    *       .get(<jsf>URI</jsf>)
1954    *       .queryDataPairs(<js>"foo"</js>,<js>"bar"</js>,<js>"baz"</js>,<js>"qux"</js>)
1955    *       .run();
1956    * </p>
1957    *
1958    * @param pairs The query key/value pairs.
1959    *    <ul>
1960    *       <li>Values can be any POJO.
1961    *       <li>Values converted to a string using the configured part serializer.
1962    *    </ul>
1963    *    <br>Cannot be <jk>null</jk>.
1964    *    <br>Must have an even number of elements (key/value pairs).
1965    *    <br>Null keys are ignored (parameter will not be created).
1966    *    <br>Null values are allowed.
1967    * @return This object.
1968    * @throws RestCallException Invalid input.
1969    */
1970   public RestRequest queryDataPairs(String...pairs) throws RestCallException {
1971      req(pairs.length % 2 == 0, "Odd number of parameters passed into queryDataPairs(String...)");
1972      var b = queryData;
1973      for (var i = 0; i < pairs.length; i += 2)
1974         b.append(pairs[i], pairs[i + 1]);
1975      return this;
1976   }
1977
1978   /**
1979    * Removes a header from this message.
1980    *
1981    * @param header The header to remove.
1982    *    <br>Can be <jk>null</jk> (no-op).
1983    */
1984   @Override /* Overridden from HttpMessage */
1985   public void removeHeader(Header header) {
1986      headerData.remove(header);
1987   }
1988
1989   /**
1990    * Removes all headers with a certain name from this message.
1991    *
1992    * @param name The name of the headers to remove.
1993    *    <br>Cannot be <jk>null</jk>.
1994    */
1995   @Override /* Overridden from HttpMessage */
1996   public void removeHeaders(String name) {
1997      headerData.remove(name);
1998   }
1999
2000   /**
2001    * Rethrow any of the specified exception types if a matching <c>Exception-Name</c> header is found.
2002    *
2003    * <p>
2004    * Rethrown exceptions will be set on the caused-by exception of {@link RestCallException} when
2005    * thrown from the {@link #run()} method.
2006    *
2007    * <p>
2008    * Can be called multiple times to append multiple rethrows.
2009    *
2010    * @param values The classes to rethrow.
2011    *    <br>Cannot be <jk>null</jk>.
2012    *    <br>Null elements are ignored (only non-null classes that extend {@link Throwable} are added).
2013    * @return This object.
2014    */
2015   public RestRequest rethrow(Class<?>...values) {
2016      if (rethrow == null)
2017         rethrow = list();
2018      for (var v : values) {
2019         if (nn(v) && Throwable.class.isAssignableFrom(v))
2020            rethrow.add((Class<? extends Throwable>)v);
2021      }
2022      return this;
2023   }
2024
2025   /**
2026    * Runs this request and returns the resulting response object.
2027    *
2028    * <h5 class='section'>Example:</h5>
2029    * <p class='bjava'>
2030    *    <jk>try</jk> {
2031    *       <jk>int</jk> <jv>rc</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).execute().getResponseStatus();
2032    *       <jc>// Succeeded!</jc>
2033    *    } <jk>catch</jk> (RestCallException <jv>e</jv>) {
2034    *       <jc>// Failed!</jc>
2035    *    }
2036    * </p>
2037    *
2038    * <h5 class='section'>Notes:</h5><ul>
2039    *    <li class='note'>Calling this method multiple times will return the same original response object.
2040    *    <li class='note'>You must close the returned object if you do not consume the response or execute a method that consumes
2041    *       the response.
2042    *    <li class='note'>If you are only interested in the response code, use the {@link #complete()} method which will automatically
2043    *       consume the response so that you don't need to call {@link InputStream#close()} on the response body.
2044    * </ul>
2045    *
2046    * @return The response object.
2047    * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt.
2048    */
2049   @SuppressWarnings({
2050      "java:S3776", // Cognitive complexity acceptable for this specific logic
2051      "java:S6541", // Single-threaded context; synchronization unnecessary
2052      "null" // Null handling verified by context or framework
2053   })
2054   public RestResponse run() throws RestCallException {
2055      if (nn(response))
2056         throw new RestCallException(response, null, "run() already called.");
2057
2058      try {
2059
2060         queryData.stream().map(SimpleQuery::new).filter(SimplePart::isValid).forEach(x -> uriBuilder.addParameter(x.name, x.value));
2061
2062         pathData.stream().map(SimplePath::new).forEach(x -> {
2063            var path = uriBuilder.getPath();
2064            var name = x.name;
2065            var value = x.value;
2066            var pathVar = "{" + name + "}";
2067            if (path.indexOf(pathVar) == -1 && neq(name, "/*"))
2068               throw isex("Path variable {%s} was not found in path.", name);
2069            if (eq(name, "/*"))
2070               path = path.replaceAll("\\/\\*$", URI_PATH_SEPARATOR + value);
2071            else
2072               path = path.replace(pathVar, String.valueOf(value));
2073            uriBuilder.setPath(path);
2074         });
2075
2076         HttpEntityEnclosingRequestBase request2 = request instanceof HttpEntityEnclosingRequestBase request3 ? request3 : null;
2077         request.setURI(uriBuilder.build());
2078
2079         // Pick the serializer if it hasn't been overridden.
2080         var hl = headerData;
2081         var h = hl.getLast(HEADER_CONTENT_TYPE);
2082         var contentType = h.isPresent() ? h.get().getValue() : null;
2083         var serializer2 = this.serializer;
2084         if (serializer2 == null)
2085            serializer2 = client.getMatchingSerializer(contentType);
2086         if (contentType == null && nn(serializer2))
2087            contentType = serializer2.getPrimaryMediaType().toString();
2088
2089         // Pick the parser if it hasn't been overridden.
2090         h = hl.getLast(HEADER_ACCEPT);
2091         var accept = h.isPresent() ? h.get().getValue() : null;
2092         var parser2 = this.parser;
2093         if (parser2 == null)
2094            parser2 = client.getMatchingParser(accept);
2095         if (accept == null && nn(parser2))
2096            hl.set(Accept.of(parser2.getPrimaryMediaType()));
2097
2098         headerData.stream().map(SimpleHeader::new).filter(SimplePart::isValid).forEach(request::addHeader);
2099
2100         if (request2 == null && content != NO_BODY)
2101            throw new RestCallException(null, null, "Method does not support content entity.  Method=%s, URI=%s", getMethod(), getURI());
2102
2103         if (nn(request2)) {
2104
2105            Object input2 = null;
2106            if (content != NO_BODY) {
2107               input2 = content;
2108            } else {
2109               input2 = new UrlEncodedFormEntity(formData.stream().map(SimpleFormData::new).filter(SimplePart::isValid).toList());
2110            }
2111
2112            if (input2 instanceof Supplier<?> s)
2113               input2 = s.get();
2114
2115            HttpEntity entity = null;
2116            if (input2 instanceof PartList input3)
2117               entity = new UrlEncodedFormEntity(input3.stream().map(SimpleFormData::new).filter(SimplePart::isValid).toList());
2118            else if (input2 instanceof HttpResource input3) {
2119               input3.getHeaders().forEach(request::addHeader);
2120               entity = (HttpEntity)input2;
2121            } else if (input2 instanceof HttpEntity input3) {
2122               if (input3 instanceof SerializedEntity input4) {
2123                  entity = input4.copyWith(serializer2, contentSchema);
2124               } else {
2125                  entity = input3;
2126               }
2127            } else if (input2 instanceof Reader input3)
2128               entity = readerEntity(input3, getRequestContentType(TEXT_PLAIN));
2129            else if (input2 instanceof InputStream input3)
2130               entity = streamEntity(input3, -1, getRequestContentType(ContentType.APPLICATION_OCTET_STREAM));
2131            else if (input2 instanceof RecordStreamBody input3)
2132               entity = streamBodyEntity(input3, serializer2);
2133            else if (nn(serializer2))
2134               entity = serializedEntity(input2, serializer2, contentSchema).setContentType(contentType);
2135            else {
2136               if (client.hasSerializers()) {
2137                  if (contentType == null)
2138                     throw new RestCallException(null, null,
2139                        "Content-Type not specified on request.  Cannot match correct serializer.  Use contentType(String) or mediaType(String) to specify transport language.");
2140                  throw new RestCallException(null, null, "No matching serializer for media type '%s'", contentType);
2141               }
2142               entity = stringEntity(input2 == null ? "" : MarshallingContext.DEFAULT.getClassMetaForObject(input2).toString(input2), getRequestContentType(TEXT_PLAIN));
2143            }
2144
2145            request2.setEntity(entity);
2146         }
2147
2148         if (RemoteUrlPolicyState.isActive())
2149            ssrfGuarded = true;
2150         var httpResponse = ssrfGuarded ? runWithPolicyRedirects() : client.run(target, request, context);
2151         response = client.createResponse(this, httpResponse, parser2);
2152
2153         if (isDebug() || client.logRequests == DetailLevel.FULL)
2154            response.cacheContent();
2155
2156         for (var rci : interceptors)
2157            rci.onConnect(this, response);
2158         client.onCallConnect(this, response);
2159
2160         var method = getMethod();
2161         var sc = response.getStatusCode();
2162
2163         var thrown = response.getHeader("Thrown").asHeader(Thrown.class);
2164         if (thrown.isPresent() && nn(rethrow)) {
2165            var partsOpt = thrown.asParts();
2166            if (!partsOpt.isPresent() || partsOpt.get().isEmpty())
2167               return response;
2168            var thrownPart = partsOpt.get().get(0);
2169            var className = thrownPart.getClassName();
2170            var message = thrownPart.getMessage();
2171            for (var t : rethrow) {
2172               if (t.getName().equals(className)) {
2173                  ConstructorInfo c = null;
2174                  var ci = ClassInfo.of(t);
2175                  Throwable thrownInstance = null;
2176                  c = ci.getPublicConstructor(x -> x.hasParameterTypes(HttpResponse.class)).orElse(null);
2177                  if (nn(c))
2178                     thrownInstance = newThrownFromResponse(c);
2179                  if (thrownInstance == null) {
2180                     c = ci.getPublicConstructor(x -> x.hasParameterTypes(String.class)).orElse(null);
2181                     if (nn(c)) {
2182                        thrownInstance = c.<Throwable>newInstance(nn(message) ? message : response.getContent().asString());
2183                     } else {
2184                        c = ci.getPublicConstructor(x -> x.hasParameterTypes(String.class, Throwable.class)).orElse(null);
2185                        if (nn(c)) {
2186                           thrownInstance = c.<Throwable>newInstance(nn(message) ? message : response.getContent().asString(), null);
2187                        } else {
2188                           c = ci.getPublicConstructor(cons -> cons.getParameterCount() == 0).orElse(null);
2189                           if (nn(c)) {
2190                              thrownInstance = c.<Throwable>newInstance();
2191                           }
2192                        }
2193                     }
2194                  }
2195                  if (nn(thrownInstance)) {
2196                     if (thrownInstance instanceof Exception thrownInstance2)
2197                        throw thrownInstance2;
2198                     throw new RestCallException(response, thrownInstance, "Server threw non-Exception: %s", className);
2199                  }
2200               }
2201            }
2202         }
2203
2204         if (errorCodes.test(sc) && ! ignoreErrors) {
2205            // Surface the server-reported exception detail from the Thrown header (authoritative and independent of the response body) so callers still see the original failure detail.
2206            throw new RestCallException(response, null, "HTTP method '%s' call to '%s' caused response code '%s, %s'.\nResponse: \n%s%s", method, getURI(), sc, response.getReasonPhrase(),
2207               response.getContent().asAbbreviatedString(1000), getThrownDetailSuffix(thrown));
2208         }
2209
2210      } catch (RuntimeException | RestCallException e) {
2211         if (nn(response))
2212            response.close();
2213         throw e;
2214      } catch (Exception e) {
2215         if (nn(response))
2216            response.close();
2217         throw new RestCallException(response, e, "Call failed.");
2218      }
2219
2220      return this.response;
2221   }
2222
2223   // -----------------------------------------------------------------------------------------------------------------
2224   // SSRF-guard redirect revalidation (active only for a guarded request -- see SsrfGuardMarker)
2225   // -----------------------------------------------------------------------------------------------------------------
2226
2227   // Marks this as the request of an SSRF-guard-active @Remote call.
2228   void ssrfGuard() {
2229      ssrfGuarded = true;
2230   }
2231
2232   // Whether this is the request of an SSRF-guard-active @Remote call.
2233   boolean isSsrfGuarded() { return ssrfGuarded; }
2234
2235   /**
2236    * Runs this request with Apache HttpClient's own automatic redirect-following disabled (see
2237    * {@code PolicyAwareRedirectStrategy}) and instead follows {@code 3xx} hops in a Juneau-controlled loop that
2238    * re-validates the {@code Location} of every hop against {@link RemoteUrlPolicy} &mdash; and, since every hop is
2239    * sent with the {@code SsrfGuardMarker} on its context, every hop leases its connection from the client's dedicated
2240    * pinned pool ({@code SsrfGuardConnectionManager}), which pin-on-connects each hop's resolved address and never hands
2241    * out a connection an ordinary request opened, on whichever thread the client runs it.  Only called for a guarded
2242    * request, i.e. for a {@code @Remote} call with the SSRF guard in effect.
2243    */
2244   private HttpResponse runWithPolicyRedirects() throws IOException {
2245      // A context set with context(...) carries the marker, and the redirect credential guard's origin, for the call only.
2246      var previousMarker = nn(context) ? SsrfGuardMarker.mark(context) : null;
2247      var previousOrigin = nn(context) ? ClassicRedirectCredentialGuard.origin(context) : null;
2248      try {
2249         return runWithPolicyRedirects2();
2250      } finally {
2251         if (nn(context)) {
2252            context.setAttribute(SsrfGuardMarker.ATTRIBUTE, previousMarker);
2253            ClassicRedirectCredentialGuard.origin(context, previousOrigin);
2254         }
2255      }
2256   }
2257
2258   private HttpResponse runWithPolicyRedirects2() throws IOException {
2259      var currentTarget = target;
2260      HttpRequestBase currentRequest = request;
2261      URI firstOrigin = null;
2262      for (var hop = 0; hop < RemoteUrlPolicy.MAX_REDIRECT_HOPS; hop++) {
2263         // Hop 0's URI was already validated (or is the operator-configured client root, which the resolution
2264         // path intentionally does not re-check -- see RemoteUrlPolicy.requireAllowedUrl) before this request
2265         // was built; only a redirect Location -- a URL discovered at runtime -- needs a fresh pre-check here.
2266         if (hop > 0)
2267            requirePolicyAllowed(currentRequest.getURI());
2268         // Without a context(...) setting each hop gets a fresh context, as Apache HttpClient gives one to a request
2269         // sent with none: sharing one would carry the authentication state of a hop to the next host.
2270         var hopContext = context;
2271         if (hopContext == null) {
2272            hopContext = new BasicHttpContext();
2273            SsrfGuardMarker.mark(hopContext);
2274         }
2275         // The redirect credential guard compares every hop with the first hop's origin, as it does on the one context
2276         // of a redirect Apache HttpClient follows itself, and strips what the client's interceptors and default headers
2277         // add on another origin.  On hop 0 this clears an origin an earlier call left on a context(...) setting.
2278         ClassicRedirectCredentialGuard.origin(hopContext, firstOrigin);
2279         var httpResponse = client.run(currentTarget, currentRequest, hopContext);
2280         if (hop == 0)
2281            firstOrigin = ClassicRedirectCredentialGuard.origin(hopContext);
2282         var sc = httpResponse.getStatusLine().getStatusCode();
2283         if (! isRedirectStatus(sc))
2284            return httpResponse;
2285         var locationHeader = httpResponse.getFirstHeader("Location");
2286         EntityUtils.consumeQuietly(httpResponse.getEntity());
2287         if (locationHeader == null)
2288            throw new ClientProtocolException("Redirect response (status " + sc + ") is missing a Location header");
2289         URI target2;
2290         try {
2291            target2 = RemoteUrlPolicy.resolveRedirectLocation(currentRequest.getURI(), locationHeader.getValue());
2292         } catch (IllegalArgumentException e) {
2293            throw new ClientProtocolException(e.getMessage(), e);
2294         }
2295         // On a context(...) setting shared by all hops, the authentication state of the target is reset on a hop to
2296         // another origin, as Apache HttpClient's own redirect handling resets it on a change of host: otherwise the
2297         // credentials that answered one host's challenge are sent unasked to the next.  The credentials provider is
2298         // still asked per AuthScope, and the auth cache is keyed by host, so neither carries them across.
2299         if (nn(context) && crossesOrigin(currentRequest.getURI(), target2))
2300            resetTargetAuthState(context);
2301         currentRequest = buildRedirectRequest(currentRequest, target2, sc);
2302         currentTarget = null; // Recomputed by the connection route planner from the request's own absolute URI.
2303      }
2304      throw new ClientProtocolException("Redirect limit exceeded (" + RemoteUrlPolicy.MAX_REDIRECT_HOPS
2305         + " hops) while following a policy-covered @Remote redirect chain, starting at: " + request.getURI());
2306   }
2307
2308   private void requirePolicyAllowed(URI uri) throws ClientProtocolException {
2309      try {
2310         RemoteUrlPolicy.requireAllowedUrl(uri.toString(), false, client.getAllowedAddressRanges());
2311      } catch (IllegalArgumentException e) {
2312         throw new ClientProtocolException(e.getMessage(), e);
2313      }
2314   }
2315
2316   private static boolean isRedirectStatus(int statusCode) {
2317      return statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307 || statusCode == 308;
2318   }
2319
2320   /**
2321    * Constructs the server-thrown exception through its {@code (HttpResponse)} constructor.
2322    *
2323    * @param c The constructor.
2324    * @return The instance, or <jk>null</jk> if the constructor rejected the response's status code.
2325    */
2326   private Throwable newThrownFromResponse(ConstructorInfo c) {
2327      try {
2328         return c.<Throwable>newInstance(response);
2329      } catch (ExecutableException e) {
2330         // Only a status-code rejection (IllegalArgumentException from the Xxx(HttpResponse) status check) falls through to the remaining constructors; anything else propagates unchanged.
2331         if (! (e.getCause() instanceof IllegalArgumentException))
2332            throw e;
2333         return null;
2334      }
2335   }
2336
2337   /**
2338    * Builds the request for the next hop of a policy-covered redirect chain, following conventional {@code 3xx}
2339    * semantics: {@code 301}/{@code 302}/{@code 303} rewrite the method to {@code GET} with no body unless the
2340    * original method was already {@code GET}/{@code HEAD}; {@code 307}/{@code 308} preserve the original method
2341    * and entity. A non-{@linkplain HttpEntity#isRepeatable() repeatable} entity cannot be safely re-sent, so such a
2342    * redirect is refused (fail closed) rather than risking a corrupt or partial replay.
2343    *
2344    * <p>
2345    * A hop to another origin (or an {@code https}&rarr;{@code http} downgrade) drops the caller-set credential headers
2346    * ({@link RedirectSecurity#stripOnCrossOrigin()}), as {@code ClassicRedirectCredentialGuard} does for a redirect
2347    * Apache HttpClient follows itself: that guard keys the first origin on the context, and here each hop may have a
2348    * context of its own.  Once dropped they stay dropped, even if a later hop comes back to the first origin.
2349    */
2350   private HttpRequestBase buildRedirectRequest(HttpRequestBase current, URI target2, int statusCode) throws ClientProtocolException {
2351      var method = current.getMethod();
2352      var preserveMethod = eqic(method, "GET") || eqic(method, "HEAD");
2353      var rewriteToGet = ! preserveMethod && (statusCode == 301 || statusCode == 302 || statusCode == 303);
2354      var entity = (! rewriteToGet && current instanceof HttpEntityEnclosingRequestBase e) ? e.getEntity() : null;
2355
2356      HttpRequestBase next;
2357      if (entity != null) {
2358         if (! entity.isRepeatable())
2359            throw new ClientProtocolException("Redirect (status " + statusCode + ") requires re-sending a "
2360               + "non-repeatable request body, which cannot be safely replayed; refusing to follow (fail "
2361               + "closed) for a policy-covered @Remote redirect chain: " + target2);
2362         var next2 = new BasicHttpEntityRequestBase(this, method);
2363         next2.setEntity(entity);
2364         next = next2;
2365      } else {
2366         next = new BasicHttpRequestBase(this, rewriteToGet ? "GET" : method);
2367      }
2368      next.setURI(target2);
2369      var strip = crossesOrigin(current.getURI(), target2);
2370      for (var h : current.getAllHeaders())
2371         if (! (rewriteToGet && (eqic(h.getName(), "Content-Length") || eqic(h.getName(), HEADER_CONTENT_TYPE))) && ! (strip && isCredentialHeader(h.getName())))
2372            next.addHeader(h);
2373      return next;
2374   }
2375
2376   // Whether a hop from one URI to the other leaves the origin, or cannot be told not to (either is not absolute).
2377   private static boolean crossesOrigin(URI from, URI to) {
2378      return from.getScheme() == null || from.getHost() == null || to.getScheme() == null || to.getHost() == null
2379         || RedirectSecurity.shouldStripCredentials(from, to);
2380   }
2381
2382   // The shared RedirectSecurity set, plus Proxy-Authorization whatever that set holds: a guarded call never goes
2383   // through a proxy (one is refused), so on any hop it could only be a credential sent to the target itself.
2384   // Defense in depth: the shared set holds Proxy-Authorization today, so no test can tell this pin from it.
2385   private static boolean isCredentialHeader(String name) {
2386      return eqic(name, "Proxy-Authorization") || RedirectSecurity.stripOnCrossOrigin().stream().anyMatch(x -> eqic(x, name));
2387   }
2388
2389   private static void resetTargetAuthState(HttpContext context) {
2390      var state = HttpClientContext.adapt(context).getTargetAuthState();
2391      if (nn(state))
2392         state.reset();
2393   }
2394
2395   /**
2396    * Returns a message suffix describing the first entry of the given {@link Thrown} header, if present and
2397    * non-blank, or an empty string otherwise.
2398    *
2399    * @param thrown The parsed {@code Thrown} response header.
2400    * @return A <js>"\nThrown: &lt;className&gt;: &lt;message&gt;"</js> suffix, or an empty string.
2401    */
2402   private static String getThrownDetailSuffix(Thrown thrown) {
2403      if (thrown.isPresent()) {
2404         var partsOpt = thrown.asParts();
2405         if (partsOpt.isPresent() && ! partsOpt.get().isEmpty()) {
2406            var part = partsOpt.get().get(0);
2407            if (inb(part.getClassName()) && inb(part.getMessage()))
2408               return "\nThrown: " + part.getClassName() + ": " + part.getMessage();
2409         }
2410      }
2411      return "";
2412   }
2413
2414   /**
2415    * Same as {@link #run()} but allows you to run the call asynchronously.
2416    *
2417    * <h5 class='section'>Example:</h5>
2418    * <p class='bjava'>
2419    *    Future&lt;RestResponse&gt; <jv>future</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).runFuture();
2420    *
2421    *    <jc>// Do some other stuff</jc>
2422    *
2423    *    <jk>try</jk> {
2424    *       String <jv>body</jv> = <jv>future</jv>.get().getContent().asString();
2425    *       <jc>// Succeeded!</jc>
2426    *    } <jk>catch</jk> (RestCallException <jv>e</jv>) {
2427    *       <jc>// Failed!</jc>
2428    *    }
2429    * </p>
2430    *
2431    * <h5 class='section'>Notes:</h5><ul>
2432    *    <li class='note'>Use the {@link RestClient.Builder#executorService(ExecutorService, boolean)} method to customize the
2433    *       executor service used for creating {@link Future Futures}.
2434    * </ul>
2435    *
2436    * @return The HTTP status code.
2437    * @throws RestCallException If the executor service was not defined.
2438    */
2439   public Future<RestResponse> runFuture() throws RestCallException {
2440      return client.getExecutorService().submit(this::run);
2441   }
2442
2443   /**
2444    * Specifies the serializer to use on the request body.
2445    *
2446    * <p>
2447    * Overrides the serializers specified on the {@link RestClient}.
2448    *
2449    * <p>
2450    *    The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
2451    *    bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
2452    *
2453    * <p>
2454    * If the <c>Content-Type</c> header is not set on the request, it will be set to the media type of this serializer.
2455    *
2456    * @param serializer The serializer used to serialize POJOs to the body of the HTTP request.
2457    *    <br>Can be <jk>null</jk> (will use default serializer matching Content-Type).
2458    * @return This object.
2459    */
2460   public RestRequest serializer(Class<? extends Serializer> serializer) {
2461      this.serializer = serializer == null ? null : client.getInstance(serializer);
2462      return this;
2463   }
2464
2465   /**
2466    * Specifies the serializer to use on the request body.
2467    *
2468    * <p>
2469    * Overrides the serializers specified on the {@link RestClient}.
2470    *
2471    * <p>
2472    *    The serializer is not modified by an of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
2473    *    bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
2474    *
2475    * <p>
2476    * If the <c>Content-Type</c> header is not set on the request, it will be set to the media type of this serializer.
2477    *
2478    * @param serializer The serializer used to serialize POJOs to the body of the HTTP request.
2479    *    <br>Can be <jk>null</jk> (will use default serializer matching Content-Type).
2480    * @return This object.
2481    */
2482   public RestRequest serializer(Serializer serializer) {
2483      this.serializer = serializer;
2484      return this;
2485   }
2486
2487   /**
2488    * Overwrites the first header with the same name.
2489    *
2490    * The new header will be appended to the end of the list, if no header with the given name can be found.
2491    *
2492    * @param header The header to set.
2493    *    <br>Can be <jk>null</jk> (ignored).
2494    */
2495   @Override /* Overridden from HttpMessage */
2496   public void setHeader(Header header) {
2497      headerData.set(header);
2498   }
2499
2500   /**
2501    * Overwrites the first header with the same name.
2502    *
2503    * The new header will be appended to the end of the list, if no header with the given name can be found.
2504    *
2505    * @param name The name of the header.
2506    *    <br>Cannot be <jk>null</jk> or empty (header will not be created).
2507    * @param value The value of the header.
2508    *    <br>Can be <jk>null</jk>.
2509    */
2510   @Override /* Overridden from HttpMessage */
2511   public void setHeader(String name, String value) {
2512      headerData.set(stringHeader(name, value));
2513   }
2514
2515   /**
2516    * Overwrites all the headers in the message.
2517    *
2518    * @param headers The array of headers to set.
2519    *    <br>Can be <jk>null</jk> (all headers will be cleared).
2520    *    <br>Can contain <jk>null</jk> values (ignored).
2521    */
2522   @Override /* Overridden from HttpMessage */
2523   public void setHeaders(Header[] headers) {
2524      headerData.set(headers);
2525   }
2526
2527   /**
2528    * Provides parameters to be used for the processing of this message.
2529    *
2530    * @param params The parameters.
2531    *    <br>Can be <jk>null</jk> (default parameters will be used).
2532    * @deprecated Use constructor parameters of configuration API provided by HttpClient.
2533    */
2534   @Override /* Overridden from HttpMessage */
2535   @Deprecated(since = "10.0", forRemoval = true)
2536   public void setParams(HttpParams params) {
2537      request.setParams(params);
2538   }
2539
2540   /**
2541    * Causes logging to be suppressed for the duration of this request.
2542    *
2543    * <p>
2544    * Overrides the {@link #debug()} setting on this request or client.
2545    *
2546    * @return This object.
2547    */
2548   public RestRequest suppressLogging() {
2549      this.suppressLogging = true;
2550      return this;
2551   }
2552
2553   /**
2554    * Specifies the target host for the request.
2555    *
2556    * @param target The target host for the request.
2557    *    Implementations may accept <jk>null</jk> if they can still determine a route, for example to a default
2558    *    target or by inspecting the request.
2559    * @return This object.
2560    */
2561   public RestRequest target(HttpHost target) {
2562      this.target = target;
2563      return this;
2564   }
2565
2566   /**
2567    * Convenience method for specifying UON as the marshalling transmission media type for this request only.
2568    *
2569    * <p>
2570    * UON is Url-Encoding Object notation that is equivalent to JSON but suitable for transmission as URL-encoded
2571    * query and form post values.
2572    *
2573    * <p>
2574    *    {@link UonSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
2575    *    <ul>
2576    *       <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
2577    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
2578    *    </ul>
2579    * <p>
2580    *    {@link UonParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
2581    *    <ul>
2582    *       <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
2583    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
2584    *    </ul>
2585    * <p>
2586    *    <c>Accept</c> request header will be set to <js>"text/uon"</js> unless overridden
2587    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}.
2588    * <p>
2589    *    <c>Content-Type</c> request header will be set to <js>"text/uon"</js> unless overridden
2590    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}.
2591    * <p>
2592    *    Identical to calling <c>serializer(UonSerializer.<jk>class</jk>).parser(UonParser.<jk>class</jk>)</c>.
2593    *
2594    * @return This object.
2595    */
2596   public RestRequest uon() {
2597      return serializer(UonSerializer.class).parser(UonParser.class);
2598   }
2599
2600   /**
2601    * Sets the URI for this request.
2602    *
2603    * <p>
2604    * Can be any of the following types:
2605    * <ul>
2606    *    <li>{@link URI}
2607    *    <li>{@link URL}
2608    *    <li>{@link URIBuilder}
2609    *    <li>Anything else converted to a string using {@link Object#toString()}.
2610    * </ul>
2611    *
2612    * <p>
2613    * Relative URI strings will be interpreted as relative to the root URI defined on the client.
2614    *
2615    * @param uri
2616    *    The URI of the remote REST resource.
2617    *    <br>This overrides the URI passed in from the client.
2618    *    <br>Can be any of the following types:
2619    *    <ul>
2620    *       <li>{@link URIBuilder}
2621    *       <li>{@link URI}
2622    *       <li>{@link URL}
2623    *       <li>{@link String}
2624    *       <li>{@link Object} - Converted to <c>String</c> using <c>toString()</c>
2625    *    </ul>
2626    *    <br>Cannot be <jk>null</jk>.
2627    * @return This object.
2628    * @throws RestCallException Invalid URI syntax detected.
2629    */
2630   public RestRequest uri(Object uri) throws RestCallException {
2631      var x = client.toUri(uri, null);
2632      if (nn(x.getScheme()))
2633         uriBuilder.setScheme(x.getScheme());
2634      if (nn(x.getHost()))
2635         uriBuilder.setHost(x.getHost());
2636      if (x.getPort() != -1)
2637         uriBuilder.setPort(x.getPort());
2638      if (nn(x.getUserInfo()))
2639         uriBuilder.setUserInfo(x.getUserInfo());
2640      if (nn(x.getFragment()))
2641         uriBuilder.setFragment(x.getFragment());
2642      if (nn(x.getQuery()))
2643         uriBuilder.setCustomQuery(x.getQuery());
2644      uriBuilder.setPath(x.getPath());
2645      return this;
2646   }
2647
2648   /**
2649    * Sets the URI fragment.
2650    *
2651    * @param fragment The URI fragment.  The value is expected to be unescaped and may contain non ASCII characters.
2652    *    <br>Can be <jk>null</jk> (fragment will be cleared).
2653    * @return This object.
2654    */
2655   public RestRequest uriFragment(String fragment) {
2656      uriBuilder.setFragment(fragment);
2657      return this;
2658   }
2659
2660   /**
2661    * Sets the URI host.
2662    *
2663    * @param host The new URI host.
2664    *    <br>Can be <jk>null</jk> (host will be cleared).
2665    * @return This object.
2666    */
2667   public RestRequest uriHost(String host) {
2668      uriBuilder.setHost(host);
2669      return this;
2670   }
2671
2672   /**
2673    * Sets the URI port.
2674    *
2675    * @param port The new URI port.
2676    * @return This object.
2677    */
2678   public RestRequest uriPort(int port) {
2679      uriBuilder.setPort(port);
2680      return this;
2681   }
2682
2683   /**
2684    * Sets the URI scheme.
2685    *
2686    * @param scheme The new URI scheme.
2687    *    <br>Can be <jk>null</jk> (scheme will be cleared).
2688    * @return This object.
2689    */
2690   public RestRequest uriScheme(String scheme) {
2691      uriBuilder.setScheme(scheme);
2692      return this;
2693   }
2694
2695   /**
2696    * Sets the URI user info.
2697    *
2698    * @param userInfo The new URI user info.
2699    *    <br>Can be <jk>null</jk> (user info will be cleared).
2700    * @return This object.
2701    */
2702   public RestRequest uriUserInfo(String userInfo) {
2703      uriBuilder.setUserInfo(userInfo);
2704      return this;
2705   }
2706
2707   /**
2708    * Sets the URI user info.
2709    *
2710    * @param username The new URI username.
2711    *    <br>Can be <jk>null</jk>.
2712    * @param password The new URI password.
2713    *    <br>Can be <jk>null</jk>.
2714    * @return This object.
2715    */
2716   public RestRequest uriUserInfo(String username, String password) {
2717      uriBuilder.setUserInfo(username, password);
2718      return this;
2719   }
2720
2721   /**
2722    * Convenience method for specifying URL-Encoding as the marshalling transmission media type for this request only.
2723    *
2724    * <p>
2725    *    {@link UrlEncodingSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
2726    *    <ul>
2727    *       <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
2728    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
2729    *       <li>This serializer is NOT used when using the {@link RestRequest#formData(String, Object)} (and related) methods for constructing
2730    *          the request body.  Instead, the part serializer specified via {@link RestClient.Builder#partSerializer(Class)} is used.
2731    *    </ul>
2732    * <p>
2733    *    {@link UrlEncodingParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
2734    *    <ul>
2735    *       <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
2736    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
2737    *    </ul>
2738    * <p>
2739    *    <c>Accept</c> request header will be set to <js>"application/x-www-form-urlencoded"</js> unless overridden
2740    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}.
2741    * <p>
2742    *    <c>Content-Type</c> request header will be set to <js>"application/x-www-form-urlencoded"</js> unless overridden
2743    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}.
2744    * <p>
2745    *    Identical to calling <c>serializer(UrlEncodingSerializer.<jk>class</jk>).parser(UrlEncodingParser.<jk>class</jk>)</c>.
2746    *
2747    * @return This object.
2748    */
2749   public RestRequest urlEnc() {
2750      return serializer(UrlEncodingSerializer.class).parser(UrlEncodingParser.class);
2751   }
2752
2753   /**
2754    * Convenience method for specifying XML as the marshalling transmission media type for this request only.
2755    *
2756    * <p>
2757    * {@link XmlSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
2758    *    <ul>
2759    *       <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or
2760    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
2761    *    </ul>
2762    * <p>
2763    *    {@link XmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
2764    *    <ul>
2765    *       <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or
2766    *          bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class.
2767    *    </ul>
2768    * <p>
2769    *    <c>Accept</c> request header will be set to <js>"text/xml"</js> unless overridden
2770    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}.
2771    * <p>
2772    *    <c>Content-Type</c> request header will be set to <js>"text/xml"</js> unless overridden
2773    *       by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}.
2774    * <p>
2775    *    Identical to calling <c>serializer(XmlSerializer.<jk>class</jk>).parser(XmlParser.<jk>class</jk>)</c>.
2776    *
2777    * @return This object.
2778    */
2779   public RestRequest xml() {
2780      return serializer(XmlSerializer.class).parser(XmlParser.class);
2781   }
2782
2783   private Header createHeader(String name, Object value) {
2784      return createHeader(name, value, null, null, null);
2785   }
2786
2787   private NameValuePair createPart(HttpPartType type, String name, Object value) {
2788      return createPart(name, value, type, null, null, null);
2789   }
2790
2791   private HttpPartSerializerSession getPartSerializerSession(HttpPartSerializer serializer) {
2792      if (serializer == null)
2793         serializer = client.getPartSerializer();
2794      return partSerializerSessions.computeIfAbsent(serializer, HttpPartSerializer::getPartSession);
2795   }
2796
2797   private ContentType getRequestContentType(ContentType def) {
2798      var h = request.getFirstHeader(HEADER_CONTENT_TYPE);
2799      if (nn(h)) {
2800         var s = h.getValue();
2801         if (ine(s))
2802            return ContentType.of(s);
2803      }
2804      return def;
2805   }
2806
2807   /**
2808    * Creates a new header.
2809    *
2810    * @param name The header name.
2811    * @param value The header value.
2812    * @param serializer The part serializer to use, or <jk>null</jk> to use the part serializer defined on the client.
2813    * @param schema Optional HTTP part schema to provide to the part serializer.
2814    * @param skipIfEmpty If <jk>true</jk>, empty string values will be ignored on the request.
2815    * @return A new header.
2816    */
2817   protected Header createHeader(String name, Object value, HttpPartSerializer serializer, HttpPartSchema schema, Boolean skipIfEmpty) {
2818      if (ie(name))
2819         return null;
2820      if (skipIfEmpty == null)
2821         skipIfEmpty = client.isSkipEmptyHeaderData();
2822      if (serializer == null)
2823         serializer = client.getPartSerializer();
2824      return new SerializedHeader(name, value, getPartSerializerSession(serializer), schema, skipIfEmpty);
2825   }
2826
2827   /**
2828    * Constructs the {@link HttpRequestBase} object that ends up being passed to the client execute method.
2829    *
2830    * <p>
2831    * Subclasses can override this method to create their own request base objects.
2832    *
2833    * @param method The HTTP method.
2834    * @param uri The HTTP URI.
2835    * @param hasBody Whether the HTTP request has a body.
2836    * @return A new {@link HttpRequestBase} object.
2837    */
2838   protected HttpRequestBase createInnerRequest(String method, URI uri, boolean hasBody) {
2839      var req = hasBody ? new BasicHttpEntityRequestBase(this, method) : new BasicHttpRequestBase(this, method);
2840      req.setURI(uri);
2841      return req;
2842   }
2843
2844   /**
2845    * Creates a new query/form-data/path part.
2846    *
2847    * @param name The part name.
2848    * @param value The part value.
2849    * @param type The HTTP part type.
2850    * @param serializer The part serializer to use, or <jk>null</jk> to use the part serializer defined on the client.
2851    * @param schema Optional HTTP part schema to provide to the part serializer.
2852    * @param skipIfEmpty If <jk>true</jk>, empty string values will be ignored on the request.
2853    * @return A new part.
2854    */
2855   protected NameValuePair createPart(String name, Object value, HttpPartType type, HttpPartSerializer serializer, HttpPartSchema schema, Boolean skipIfEmpty) {
2856      if (ie(name))
2857         return null;
2858      if (skipIfEmpty == null) {
2859         if (type == QUERY)
2860            skipIfEmpty = client.isSkipEmptyQueryData();
2861         else if (type == FORMDATA)
2862            skipIfEmpty = client.isSkipEmptyFormData();
2863         else
2864            skipIfEmpty = false;
2865      }
2866      if (serializer == null)
2867         serializer = client.getPartSerializer();
2868      return new SerializedPart(name, value, type, getPartSerializerSession(serializer), schema, skipIfEmpty);
2869   }
2870
2871   @Override /* Overridden from MarshallingSession */
2872   protected FluentMap<String,Object> properties() {
2873      return super.properties()
2874         .a("client", client.properties())
2875         .a("ignoreErrors", ignoreErrors)
2876         .a("interceptors", interceptors)
2877         .a("requestBodySchema", contentSchema)
2878         .a("response", response)
2879         .a("serializer", serializer);
2880   }
2881
2882   RestRequest formDataArg(String name, Object value, HttpPartSchema schema, HttpPartSerializer serializer, boolean skipIfEmpty) {
2883      var isMulti = ie(name) || eq(name, "*") || value instanceof PartList || isNameValuePairArray(value);
2884
2885      if (! isMulti) {
2886         if (! (skipIfEmpty && ie(s(value))))
2887            return formData(createPart(name, value, FORMDATA, serializer, schema, skipIfEmpty));
2888         return this;
2889      }
2890
2891      List<NameValuePair> l = list();
2892
2893      if (HttpParts.canCast(value)) {
2894         l.add(HttpParts.cast(value));
2895      } else if (value instanceof PartList value2) {
2896         value2.forEach(l::add);
2897      } else if (value instanceof Collection<?> value3) {
2898         value3.forEach(x -> l.add(HttpParts.cast(x)));
2899      } else if (isArray(value)) {
2900         for (var i = 0; i < Array.getLength(value); i++)
2901            l.add(HttpParts.cast(Array.get(value, i)));
2902      } else if (value instanceof Map value2) {
2903         toMap(value2).forEach((k, v) -> l.add(createPart(s(k), v, FORMDATA, serializer, schema, skipIfEmpty)));
2904      } else if (isBean(value)) {
2905         toBeanMap(value).forEach((k, v) -> l.add(createPart(k, v, FORMDATA, serializer, schema, skipIfEmpty)));
2906      } else if (nn(value)) {
2907         formDataCustom(value);
2908         return this;
2909      }
2910
2911      if (skipIfEmpty)
2912         l.removeIf(x -> ie(x.getValue()));
2913
2914      formData.append(l);
2915
2916      return this;
2917   }
2918
2919   HttpPartSerializerSession getPartSerializerSession() {
2920      if (partSerializerSession == null)
2921         partSerializerSession = getPartSerializerSession(null);
2922      return partSerializerSession;
2923   }
2924
2925   RestRequest headerArg(String name, Object value, HttpPartSchema schema, HttpPartSerializer serializer, boolean skipIfEmpty) {
2926      var isMulti = ie(name) || eq(name, "*") || value instanceof HeaderList || isHeaderArray(value);
2927
2928      if (! isMulti) {
2929         if (! (skipIfEmpty && ie(s(value))))
2930            return header(createHeader(name, value, serializer, schema, skipIfEmpty));
2931         return this;
2932      }
2933
2934      List<Header> l = list();
2935
2936      if (HttpHeaders.canCast(value)) {
2937         l.add(HttpHeaders.cast(value));
2938      } else if (value instanceof HeaderList value2) {
2939         value2.forEach(l::add);
2940      } else if (value instanceof Collection<?> value3) {
2941         value3.forEach(x -> l.add(HttpHeaders.cast(x)));
2942      } else if (isArray(value)) {
2943         for (var i = 0; i < Array.getLength(value); i++)
2944            l.add(HttpHeaders.cast(Array.get(value, i)));
2945      } else if (value instanceof Map value2) {
2946         toMap(value2).forEach((k, v) -> l.add(createHeader(s(k), v, serializer, schema, skipIfEmpty)));
2947      } else if (isBean(value)) {
2948         toBeanMap(value).forEach((k, v) -> l.add(createHeader(k, v, serializer, schema, skipIfEmpty)));
2949      } else if (nn(value)) {
2950         throw rex("Invalid value type for header arg '%s': %s", name, cn(value));
2951      }
2952
2953      if (skipIfEmpty)
2954         l.removeIf(x -> ie(x.getValue()));
2955
2956      headerData.append(l);
2957
2958      return this;
2959   }
2960
2961   boolean isLoggingSuppressed() { return suppressLogging; }
2962
2963   /**
2964    * Returns <jk>true</jk> if the request body can be safely re-sent (required for a gated automatic retry).
2965    *
2966    * <p>
2967    * A missing body is trivially repeatable; a streaming body ({@link InputStream}/{@link Reader}/record-stream) is
2968    * not; an {@link HttpEntity} body defers to {@link HttpEntity#isRepeatable()}; any other (buffered POJO/String)
2969    * body is repeatable.
2970    *
2971    * @return <jk>true</jk> if the body can be safely re-sent.
2972    */
2973   boolean isBodyRepeatable() {
2974      var c = content;
2975      if (c == null || c == NO_BODY)
2976         return true;
2977      if (c instanceof InputStream || c instanceof Reader || c instanceof RecordStreamBody)
2978         return false;
2979      if (c instanceof HttpEntity e)
2980         return e.isRepeatable();
2981      return true;
2982   }
2983
2984   RestRequest pathArg(String name, Object value, HttpPartSchema schema, HttpPartSerializer serializer) {
2985      var isMulti = ie(name) || eq(name, "*") || value instanceof PartList || isNameValuePairArray(value);
2986
2987      if (! isMulti)
2988         return pathData(createPart(name, value, PATH, serializer, schema, false));
2989
2990      List<NameValuePair> l = list();
2991
2992      if (HttpParts.canCast(value)) {
2993         l.add(HttpParts.cast(value));
2994      } else if (value instanceof PartList value2) {
2995         value2.forEach(l::add);
2996      } else if (value instanceof Collection<?> value3) {
2997         value3.forEach(x -> l.add(HttpParts.cast(x)));
2998      } else if (isArray(value)) {
2999         for (var i = 0; i < Array.getLength(value); i++)
3000            l.add(HttpParts.cast(Array.get(value, i)));
3001      } else if (value instanceof Map value2) {
3002         toMap(value2).forEach((k, v) -> l.add(createPart(s(k), v, PATH, serializer, schema, false)));
3003      } else if (isBean(value)) {
3004         toBeanMap(value).forEach((k, v) -> l.add(createPart(k, v, PATH, serializer, schema, false)));
3005      } else if (nn(value)) {
3006         throw rex("Invalid value type for path arg '%s': %s", name, cn(value));
3007      }
3008
3009      pathData.append(l);
3010
3011      return this;
3012   }
3013
3014   RestRequest queryArg(String name, Object value, HttpPartSchema schema, HttpPartSerializer serializer, boolean skipIfEmpty) {
3015      if (schema != null && schema.getCollectionFormat() == HttpPartCollectionFormat.MULTI) {
3016         if (value instanceof Collection<?> value2) {
3017            List<NameValuePair> l = list();
3018            value2.forEach(x -> l.add(createPart(name, x, QUERY, serializer, schema, skipIfEmpty)));
3019            if (skipIfEmpty)
3020               l.removeIf(x -> ie(x.getValue()));
3021            queryData.append(l);
3022            return this;
3023         }
3024         if (isArray(value) && ! isNameValuePairArray(value)) {
3025            List<NameValuePair> l = list();
3026            for (var i = 0; i < Array.getLength(value); i++)
3027               l.add(createPart(name, Array.get(value, i), QUERY, serializer, schema, skipIfEmpty));
3028            if (skipIfEmpty)
3029               l.removeIf(x -> ie(x.getValue()));
3030            queryData.append(l);
3031            return this;
3032         }
3033         if (value instanceof Map<?,?> value2) {
3034            List<NameValuePair> l = list();
3035            var sess = getPartSerializerSession(serializer);
3036            for (var e : value2.entrySet()) {
3037               try {
3038                  var uon = sess.write(QUERY, schema, e.getValue());
3039                  l.add(BasicPart.of(name, s(e.getKey()) + "=" + (uon == null ? "" : uon)));
3040               } catch (SerializeException | SchemaValidationException ex) {
3041                  throw rex(ex, "Serialization error on request query part '%s'", name);
3042               }
3043            }
3044            if (skipIfEmpty)
3045               l.removeIf(x -> ie(x.getValue()));
3046            queryData.append(l);
3047            return this;
3048         }
3049      }
3050
3051      var isMulti = ie(name) || eq(name, "*") || value instanceof PartList || isNameValuePairArray(value);
3052
3053      if (! isMulti) {
3054         if (! (skipIfEmpty && ie(s(value))))
3055            return queryData(createPart(name, value, QUERY, serializer, schema, skipIfEmpty));
3056         return this;
3057      }
3058
3059      List<NameValuePair> l = list();
3060
3061      if (HttpParts.canCast(value)) {
3062         l.add(HttpParts.cast(value));
3063      } else if (value instanceof PartList value2) {
3064         value2.forEach(l::add);
3065      } else if (value instanceof Collection<?> value3) {
3066         value3.forEach(x -> l.add(HttpParts.cast(x)));
3067      } else if (isArray(value)) {
3068         for (var i = 0; i < Array.getLength(value); i++)
3069            l.add(HttpParts.cast(Array.get(value, i)));
3070      } else if (value instanceof Map value2) {
3071         toMap(value2).forEach((k, v) -> l.add(createPart(s(k), v, QUERY, serializer, schema, skipIfEmpty)));
3072      } else if (isBean(value)) {
3073         toBeanMap(value).forEach((k, v) -> l.add(createPart(k, v, QUERY, serializer, schema, skipIfEmpty)));
3074      } else if (nn(value)) {
3075         queryCustom(value);
3076         return this;
3077      }
3078
3079      if (skipIfEmpty)
3080         l.removeIf(x -> ie(x.getValue()));
3081
3082      queryData.append(l);
3083
3084      return this;
3085   }
3086
3087   /**
3088    * Resolves a {@link RecordStreamBody} body by invoking the caller's consumer against a
3089    * cursor opened over a temporary buffer, then sending the buffered bytes as the request
3090    * body.
3091    */
3092   private HttpEntity streamBodyEntity(RecordStreamBody body, Serializer serializer) {
3093      if (serializer == null)
3094         throw iaex("No serializer registered for cursor-streamed request body.");
3095      try {
3096         var consumer = body.getConsumer();
3097         var writerKind = body.getWriterKind();
3098
3099         if (writerKind == TokenWriter.class && !(serializer instanceof TokenWritable))
3100            throw iaex("Serializer '%s' does not support the token-writer surface.", serializer.getClass().getName());
3101         if (writerKind == RecordWriter.class && !(serializer instanceof RecordWritable))
3102            throw iaex("Serializer '%s' does not support the record-writer surface.", serializer.getClass().getName());
3103
3104         var buf = new ByteArrayOutputStream();
3105         Object output = serializer.isWriterSerializer()
3106            ? new OutputStreamWriter(buf, java.nio.charset.StandardCharsets.UTF_8)
3107            : buf;
3108
3109         writeCursor(serializer, writerKind, consumer, output);
3110
3111         // Use the serializer's primary produces() media type if no explicit Content-Type set.
3112         var ct = serializer.getPrimaryMediaType();
3113         var contentType2 = ct != null ? ContentType.of(ct.toString()) : ContentType.APPLICATION_OCTET_STREAM;
3114         return byteArrayEntity(buf.toByteArray(), getRequestContentType(contentType2));
3115      } catch (IOException e) {
3116         throw rex(e, "I/O error streaming request body.");
3117      }
3118   }
3119
3120   @SuppressWarnings({
3121      "rawtypes" // The body consumer's element type depends on the writer kind, so it is invoked through a raw Consumer.
3122   })
3123   private static void writeCursor(Serializer serializer, Class<?> writerKind, Object consumer, Object output) throws IOException {
3124      try (var w = (writerKind == TokenWriter.class)
3125            ? ((TokenWritable) serializer).writeTokens(output)
3126            : ((RecordWritable) serializer).writeRecords(output)) {
3127         ((Consumer) consumer).accept(w);
3128         w.flush();
3129      }
3130   }
3131}