content: /*
   * Copyright The OpenTelemetry Authors
   * SPDX-License-Identifier: Apache-2.0
   */
  
  package io.opentelemetry.javaagent.instrumentation.spring.gateway.v2_0;
  
  import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
  
  import io.opentelemetry.api.common.AttributeKey;
  import io.opentelemetry.api.trace.Span;
  import io.opentelemetry.context.Context;
  import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
  import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig;
  import java.util.regex.Pattern;
  import org.springframework.cloud.gateway.route.Route;
  import org.springframework.web.server.ServerWebExchange;
  
  public final class ServerWebExchangeHelper {
  
    /** Route ID attribute key. */
    private static final AttributeKey<String> ROUTE_ID_ATTRIBUTE =
        AttributeKey.stringKey("spring-cloud-gateway.route.id");
  
    /** Route URI attribute key. */
    private static final AttributeKey<String> ROUTE_URI_ATTRIBUTE =
        AttributeKey.stringKey("spring-cloud-gateway.route.uri");
  
    /** Route order attribute key. */
    private static final AttributeKey<Long> ROUTE_ORDER_ATTRIBUTE =
        AttributeKey.longKey("spring-cloud-gateway.route.order");
  
    /** Route filter size attribute key. */
    private static final AttributeKey<Long> ROUTE_FILTER_SIZE_ATTRIBUTE =
        AttributeKey.longKey("spring-cloud-gateway.route.filter.size");
  
    private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES;
  
    static {
      CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
          AgentInstrumentationConfig.get()
              .getBoolean(
                  "otel.instrumentation.spring-cloud-gateway.experimental-span-attributes", false);
    }
  
    /* Regex for UUID */
    private static final Pattern UUID_REGEX =
        Pattern.compile(
            "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");
  
    private static final String INVALID_RANDOM_ROUTE_ID =
        "org.springframework.util.AlternativeJdkIdGenerator@";
  
    private ServerWebExchangeHelper() {}
  
    public static void extractAttributes(ServerWebExchange exchange, Context context) {
      // Record route info
      Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
      if (route != null && CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
        Span serverSpan = LocalRootSpan.fromContextOrNull(context);
        if (serverSpan == null) {
          return;
        }
        serverSpan.setAttribute(ROUTE_ID_ATTRIBUTE, route.getId());
        serverSpan.setAttribute(ROUTE_URI_ATTRIBUTE, route.getUri().toASCIIString());
        serverSpan.setAttribute(ROUTE_ORDER_ATTRIBUTE, route.getOrder());
        serverSpan.setAttribute(ROUTE_FILTER_SIZE_ATTRIBUTE, route.getFilters().size());
      }
    }
  
    public static String extractServerRoute(ServerWebExchange exchange) {
      Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
      if (route != null) {
        return convergeRouteId(route);
      }
      return null;
    }
  
    /**
     * To avoid high cardinality, we ignore random UUID generated by Spring Cloud Gateway. Spring
     * Cloud Gateway generate invalid random routeID, and it is fixed until 3.1.x
     *
     * @see <a
     *     href="https://github.com/spring-cloud/spring-cloud-gateway/commit/5002fe2e0a2825ef47dd667cade37b844c276cf6"/>
     */
    private static String convergeRouteId(Route route) {
      String routeId = route.getId();
      if (routeId == null || routeId.isEmpty()) {
        return null;
      }
      if (UUID_REGEX.matcher(routeId).matches()) {
        return null;
      }
      if (routeId.startsWith(INVALID_RANDOM_ROUTE_ID)) {
        return null;
      }
      return routeId;
    }
  }
  