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
019
020import java.util.function.*;
021
022import org.apache.juneau.commons.utils.*;
023import static org.apache.juneau.commons.utils.Shorts.*;
024
025/**
026 * Configuration for the always-on request-id correlation resolver run at {@link RestSession} build time.
027 *
028 * <p>
029 * This is the public write-surface for tuning how the framework mints or honors the {@code X-Request-Id} correlation id
030 * for every request &mdash; including 404 / early-error paths that never reach a {@code @RestStartCall} hook.  Register a
031 * configured instance as a bean, or return one from a {@code @Bean} factory method on the resource; the resolver picks it
032 * up via {@link RestContext#getRequestIdSettings()}.
033 *
034 * <h5 class='topic'>Resolution behavior</h5>
035 * <ol>
036 *    <li>If the request already carries a non-empty id under {@link #getAttributeKey() attributeKey} (idempotent re-entry
037 *       or a parent filter), that value is honored unchanged.
038 *    <li>Otherwise, if an incoming {@code X-Request-Id} header is present, it is
039 *       {@linkplain org.apache.juneau.http.DebugTextSanitizer#sanitize(String, int) sanitized} and length-capped to
040 *       {@link org.apache.juneau.http.RequestIdConstants#MAX_LEN}; the sanitized value is honored when it is non-empty,
041 *       not truncated, and accepted by {@link #getValidator() validator}.
042 *    <li>Otherwise a fresh id is minted via {@link #getIdSupplier() idSupplier}.
043 * </ol>
044 *
045 * <h5 class='topic'>Default validator &mdash; sanitize-and-accept</h5>
046 * <p>
047 * Unlike a reject-and-remint regex, the default validator {@linkplain Builder#validator(Predicate) accepts} any candidate
048 * that survives sanitization within the length cap.  The sanitizer already neutralizes CR/LF/control-character
049 * header-injection payloads (escaping them to inert, ASCII-safe {@code \\uXXXX}), so the default honors a cleaned form
050 * rather than discarding a slightly-dirty id.  Supply a stricter validator (e.g. UUID-only) to opt into reject-and-remint.
051 *
052 * <h5 class='section'>See Also:</h5><ul>
053 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerLoggingAndDebugging">Logging / Debugging</a>
054 * </ul>
055 *
056 * @since 10.0.0
057 */
058@SuppressWarnings({
059   "java:S1192" // Duplicated literals read more clearly inline than as constants.
060})
061public class RequestIdSettings {
062
063   private final Predicate<String> validator;
064   private final String attributeKey;
065   private final Supplier<String> idSupplier;
066
067   /**
068    * Constructor.
069    *
070    * @param b The builder configuring these settings.  Must not be <jk>null</jk>.
071    */
072   protected RequestIdSettings(Builder b) {
073      reqnn("builder", b);
074      this.validator = b.validator;
075      this.attributeKey = b.attributeKey;
076      this.idSupplier = b.idSupplier;
077   }
078
079   /**
080    * Creates a new builder.
081    *
082    * @return A new builder.
083    */
084   public static Builder create() {
085      return new Builder();
086   }
087
088   /**
089    * Returns the predicate applied to a sanitized incoming id to decide whether to honor it.
090    *
091    * @return The validator predicate.  Never <jk>null</jk>.
092    */
093   public Predicate<String> getValidator() { return validator; }
094
095   /**
096    * Returns the servlet-request attribute key under which the resolved id is stashed.
097    *
098    * @return The attribute key.  Never <jk>null</jk>.
099    */
100   public String getAttributeKey() { return attributeKey; }
101
102   /**
103    * Returns the supplier used to mint a fresh id when none is honored from the request.
104    *
105    * @return The id supplier.  Never <jk>null</jk>.
106    */
107   public Supplier<String> getIdSupplier() { return idSupplier; }
108
109   /**
110    * Builder for {@link RequestIdSettings}.
111    */
112   public static class Builder {
113
114      Predicate<String> validator = s -> true;
115      String attributeKey = "requestId";
116      Supplier<String> idSupplier = Uuid7::createString;
117
118      /**
119       * Constructor.
120       */
121      protected Builder() {}
122
123      /**
124       * Sets the predicate applied to a <b>sanitized</b> incoming {@code X-Request-Id} value.
125       *
126       * <p>
127       * Defaults to accept-all (sanitize-and-accept): any candidate that survives sanitization within
128       * {@link org.apache.juneau.http.RequestIdConstants#MAX_LEN} is honored.  Supply a stricter predicate to
129       * reject-and-remint values that do not match a required shape.
130       *
131       * @param value The predicate, tested against the sanitized candidate.  Must not be <jk>null</jk>.
132       * @return This object.
133       */
134      public Builder validator(Predicate<String> value) {
135         reqnn("value", value);
136         validator = value;
137         return this;
138      }
139
140      /**
141       * Overrides the servlet-request attribute key under which the resolved id is stashed.
142       *
143       * <p>
144       * Defaults to {@code "requestId"}.  Override only when coexisting with a third-party
145       * filter that publishes the id under a different key.
146       *
147       * @param value The attribute key.  Must not be <jk>null</jk> or blank.
148       * @return This object.
149       */
150      public Builder attributeKey(String value) {
151         reqnn("value", value);
152         if (value.isBlank())
153            throw new IllegalArgumentException("Argument 'value' must not be blank.");
154         attributeKey = value;
155         return this;
156      }
157
158      /**
159       * Sets the supplier used to mint a new id when none is honored from the request.
160       *
161       * <p>
162       * Defaults to a time-ordered {@linkplain Uuid7 RFC&nbsp;9562 version-7 UUID}.  Swap in a smaller / shorter id
163       * scheme when payload size matters.
164       *
165       * @param value The supplier.  Must not be <jk>null</jk>.
166       * @return This object.
167       */
168      public Builder idSupplier(Supplier<String> value) {
169         reqnn("value", value);
170         idSupplier = value;
171         return this;
172      }
173
174      /**
175       * Builds the settings.
176       *
177       * @return A new {@link RequestIdSettings}.
178       */
179      public RequestIdSettings build() {
180         return new RequestIdSettings(this);
181      }
182   }
183}