content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.cassandra.sidecar.client;
  
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.UUID;
  
  import org.apache.cassandra.sidecar.client.retry.ExponentialBackoffRetryPolicy;
  import org.apache.cassandra.sidecar.client.retry.NoRetryPolicy;
  import org.apache.cassandra.sidecar.client.retry.RetryPolicy;
  import org.apache.cassandra.sidecar.client.selection.InstanceSelectionPolicy;
  import org.apache.cassandra.sidecar.client.selection.SingleInstanceSelectionPolicy;
  import org.apache.cassandra.sidecar.common.request.CassandraJmxHealthRequest;
  import org.apache.cassandra.sidecar.common.request.CassandraNativeHealthRequest;
  import org.apache.cassandra.sidecar.common.request.CleanSSTableUploadSessionRequest;
  import org.apache.cassandra.sidecar.common.request.ClearSnapshotRequest;
  import org.apache.cassandra.sidecar.common.request.CompactionStatsRequest;
  import org.apache.cassandra.sidecar.common.request.ConnectedClientStatsRequest;
  import org.apache.cassandra.sidecar.common.request.CreateSnapshotRequest;
  import org.apache.cassandra.sidecar.common.request.GossipHealthRequest;
  import org.apache.cassandra.sidecar.common.request.GossipInfoRequest;
  import org.apache.cassandra.sidecar.common.request.GossipUpdateRequest;
  import org.apache.cassandra.sidecar.common.request.ImportSSTableRequest;
  import org.apache.cassandra.sidecar.common.request.LifecycleInfoRequest;
  import org.apache.cassandra.sidecar.common.request.LifecycleUpdateRequest;
  import org.apache.cassandra.sidecar.common.request.ListOperationalJobsRequest;
  import org.apache.cassandra.sidecar.common.request.ListSnapshotFilesRequest;
  import org.apache.cassandra.sidecar.common.request.NativeUpdateRequest;
  import org.apache.cassandra.sidecar.common.request.NodeDecommissionRequest;
  import org.apache.cassandra.sidecar.common.request.NodeSettingsRequest;
  import org.apache.cassandra.sidecar.common.request.OperationalJobRequest;
  import org.apache.cassandra.sidecar.common.request.ReportSchemaRequest;
  import org.apache.cassandra.sidecar.common.request.Request;
  import org.apache.cassandra.sidecar.common.request.RingRequest;
  import org.apache.cassandra.sidecar.common.request.SSTableComponentRequest;
  import org.apache.cassandra.sidecar.common.request.SchemaRequest;
  import org.apache.cassandra.sidecar.common.request.SidecarHealthRequest;
  import org.apache.cassandra.sidecar.common.request.StreamStatsRequest;
  import org.apache.cassandra.sidecar.common.request.TableStatsRequest;
  import org.apache.cassandra.sidecar.common.request.TimeSkewRequest;
  import org.apache.cassandra.sidecar.common.request.TokenRangeReplicasRequest;
  import org.apache.cassandra.sidecar.common.request.UploadSSTableRequest;
  import org.apache.cassandra.sidecar.common.request.data.Digest;
  import org.apache.cassandra.sidecar.common.request.data.NodeCommandRequestPayload;
  import org.apache.cassandra.sidecar.common.response.ListSnapshotFilesResponse;
  import org.apache.cassandra.sidecar.common.utils.HttpRange;
  import org.jetbrains.annotations.NotNull;
  import org.jetbrains.annotations.Nullable;
  
  /**
   * The context for a given request that include the {@link InstanceSelectionPolicy}, the {@link RetryPolicy}, and
   * the {@link Request}
   */
  public class RequestContext
  {
      protected static final SidecarHealthRequest SIDECAR_HEALTH_REQUEST = new SidecarHealthRequest();
  
      /**
       * @deprecated in favor of {@link #CASSANDRA_NATIVE_HEALTH_REQUEST}
       */
      @Deprecated
      protected static final CassandraNativeHealthRequest CASSANDRA_HEALTH_REQUEST =
      new CassandraNativeHealthRequest(true /* useDeprecatedHealthEndpoint */);
      protected static final CassandraNativeHealthRequest CASSANDRA_NATIVE_HEALTH_REQUEST =
      new CassandraNativeHealthRequest();
      protected static final CassandraJmxHealthRequest CASSANDRA_JMX_HEALTH_REQUEST = new CassandraJmxHealthRequest();
      protected static final SchemaRequest FULL_SCHEMA_REQUEST = new SchemaRequest();
      protected static final TimeSkewRequest TIME_SKEW_REQUEST = new TimeSkewRequest();
      protected static final NodeSettingsRequest NODE_SETTINGS_REQUEST = new NodeSettingsRequest();
      protected static final RingRequest RING_REQUEST = new RingRequest();
      protected static final GossipInfoRequest GOSSIP_INFO_REQUEST = new GossipInfoRequest();
      protected static final ListOperationalJobsRequest LIST_JOBS_REQUEST = new ListOperationalJobsRequest();
      protected static final NodeDecommissionRequest NODE_DECOMMISSION_REQUEST = new NodeDecommissionRequest();
  
      protected static final StreamStatsRequest STREAM_STATS_REQUEST = new StreamStatsRequest();
      protected static final LifecycleInfoRequest LIFECYCLE_INFO_REQUEST = new LifecycleInfoRequest();
      protected static final RetryPolicy DEFAULT_NO_RETRY_POLICY = new NoRetryPolicy();
      protected static final RetryPolicy DEFAULT_EXPONENTIAL_BACKOFF_RETRY_POLICY =
      new ExponentialBackoffRetryPolicy(10, 500L, 60_000L);
  
      private final InstanceSelectionPolicy instanceSelectionPolicy;
      private final Request request;
      private final RetryPolicy retryPolicy;
      private final Map<String, String> customHeaders;
  
      private RequestContext(Builder builder)
      {
          instanceSelectionPolicy = builder.instanceSelectionPolicy;
          request = builder.request;
          retryPolicy = builder.retryPolicy;
          customHeaders = Collections.unmodifiableMap(builder.customHeaders);
      }
  
      /**
       * @return the instance selection policy for the request
       */
      public InstanceSelectionPolicy instanceSelectionPolicy()
      {
          return instanceSelectionPolicy;
      }
  
      /**
       * @return the retry policy associated to the request
       */
      public RetryPolicy retryPolicy()
      {
          return retryPolicy;
      }
  
      /**
       * @return the request for the Sidecar service
       */
      public Request request()
      {
          return request;
      }
  
      /**
       * @return the custom headers for the request
       */
      public Map<String, String> customHeaders()
      {
          return customHeaders;
      }
  
      /**
       * {@code RequestContext} builder static inner class.
       */
      public static final class Builder
      {
          private InstanceSelectionPolicy instanceSelectionPolicy;
          private Request request;
          private RetryPolicy retryPolicy = DEFAULT_NO_RETRY_POLICY;
          private final Map<String, String> customHeaders;
  
          public Builder()
          {
              customHeaders = new HashMap<>();
          }
  
          private Builder(Builder builder)
          {
              request = builder.request;
              instanceSelectionPolicy = builder.instanceSelectionPolicy;
              retryPolicy = builder.retryPolicy;
              customHeaders = new HashMap<>(builder.customHeaders);
          }
  
          /**
           * Sets the {@code instanceSelectionPolicy} and returns a reference to this Builder enabling method chaining.
           *
           * @param instanceSelectionPolicy the {@code instanceSelectionPolicy} to set
           * @return a reference to this Builder
           */
          public Builder instanceSelectionPolicy(InstanceSelectionPolicy instanceSelectionPolicy)
          {
              this.instanceSelectionPolicy = instanceSelectionPolicy;
              return this;
          }
  
          /**
           * Sets the {@code instanceSelectionPolicy} to the {@link SingleInstanceSelectionPolicy} for the provided
           * {@code sidecarInstance} and returns a reference to this Builder enabling method chaining.
           *
           * @param sidecarInstance the Sidecar instance where the request will be performed
           * @return a reference to this Builder
           */
          public Builder singleInstanceSelectionPolicy(SidecarInstance sidecarInstance)
          {
              return instanceSelectionPolicy(new SingleInstanceSelectionPolicy(sidecarInstance));
          }
  
          /**
           * Sets the {@code request} and returns a reference to this Builder enabling method chaining.
           *
           * @param request the {@code request} to set
           * @return a reference to this Builder
           */
          public Builder request(Request request)
          {
              this.request = request;
              return this;
          }
  
          /**
           * Sets the {@code retryPolicy} and returns a reference to this Builder enabling method chaining.
           *
           * @param retryPolicy the {@code retryPolicy} to set
           * @return a reference to this Builder
           */
          public Builder retryPolicy(RetryPolicy retryPolicy)
          {
              this.retryPolicy = retryPolicy;
              return this;
          }
  
          /**
           * Sets the {@code request} to be a {@link SidecarHealthRequest}
           * and returns a reference to this Builder enabling method chaining
           *
           * @return a reference to this Builder
           */
          public Builder sidecarHealthRequest()
          {
              return request(SIDECAR_HEALTH_REQUEST);
          }
  
          /**
           * Sets the {@code request} to be a {@link CassandraNativeHealthRequest}
           * with the {@code useDeprecatedHealthEndpoint} parameter set to {@code true}
           * and returns a reference to this Builder enabling method chaining.
           *
           * @return a reference to this Builder
           * @deprecated in favor of {@link #cassandraNativeHealthRequest()}
           */
          @Deprecated
          public Builder cassandraHealthRequest()
          {
              return request(CASSANDRA_HEALTH_REQUEST);
          }
  
          /**
           * Sets the {@code request} to be a {@link CassandraNativeHealthRequest}
           * and returns a reference to this Builder enabling method chaining
           *
           * @return a reference to this Builder
           */
          public Builder cassandraNativeHealthRequest()
          {
              return request(CASSANDRA_NATIVE_HEALTH_REQUEST);
          }
  
          /**
           * Sets the {@code request} to be a {@link CassandraJmxHealthRequest}
           * and returns a reference to this Builder enabling method chaining
           *
           * @return a reference to this Builder
           */
          public Builder cassandraJmxHealthRequest()
          {
              return request(CASSANDRA_JMX_HEALTH_REQUEST);
          }
  
          /**
           * Sets the {@code request} to be a {@link SchemaRequest} for the full schema and returns a reference to
           * this Builder enabling method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder schemaRequest()
          {
              return request(FULL_SCHEMA_REQUEST);
          }
  
          /**
           * Sets the {@code request} to be a {@link SchemaRequest} for the specified {@code keyspace} and returns a
           * reference to this Builder enabling method chaining
           *
           * @param keyspace the keyspace in Cassandra
           * @return a reference to this Builder
           */
          public Builder schemaRequest(String keyspace)
          {
              return request(new SchemaRequest(keyspace));
          }
  
          /**
           * Sets the {@code request} to be a {@link TimeSkewRequest} and returns a reference to this Builder enabling
           * method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder timeSkewRequest()
          {
              return request(TIME_SKEW_REQUEST);
          }
  
          /**
           * Sets the {@code request} to be a {@link TokenRangeReplicasRequest} and returns a reference to this Builder
           * enabling method chaining.
           *
           * @param keyspace the keyspace in Cassandra
           * @return a reference to this Builder
           */
          public Builder tokenRangeReplicasRequest(String keyspace)
          {
              return request(new TokenRangeReplicasRequest(keyspace));
          }
  
          /**
           * Sets the {@code request} to be a {@link NodeSettingsRequest} and returns a reference to this Builder
           * enabling method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder nodeSettingsRequest()
          {
              return request(NODE_SETTINGS_REQUEST);
          }
  
          /**
           * Sets the {@code request} to be a {@link RingRequest} and returns a reference to this Builder enabling
           * method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder ringRequest()
          {
              return request(RING_REQUEST);
          }
  
          /**
           * Sets the {@code request} to be a {@link RingRequest} for the given {@code keyspace} and returns a reference
           * to this Builder enabling method chaining.
           *
           * @param keyspace the keyspace in Cassandra
           * @return a reference to this Builder
           */
          public Builder ringRequest(String keyspace)
          {
              return request(new RingRequest(keyspace));
          }
  
          /**
           * Sets the {@code request} to be a {@link GossipInfoRequest} and returns a reference to this Builder enabling
           * method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder gossipInfoRequest()
          {
              return request(GOSSIP_INFO_REQUEST);
          }
  
          /**
           * Sets the {@code request} to be a {@link GossipHealthRequest} and returns a reference to this Builder enabling
           * method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder gossipHealthRequest()
          {
              return request(new GossipHealthRequest());
          }
  
          /**
           * Sets the {@code request} to be a {@link SSTableComponentRequest} for the given {@code keyspace},
           * {@code tableName}, {@code snapshotName}, {@code componentName}, requesting the specified {@code range} and
           * returns a reference to this Builder enabling method chaining.
           *
           * @param keyspace      the keyspace in Cassandra
           * @param tableName     the table name in Cassandra
           * @param snapshotName  the name of the snapshot
           * @param componentName the name of the SSTable component
           * @param range         the HTTP range for the request
           * @return a reference to this Builder
           */
          public Builder ssTableComponentRequest(String keyspace,
                                                 String tableName,
                                                 String snapshotName,
                                                 String componentName,
                                                 HttpRange range)
          {
              return request(new SSTableComponentRequest(keyspace, tableName, snapshotName, componentName, range));
          }
  
          /**
           * Sets the {@code request} to be a {@link SSTableComponentRequest} for the requested {@code fileInfo}
           * requesting the specified {@code range} and  returns a reference to this Builder enabling method chaining.
           *
           * @param fileInfo contains information about the file to stream
           * @param range    the HTTP range for the request
           * @return a reference to this Builder
           */
          public Builder ssTableComponentRequest(ListSnapshotFilesResponse.FileInfo fileInfo, HttpRange range)
          {
              return request(new SSTableComponentRequest(fileInfo, range));
          }
  
          /**
           * Sets the {@code request} to be a {@link ListSnapshotFilesRequest} for the given {@code keyspace},
           * {@code tableName}, and {@code snapshotName} and returns a reference to this Builder enabling method chaining.
           *
           * @param keyspace     the keyspace in Cassandra
           * @param tableName    the table name in Cassandra
           * @param snapshotName the name of the snapshot
           * @return a reference to this Builder
           */
          public Builder listSnapshotFilesRequest(String keyspace, String tableName, String snapshotName)
          {
              return listSnapshotFilesRequest(keyspace, tableName, snapshotName, false);
          }
  
          /**
           * Sets the {@code request} to be a {@link CreateSnapshotRequest} for the given {@code keyspace},
           * {@code tableName}, and {@code snapshotName} and returns a reference to this Builder enabling method chaining.
           *
           * @param keyspace     the keyspace in Cassandra
           * @param tableName    the table name in Cassandra
           * @param snapshotName the name of the snapshot
           * @param snapshotTTL  an optional time to live option for the snapshot (available since Cassandra 4.1+)
           *                     The TTL option must specify the units, for example 2d represents a TTL for 2 days;
           *                     1h represents a TTL of 1 hour, etc. Valid units are {@code d}, {@code h}, {@code s},
           *                     {@code ms}, {@code us}, {@code µs}, {@code ns}, and {@code m}.
           * @return a reference to this Builder
           */
          public Builder createSnapshotRequest(String keyspace, String tableName, String snapshotName,
                                               @Nullable String snapshotTTL)
          {
              return request(new CreateSnapshotRequest(keyspace, tableName, snapshotName, snapshotTTL));
          }
  
          /**
           * Sets the {@code request} to be a {@link ListSnapshotFilesRequest} for the given {@code keyspace},
           * {@code tableName}, and {@code snapshotName} and returns a reference to this Builder enabling method chaining.
           *
           * @param keyspace                   the keyspace in Cassandra
           * @param tableName                  the table name in Cassandra
           * @param snapshotName               the name of the snapshot
           * @param includeSecondaryIndexFiles whether to include secondary index files
           * @return a reference to this Builder
           */
          public Builder listSnapshotFilesRequest(String keyspace, String tableName, String snapshotName,
                                                  boolean includeSecondaryIndexFiles)
          {
              return request(new ListSnapshotFilesRequest(keyspace, tableName, snapshotName, includeSecondaryIndexFiles));
          }
  
          /**
           * Sets the {@code request} to be a {@link ClearSnapshotRequest} for the given {@code keyspace},
           * {@code tableName}, and {@code snapshotName} and returns a reference to this Builder enabling method chaining.
           *
           * @param keyspace     the keyspace in Cassandra
           * @param tableName    the table name in Cassandra
           * @param snapshotName the name of the snapshot
           * @return a reference to this Builder
           */
          public Builder clearSnapshotRequest(String keyspace, String tableName, String snapshotName)
          {
              return request(new ClearSnapshotRequest(keyspace, tableName, snapshotName));
          }
  
          /**
           * Sets the {@code request} to be a {@link CleanSSTableUploadSessionRequest} for the given {@code uploadId}
           * and returns a reference to this Builder enabling method chaining.
           *
           * @param uploadId the unique identifier for the upload
           * @return a reference to this Builder
           */
          public Builder cleanSSTableUploadSessionRequest(String uploadId)
          {
              return request(new CleanSSTableUploadSessionRequest(uploadId));
          }
  
          /**
           * Sets the {@code request} to be a {@link ImportSSTableRequest} for the given {@code keyspace},
           * {@code tableName}, {@code uploadId}, and selected {@link ImportSSTableRequest.ImportOptions importOptions}
           * and returns a reference to this Builder enabling method chaining.
           *
           * @param keyspace      the keyspace in Cassandra
           * @param tableName     the table name in Cassandra
           * @param uploadId      an identifier for the upload
           * @param importOptions additional options for the import process
           * @return a reference to this Builder
           */
          public Builder importSSTableRequest(String keyspace, String tableName, String uploadId,
                                              ImportSSTableRequest.ImportOptions importOptions)
          {
              return request(new ImportSSTableRequest(keyspace, tableName, uploadId, importOptions));
          }
  
          /**
           * Sets the {@code} request to be a {@link UploadSSTableRequest} for the given {@code keyspace},
           * {@code tableName}, {@code uploadId}, {@code component}, and returns a
           * reference to this Builder enabling method chaining.
           *
           * @param keyspace  the keyspace in Cassandra
           * @param tableName the table name in Cassandra
           * @param uploadId  an identifier for the upload
           * @param component SSTable component being uploaded
           * @param digest    digest value to check integrity of SSTable component uploaded
           * @param filename  the path to the file to be uploaded
           * @return a reference to this Builder
           */
          public Builder uploadSSTableRequest(String keyspace, String tableName, String uploadId, String component,
                                              Digest digest, String filename)
          {
              return request(new UploadSSTableRequest(keyspace, tableName, uploadId, component, digest, filename));
          }
  
          /**
           * Sets the {@code request} to be a new instance of a {@link ReportSchemaRequest}
           *
           * @return this {@link Builder} for method chaining
           */
          public Builder reportSchemaRequest()
          {
              return request(new ReportSchemaRequest());
          }
  
          /**
           * Sets the {@code request} to be a {@link ConnectedClientStatsRequest} and returns a reference to this Builder
           * enabling method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder connectedClientStatsRequest()
          {
              return request(new ConnectedClientStatsRequest());
          }
  
          /**
           * Sets the {@code request} to be a {@link CompactionStatsRequest} and returns a reference to this Builder
           * enabling method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder compactionStatsRequest()
          {
              return request(new CompactionStatsRequest());
          }
  
          /**
           * Sets the {@code request} to be a {@link TableStatsRequest} and returns a reference to this Builder
           * enabling method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder tableStatsRequest(String keyspace, String table)
          {
              return request(new TableStatsRequest(keyspace, table));
          }
  
          /**
           * Sets the {@code request} to be a {@link OperationalJobRequest} and returns a reference to this Builder
           * enabling method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder operationalJobRequest(UUID jobId)
          {
              return request(new OperationalJobRequest(jobId));
          }
  
          /**
           * Sets the {@code request} to be a {@link ListOperationalJobsRequest} and returns a reference to this Builder
           * enabling method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder listOperationalJobsRequest()
          {
              return request(LIST_JOBS_REQUEST);
          }
  
          /**
           * Sets the {@code request} to be a {@link NodeDecommissionRequest} and returns a reference to this Builder
           * enabling method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder nodeDecommissionRequest()
          {
              return request(NODE_DECOMMISSION_REQUEST);
          }
  
          /**
           * Sets the {@code request} to be a {@link GossipUpdateRequest} for the
           * given {@link NodeCommandRequestPayload.State state}, and returns a reference to this Builder enabling method chaining.
           *
           * @param state  the desired state for gossip
           * @return a reference to this Builder
           */
          public Builder nodeGossipUpdateRequest(@NotNull NodeCommandRequestPayload.State state)
          {
              return request(new GossipUpdateRequest(state));
          }
  
          /**
           * Sets the {@code request} to be a {@link NativeUpdateRequest} for the
           * given {@link NodeCommandRequestPayload.State state}, and returns a reference to this Builder enabling method chaining.
           *
           * @param state  the desired state for native transport
           * @return a reference to this Builder
           */
          public Builder nodeNativeUpdateRequest(@NotNull NodeCommandRequestPayload.State state)
          {
              return request(new NativeUpdateRequest(state));
          }
  
          /**
           * Sets the {@code request} to be a {@link StreamStatsRequest} and returns a reference to this Builder
           * enabling method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder streamsStatsRequest()
          {
              return request(STREAM_STATS_REQUEST);
          }
  
          /**
           * Sets the {@code request} to be a {@link LifecycleUpdateRequest} for the
           * given {@link NodeCommandRequestPayload.State state}, and returns a reference to this Builder enabling method chaining.
           *
           * @param state the intended state for a Cassandra node
           * @return a reference to this Builder
           */
          public Builder nodeLifecycleUpdateRequest(@NotNull NodeCommandRequestPayload.State state)
          {
              return request(new LifecycleUpdateRequest(state));
          }
  
          /**
           * Sets the {@code request} to be a {@link LifecycleInfoRequest} and returns a reference to this Builder
           * enabling method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder nodeLifecycleInfoRequest()
          {
              return request(LIFECYCLE_INFO_REQUEST);
          }
  
          /**
           * Sets the {@code retryPolicy} to be an instance of {@link NoRetryPolicy}
           *
           * @return this {@link Builder} for method chaining
           */
          public Builder noRetryPolicy()
          {
              return retryPolicy(DEFAULT_NO_RETRY_POLICY);
          }
  
          /**
           * Sets the {@code retryPolicy} to be an
           * {@link org.apache.cassandra.sidecar.client.retry.ExponentialBackoffRetryPolicy} configured with
           * {@code 10} {@code maxRetries}, {@code 500} {@code retryDelayMillis}, and {@code 60,000}
           * {@code maxRetryDelayMillis}; and returns a reference to this Builder enabling method chaining.
           *
           * @return a reference to this Builder
           */
          public Builder exponentialBackoffRetryPolicy()
          {
              return retryPolicy(DEFAULT_EXPONENTIAL_BACKOFF_RETRY_POLICY);
          }
  
          /**
           * Sets the {@code retryPolicy} to be an
           * {@link org.apache.cassandra.sidecar.client.retry.ExponentialBackoffRetryPolicy} configured with the provided
           * {@code maxRetries}, {@code retryDelayMillis}, and {@code maxRetryDelayMillis}; and returns a reference to
           * this Builder enabling method chaining.
           *
           * @param maxRetries          the maximum number of retries
           * @param retryDelayMillis    the delay between retries in milliseconds
           * @param maxRetryDelayMillis the maximum retry delay in milliseconds
           * @return a reference to this Builder
           */
          public Builder exponentialBackoffRetryPolicy(int maxRetries, long retryDelayMillis, long maxRetryDelayMillis)
          {
              return retryPolicy(new ExponentialBackoffRetryPolicy(maxRetries, retryDelayMillis, maxRetryDelayMillis));
          }
  
          /**
           * Adds a custom header for the request and returns a reference to this Builder enabling method chaining.
           *
           * @param name  the header name
           * @param value the header value
           * @return a reference to this Builder
           */
          public Builder addCustomHeader(String name, String value)
          {
              customHeaders.put(name, value);
              return this;
          }
  
          /**
           * @return a new Builder with references to the parameters previously set in this Builder
           */
          public Builder copy()
          {
              return new Builder(this);
          }
  
          /**
           * Returns a {@code RequestContext} built from the parameters previously set.
           *
           * @return a {@code RequestContext} built with parameters of this {@code RequestContext.Builder}
           */
          public RequestContext build()
          {
              return new RequestContext(this);
          }
      }
  }
  