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.server;
018
019import static java.time.format.DateTimeFormatter.*;
020import static org.apache.juneau.commons.httppart.HttpPartType.*;
021import static org.apache.juneau.commons.utils.IoUtils.*;
022import static org.apache.juneau.commons.utils.ObjectUtils.*;
023import static org.apache.juneau.commons.utils.Shorts.*;
024import static org.apache.juneau.commons.utils.StringUtils.*;
025import static org.apache.juneau.commons.utils.ThrowableUtils.*;
026import static org.apache.juneau.rest.RestSharedConstants.*;
027
028import java.io.*;
029import java.lang.reflect.*;
030import java.lang.reflect.Proxy;
031import java.net.*;
032import java.nio.charset.*;
033import java.security.*;
034import java.text.*;
035import java.time.*;
036import java.time.format.*;
037import java.time.temporal.*;
038import java.util.*;
039import java.util.concurrent.atomic.*;
040import java.util.function.*;
041import java.util.stream.*;
042
043import org.apache.juneau.bean.openapi3.OpenApi;
044import org.apache.juneau.bean.swagger.Operation;
045import org.apache.juneau.bean.swagger.Swagger;
046import org.apache.juneau.commons.bean.*;
047import org.apache.juneau.commons.http.*;
048import org.apache.juneau.commons.http.MediaType;
049import org.apache.juneau.commons.httppart.*;
050import org.apache.juneau.commons.logging.*;
051import org.apache.juneau.commons.svl.*;
052import org.apache.juneau.commons.utils.*;
053import org.apache.juneau.config.*;
054import org.apache.juneau.http.*;
055import org.apache.juneau.http.header.*;
056import org.apache.juneau.http.header.Date;
057import org.apache.juneau.http.request.*;
058import org.apache.juneau.http.response.*;
059import org.apache.juneau.httppart.bean.*;
060import org.apache.juneau.marshall.*;
061import org.apache.juneau.marshall.collections.*;
062import org.apache.juneau.marshall.cp.Messages;
063import org.apache.juneau.marshall.httppart.*;
064import org.apache.juneau.marshall.marshaller.*;
065import org.apache.juneau.marshall.marshaller.Uon;
066import org.apache.juneau.marshall.parser.ParseException;
067import org.apache.juneau.marshall.uon.*;
068import org.apache.juneau.rest.server.arg.*;
069import org.apache.juneau.rest.server.assertions.*;
070import org.apache.juneau.rest.server.auth.*;
071import org.apache.juneau.rest.server.guard.*;
072import org.apache.juneau.rest.server.httppart.*;
073import org.apache.juneau.rest.server.staticfile.*;
074import org.apache.juneau.rest.server.util.*;
075import org.apache.juneau.bct.assertions.*;
076
077import jakarta.servlet.*;
078import jakarta.servlet.http.*;
079
080/**
081 * Represents an HTTP request for a REST resource.
082 *
083 * <p>
084 *    The {@link RestRequest} object is an extension of the <l>HttpServletRequest</l> class
085 *    with various built-in convenience methods for use in building REST interfaces.
086 *    It can be accessed by passing it as a parameter on your REST Java method:
087 * </p>
088 *
089 * <p class='bjava'>
090 *    <ja>@RestPost</ja>(...)
091 *    <jk>public</jk> Object myMethod(RestRequest <jv>req</jv>) {...}
092 * </p>
093 *
094 * <p>
095 *    The primary methods on this class are:
096 * </p>
097 * <ul class='javatree'>
098 *    <li class='jc'>{@link RestRequest}
099 *    <ul class='spaced-list'>
100 *       <li>Methods for accessing the request content:
101 *       <ul class='javatreec'>
102 *          <li class='jm'>{@link RestRequest#getContent() getContent()}
103 *          <li class='jm'>{@link RestRequest#getInputStream() getInputStream()}
104 *          <li class='jm'>{@link RestRequest#getReader() getReader()}
105 *       </ul>
106 *       <li>Methods for accessing HTTP parts:
107 *       <ul class='javatreec'>
108 *          <li class='jm'>{@link RestRequest#checkPreconditions(RestResponse) checkPreconditions(RestResponse)}
109 *          <li class='jm'>{@link RestRequest#containsFormParam(String) containsFormParam(String)}
110 *          <li class='jm'>{@link RestRequest#containsHeader(String) containsHeader(String)}
111 *          <li class='jm'>{@link RestRequest#containsQueryParam(String) containsQueryParam(String)}
112 *          <li class='jm'>{@link RestRequest#getHeader(Class) getHeader(Class)}
113 *          <li class='jm'>{@link RestRequest#getHeader(String) getHeader(String)}
114 *          <li class='jm'>{@link RestRequest#getHeaders() getHeaders()}
115 *          <li class='jm'>{@link RestRequest#getFormParam(Class) getFormParam(Class)}
116 *          <li class='jm'>{@link RestRequest#getFormParam(String) getFormParam(String)}
117 *          <li class='jm'>{@link RestRequest#getFormParams() getFormParams()}
118 *          <li class='jm'>{@link RestRequest#getPathParam(Class) getPathParam(Class)}
119 *          <li class='jm'>{@link RestRequest#getPathParam(String) getPathParam(String)}
120 *          <li class='jm'>{@link RestRequest#getPathParams() getPathParams()}
121 *          <li class='jm'>{@link RestRequest#getPathRemainder() getPathRemainder()}
122 *          <li class='jm'>{@link RestRequest#getQueryParam(Class) getQueryParam(Class)}
123 *          <li class='jm'>{@link RestRequest#getQueryParam(String) getQueryParam(String)}
124 *          <li class='jm'>{@link RestRequest#getQueryParams() getQueryParams()}
125 *          <li class='jm'>{@link RestRequest#getQueryString() getQueryString()}
126 *       </ul>
127 *       <li>Methods for localization:
128 *       <ul class='javatreec'>
129 *          <li class='jm'>{@link RestRequest#getLocale() getLocale()}
130 *          <li class='jm'>{@link RestRequest#getMessage(String,Object...) getMessage(String,Object...)}
131 *          <li class='jm'>{@link RestRequest#getMessages() getMessages()}
132 *          <li class='jm'>{@link RestRequest#getTimeZone() getTimeZone()}
133 *       </ul>
134 *       <li>Methods for accessing static files:
135 *       <ul class='javatreec'>
136 *          <li class='jm'>{@link RestRequest#getVarResolverSession() getVarResolverSession()}
137 *       </ul>
138 *       <li>Methods for assertions:
139 *       <ul class='javatreec'>
140 *          <li class='jm'>{@link RestRequest#assertContent() assertContent()}
141 *          <li class='jm'>{@link RestRequest#assertCharset() assertCharset()}
142 *          <li class='jm'>{@link RestRequest#assertFormParam(String) assertFormParam(String)}
143 *          <li class='jm'>{@link RestRequest#assertHeader(String) assertHeader(String)}
144 *          <li class='jm'>{@link RestRequest#assertQueryParam(String) assertQueryParam(String)}
145 *          <li class='jm'>{@link RestRequest#assertRequestLine() assertRequestLine()}
146 *       </ul>
147 *       <li>Other:
148 *       <ul class='javatreec'>
149 *          <li class='jm'>{@link RestRequest#getAttribute(String) getAttribute(String)}
150 *          <li class='jm'>{@link RestRequest#getAttributes() getAttributes()}
151 *          <li class='jm'>{@link RestRequest#getAuthorityPath() getAuthorityPath()}
152 *          <li class='jm'>{@link RestRequest#getMarshallingSession() getMarshallingSession()}
153 *          <li class='jm'>{@link RestRequest#getCharset() getCharset()}
154 *          <li class='jm'>{@link RestRequest#getConfig() getConfig()}
155 *          <li class='jm'>{@link RestRequest#getContext() getContext()}
156 *          <li class='jm'>{@link RestRequest#getContextPath() getContextPath()}
157 *          <li class='jm'>{@link RestRequest#getHttpServletRequest() getHttpServletRequest()}
158 *          <li class='jm'>{@link RestRequest#getMethod() getMethod()}
159 *          <li class='jm'>{@link RestRequest#getOpContext() getOpContext()}
160 *          <li class='jm'>{@link RestRequest#getOperationSwagger() getOperationSwagger()}
161 *          <li class='jm'>{@link RestRequest#getPartParserSession() getPartParserSession()}
162 *          <li class='jm'>{@link RestRequest#getPartSerializerSession() getPartSerializerSession()}
163 *          <li class='jm'>{@link RestRequest#getPathInfo() getPathInfo()}
164 *          <li class='jm'>{@link RestRequest#getProtocolVersion() getProtocolVersion()}
165 *          <li class='jm'>{@link RestRequest#getRequest(Class) getRequest(Class)}
166 *          <li class='jm'>{@link RestRequest#getRequestLine() getRequestLine()}
167 *          <li class='jm'>{@link RestRequest#getRequestURI() getRequestURI()}
168 *          <li class='jm'>{@link RestRequest#getRequestURL() getRequestURL()}
169 *          <li class='jm'>{@link RestRequest#getServletPath() getServletPath()}
170 *          <li class='jm'>{@link RestRequest#getSession() getSession()}
171 *          <li class='jm'>{@link RestRequest#getSwagger() getSwagger()}
172 *          <li class='jm'>{@link RestRequest#getUriContext() getUriContext()}
173 *          <li class='jm'>{@link RestRequest#getUriResolver() getUriResolver()}
174 *          <li class='jm'>{@link RestRequest#isDebug() isDebug()}
175 *          <li class='jm'>{@link RestRequest#isDebugMarshalling() isDebugMarshalling()}
176 *          <li class='jm'>{@link RestRequest#isPlainText() isPlainText()}
177 *          <li class='jm'>{@link RestRequest#isUserInRole(String) isUserInRole(String)}
178 *          <li class='jm'>{@link RestRequest#setAttribute(String,Object) setAttribute(String,Object)}
179 *          <li class='jm'>{@link RestRequest#setCharset(Charset) setCharset(Charset)}
180 *          <li class='jm'>{@link RestRequest#setException(Throwable) setException(Throwable)}
181 *          <li class='jm'>{@link RestRequest#setNoTrace() setNoTrace()}
182 *       </ul>
183 *    </ul>
184 * </ul>
185 *
186 */
187@SuppressWarnings({
188   "java:S3776", // checkPreconditions() walks the chained RFC 7232 If-Match/If-Unmodified-Since/If-None-Match/If-Modified-Since steps in order; splitting would obscure the precedence rules
189   "java:S6539", // Collection.toArray() usage intentional
190   "javabugs:S2259", // Null accesses are guarded by nn() checks; Sonar's flow analysis does not track nn() as a null guard
191   "rawtypes", // Collection constructed from ClassMeta.newInstance() is untyped.
192   "resource", // Streams returned to servlet container; lifecycle managed by the container
193   "unchecked", // Type erasure requires unchecked casts in REST client operations
194   "unused" // Unused parameters in HttpServletRequest overrides and helper methods
195})
196public class RestRequest extends HttpServletRequestWrapper {
197
198   /**
199    * Request-attribute name under which the per-response Content-Security-Policy nonce is cached.
200    *
201    * <p>
202    * Set by {@link RestResponse} when an emitted CSP header contains a nonce placeholder, and read by the
203    * serialization layer (to stamp the matching {@code nonce="..."} on inline {@code <script>}/{@code <style>} tags)
204    * so the header and the markup share the same nonce.
205    */
206   public static final String CSP_NONCE_ATTR = "CspNonce";
207
208   /**
209    * Returns {@code true} if {@code method} is one of the methods the {@code content} URL-parameter body
210    * override (see {@link Rest#disableContentParam()}) is documented to apply to.
211    *
212    * <p>
213    * Package-private for direct unit-test coverage. Restricting the override to {@code PUT}/{@code POST} (its
214    * documented scope all along) keeps a plain cross-origin {@code GET} &mdash; the kind a browser will issue
215    * with no preflight and no custom header for something as simple as an <code>&lt;img src=...&gt;</code> tag
216    * &mdash; from ever having its body-carrying {@code Content-Type} gate bypassed via a query string, without
217    * touching the {@link Rest#disableContentParam()} default itself.
218    *
219    * @param method The request's HTTP method. Can be {@code null} (returns {@code false}).
220    * @return {@code true} if {@code method} is {@code PUT} or {@code POST}, case-insensitively.
221    */
222   static boolean isContentParamMethod(String method) {
223      return eqic("PUT", method) || eqic("POST", method);
224   }
225
226   /*
227    * Converts an Accept-Language value entry to a Locale.
228    */
229   @SuppressWarnings({
230      "java:S1874" // The new Locale(String,String) constructor is not deprecated on the Java 17 baseline; its replacement Locale.of(...) was only added in Java 19.
231   })
232   private static Locale toLocale(String lang) {
233      var country = "";
234      var i = lang.indexOf('-');
235      if (i > -1) {
236         country = lang.substring(i + 1).trim();
237         lang = lang.substring(0, i).trim();
238      }
239      return new Locale(lang, country);
240   }
241
242   // Constructor initialized.
243   private final HttpServletRequest inner;
244   private final RestContext context;
245   private final RestOpContext opContext;
246   private final RequestContent content;
247   private final MarshallingSession marshallingSession;
248   private final RequestQueryParamList queryParams;
249   private final RequestPathParamList pathParams;
250   private final RequestHeaderList headers;
251   private final RequestAttributes attrs;
252   private final HttpPartParserSession partParserSession;
253
254   private final RestSession session;
255   // Lazy initialized.
256   private VarResolverSession varSession;
257   private RequestFormParamList formParams;
258   private UriContext uriContext;
259   private String authorityPath;
260   private Config config;
261   private Swagger swagger;
262
263   private Charset charset;
264   private Map<String,Object> serializerSessionProperties;
265   private Map<String,Object> parserSessionProperties;
266
267   // Framework-set resource-level authentication result; may be null.
268   private AuthResult authResult;
269
270   /**
271    * Constructor.
272    */
273   RestRequest(RestOpContext opContext, RestSession session) {
274      super(session.getRequest());
275      this.session = session;
276      this.opContext = opContext;
277
278      inner = session.getRequest();
279      context = session.getContext();
280
281      attrs = new RequestAttributes(this);
282
283      queryParams = new RequestQueryParamList(this, session.getQueryParams(), true);
284
285      headers = new RequestHeaderList(this, queryParams, false);
286
287      content = new RequestContent(this);
288
289      if (context.isAllowContentParam() && isContentParamMethod(inner.getMethod())) {
290         var b = queryParams.get("content").asString().orElse(null);
291         if (b != null) {
292            headers.set("Content-Type", UonSerializer.DEFAULT.getResponseContentType());
293            content.mediaType(MediaType.UON).parser(UonParser.DEFAULT).content(b.getBytes(UTF8));
294         }
295      }
296
297      pathParams = new RequestPathParamList(session, this, true);
298
299      marshallingSession = opContext.getMarshallingContext().getSession();
300
301      partParserSession = new LazyPartParserSession(opContext.getPartParser());
302
303      pathParams.parser(partParserSession);
304
305      // @formatter:off
306      queryParams
307         .addDefault(opContext.getDefaultRequestQueryData().getAll())
308         .parser(partParserSession);
309
310      headers
311         .addDefault(opContext.getDefaultRequestHeaders().getAll())
312         .addDefault(context.getDefaultRequestHeaders().getAll())
313         .parser(partParserSession);
314
315      content
316         .encoders(opContext.getEncoders())
317         .parsers(opContext.getParsers())
318         .maxInput(opContext.getMaxInput());
319
320      attrs
321         .addDefault(opContext.getDefaultRequestAttributes())
322         .addDefault(context.getDefaultRequestAttributes());
323      // @formatter:on
324
325      // Body-caching wrappers are installed by the two-phase debug pipeline before this object is
326      // constructed, based on the resolved logger's level. Nothing to wrap here.
327   }
328
329   /**
330    * Provides the ability to perform fluent-style assertions on the response character encoding.
331    *
332    * <h5 class='section'>Examples:</h5>
333    * <p class='bjava'>
334    *    <jc>// Validates that the response content charset is UTF-8.</jc>
335    *    <jv>request</jv>
336    *       .assertCharset().is(<js>"utf-8"</js>);
337    * </p>
338    *
339    * @return A new fluent assertion object.
340    * @throws BasicHttpException If REST call failed.
341    */
342   public FluentStringAssertion<RestRequest> assertCharset() {
343      return new FluentStringAssertion<>(getCharset().name(), this);
344   }
345
346   /**
347    * Returns a fluent assertion for the request content.
348    *
349    * <h5 class='section'>Example:</h5>
350    * <p class='bjava'>
351    *    <jc>// Validates the request content contains "foo".</jc>
352    *    <jv>request</jv>
353    *       .assertContent().asString().is(<js>"foo"</js>);
354    * </p>
355    *
356    * @return A new fluent assertion on the content, never <jk>null</jk>.
357    */
358   public FluentRequestContentAssertion<RestRequest> assertContent() {
359      return new FluentRequestContentAssertion<>(getContent(), this);
360   }
361
362   /**
363    * Returns a fluent assertion for the specified form parameter.
364    *
365    * <h5 class='section'>Example:</h5>
366    * <p class='bjava'>
367    *    <jc>// Validates the content type is JSON.</jc>
368    *    <jv>request</jv>
369    *       .assertFormParam(<js>"foo"</js>).asString().contains(<js>"bar"</js>);
370    * </p>
371    *
372    * @param name The query parameter name.
373    * @return A new fluent assertion on the parameter, never <jk>null</jk>.
374    */
375   public FluentRequestFormParamAssertion<RestRequest> assertFormParam(String name) {
376      return new FluentRequestFormParamAssertion<>(getFormParam(name), this);
377   }
378
379   /**
380    * Returns a fluent assertion for the specified header.
381    *
382    * <h5 class='section'>Example:</h5>
383    * <p class='bjava'>
384    *    <jc>// Validates the content type is JSON.</jc>
385    *    <jv>request</jv>
386    *       .assertHeader(<js>"Content-Type"</js>).asString().is(<js>"application/json"</js>);
387    * </p>
388    *
389    * @param name The header name.
390    * @return A new fluent assertion on the parameter, never <jk>null</jk>.
391    */
392   public FluentRequestHeaderAssertion<RestRequest> assertHeader(String name) {
393      return new FluentRequestHeaderAssertion<>(getHeaderParam(name), this);
394   }
395
396   /**
397    * Returns a fluent assertion for the specified query parameter.
398    *
399    * <h5 class='section'>Example:</h5>
400    * <p class='bjava'>
401    *    <jc>// Validates the content type is JSON.</jc>
402    *    <jv>request</jv>
403    *       .assertQueryParam(<js>"foo"</js>).asString().contains(<js>"bar"</js>);
404    * </p>
405    *
406    * @param name The query parameter name.
407    * @return A new fluent assertion on the parameter, never <jk>null</jk>.
408    */
409   public FluentRequestQueryParamAssertion<RestRequest> assertQueryParam(String name) {
410      return new FluentRequestQueryParamAssertion<>(getQueryParam(name), this);
411   }
412
413   /**
414    * Returns an assertion on the request line returned by {@link #getRequestLine()}.
415    *
416    * <h5 class='section'>Example:</h5>
417    * <p class='bjava'>
418    *    <jc>// Validates the request content contains "foo".</jc>
419    *    <jv>request</jv>
420    *       .assertRequestLine().protocol().minor().is(1);
421    * </p>
422    *
423    * @return A new assertion object.
424    */
425   public FluentRequestLineAssertion<RestRequest> assertRequestLine() {
426      return new FluentRequestLineAssertion<>(getRequestLine(), this);
427   }
428
429   /**
430    * Evaluates the conditional-request headers on this request against the response's currently-set
431    * <c>ETag</c> and <c>Last-Modified</c> values.
432    *
433    * <p>
434    * Implements the precondition-evaluation order defined by
435    * <a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7232#section-6'>RFC 7232 §6</a>:
436    * <ol>
437    *    <li><c>If-Match</c> — strong comparison; returns <c>412 Precondition Failed</c> on mismatch.
438    *    <li><c>If-Unmodified-Since</c> — only consulted when <c>If-Match</c> is absent;
439    *       returns <c>412 Precondition Failed</c> when the response's <c>Last-Modified</c> is
440    *       strictly after the supplied date.
441    *    <li><c>If-None-Match</c> — weak comparison; returns <c>304 Not Modified</c> on match
442    *       (for safe methods, per RFC 7232 §6).
443    *    <li><c>If-Modified-Since</c> — only consulted when <c>If-None-Match</c> is absent;
444    *       returns <c>304 Not Modified</c> when the response's <c>Last-Modified</c> is at-or-before
445    *       the supplied date.
446    * </ol>
447    *
448    * <h5 class='section'>Usage:</h5>
449    *
450    * <p>
451    * Set the response's <c>ETag</c> and/or <c>Last-Modified</c> <b>first</b>, then call this method.
452    * The check reads the headers currently on the response, so any value set after the call is not
453    * considered.
454    *
455    * <p class='bjava'>
456    *    <ja>@RestGet</ja>(<js>"/{id}"</js>)
457    *    <jk>public</jk> Order get(<ja>@Path</ja> <jk>long</jk> <jv>id</jv>, RestRequest <jv>req</jv>, RestResponse <jv>res</jv>) {
458    *       <jk>var</jk> <jv>order</jv> = <jv>repo</jv>.find(<jv>id</jv>);
459    *       <jv>res</jv>.eTag(<js>"\""</js> + <jv>order</jv>.version() + <js>"\""</js>).lastModified(<jv>order</jv>.updated());
460    *       <jv>req</jv>.checkPreconditions(<jv>res</jv>).ifPresent(<jv>e</jv> -&gt; { <jk>throw</jk> <jv>e</jv>; });
461    *       <jk>return</jk> <jv>order</jv>;
462    *    }
463    * </p>
464    *
465    * <h5 class='section'>Notes:</h5><ul>
466    *    <li class='note'>Returns an {@link Optional} so the caller controls how to handle the failure
467    *       (throw inline, hand off to a logger, etc.). Both the returned <c>304</c> and <c>412</c>
468    *       failures are {@link BasicHttpException} instances, which are {@link RuntimeException}s,
469    *       so {@code ifPresent(e -&gt; { throw e; })} compiles cleanly.
470    *    <li class='note'><c>If-None-Match: *</c> matches any present <c>ETag</c>; <c>If-Match: *</c>
471    *       fails only when no <c>ETag</c> is set on the response.
472    *    <li class='note'>Per RFC 7232 §3.1, <c>If-Match</c> requires strong comparison: a weak
473    *       <c>ETag</c> on the response side never satisfies an <c>If-Match</c>.
474    *    <li class='note'>Per RFC 7232 §3.2, <c>If-None-Match</c> uses weak comparison: the weak/strong
475    *       flag is ignored when comparing tag values.
476    * </ul>
477    *
478    * <h5 class='section'>See Also:</h5><ul>
479    *    <li class='jm'>{@link RestResponse#eTag(String) RestResponse.eTag(String)}
480    *    <li class='jm'>{@link RestResponse#lastModified(Instant) RestResponse.lastModified(Instant)}
481    * </ul>
482    *
483    * @param response The response carrying the resource's current <c>ETag</c> and/or <c>Last-Modified</c>.
484    *    Must not be <jk>null</jk>.
485    * @return Empty if all preconditions pass; otherwise an {@link Optional} holding either a
486    *    <c>304 Not Modified</c> {@link BasicHttpException} (when the request's cache copy is still
487    *    fresh) or a {@link PreconditionFailed} (when an <c>If-Match</c> / <c>If-Unmodified-Since</c>
488    *    guard failed).
489    * @throws IllegalArgumentException If {@code response} is <jk>null</jk>.
490    */
491   public Optional<BasicHttpException> checkPreconditions(RestResponse response) {
492      reqnn("response", response);
493      var resTagStr = response.getHeader(ETag.NAME);
494      var resTag = nn(resTagStr) ? EntityTag.of(resTagStr) : null;
495      var resLastModStr = response.getHeader(LastModified.NAME);
496      var resLastMod = nn(resLastModStr) ? parseHttpDate(resLastModStr) : null;
497
498      // RFC 7232 §6 step 1: If-Match (strong comparison; 412 on failure).
499      var ifMatch = getHeaderParam(IfMatch.NAME).orElse(null);
500      if (nn(ifMatch)) {
501         if (! matchesAnyStrong(EntityTags.of(ifMatch), resTag))
502            return o(preconditionFailed(IfMatch.NAME));
503      } else {
504         // RFC 7232 §6 step 2: If-Unmodified-Since (only when If-Match absent).
505         var ius = getHeaderParam(IfUnmodifiedSince.NAME).orElse(null);
506         if (nn(ius)) {
507            var iusDate = parseHttpDate(ius);
508            if (nn(iusDate) && nn(resLastMod) && resLastMod.toInstant().isAfter(iusDate.toInstant()))
509               return o(preconditionFailed(IfUnmodifiedSince.NAME));
510         }
511      }
512
513      // RFC 7232 §6 step 3: If-None-Match (weak comparison; 304 on match for safe methods).
514      var ifNoneMatch = getHeaderParam(IfNoneMatch.NAME).orElse(null);
515      if (nn(ifNoneMatch)) {
516         if (matchesAnyWeak(EntityTags.of(ifNoneMatch), resTag))
517            return o(notModified());
518      } else {
519         // RFC 7232 §6 step 4: If-Modified-Since (only when If-None-Match absent; only for GET/HEAD).
520         if (isSafeMethod()) {
521            var ims = getHeaderParam(IfModifiedSince.NAME).orElse(null);
522            if (nn(ims)) {
523               var imsDate = parseHttpDate(ims);
524               if (nn(imsDate) && nn(resLastMod) && ! resLastMod.toInstant().isAfter(imsDate.toInstant()))
525                  return o(notModified());
526            }
527         }
528      }
529
530      return oe();
531   }
532
533   private boolean isSafeMethod() {
534      var m = getMethod();
535      return eqic("GET", m) || eqic("HEAD", m);
536   }
537
538   private static ZonedDateTime parseHttpDate(String value) {
539      try {
540         return ZonedDateTime.from(RFC_1123_DATE_TIME.parse(value)).truncatedTo(ChronoUnit.SECONDS);
541      } catch (@SuppressWarnings("unused") DateTimeParseException e) {
542         return null;
543      }
544   }
545
546   private static boolean matchesAnyStrong(EntityTags clientTags, EntityTag resTag) {
547      if (clientTags == null)
548         return false;
549      for (var t : clientTags.toArray()) {
550         if (t.isAny())
551            return resTag != null;
552         // Strong comparison: both sides must be strong AND opaque-tag values must be equal.
553         if (! t.isWeak() && resTag != null && ! resTag.isWeak() && eq(t.getEntityValue(), resTag.getEntityValue()))
554            return true;
555      }
556      return false;
557   }
558
559   private static boolean matchesAnyWeak(EntityTags clientTags, EntityTag resTag) {
560      if (clientTags == null)
561         return false;
562      for (var t : clientTags.toArray()) {
563         if (t.isAny())
564            return resTag != null;
565         // Weak comparison: opaque-tag values must be equal; weak/strong flag is ignored.
566         if (resTag != null && eq(t.getEntityValue(), resTag.getEntityValue()))
567            return true;
568      }
569      return false;
570   }
571
572   private static BasicHttpException notModified() {
573      return new BasicHttpException(NotModified.STATUS_CODE, NotModified.REASON_PHRASE);
574   }
575
576   private static PreconditionFailed preconditionFailed(String headerName) {
577      return new PreconditionFailed("Precondition '%s' failed.", headerName);
578   }
579
580   /**
581    * Returns <jk>true</jk> if this request contains the specified header.
582    *
583    * @param name The header name.
584    * @return <jk>true</jk> if this request contains the specified header.
585    */
586   public boolean containsFormParam(String name) {
587      return getFormParams().contains(name);
588   }
589
590   /**
591    * Returns <jk>true</jk> if this request contains the specified header.
592    *
593    * @param name The header name.
594    * @return <jk>true</jk> if this request contains the specified header.
595    */
596   public boolean containsHeader(String name) {
597      return headers.contains(name);
598   }
599
600   /**
601    * Returns <jk>true</jk> if this request contains the specified header.
602    *
603    * @param name The header name.
604    * @return <jk>true</jk> if this request contains the specified header.
605    */
606   public boolean containsQueryParam(String name) {
607      return queryParams.contains(name);
608   }
609
610   /**
611    * Returns the request attribute with the specified name.
612    *
613    * @param name The attribute name.
614    * @return The attribute value, never <jk>null</jk>.
615    */
616   @Override
617   public RequestAttribute getAttribute(String name) {
618      return attrs.get(name);
619   }
620
621   /**
622    * Request attributes.
623    *
624    * <p>
625    * Returns a {@link RequestAttributes} object that encapsulates access to attributes on the request.
626    *
627    * <h5 class='section'>Example:</h5>
628    * <p class='bjava'>
629    *    <ja>@RestPost</ja>(...)
630    *    <jk>public</jk> Object myMethod(RestRequest <jv>req</jv>) {
631    *
632    *       <jc>// Get access to attributes.</jc>
633    *       RequestAttributes <jv>attributes</jv> = <jv>req</jv>.getAttributes();
634    *
635    *    <jc>// Get a header value as a POJO.</jc>
636    *       UUID <jv>etag</jv> = <jv>attributes</jv>.get(<js>"ETag"</js>, UUID.<jk>class</jk>);
637    *    }
638    * </p>
639    *
640    * <h5 class='section'>Notes:</h5><ul>
641    *    <li class='note'>
642    *       This object is modifiable.
643    *    <li class='note'>
644    *       Values are converted from strings using the registered part parser on the resource class.
645    *    <li class='note'>
646    *       The {@link RequestAttributes} object can also be passed as a parameter on the method.
647    *    <li class='note'>
648    *       The {@link Attr @Attr} annotation can be used to access individual attribute values.
649    * </ul>
650    *
651    * <h5 class='section'>See Also:</h5><ul>
652    *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerHttpParts">HTTP parts in REST servers</a>
653    * </ul>
654    *
655    * @return
656    *    The headers on this request.
657    *    <br>Never <jk>null</jk>.
658    */
659   public RequestAttributes getAttributes() { return attrs; }
660
661   /**
662    * Returns the URI authority portion of the request.
663    *
664    * @return The URI authority portion of the request.
665    */
666   public String getAuthorityPath() {
667      if (authorityPath == null)
668         authorityPath = context.getUriAuthority();
669      if (authorityPath == null) {
670         var scheme = inner.getScheme();
671         var port = inner.getServerPort();
672         var sb = new StringBuilder(inner.getScheme()).append("://").append(inner.getServerName());
673         if (! (port == 80 && eq(scheme, "http") || port == 443 && eq(scheme, "https")))
674            sb.append(':').append(port);
675         authorityPath = sb.toString();
676      }
677      return authorityPath;
678   }
679
680   /**
681    * Returns the {@link MarshallingSession} associated with this request.
682    *
683    * @return The request bean session.
684    */
685   public MarshallingSession getMarshallingSession() { return marshallingSession; }
686
687   /**
688    * Returns the charset specified on the <c>Content-Type</c> header, or <js>"UTF-8"</js> if not specified.
689    *
690    * @return The charset to use to decode the request content.
691    */
692   public Charset getCharset() {
693      if (charset == null) {
694         // Determine charset
695         // NOTE:  Don't use super.getCharacterEncoding() because the spec is implemented inconsistently.
696         // Jetty returns the default charset instead of null if the character is not specified on the request.
697         var h = getHeaderParam("Content-Type").orElse(null);
698         if (nn(h)) {
699            var i = h.indexOf(";charset=");
700            if (i > 0)
701               charset = Charset.forName(h.substring(i + 9).trim());
702         }
703         if (charset == null)
704            charset = opContext.getDefaultCharset();
705         if (charset == null)
706            charset = StandardCharsets.UTF_8;
707      }
708      return charset;
709   }
710
711   /**
712    * Config file associated with the resource.
713    *
714    * <p>
715    * Returns a config file with session-level variable resolution.
716    *
717    * The config file is identified via {@link Rest#config()}.
718    *
719    * <h5 class='section'>Example:</h5>
720    * <p class='bjava'>
721    *    <ja>@RestGet</ja>(...)
722    *    <jk>public void</jk> doGet(RestRequest <jv>req</jv>) {
723    *
724    *       <jc>// Get config file.</jc>
725    *       Config <jv>config</jv> = <jv>req</jv>.getConfig();
726    *
727    *       <jc>// Get simple values from config file.</jc>
728    *       <jk>int</jk> <jv>timeout</jv> = <jv>config</jv>.get(<js>"MyResource/timeout"</js>).asInteger().orElse(=10000);
729    *
730    *       <jc>// Get complex values from config file.</jc>
731    *       MyBean <jv>bean</jv> = <jv>config</jv>.get(<js>"MyResource/myBean"</js>).as(MyBean.<jk>class</jk>).orElse(<jk>null</jk>);
732    *    }
733    * </p>
734    *
735    * <h5 class='section'>Notes:</h5><ul>
736    *    <li class='note'>
737    *       The {@link Config} object can also be passed as a parameter on the method.
738    * </ul>
739    *
740    * <h5 class='section'>See Also:</h5><ul>
741    *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ConfigurationFiles">Configuration Files</a>
742    * </ul>
743    *
744    * @return
745    *    The config file associated with the resource, or <jk>null</jk> if resource does not have a config file
746    *    associated with it.
747    */
748   public Config getConfig() {
749      if (config == null)
750         config = context.getConfig().resolving(getVarResolverSession());
751      return config;
752   }
753
754   /**
755    * Request content.
756    *
757    * <p>
758    * Returns a {@link RequestContent} object that encapsulates access to the HTTP request content.
759    *
760    * <h5 class='section'>Example:</h5>
761    * <p class='bjava'>
762    *    <ja>@RestPost</ja>(...)
763    *    <jk>public void</jk> doPost(RestRequest <jv>req</jv>) {
764    *
765    *       <jc>// Convert content to a linked list of Person objects.</jc>
766    *       List&lt;Person&gt; <jv>list</jv> = <jv>req</jv>.getContent().as(LinkedList.<jk>class</jk>, Person.<jk>class</jk>);
767    *       ..
768    *    }
769    * </p>
770    *
771    * <h5 class='section'>Notes:</h5><ul>
772    *    <li class='note'>
773    *       The {@link RequestContent} object can also be passed as a parameter on the method.
774    *    <li class='note'>
775    *       The {@link Content @Content} annotation can be used to access the content as well.
776    * </ul>
777    *
778    * <h5 class='section'>See Also:</h5><ul>
779    *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerHttpParts">HTTP parts in REST servers</a>
780    * </ul>
781    *
782    * @return
783    *    The content of this HTTP request.
784    *    <br>Never <jk>null</jk>.
785    */
786   public RequestContent getContent() { return content; }
787
788   /**
789    * Returns the resource context handling the request.
790    *
791    * <p>
792    * Can be used to access servlet-init parameters or annotations during requests, such as in calls to
793    * {@link RestGuard#guard(RestRequest, RestResponse)}..
794    *
795    * @return The resource context handling the request.
796    */
797   public RestContext getContext() { return context; }
798
799   /**
800    * Returns the correlation id resolved for this request (mint-or-honor), cached at session-build time.
801    *
802    * <p>
803    * Delegates to {@link RestSession#getRequestId()} &mdash; the synchronous, session-cached value, never a live
804    * public/custom-key attribute re-read.  This is the {@code X-Request-Id} correlation id echoed on the response and
805    * carried in the {@code requestId} log-context field.
806    *
807    * @return The resolved request id.  Never <jk>null</jk> (unless a custom id supplier returned <jk>null</jk>).
808    * @since 10.0.0
809    */
810   @Override /* Overridden from ServletRequest (jakarta.servlet-api 6.1+ default method) */
811   public String getRequestId() { return session.getRequestId(); }
812
813   /**
814    * Returns the portion of the request URI that indicates the context of the request.
815    *
816    * <p>The context path always comes first in a request URI.
817    * The path starts with a <js>"/"</js> character but does not end with a <js>"/"</js> character.
818    * For servlets in the default (root) context, this method returns <js>""</js>.
819    * The container does not decode this string.
820    *
821    * @return The context path, never <jk>null</jk>.
822    * @see HttpServletRequest#getContextPath()
823    */
824   @Override
825   public String getContextPath() {
826      var cp = context.getUriContext();
827      return cp == null ? inner.getContextPath() : cp;
828   }
829
830   /**
831    * Returns the request form-data parameter of the specified type.
832    *
833    * <p>
834    * Type must have a name specified via the {@link FormData} annotation
835    * and a public constructor that takes in either <c>value</c> or <c>name,value</c> as strings.
836    *
837    * @param <T> The bean type to create.
838    * @param type The bean type to create.
839    * @return The parsed form-data parameter on the request, never <jk>null</jk>.
840    */
841   public <T> Optional<T> getFormParam(Class<T> type) {
842      return getFormParams().get(type);
843   }
844
845   /**
846    * Shortcut for calling <c>getFormParams().get(name)</c>.
847    *
848    * @param name The form data parameter name.
849    * @return The form data parameter, never <jk>null</jk>.
850    */
851   public RequestFormParam getFormParam(String name) {
852      return getFormParams().get(name);
853   }
854
855   /**
856    * Form-data.
857    *
858    * <p>
859    * Returns a {@link RequestFormParamList} object that encapsulates access to form post parameters.
860    *
861    * <p>
862    * Similar to {@link HttpServletRequest#getParameterMap()}, but only looks for form data in the HTTP content.
863    *
864    * <h5 class='section'>Example:</h5>
865    * <p class='bjava'>
866    *    <ja>@RestPost</ja>(...)
867    *    <jk>public void</jk> doPost(RestRequest <jv>req</jv>) {
868    *
869    *       <jc>// Get access to parsed form data parameters.</jc>
870    *       RequestFormParamList <jv>formParams</jv> = <jv>req</jv>.getFormParams();
871    *
872    *       <jc>// Get form data parameters converted to various types.</jc>
873    *       <jk>int</jk> <jv>p1</jv> = <jv>formParams</jv>.get(<js>"p1"</js>).asInteger().orElse(0);
874    *       String <jv>p2</jv> = <jv>formParams</jv>.get(<js>"p2"</js>).asString().orElse(<jk>null</jk>);
875    *       UUID <jv>p3</jv> = <jv>formParams</jv>.get(<js>"p3"</js>).as(UUID.<jk>class</jk>).orElse(<jk>null</jk>);
876    *    }
877    * </p>
878    *
879    * <h5 class='section'>Notes:</h5><ul>
880    *    <li class='note'>
881    *       This object is modifiable.
882    *    <li class='note'>
883    *       Values are converted from strings using the registered part parser on the resource class.
884    *    <li class='note'>
885    *       The {@link RequestFormParamList} object can also be passed as a parameter on the method.
886    *    <li class='note'>
887    *       The {@link FormData @FormDAta} annotation can be used to access individual form data parameter values.
888    * </ul>
889    *
890    * <h5 class='section'>See Also:</h5><ul>
891    *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerHttpParts">HTTP parts in REST servers</a>
892    * </ul>
893    *
894    * @return
895    *    The URL-encoded form data from the request.
896    *    <br>Never <jk>null</jk>.
897    * @throws InternalServerError If query parameters could not be parsed.
898    * @see FormData
899    */
900   public RequestFormParamList getFormParams() throws InternalServerError {
901      try {
902         if (formParams == null)
903            formParams = new RequestFormParamList(this, true).parser(partParserSession);
904         formParams.addDefault(opContext.getDefaultRequestFormData().getAll());
905         return formParams;
906      } catch (Exception e) {
907         throw new InternalServerError(e);
908      }
909   }
910
911   /**
912    * Returns the request header of the specified type.
913    *
914    * <p>
915    * Type must have a name specified via the {@link Header} annotation
916    * and a public constructor that takes in either <c>value</c> or <c>name,value</c> as strings.
917    *
918    * <p>
919    * Typically any of the following:
920    * <ul class='javatreec'>
921    *    <li class='jc'>{@link Accept}
922    *    <li class='jc'>{@link AcceptCharset}
923    *    <li class='jc'>{@link AcceptEncoding}
924    *    <li class='jc'>{@link AcceptLanguage}
925    *    <li class='jc'>{@link AcceptRanges}
926    *    <li class='jc'>{@link Authorization}
927    *    <li class='jc'>{@link CacheControl}
928    *    <li class='jc'>{@link ClientVersion}
929    *    <li class='jc'>{@link Connection}
930    *    <li class='jc'>{@link ContentDisposition}
931    *    <li class='jc'>{@link ContentEncoding}
932    *    <li class='jc'>{@link ContentLength}
933    *    <li class='jc'>{@link ContentType}
934    *    <li class='jc'>{@link Date}
935    *    <li class='jc'>{@link Debug}
936    *    <li class='jc'>{@link Expect}
937    *    <li class='jc'>{@link Forwarded}
938    *    <li class='jc'>{@link From}
939    *    <li class='jc'>{@link Host}
940    *    <li class='jc'>{@link IfMatch}
941    *    <li class='jc'>{@link IfModifiedSince}
942    *    <li class='jc'>{@link IfNoneMatch}
943    *    <li class='jc'>{@link IfRange}
944    *    <li class='jc'>{@link IfUnmodifiedSince}
945    *    <li class='jc'>{@link MaxForwards}
946    *    <li class='jc'>{@link NoTrace}
947    *    <li class='jc'>{@link Origin}
948    *    <li class='jc'>{@link Pragma}
949    *    <li class='jc'>{@link ProxyAuthorization}
950    *    <li class='jc'>{@link Range}
951    *    <li class='jc'>{@link Referer}
952    *    <li class='jc'>{@link TE}
953    *    <li class='jc'>{@link Thrown}
954    *    <li class='jc'>{@link Upgrade}
955    *    <li class='jc'>{@link UserAgent}
956    *    <li class='jc'>{@link Warning}
957    * </ul>
958    *
959    * @param <T> The bean type to create.
960    * @param type The bean type to create.
961    * @return The parsed header on the request, never <jk>null</jk>.
962    */
963   public <T> Optional<T> getHeader(Class<T> type) {
964      return headers.get(type);
965   }
966
967   /**
968    * Returns the last header with a specified name of this message.
969    *
970    * <p>
971    * If there is more than one matching header in the message the last element of <c>getHeaders(String)</c> is returned.
972    * <br>If there is no matching header in the message, an empty request header object is returned.
973    *
974    * <h5 class='section'>Example:</h5>
975    * <p class='bjava'>
976    *    <jc>// Gets a header and throws a BadRequest if it doesn't exist.</jc>
977    *    <jv>request</jv>
978    *       .getHeader(<js>"Foo"</js>)
979    *       .assertValue().exists()
980    *       .get();
981    * </p>
982    *
983    * @param name The header name.
984    * @return The request header object, never <jk>null</jk>.
985    */
986   public RequestHeader getHeaderParam(String name) {
987      return headers.getLast(name);
988   }
989
990   /**
991    * Request headers.
992    *
993    * <p>
994    * Returns a {@link RequestHeaderList} object that encapsulates access to HTTP headers on the request.
995    *
996    * <h5 class='section'>Example:</h5>
997    * <p class='bjava'>
998    *    <ja>@RestPost</ja>(...)
999    *    <jk>public</jk> Object myMethod(RestRequest <jv>req</jv>) {
1000    *
1001    *       <jc>// Get access to headers.</jc>
1002    *       RequestHeaderList <jv>headers</jv> = <jv>req</jv>.getRequestHeaders();
1003    *
1004    *       <jc>// Add a default value.</jc>
1005    *       <jv>headers</jv>.addDefault(<js>"ETag"</js>, <jsf>DEFAULT_UUID</jsf>);
1006    *
1007    *    <jc>// Get a header value as a POJO.</jc>
1008    *       UUID etag = <jv>headers</jv>.get(<js>"ETag"</js>).as(UUID.<jk>class</jk>).orElse(<jk>null</jk>);
1009    *
1010    *       <jc>// Get a standard header.</jc>
1011    *       Optional&lt;CacheControl&gt; = <jv>headers</jv>.getCacheControl();
1012    *    }
1013    * </p>
1014    *
1015    * <h5 class='section'>Notes:</h5><ul>
1016    *    <li class='note'>
1017    *       This object is modifiable.
1018    *    <li class='note'>
1019    *       Values are converted from strings using the registered part parser on the resource class.
1020    *    <li class='note'>
1021    *       The {@link RequestHeaderList} object can also be passed as a parameter on the method.
1022    *    <li class='note'>
1023    *       The {@link Header @Header} annotation can be used to access individual header values.
1024    * </ul>
1025    *
1026    * <h5 class='section'>See Also:</h5><ul>
1027    *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerHttpParts">HTTP parts in REST servers</a>
1028    * </ul>
1029    *
1030    * @return
1031    *    The headers on this request.
1032    *    <br>Never <jk>null</jk>.
1033    */
1034   public RequestHeaderList getHeaders() { return headers; }
1035
1036   /**
1037    * Returns the wrapped servlet request.
1038    *
1039    * @return The wrapped servlet request.
1040    */
1041   public HttpServletRequest getHttpServletRequest() { return inner; }
1042
1043   /**
1044    * Returns the HTTP content content as an {@link InputStream}.
1045    *
1046    * <p>
1047    * Automatically handles GZipped input streams.
1048    *
1049    * <p>
1050    * This method is equivalent to calling <c>getContent().getInputStream()</c>.
1051    *
1052    * @return The negotiated input stream.
1053    * @throws IOException If any error occurred while trying to get the input stream or wrap it in the GZIP wrapper.
1054    */
1055   @Override
1056   public ServletInputStream getInputStream() throws IOException { return getContent().getInputStream(); }
1057
1058   /**
1059    * Returns the preferred Locale that the client will accept content in, based on the Accept-Language header.
1060    *
1061    * <p>
1062    * If the client request doesn't provide an <c>Accept-Language</c> header, this method returns the default locale for the server.
1063    *
1064    * @return The preferred Locale that the client will accept content in.  Never <jk>null</jk>.
1065    */
1066   @Override
1067   public Locale getLocale() {
1068      var best = inner.getLocale();
1069      var h = headers.get("Accept-Language").asString().orElse(null);
1070      if (nn(h)) {
1071         var sr = StringRanges.of(h);
1072         float qValue = 0;
1073         for (var r : sr.toList()) {
1074            if (r.getQValue() > qValue) {
1075               best = toLocale(r.getName());
1076               qValue = r.getQValue();
1077            }
1078         }
1079      }
1080      return best;
1081   }
1082
1083   /**
1084    * Shortcut method for calling {@link RestRequest#getMessages()} and {@link Messages#getString(String,Object...)}.
1085    *
1086    * @param key The message key.
1087    * @param args Optional {@link MessageFormat}-style arguments.
1088    * @return The localized message.
1089    */
1090   public String getMessage(String key, Object...args) {
1091      return getMessages().getString(key, args);
1092   }
1093
1094   /**
1095    * Returns the resource bundle for the request locale.
1096    *
1097    * <h5 class='section'>Example:</h5>
1098    * <p class='bjava'>
1099    *    <ja>@RestGet</ja>
1100    *    <jk>public</jk> String hello(RestRequest <jv>req</jv>, <ja>@Query</ja>(<js>"user"</js>) String <jv>user</jv>) {
1101    *
1102    *       <jc>// Return a localized message.</jc>
1103    *       <jk>return</jk> <jv>req</jv>.getMessages().getString(<js>"hello.message"</js>, <jv>user</jv>);
1104    *    }
1105    * </p>
1106    *
1107    * <h5 class='section'>Notes:</h5><ul>
1108    *    <li class='note'>
1109    *       The {@link Messages} object can also be passed as a parameter on the method.
1110    * </ul>
1111    *
1112    * <h5 class='section'>See Also:</h5><ul>
1113    *    <li class='jm'>{@link RestRequest#getMessage(String,Object...)}
1114    *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/LocalizedMessages">Localized Messages</a>
1115    * </ul>
1116    *
1117    * @return
1118    *    The resource bundle.
1119    *    <br>Never <jk>null</jk>.
1120    */
1121   public Messages getMessages() { return context.getMessages().forLocale(getLocale()); }
1122
1123   /**
1124    * Returns the HTTP method of this request.
1125    *
1126    * <p>
1127    * If <c>allowHeaderParams</c> init parameter is <jk>true</jk>, then first looks for
1128    * <c>&amp;method=xxx</c> in the URL query string.
1129    *
1130    * @return The HTTP method of this request.
1131    */
1132   @Override
1133   public String getMethod() { return session.getMethod(); }
1134
1135   /**
1136    * Returns access to the inner {@link RestOpContext} of this method.
1137    *
1138    * @return The {@link RestOpContext} of this method.  May be <jk>null</jk> if method has not yet been found.
1139    */
1140   public RestOpContext getOpContext() { return opContext; }
1141
1142   /**
1143    * Returns the swagger for the Java method invoked.
1144    *
1145    * @return The swagger for the Java method as an {@link Optional}.  Never <jk>null</jk>.
1146    */
1147   public Optional<Operation> getOperationSwagger() {
1148
1149      var swagger2 = getSwagger();
1150      if (! swagger2.isPresent())
1151         return oe();
1152
1153      return o(swagger2.get().getOperation(opContext.getPathPattern(), lcr(getMethod())));
1154   }
1155
1156   /**
1157    * Returns the part parser session associated with this request.
1158    *
1159    * @return The part parser session associated with this request.
1160    */
1161   public HttpPartParserSession getPartParserSession() { return partParserSession; }
1162
1163   /**
1164    * Returns the part serializer session for this request.
1165    *
1166    * @return The part serializer session for this request.
1167    */
1168   public HttpPartSerializerSession getPartSerializerSession() { return opContext.getPartSerializer().getPartSession(); }
1169
1170   /**
1171    * Returns the request path parameter of the specified type.
1172    *
1173    * <p>
1174    * Type must have a name specified via the {@link Path} annotation
1175    * and a public constructor that takes in either <c>value</c> or <c>name,value</c> as strings.
1176    *
1177    * @param <T> The bean type to create.
1178    * @param type The bean type to create.
1179    * @return The parsed path parameter on the request, never <jk>null</jk>.
1180    */
1181   public <T> Optional<T> getPathParam(Class<T> type) {
1182      return pathParams.get(type);
1183   }
1184
1185   /**
1186    * Shortcut for calling <c>getPathParams().get(<jv>name</jv>)</c>.
1187    *
1188    * @param name The path parameter name.
1189    * @return The path parameter, never <jk>null</jk>.
1190    */
1191   public RequestPathParam getPathParam(String name) {
1192      return pathParams.get(name);
1193   }
1194
1195   /**
1196    * Path parameters.
1197    *
1198    * <p>
1199    * Returns a {@link RequestPathParamList} object that encapsulates access to URL path parameters.
1200    *
1201    * <h5 class='section'>Example:</h5>
1202    * <p class='bjava'>
1203    *    <ja>@RestGet</ja>(<js>"/{foo}/{bar}/{baz}/*"</js>)
1204    *    <jk>public void</jk> doGet(RestRequest <jv>req</jv>) {
1205    *
1206    *       <jc>// Get access to path data.</jc>
1207    *       RequestPathParamList <jv>pathParams</jv> = <jv>req</jv>.getPathParams();
1208    *
1209    *       <jc>// Example URL:  /123/qux/true/quux</jc>
1210    *
1211    *       <jk>int</jk> <jv>foo</jv> = <jv>pathParams</jv>.get(<js>"foo"</js>).asInteger().orElse(-1);  <jc>// =123</jc>
1212    *       String <jv>bar</jv> = <jv>pathParams</jv>.get(<js>"bar"</js>).orElse(<jk>null</jk>);  <jc>// =qux</jc>
1213    *       <jk>boolean</jk> <jv>baz</jv> = <jv>pathParams</jv>.get(<js>"baz"</js>).asBoolean().orElse(<jk>false</jk>);  <jc>// =true</jc>
1214    *       String <jv>remainder</jv> = <jv>pathParams</jv>.getRemainder().orElse(<jk>null</jk>);  <jc>// =quux</jc>
1215    *    }
1216    * </p>
1217    *
1218    * <h5 class='section'>Notes:</h5><ul>
1219    *    <li class='note'>
1220    *       This object is modifiable.
1221    * </ul>
1222    *
1223    * @return
1224    *    The path parameters.
1225    *    <br>Never <jk>null</jk>.
1226    */
1227   public RequestPathParamList getPathParams() { return pathParams; }
1228
1229   /**
1230    * Shortcut for calling <c>getPathParams().getRemainder()</c>.
1231    *
1232    * @return The path remainder value, never <jk>null</jk>.
1233    */
1234   public RequestPathParam getPathRemainder() { return pathParams.getRemainder(); }
1235
1236   /**
1237    * Returns the protocol version from the request line of this request.
1238    *
1239    * @return The protocol version from the request line of this request.
1240    */
1241   public HttpProtocolVersion getProtocolVersion() { return getRequestLine().getProtocolVersion(); }
1242
1243   /**
1244    * Returns the request query parameter of the specified type.
1245    *
1246    * <p>
1247    * Type must have a name specified via the {@link Query} annotation
1248    * and a public constructor that takes in either <c>value</c> or <c>name,value</c> as strings.
1249    *
1250    * @param <T> The bean type to create.
1251    * @param type The bean type to create.
1252    * @return The parsed query parameter on the request, never <jk>null</jk>.
1253    */
1254   public <T> Optional<T> getQueryParam(Class<T> type) {
1255      return queryParams.get(type);
1256   }
1257
1258   /**
1259    * Shortcut for calling <c>getRequestQuery().getLast(<jv>name</jv>)</c>.
1260    *
1261    * @param name The query parameter name.
1262    * @return The query parameter, never <jk>null</jk>.
1263    */
1264   public RequestQueryParam getQueryParam(String name) {
1265      return queryParams.get(name);
1266   }
1267
1268   /**
1269    * Query parameters.
1270    *
1271    * <p>
1272    * Returns a {@link RequestQueryParamList} object that encapsulates access to URL GET parameters.
1273    *
1274    * <p>
1275    * Similar to {@link HttpServletRequest#getParameterMap()} but only looks for query parameters in the URL and not form posts.
1276    *
1277    * <h5 class='section'>Example:</h5>
1278    * <p class='bjava'>
1279    *    <ja>@RestGet</ja>(...)
1280    *    <jk>public void</jk> doGet(RestRequest <jv>req</jv>) {
1281    *
1282    *       <jc>// Get access to query parameters on the URL.</jc>
1283    *       RequestQueryParamList <jv>query</jv> = <jv>req</jv>.getQuery();
1284    *
1285    *       <jc>// Get query parameters converted to various types.</jc>
1286    *       <jk>int</jk> <jv>p1/</jv> = <jv>query</jv>.getInteger(<js>"p1"</js>).orElse(<jk>null</jk>);
1287    *       String <jv>p2</jv> = <jv>query</jv>.getString(<js>"p2"</js>).orElse(<jk>null</jk>);
1288    *       UUID <jv>p3</jv> = <jv>query</jv>.get(<js>"p3"</js>).as(UUID.<jk>class</jk>).orElse(<jk>null</jk>);
1289    *    }
1290    * </p>
1291    *
1292    * <h5 class='section'>Notes:</h5><ul>
1293    *    <li class='note'>
1294    *       This object is modifiable.
1295    * </ul>
1296    *
1297    * <h5 class='section'>See Also:</h5><ul>
1298    *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerHttpParts">HTTP parts in REST servers</a>
1299    * </ul>
1300    *
1301    * @return
1302    *    The query parameters as a modifiable map.
1303    *    <br>Never <jk>null</jk>.
1304    */
1305   public RequestQueryParamList getQueryParams() { return queryParams; }
1306
1307   /**
1308    * Returns the HTTP content content as a {@link Reader}.
1309    *
1310    * <p>
1311    * If {@code allowHeaderParams} init parameter is true, then first looks for {@code &content=xxx} in the URL query
1312    * string.
1313    *
1314    * <p>
1315    * Automatically handles GZipped input streams.
1316    *
1317    * <p>
1318    * This method is equivalent to calling <c>getContent().getReader()</c>.
1319    *
1320    * @return The HTTP content content as a {@link Reader}.
1321    * @throws IOException If content could not be read.
1322    */
1323   @Override
1324   public BufferedReader getReader() throws IOException { return getContent().getReader(); }
1325
1326   /**
1327    * Creates a proxy interface to retrieve HTTP parts of this request as a proxy bean.
1328    *
1329    * <h5 class='section'>Examples:</h5>
1330    * <p class='bjava'>
1331    *    <ja>@RestPost</ja>(<js>"/mypath/{p1}/{p2}/*"</js>)
1332    *    <jk>public void</jk> myMethod(<ja>@Request</ja> MyRequest <jv>requestBean</jv>) {...}
1333    *
1334    *    <jk>public interface</jk> MyRequest {
1335    *
1336    *       <ja>@Path</ja> <jc>// Path variable name inferred from getter.</jc>
1337    *       String getP1();
1338    *
1339    *       <ja>@Path</ja>(<js>"p2"</js>)
1340    *       String getX();
1341    *
1342    *       <ja>@Path</ja>(<js>"/*"</js>)
1343    *       String getRemainder();
1344    *
1345    *       <ja>@Query</ja>
1346    *       String getQ1();
1347    *
1348    *    <jc>// Schema-based query parameter:  Pipe-delimited lists of comma-delimited lists of integers.</jc>
1349    *       <ja>@Query</ja>(
1350    *          collectionFormat=<js>"pipes"</js>
1351    *          items=<ja>@Items</ja>(
1352    *             items=<ja>@SubItems</ja>(
1353    *                collectionFormat=<js>"csv"</js>
1354    *                type=<js>"integer"</js>
1355    *             )
1356    *          )
1357    *       )
1358    *       <jk>int</jk>[][] getQ3();
1359    *
1360    *       <ja>@Header</ja>(<js>"*"</js>)
1361    *       Map&lt;String,Object&gt; getHeaders();
1362    * </p>
1363    *
1364    * @param <T> The request bean interface to instantiate.
1365    * @param c The request bean interface to instantiate.
1366    * @return A new request bean proxy for this REST request.
1367    */
1368   public <T> T getRequest(Class<T> c) {
1369      return getRequest(RequestBeanMeta.create(c, getContext().getAnnotations()));
1370   }
1371
1372   /**
1373    * Same as {@link #getRequest(Class)} but used on pre-instantiated {@link RequestBeanMeta} objects.
1374    *
1375    * @param <T> The request bean interface to instantiate.
1376    * @param rbm The metadata about the request bean interface to create.
1377    *    <br>Must not be <jk>null</jk>.
1378    * @return A new request bean proxy for this REST request.
1379    */
1380   public <T> T getRequest(RequestBeanMeta rbm) {
1381      return getRequest(rbm, null);
1382   }
1383
1384   /**
1385    * Same as {@link #getRequest(RequestBeanMeta)}, but reuses setters already resolved when the {@code @RestOp}'s
1386    * arguments were built instead of resolving them on this call.
1387    *
1388    * @param <T> The request bean interface to instantiate.
1389    * @param rbm The metadata about the request bean interface to create.
1390    *    <br>Must not be <jk>null</jk>.
1391    * @param setters
1392    *    The pre-resolved constructor/setters for a concrete bean class, or <jk>null</jk> to resolve them on this call
1393    *    (callers such as {@code Queryable} that only have a {@link Class}, not a registered {@link RequestBeanArg}).
1394    *    Resolving here throws the same {@link IllegalArgumentException} message as {@link RequestBeanArg},
1395    *    wrapped the same way as any other exception from this method.
1396    * @return A new request bean proxy or instance for this REST request.
1397    */
1398   public <T> T getRequest(RequestBeanMeta rbm, RequestBeanSetters setters) {
1399      try {
1400         var c = (Class<T>)rbm.getBeanInfo().inner();
1401         final MarshallingSession bs = getMarshallingSession();
1402         if (c.isInterface())
1403            return (T)Proxy.newProxyInstance(c.getClassLoader(), a(c), (InvocationHandler)(proxy, method, args) -> {
1404               RequestBeanPropertyMeta pm = rbm.getProperty(method.getName());
1405               if (nn(pm))
1406                  return resolveRequestBeanProperty(pm, method, bs);
1407               return null;
1408            });
1409         return newRequestBean(rbm, bs, nn(setters) ? setters : RequestBeanSetters.resolve(c, rbm));
1410      } catch (Exception e) {
1411         throw toRex(e);
1412      }
1413   }
1414
1415   /**
1416    * Instantiates a concrete {@link Request @Request} bean class via the resolved {@link RequestBeanSetters} and
1417    * populates it by invoking the setter matching each annotated getter.  Absent parameters (a <jk>null</jk> resolved
1418    * value) are left at their default.
1419    */
1420   private <T> T newRequestBean(RequestBeanMeta rbm, MarshallingSession bs, RequestBeanSetters setters) throws Exception {
1421      T bean = setters.newInstance();
1422      for (var pm : rbm.getProperties()) {
1423         var getter = pm.getGetter();
1424         var value = resolveRequestBeanProperty(pm, getter, bs);
1425         if (value == null)
1426            continue;
1427         setters.set(bean, getter, value);
1428      }
1429      return bean;
1430   }
1431
1432   private Object resolveRequestBeanProperty(RequestBeanPropertyMeta pm, java.lang.reflect.Method method, MarshallingSession bs) throws Exception {
1433      HttpPartParserSession pp = pm.getParser(getPartParserSession());
1434      HttpPartSchema schema = pm.getSchema();
1435      String name = pm.getPartName();
1436      var type = bs.getClassMeta(method.getGenericReturnType());
1437      HttpPartType pt = pm.getPartType();
1438      if (pt == HttpPartType.BODY)
1439         return getContent().setSchema(schema).as(type);
1440      if (schema.getCollectionFormat() == HttpPartCollectionFormat.MULTI) {
1441         var valueType = type.isOptional() ? type.getElementType() : type;
1442         if (valueType.isMap())
1443            return resolveRequestBeanMultiMap(pt, name, schema, type);
1444         if (valueType.isCollectionOrArray())
1445            return resolveRequestBeanMultiCollection(pt, name, pp, schema, type);
1446      }
1447      var part = requestBeanPartLast(pt, name);
1448      if (part == null)
1449         return type.isOptional() ? Optional.empty() : null;
1450      return part.parser(pp).schema(schema).def(schema.getDefault()).as(type).orElse(null);
1451   }
1452
1453   private Object resolveRequestBeanMultiMap(HttpPartType pt, String name, HttpPartSchema schema, ClassMeta<?> type) {
1454      try {
1455         var parts = requestBeanParts(pt, name);
1456         var optional = type.isOptional();
1457         if (parts.isEmpty()) {
1458            if (optional) {
1459               if (ine(schema.getDefault()))
1460                  return Optional.of(UonUtils.mergePairs(schema.getDefault()));
1461               return Optional.empty();
1462            }
1463            return JsonMap.create();
1464         }
1465         var dest = JsonMap.create();
1466         for (var p : parts)
1467            UonUtils.mergePair(dest, p.getValue());
1468         return optional ? o(dest) : dest;
1469      } catch (ParseException | SchemaValidationException e) {
1470         throw new BadRequest(e, "Could not parse query parameter '%s'.", name);
1471      }
1472   }
1473
1474   private Object resolveRequestBeanMultiCollection(HttpPartType pt, String name, HttpPartParserSession pp, HttpPartSchema schema, ClassMeta<?> type) throws Exception {
1475      var optional = type.isOptional();
1476      var raw = optional ? type.getElementType() : type;
1477      var elemType = raw.getElementType();
1478      var parts = requestBeanParts(pt, name);
1479      if (parts.isEmpty()) {
1480         if (optional) {
1481            if (ine(schema.getDefault()))
1482               return Optional.of(readDefaultCollection(schema.getDefault(), raw, elemType, pp, schema, pt));
1483            return Optional.empty();
1484         }
1485         return emptyCollection(raw, elemType);
1486      }
1487      Collection c = newCollection(raw);
1488      for (var p : parts) {
1489         if (elemType.is(String.class)) {
1490            var v = p.getValue();
1491            c.add(v == null ? "" : v);
1492         } else {
1493            c.add(p.parser(pp).schema(schema).as(elemType).orElse(null));
1494         }
1495      }
1496      if (optional && elemType.is(String.class) && c.stream().allMatch(x -> isBlank(s(x))))
1497         return Optional.of(emptyCollection(raw, elemType));
1498      var collected = raw.isArray() ? CollectionUtils.toArray(c, elemType.inner()) : c;
1499      return optional ? o(collected) : collected;
1500   }
1501
1502   @SuppressWarnings({
1503      "java:S112" // throws Exception intentional - HttpPartParserSession.read declares Exception
1504   })
1505   private Object readDefaultCollection(String def, ClassMeta<?> raw, ClassMeta<?> elemType, HttpPartParserSession pp, HttpPartSchema schema, HttpPartType pt) throws Exception {
1506      try {
1507         var one = pp.read(pt, schema, def, elemType);
1508         var c = newCollection(raw);
1509         c.add(one);
1510         return raw.isArray() ? CollectionUtils.toArray(c, elemType.inner()) : c;
1511      } catch (ParseException | SchemaValidationException e) {
1512         throw new BadRequest(e, "Could not parse %s parameter '%s'.", pt.toString().toLowerCase(), schema.getName());
1513      }
1514   }
1515
1516   private static Collection newCollection(ClassMeta<?> raw) {
1517      if (raw.isArray())
1518         return CollectionUtils.list();
1519      if (raw.canCreateNewInstance())
1520         return (Collection)raw.newInstance();
1521      return new JsonList();
1522   }
1523
1524   private static Object emptyCollection(ClassMeta<?> raw, ClassMeta<?> elemType) {
1525      var c = newCollection(raw);
1526      return raw.isArray() ? CollectionUtils.toArray(c, elemType.inner()) : c;
1527   }
1528
1529   private List<? extends RequestHttpPart> requestBeanParts(HttpPartType pt, String name) {
1530      return switch (pt) {
1531         case QUERY -> getQueryParams().getAll(name);
1532         case FORMDATA -> getFormParams().getAll(name);
1533         case HEADER -> getHeaders().getAll(name);
1534         case PATH -> getPathParams().getAll(name);
1535         default -> List.of();
1536      };
1537   }
1538
1539   private RequestHttpPart requestBeanPartLast(HttpPartType pt, String name) {
1540      return switch (pt) {
1541         case QUERY -> getQueryParams().getLast(name);
1542         case FORMDATA -> getFormParams().getLast(name);
1543         case HEADER -> getHeaderParam(name);
1544         case PATH -> getPathParams().getLast(name);
1545         default -> null;
1546      };
1547   }
1548
1549   /**
1550    * Returns the request line of this request.
1551    *
1552    * @return The request line of this request.
1553    */
1554   public HttpRequestLine getRequestLine() {
1555      var x = inner.getProtocol();
1556      var i = x.indexOf('/');
1557      var j = x.indexOf('.', i);
1558      var pv = HttpProtocolVersion.of(x.substring(0, i), StringUtils.parseInt(x.substring(i + 1, j)), StringUtils.parseInt(x.substring(j + 1)));
1559      return HttpRequestLineBean.of(inner.getMethod(), inner.getRequestURI(), pv);
1560   }
1561
1562   /**
1563    * Returns the HTTP response associated with this request.
1564    *
1565    * <p>
1566    * Only safe to call once the REST call has reached the point of invoking the matched Java method, e.g. from inside
1567    * a {@link org.apache.juneau.rest.server.converter.RestConverter}, which only ever runs after method invocation.
1568    * Calling it any earlier throws {@link InternalServerError}.
1569    *
1570    * @return The response associated with this request.
1571    */
1572   public RestResponse getResponse() {
1573      return session.getOpSession().getResponse();
1574   }
1575
1576   /**
1577    * Returns the part of this request's URL that calls the servlet.
1578    *
1579    * <p>
1580    * This path starts with a <js>"/"</js> character and includes either the servlet name or a path to the servlet,
1581    * but does not include any extra path information or a query string.
1582    *
1583    * @return The servlet path, never <jk>null</jk>.
1584    * @see HttpServletRequest#getServletPath()
1585    */
1586   @Override
1587   public String getServletPath() {
1588      var cp = context.getUriContext();
1589      var sp = inner.getServletPath();
1590      return cp == null || ! sp.startsWith(cp) ? sp : sp.substring(cp.length());
1591   }
1592
1593   /**
1594    * Returns the static files associated with the resource.
1595    *
1596    * <p>
1597    * Shortcut for {@link RestContext#getStaticFiles()}.
1598    *
1599    * @return
1600    *    The static files for this resource.
1601    *    <br>Never <jk>null</jk>.
1602    */
1603   public StaticFiles getStaticFiles() { return context.getStaticFiles(); }
1604
1605   /**
1606    * Returns the localized swagger associated with the resource.
1607    *
1608    * <p>
1609    * A shortcut for calling <c>getInfoProvider().getSwagger(request);</c>
1610    *
1611    * <h5 class='section'>Example:</h5>
1612    * <p class='bjava'>
1613    *    <ja>@RestGet</ja>
1614    *    <jk>public</jk> List&lt;Tag&gt; swaggerTags(RestRequest <jv>req</jv>) {
1615    *       <jk>return</jk> <jv>req</jv>.getSwagger().getTags();
1616    *    }
1617    * </p>
1618    *
1619    * <h5 class='section'>Notes:</h5><ul>
1620    *    <li class='note'>
1621    *       The {@link Swagger} object can also be passed as a parameter on the method.
1622    * </ul>
1623    *
1624    * <h5 class='section'>See Also:</h5><ul>
1625    *    <li class='ja'>{@link Rest#swaggerProvider()}
1626    *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanSwagger2">juneau-bean-swagger-v2</a>
1627    * </ul>
1628    *
1629    * @return
1630    *    The swagger associated with the resource.
1631    *    <br>Never <jk>null</jk>.
1632    */
1633   public Optional<Swagger> getSwagger() { return context.getSwagger(getLocale()); }
1634
1635   /**
1636    * Returns the localized OpenAPI 3.1 document associated with the resource.
1637    *
1638    * <p>
1639    * Sibling of {@link #getSwagger()} for the OpenAPI 3.1 emission path.
1640    *
1641    * <h5 class='section'>See Also:</h5><ul>
1642    *    <li class='ja'>{@link Rest#openApiProvider()}
1643    *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ApiDocsMixins">API-Docs Mixin Pack</a>
1644    * </ul>
1645    *
1646    * @return The OpenAPI 3.1 document associated with the resource. Never <jk>null</jk>.
1647    */
1648   public Optional<OpenApi> getOpenApi() { return context.getOpenApi(getLocale()); }
1649
1650   /**
1651    * Returns the <c>Time-Zone</c> header value on the request if there is one.
1652    *
1653    * <p>
1654    * Example: <js>"GMT"</js>.
1655    *
1656    * @return The parsed header on the request, never <jk>null</jk>.
1657    */
1658   public Optional<TimeZone> getTimeZone() {
1659      var tz = headers.get("Time-Zone").asString().orElse(null);
1660      if (nn(tz))
1661         return o(TimeZone.getTimeZone(tz));
1662      return oe();
1663   }
1664
1665   /**
1666    * Returns the URI for this request.
1667    *
1668    * <p>
1669    * Similar to {@link #getRequestURI()} but returns the value as a {@link URI}.
1670    * It also gives you the capability to override the query parameters (e.g. add new query parameters to the existing
1671    * URI).
1672    *
1673    * @param includeQuery If <jk>true</jk> include the query parameters on the request.
1674    * @param addQueryParams Augment the request URI with the specified query parameters.
1675    *    <br>Can be <jk>null</jk> (no additional parameters are added).
1676    * @return A new URI.
1677    */
1678   public URI getUri(boolean includeQuery, Map<String,Object> addQueryParams) {
1679      var uri = inner.getRequestURI();
1680      if (includeQuery || nn(addQueryParams)) {
1681         var sb = new StringBuilder(uri);
1682         var rq = queryParams.copy();
1683         if (nn(addQueryParams))
1684            for (var e : addQueryParams.entrySet())
1685               rq.set(e.getKey(), e.getValue());
1686         if (! rq.isEmpty())
1687            sb.append('?').append(rq.asQueryString());
1688         uri = sb.toString();
1689      }
1690      try {
1691         return new URI(uri);
1692      } catch (URISyntaxException e) {
1693         // Shouldn't happen.
1694         throw toRex(e);
1695      }
1696   }
1697
1698   /**
1699    * Returns the URI context of the request.
1700    *
1701    * <p>
1702    * The URI context contains all the information about the URI of the request, such as the servlet URI, context
1703    * path, etc...
1704    *
1705    * @return The URI context of the request.
1706    */
1707   public UriContext getUriContext() {
1708      if (uriContext == null)
1709         uriContext = UriContext.of(getAuthorityPath(), getContextPath(), getServletPath(), urlEncodePath(inner.getPathInfo()));
1710      return uriContext;
1711   }
1712
1713   /**
1714    * Shortcut for calling {@link #getUriResolver()} using {@link UriResolution#ROOT_RELATIVE} and
1715    * {@link UriRelativity#RESOURCE}
1716    *
1717    * @return The URI resolver for this request.
1718    */
1719   public UriResolver getUriResolver() { return UriResolver.of(context.getUriResolution(), context.getUriRelativity(), getUriContext()); }
1720
1721   /**
1722    * Returns a URI resolver that can be used to convert URIs to absolute or root-relative form.
1723    *
1724    * @param resolution The URI resolution rule.
1725    * @param relativity The relative URI relativity rule.
1726    * @return The URI resolver for this request.
1727    */
1728   public UriResolver getUriResolver(UriResolution resolution, UriRelativity relativity) {
1729      return UriResolver.of(resolution, relativity, getUriContext());
1730   }
1731
1732   /**
1733    * Request-level variable resolver session.
1734    *
1735    * <p>
1736    * Used to resolve SVL variables in text.
1737    *
1738    * <h5 class='section'>Example:</h5>
1739    * <p class='bjava'>
1740    *    <ja>@RestGet</ja>
1741    *    <jk>public</jk> String hello(RestRequest <jv>req</jv>) {
1742    *
1743    *       <jc>// Get var resolver session.</jc>
1744    *       VarResolverSession <jv>session</jv> = getVarResolverSession();
1745    *
1746    *       <jc>// Use it to construct a customized message from a query parameter.</jc>
1747    *       <jk>return</jk> <jv>session</jv>.resolve(<js>"Hello $RQ{user}!"</js>);
1748    *    }
1749    * </p>
1750    *
1751    * <h5 class='section'>Notes:</h5><ul>
1752    *    <li class='note'>
1753    *       The {@link VarResolverSession} object can also be passed as a parameter on the method.
1754    * </ul>
1755    *
1756    * <h5 class='section'>See Also:</h5><ul>
1757    *    <li class='jm'>{@link RestContext#getVarResolver()}
1758    *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerSvlVariables">REST Server SVL Variables</a>
1759    * </ul>
1760    *
1761    * @return The variable resolver for this request.
1762    */
1763   public VarResolverSession getVarResolverSession() {
1764      // @formatter:off
1765      if (varSession == null)
1766         varSession = context
1767            .getVarResolver()
1768            .createSession(session.getBeanStore())
1769            .bean(RestRequest.class, this)
1770            .bean(RestSession.class, session);
1771      return varSession;
1772      // @formatter:on
1773   }
1774
1775   /**
1776    * Log-level query only: returns whether this request's resolved (per-operation) JUL logger is loggable at
1777    * {@link java.util.logging.Level#FINE FINE}-or-finer.
1778    *
1779    * <p>
1780    * This is read-only; there is no request attribute or setter driving it. It reports <b>logging verbosity</b> only
1781    * and is <b>not</b> consulted by any framework behavior. Do <b>not</b> key security or marshalling behavior on this
1782    * method &mdash; use the explicit, non-logging controls instead:
1783    * <ul class='spaced-list'>
1784    *    <li>Echo-endpoint reachability &rarr; {@link org.apache.juneau.rest.server.ops.EchoMixin.Builder#enabled(Boolean) EchoMixin enablement}.
1785    *    <li>Serializer/parser debug behavior &rarr; {@link #isDebugMarshalling()} / {@link Rest#debugMarshalling() @Rest(debugMarshalling)}.
1786    * </ul>
1787    *
1788    * @return <jk>true</jk> if the resolved logger is loggable at {@code FINE}-or-finer.
1789    */
1790   public boolean isDebug() { return opContext.getLogger().isLoggable(java.util.logging.Level.FINE); }
1791
1792   /**
1793    * Returns the resolved per-operation {@link RichLogger} for this request.
1794    *
1795    * <p>
1796    * This is the same logger {@link #isDebug()} queries (`opContext.getLogger()`, delegating to
1797    * {@link RestOpContext#getLogger()}), so anything logged through it &mdash; including the
1798    * {@code fine}/{@code info}/&hellip; convenience methods below &mdash; rides the existing parent-chain capture
1799    * propagation: a {@code LogRecordCapture} attached at a resource-class-level logger name still
1800    * observes these per-operation emissions.
1801    *
1802    * <p>
1803    * These convenience methods are for <b>application handler code</b> that wants to emit its own ad hoc log statements
1804    * <i>during</i> request processing and have them automatically carry whatever {@link RichLogger#context() LogContext}
1805    * is active (most notably {@code requestId}). They are complementary to &mdash; not a replacement for &mdash; the
1806    * REST debug pipeline's one cumulative per-request record; they produce independent, additional {@code LogRecord}s.
1807    *
1808    * @return The resolved per-operation logger. Never <jk>null</jk>.
1809    */
1810   public RichLogger getLogger() { return opContext.getLogger(); }
1811
1812   /**
1813    * Logs a formatted message at {@link java.util.logging.Level#SEVERE SEVERE} through this request's {@link #getLogger() logger}.
1814    *
1815    * @param pattern The {@link java.util.Formatter}-style message pattern.
1816    * @param args The message arguments.
1817    */
1818   public void severe(String pattern, Object...args) { getLogger().severe(pattern, args); }
1819
1820   /**
1821    * Logs a formatted message with an associated throwable at {@link java.util.logging.Level#SEVERE SEVERE}.
1822    *
1823    * @param thrown The throwable to associate with the record.
1824    * @param pattern The {@link java.util.Formatter}-style message pattern.
1825    * @param args The message arguments.
1826    */
1827   public void severe(Throwable thrown, String pattern, Object...args) { getLogger().severe(thrown, pattern, args); }
1828
1829   /**
1830    * Logs a formatted message at {@link java.util.logging.Level#WARNING WARNING} through this request's {@link #getLogger() logger}.
1831    *
1832    * @param pattern The {@link java.util.Formatter}-style message pattern.
1833    * @param args The message arguments.
1834    */
1835   public void warning(String pattern, Object...args) { getLogger().warning(pattern, args); }
1836
1837   /**
1838    * Logs a formatted message with an associated throwable at {@link java.util.logging.Level#WARNING WARNING}.
1839    *
1840    * @param thrown The throwable to associate with the record.
1841    * @param pattern The {@link java.util.Formatter}-style message pattern.
1842    * @param args The message arguments.
1843    */
1844   public void warning(Throwable thrown, String pattern, Object...args) { getLogger().warning(thrown, pattern, args); }
1845
1846   /**
1847    * Logs a formatted message at {@link java.util.logging.Level#INFO INFO} through this request's {@link #getLogger() logger}.
1848    *
1849    * @param pattern The {@link java.util.Formatter}-style message pattern.
1850    * @param args The message arguments.
1851    */
1852   public void info(String pattern, Object...args) { getLogger().info(pattern, args); }
1853
1854   /**
1855    * Logs a formatted message with an associated throwable at {@link java.util.logging.Level#INFO INFO}.
1856    *
1857    * @param thrown The throwable to associate with the record.
1858    * @param pattern The {@link java.util.Formatter}-style message pattern.
1859    * @param args The message arguments.
1860    */
1861   public void info(Throwable thrown, String pattern, Object...args) { getLogger().info(thrown, pattern, args); }
1862
1863   /**
1864    * Logs a formatted message at {@link java.util.logging.Level#CONFIG CONFIG} through this request's {@link #getLogger() logger}.
1865    *
1866    * @param pattern The {@link java.util.Formatter}-style message pattern.
1867    * @param args The message arguments.
1868    */
1869   public void config(String pattern, Object...args) { getLogger().config(pattern, args); }
1870
1871   /**
1872    * Logs a formatted message with an associated throwable at {@link java.util.logging.Level#CONFIG CONFIG}.
1873    *
1874    * @param thrown The throwable to associate with the record.
1875    * @param pattern The {@link java.util.Formatter}-style message pattern.
1876    * @param args The message arguments.
1877    */
1878   public void config(Throwable thrown, String pattern, Object...args) { getLogger().config(thrown, pattern, args); }
1879
1880   /**
1881    * Logs a formatted message at {@link java.util.logging.Level#FINE FINE} through this request's {@link #getLogger() logger}.
1882    *
1883    * @param pattern The {@link java.util.Formatter}-style message pattern.
1884    * @param args The message arguments.
1885    */
1886   public void fine(String pattern, Object...args) { getLogger().fine(pattern, args); }
1887
1888   /**
1889    * Logs a formatted message with an associated throwable at {@link java.util.logging.Level#FINE FINE}.
1890    *
1891    * @param thrown The throwable to associate with the record.
1892    * @param pattern The {@link java.util.Formatter}-style message pattern.
1893    * @param args The message arguments.
1894    */
1895   public void fine(Throwable thrown, String pattern, Object...args) { getLogger().fine(thrown, pattern, args); }
1896
1897   /**
1898    * Logs a formatted message at {@link java.util.logging.Level#FINER FINER} through this request's {@link #getLogger() logger}.
1899    *
1900    * @param pattern The {@link java.util.Formatter}-style message pattern.
1901    * @param args The message arguments.
1902    */
1903   public void finer(String pattern, Object...args) { getLogger().finer(pattern, args); }
1904
1905   /**
1906    * Logs a formatted message with an associated throwable at {@link java.util.logging.Level#FINER FINER}.
1907    *
1908    * @param thrown The throwable to associate with the record.
1909    * @param pattern The {@link java.util.Formatter}-style message pattern.
1910    * @param args The message arguments.
1911    */
1912   public void finer(Throwable thrown, String pattern, Object...args) { getLogger().finer(thrown, pattern, args); }
1913
1914   /**
1915    * Logs a formatted message at {@link java.util.logging.Level#FINEST FINEST} through this request's {@link #getLogger() logger}.
1916    *
1917    * @param pattern The {@link java.util.Formatter}-style message pattern.
1918    * @param args The message arguments.
1919    */
1920   public void finest(String pattern, Object...args) { getLogger().finest(pattern, args); }
1921
1922   /**
1923    * Logs a formatted message with an associated throwable at {@link java.util.logging.Level#FINEST FINEST}.
1924    *
1925    * @param thrown The throwable to associate with the record.
1926    * @param pattern The {@link java.util.Formatter}-style message pattern.
1927    * @param args The message arguments.
1928    */
1929   public void finest(Throwable thrown, String pattern, Object...args) { getLogger().finest(thrown, pattern, args); }
1930
1931   /**
1932    * Returns whether REST-driven serializer/parser debug behavior is enabled for this request's operation.
1933    *
1934    * <p>
1935    * Resolved from the cascading {@link Rest#debugMarshalling() @Rest(debugMarshalling)} /
1936    * {@link RestOp#debugMarshalling() @RestOp(debugMarshalling)} setting (default OFF), <b>independent</b> of the JUL
1937    * logger level. When enabled, the REST layer forces {@code Context.debug} on the serializer/parser session used to
1938    * handle this request &mdash; engaging recursion-detection-that-throws, parse-input buffering, and stack-trace-prefixed
1939    * marshalling exceptions.
1940    *
1941    * @return <jk>true</jk> if REST-driven marshalling debug is enabled for this operation.
1942    */
1943   public boolean isDebugMarshalling() { return opContext.isDebugMarshalling(); }
1944
1945   /**
1946    * Returns the captured (bounded) request body bytes for debug logging.
1947    *
1948    * <p>
1949    * Populated by the two-phase debug pipeline only when the resolved logger is loggable at
1950    * {@link java.util.logging.Level#FINEST FINEST}; otherwise returns <jk>null</jk>. The captured copy is capped at the
1951    * formatter's body cap (default 8&nbsp;KB); use {@link #getCachedContentLength()} for the full byte count.
1952    *
1953    * @return The captured request body bytes, or <jk>null</jk> if body caching was not installed.
1954    */
1955   public byte[] getCachedContent() {
1956      return inner instanceof CachingHttpServletRequest c ? c.getContent() : null;
1957   }
1958
1959   /**
1960    * Returns the total number of request body bytes read, including any beyond the capture cap.
1961    *
1962    * @return The total request body length in bytes, or {@code -1} if body caching was not installed.
1963    */
1964   public long getCachedContentLength() {
1965      return inner instanceof CachingHttpServletRequest c ? c.getTotalLength() : -1;
1966   }
1967
1968   /**
1969    * Returns the exception thrown while processing this request, if any.
1970    *
1971    * @return The thrown exception, or <jk>null</jk> if none occurred.
1972    */
1973   public Throwable getException() {
1974      return session.getException();
1975   }
1976
1977   /**
1978    * Returns the total request execution time in milliseconds, if it has been recorded.
1979    *
1980    * @return The execution time in milliseconds, or <jk>null</jk> if not yet recorded.
1981    */
1982   public Long getExecTime() {
1983      return getAttribute("ExecTime").as(Long.class).orElse(null);
1984   }
1985
1986   /**
1987    * Returns <jk>true</jk> if <c>&amp;plainText=true</c> was specified as a URL parameter.
1988    *
1989    * <p>
1990    * This indicates that the <c>Content-Type</c> of the output should always be set to <js>"text/plain"</js>
1991    * to make it easy to render in a browser.
1992    *
1993    * <p>
1994    * This feature is useful for debugging.
1995    *
1996    * @return <jk>true</jk> if {@code &amp;plainText=true} was specified as a URL parameter
1997    */
1998   public boolean isPlainText() { return eq(queryParams.get("plainText").asString().orElse("false"), "true"); }
1999
2000   /**
2001    * Sets a request attribute.
2002    *
2003    * @param name The attribute name.
2004    * @param value The attribute value.
2005    */
2006   @Override
2007   public void setAttribute(String name, Object value) {
2008      attrs.set(name, value);
2009   }
2010
2011   /**
2012    * Sets the resolved resource-level authentication result.
2013    *
2014    * <p>
2015    * Invoked by the framework after the {@link RestAuthenticator} fold resolves
2016    * an identity for this request.  The stored result drives {@link #getUserPrincipal()}, {@link #isUserInRole(String)},
2017    * and {@link #getRemoteUser()}, and (when a principal is present) stashes the principal under
2018    * {@code "juneau.principal"} so {@code @Auth} argument injection resolves it.
2019    *
2020    * @param value The result. Can be <jk>null</jk> to unset.
2021    * @return This object.
2022    * @since 10.0.0
2023    */
2024   public RestRequest setAuthResult(AuthResult value) {
2025      authResult = value;
2026      if (value != null && value.getPrincipal() != null)
2027         setAttribute("juneau.principal", value.getPrincipal());
2028      return this;
2029   }
2030
2031   @Override /* Overridden from HttpServletRequestWrapper */
2032   public Principal getUserPrincipal() {
2033      var p = authResult == null ? null : authResult.getPrincipal();
2034      return p != null ? p : super.getUserPrincipal();
2035   }
2036
2037   @Override /* Overridden from HttpServletRequestWrapper */
2038   public boolean isUserInRole(String role) {
2039      if (authResult != null && authResult.getRoles().contains(role))
2040         return true;
2041      return super.isUserInRole(role);
2042   }
2043
2044   @Override /* Overridden from HttpServletRequestWrapper */
2045   public String getRemoteUser() {
2046      var p = authResult == null ? null : authResult.getPrincipal();
2047      return p != null ? p.getName() : super.getRemoteUser();
2048   }
2049
2050   /**
2051    * Sets a serializer session property programmatically.
2052    *
2053    * <p>
2054    * Session properties override context-level properties and can be used to customize serializer behavior for this request only.
2055    *
2056    * @param name The property name (e.g. {@code "escapeSolidus"}).
2057    * @param value The property value.
2058    * @return This object.
2059    */
2060   public RestRequest setSerializerSessionProperty(String name, Object value) {
2061      if (serializerSessionProperties == null)
2062         serializerSessionProperties = m();
2063      serializerSessionProperties.put(name, value);
2064      return this;
2065   }
2066
2067   /**
2068    * Sets a parser session property programmatically.
2069    *
2070    * <p>
2071    * Session properties override context-level properties and can be used to customize parser behavior for this request only.
2072    *
2073    * @param name The property name (e.g. {@code "trimStrings"}).
2074    * @param value The property value.
2075    * @return This object.
2076    */
2077   public RestRequest setParserSessionProperty(String name, Object value) {
2078      if (parserSessionProperties == null)
2079         parserSessionProperties = m();
2080      parserSessionProperties.put(name, value);
2081      return this;
2082   }
2083
2084   /**
2085    * Sets multiple serializer session properties programmatically.
2086    *
2087    * @param values The properties to set. Can be {@code null}.
2088    * @return This object.
2089    */
2090   public RestRequest setSerializerSessionProperties(Map<String,Object> values) {
2091      if (ine(values)) {
2092         if (serializerSessionProperties == null)
2093            serializerSessionProperties = m();
2094         serializerSessionProperties.putAll(values);
2095      }
2096      return this;
2097   }
2098
2099   /**
2100    * Sets multiple parser session properties programmatically.
2101    *
2102    * @param values The properties to set. Can be {@code null}.
2103    * @return This object.
2104    */
2105   public RestRequest setParserSessionProperties(Map<String,Object> values) {
2106      if (ine(values)) {
2107         if (parserSessionProperties == null)
2108            parserSessionProperties = m();
2109         parserSessionProperties.putAll(values);
2110      }
2111      return this;
2112   }
2113
2114   /**
2115    * Returns the merged serializer session property map for this request.
2116    *
2117    * <p>
2118    * Merges (in order of increasing priority):
2119    * <ol>
2120    *    <li>Request attributes (from {@link #getAttributes()}, e.g. {@code defaultRequestAttributes})
2121    *    <li>UON-encoded {@code juneauSerializerOptions} query parameter
2122    *    <li>JSON5-encoded {@code X-Juneau-Serializer-Options} header
2123    *    <li>Programmatically-set properties via {@link #setSerializerSessionProperty}
2124    * </ol>
2125    *
2126    * <p>
2127    * Client-supplied keys are validated against the allowlist from {@link RestOpContext#getAllowedSerializerOptions()}.
2128    * Keys not in the allowlist cause a {@code 400 Bad Request} response.
2129    *
2130    * @return An unmodifiable map of session properties; may be empty but never {@code null}.
2131    */
2132   public Map<String,Object> getSerializerSessionPropertyMap() {
2133      var allowlist = opContext.getAllowedSerializerOptions();
2134      Map<String,Object> m1 = null;
2135      Map<String,Object> m2 = null;
2136
2137      var q = getQueryParams().get(QUERY_juneauSerializerOptions).asString().orElse(null);
2138      if (inb(q)) {
2139         m1 = parseUonMap(q, e -> badRequest(e, "Could not parse UON session options from query parameter '%s'.", QUERY_juneauSerializerOptions));
2140         var invalid = m1.keySet().stream().filter(k -> !allowlist.contains(k)).collect(Collectors.joining(","));
2141         if (!invalid.isEmpty())
2142            badRequest("Invalid session options in query parameter '%s': '%s'", QUERY_juneauSerializerOptions, invalid);
2143      }
2144
2145      var h = getHeaderParam(HEADER_JuneauSerializerOptions).asString().orElse(null);
2146      if (inb(h)) {
2147         m2 = parseJsonMap(h, e -> badRequest(e, "Could not parse JSON5 session options from header '%s'.", HEADER_JuneauSerializerOptions));
2148         var invalid = m2.keySet().stream().filter(k -> !allowlist.contains(k)).collect(Collectors.joining(","));
2149         if (!invalid.isEmpty())
2150            badRequest("Invalid session options in header '%s': '%s'", HEADER_JuneauSerializerOptions, invalid);
2151      }
2152
2153      return mergeSessionMaps(getAttributes().asMap(), m1, m2, serializerSessionProperties);
2154   }
2155
2156   /**
2157    * Returns the merged parser session property map for this request.
2158    *
2159    * <p>
2160    * Merges (in order of increasing priority):
2161    * <ol>
2162    *    <li>Request attributes (from {@link #getAttributes()}, e.g. {@code defaultRequestAttributes})
2163    *    <li>UON-encoded {@code juneauParserOptions} query parameter
2164    *    <li>JSON5-encoded {@code X-Juneau-Parser-Options} header
2165    *    <li>Programmatically-set properties via {@link #setParserSessionProperty}
2166    * </ol>
2167    *
2168    * <p>
2169    * Client-supplied keys are validated against the allowlist from {@link RestOpContext#getAllowedParserOptions()}.
2170    * Keys not in the allowlist cause a {@code 400 Bad Request} response.
2171    *
2172    * @return An unmodifiable map of session properties; may be empty but never {@code null}.
2173    */
2174   public Map<String,Object> getParserSessionPropertyMap() {
2175      var allowlist = opContext.getAllowedParserOptions();
2176      Map<String,Object> m1 = null;
2177      Map<String,Object> m2 = null;
2178
2179      var q = getQueryParams().get(QUERY_juneauParserOptions).asString().orElse(null);
2180      if (inb(q)) {
2181         m1 = parseUonMap(q, e -> badRequest(e, "Could not parse UON session options from query parameter '%s'.", QUERY_juneauParserOptions));
2182         var invalid = m1.keySet().stream().filter(k -> !allowlist.contains(k)).collect(Collectors.joining(","));
2183         if (!invalid.isEmpty())
2184            badRequest("Invalid session options in query parameter '%s': '%s'", QUERY_juneauParserOptions, invalid);
2185      }
2186
2187      var h = getHeaderParam(HEADER_JuneauParserOptions).asString().orElse(null);
2188      if (inb(h)) {
2189         m2 = parseJsonMap(h, e -> badRequest(e, "Could not parse JSON5 session options from header '%s'.", HEADER_JuneauParserOptions));
2190         var invalid = m2.keySet().stream().filter(k -> !allowlist.contains(k)).collect(Collectors.joining(","));
2191         if (!invalid.isEmpty())
2192            badRequest("Invalid session options in header '%s': '%s'", HEADER_JuneauParserOptions, invalid);
2193      }
2194
2195      return mergeSessionMaps(getAttributes().asMap(), m1, m2, parserSessionProperties);
2196   }
2197
2198   @SafeVarargs
2199   private static Map<String,Object> mergeSessionMaps(Map<String,Object>...maps) {
2200      Map<String,Object> result = null;
2201      for (var map : maps) {
2202         if (ine(map)) {
2203            if (result == null)
2204               result = new LinkedHashMap<>(map);
2205            else
2206               result.putAll(map);
2207         }
2208      }
2209      return result != null ? u(result) : Collections.emptyMap();
2210   }
2211
2212   private static void badRequest(String msg, Object...args) {
2213      throw new BadRequest(msg, args);
2214   }
2215
2216   private static void badRequest(Exception causedBy, String msg, Object...args) {
2217      throw new BadRequest(causedBy, msg, args);
2218   }
2219
2220   private static Map<String,Object> parseUonMap(String input, Consumer<ParseException> onError) {
2221      try {
2222         var m = Uon.to(input, JsonMap.class);
2223         return m != null ? m : Collections.emptyMap();
2224      } catch (ParseException e) {
2225         onError.accept(e);
2226         return Collections.emptyMap();
2227      }
2228   }
2229
2230   private static Map<String,Object> parseJsonMap(String input, Consumer<ParseException> onError) {
2231      try {
2232         var m = Json5.to(input, JsonMap.class);
2233         return m != null ? m : Collections.emptyMap();
2234      } catch (ParseException e) {
2235         onError.accept(e);
2236         return Collections.emptyMap();
2237      }
2238   }
2239
2240   /**
2241    * Sets the charset to expect on the request content.
2242    *
2243    * @param value The new charset to use for the request content.
2244    *    <br>Can be <jk>null</jk> (resets to the negotiated/default charset on the next {@link #getCharset()} call).
2245    */
2246   public void setCharset(Charset value) { this.charset = value; }
2247
2248   /**
2249    * Sets the <js>"Exception"</js> attribute to the specified throwable.
2250    *
2251    * <p>
2252    * This exception is surfaced by the debug pipeline as the {@code thrown} of the emitted log record.
2253    *
2254    * @param t The attribute value.
2255    * @return This object.
2256    */
2257   public RestRequest setException(Throwable t) {
2258      setAttribute("Exception", t);
2259      return this;
2260   }
2261
2262   /**
2263    * Shortcut for calling <c>setNoTrace(<jk>true</jk>)</c>.
2264    *
2265    * @return This object.
2266    */
2267   public RestRequest setNoTrace() {
2268      return setNoTrace(true);
2269   }
2270
2271   /**
2272    * Sets the <js>"NoTrace"</js> attribute to the specified boolean.
2273    *
2274    * <p>
2275    * This flag tells the framework not to trace the current request: the access-log record for the call is still
2276    * emitted, but without the exception's stack trace.  A <c>No-Trace: true</c> request header has the same effect; when both are present, the attribute wins.
2277    *
2278    * @param b The attribute value.
2279    * @return This object.
2280    */
2281   public RestRequest setNoTrace(Boolean b) {
2282      setAttribute("NoTrace", b);
2283      return this;
2284   }
2285
2286   @Override /* Overridden from Object */
2287   public String toString() {
2288      var sb = new StringBuilder("\n").append(getRequestLine()).append("\n");
2289      sb.append("---Headers---\n");
2290      getHeaders().forEach(x -> sb.append("\t").append(x).append("\n"));
2291      var m = getMethod();
2292      if (eqa(m, "PUT", "POST")) {
2293         try {
2294            sb.append("---Content UTF-8---\n");
2295            sb.append(content.asString()).append("\n");
2296            sb.append("---Content Hex---\n");
2297            sb.append(content.asSpacedHex()).append("\n");
2298         } catch (Exception e1) {
2299            sb.append(localizedMessage(e1));
2300         }
2301      }
2302      return sb.toString();
2303   }
2304
2305   /* Called by RestSession.finish() */
2306   void close() {
2307      if (nn(config)) {
2308         config.close();
2309      }
2310   }
2311
2312   /**
2313    * Creates the underlying part-parser session only when a part actually has to be parsed.
2314    *
2315    * <p>
2316    * Most requests never parse a header, query, path or form value into a schema-typed object, so building the
2317    * (comparatively heavy) OpenAPI parser session up front is wasted work.
2318    */
2319   private static final class LazyPartParserSession implements HttpPartParserSession {
2320      private final HttpPartParser parser;
2321      private final AtomicReference<HttpPartParserSession> session = new AtomicReference<>();
2322
2323      LazyPartParserSession(HttpPartParser parser) {
2324         this.parser = parser;
2325      }
2326
2327      @Override /* Overridden from HttpPartParserSession */
2328      public <T> T read(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> toType) throws ParseException, SchemaValidationException {
2329         var s = session.get();
2330         if (s == null) {
2331            s = parser.getPartSession();
2332            session.set(s);
2333         }
2334         return s.read(partType, schema, in, toType);
2335      }
2336   }
2337}