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} — the kind a browser will issue 215 * with no preflight and no custom header for something as simple as an <code><img src=...></code> tag 216 * — 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> -> { <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 -> { 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<Person> <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()} — 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<CacheControl> = <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>&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<String,Object> 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<Tag> 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 — use the explicit, non-logging controls instead: 1783 * <ul class='spaced-list'> 1784 * <li>Echo-endpoint reachability → {@link org.apache.juneau.rest.server.ops.EchoMixin.Builder#enabled(Boolean) EchoMixin enablement}. 1785 * <li>Serializer/parser debug behavior → {@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 — including the 1798 * {@code fine}/{@code info}/… convenience methods below — 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 — not a replacement for — 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 — 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 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>&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 &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}