1 /* 2 * Copyright 2018 Google LLC 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google LLC nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 package com.google.api.gax.rpc; 31 32 import com.google.api.core.ApiClock; 33 import com.google.api.core.ApiFunction; 34 import com.google.api.core.BetaApi; 35 import com.google.api.core.NanoClock; 36 import com.google.api.gax.core.CredentialsProvider; 37 import com.google.api.gax.core.ExecutorProvider; 38 import com.google.api.gax.core.FixedCredentialsProvider; 39 import com.google.api.gax.core.FixedExecutorProvider; 40 import com.google.api.gax.core.InstantiatingExecutorProvider; 41 import com.google.api.gax.core.NoCredentialsProvider; 42 import com.google.api.gax.tracing.ApiTracerFactory; 43 import com.google.api.gax.tracing.BaseApiTracerFactory; 44 import com.google.auth.oauth2.QuotaProjectIdProvider; 45 import com.google.common.base.MoreObjects; 46 import com.google.common.base.Preconditions; 47 import java.io.IOException; 48 import java.util.concurrent.Executor; 49 import javax.annotation.Nonnull; 50 import javax.annotation.Nullable; 51 import org.threeten.bp.Duration; 52 53 /** 54 * A base settings class to configure a client stub class. 55 * 56 * <p>This base class includes settings that are applicable to all services, which includes things 57 * like settings for creating an executor, credentials, transport-specific settings, and identifiers 58 * for http headers. 59 * 60 * <p>If no ExecutorProvider is set, then InstantiatingExecutorProvider will be used, which creates 61 * a default executor. 62 */ 63 public abstract class StubSettings<SettingsT extends StubSettings<SettingsT>> { 64 65 static final String QUOTA_PROJECT_ID_HEADER_KEY = "x-goog-user-project"; 66 67 private final ExecutorProvider backgroundExecutorProvider; 68 private final CredentialsProvider credentialsProvider; 69 private final HeaderProvider headerProvider; 70 private final HeaderProvider internalHeaderProvider; 71 private final TransportChannelProvider transportChannelProvider; 72 private final ApiClock clock; 73 private final String endpoint; 74 private final String mtlsEndpoint; 75 private final String quotaProjectId; 76 @Nullable private final WatchdogProvider streamWatchdogProvider; 77 @Nonnull private final Duration streamWatchdogCheckInterval; 78 @Nonnull private final ApiTracerFactory tracerFactory; 79 // Track if deprecated setExecutorProvider is called 80 private boolean deprecatedExecutorProviderSet; 81 82 /** 83 * Indicate when creating transport whether it is allowed to use mTLS endpoint instead of the 84 * default endpoint. Only the endpoint set by client libraries is allowed. User provided endpoint 85 * should always be used as it is. Client libraries can set it via the {@link 86 * Builder#setSwitchToMtlsEndpointAllowed} method. 87 */ 88 private final boolean switchToMtlsEndpointAllowed; 89 90 /** Constructs an instance of StubSettings. */ StubSettings(Builder builder)91 protected StubSettings(Builder builder) { 92 this.backgroundExecutorProvider = builder.backgroundExecutorProvider; 93 this.transportChannelProvider = builder.transportChannelProvider; 94 this.credentialsProvider = builder.credentialsProvider; 95 this.headerProvider = builder.headerProvider; 96 this.internalHeaderProvider = builder.internalHeaderProvider; 97 this.clock = builder.clock; 98 this.endpoint = builder.endpoint; 99 this.mtlsEndpoint = builder.mtlsEndpoint; 100 this.switchToMtlsEndpointAllowed = builder.switchToMtlsEndpointAllowed; 101 this.quotaProjectId = builder.quotaProjectId; 102 this.streamWatchdogProvider = builder.streamWatchdogProvider; 103 this.streamWatchdogCheckInterval = builder.streamWatchdogCheckInterval; 104 this.tracerFactory = builder.tracerFactory; 105 this.deprecatedExecutorProviderSet = builder.deprecatedExecutorProviderSet; 106 } 107 108 /** @deprecated Please use {@link #getBackgroundExecutorProvider()}. */ 109 @Deprecated getExecutorProvider()110 public final ExecutorProvider getExecutorProvider() { 111 return deprecatedExecutorProviderSet ? backgroundExecutorProvider : null; 112 } 113 getBackgroundExecutorProvider()114 public final ExecutorProvider getBackgroundExecutorProvider() { 115 return backgroundExecutorProvider; 116 } 117 getTransportChannelProvider()118 public final TransportChannelProvider getTransportChannelProvider() { 119 return transportChannelProvider; 120 } 121 getCredentialsProvider()122 public final CredentialsProvider getCredentialsProvider() { 123 return credentialsProvider; 124 } 125 getHeaderProvider()126 public final HeaderProvider getHeaderProvider() { 127 return headerProvider; 128 } 129 getInternalHeaderProvider()130 protected final HeaderProvider getInternalHeaderProvider() { 131 return internalHeaderProvider; 132 } 133 getClock()134 public final ApiClock getClock() { 135 return clock; 136 } 137 getEndpoint()138 public final String getEndpoint() { 139 return endpoint; 140 } 141 getMtlsEndpoint()142 public final String getMtlsEndpoint() { 143 return mtlsEndpoint; 144 } 145 146 /** Limit the visibility to this package only since only this package needs it. */ getSwitchToMtlsEndpointAllowed()147 final boolean getSwitchToMtlsEndpointAllowed() { 148 return switchToMtlsEndpointAllowed; 149 } 150 getQuotaProjectId()151 public final String getQuotaProjectId() { 152 return quotaProjectId; 153 } 154 155 @Nullable getStreamWatchdogProvider()156 public final WatchdogProvider getStreamWatchdogProvider() { 157 return streamWatchdogProvider; 158 } 159 160 @Nonnull getStreamWatchdogCheckInterval()161 public final Duration getStreamWatchdogCheckInterval() { 162 return streamWatchdogCheckInterval; 163 } 164 165 /** 166 * Gets the configured {@link ApiTracerFactory} that will be used to generate traces for 167 * operations. 168 */ 169 @BetaApi("The surface for tracing is not stable yet and may change in the future.") 170 @Nonnull getTracerFactory()171 public ApiTracerFactory getTracerFactory() { 172 return tracerFactory; 173 } 174 175 @Override toString()176 public String toString() { 177 return MoreObjects.toStringHelper(this) 178 .add("backgroundExecutorProvider", backgroundExecutorProvider) 179 .add("transportChannelProvider", transportChannelProvider) 180 .add("credentialsProvider", credentialsProvider) 181 .add("headerProvider", headerProvider) 182 .add("internalHeaderProvider", internalHeaderProvider) 183 .add("clock", clock) 184 .add("endpoint", endpoint) 185 .add("mtlsEndpoint", mtlsEndpoint) 186 .add("switchToMtlsEndpointAllowed", switchToMtlsEndpointAllowed) 187 .add("quotaProjectId", quotaProjectId) 188 .add("streamWatchdogProvider", streamWatchdogProvider) 189 .add("streamWatchdogCheckInterval", streamWatchdogCheckInterval) 190 .add("tracerFactory", tracerFactory) 191 .toString(); 192 } 193 toBuilder()194 public abstract StubSettings.Builder toBuilder(); 195 196 public abstract static class Builder< 197 SettingsT extends StubSettings<SettingsT>, B extends Builder<SettingsT, B>> { 198 199 private ExecutorProvider backgroundExecutorProvider; 200 private CredentialsProvider credentialsProvider; 201 private HeaderProvider headerProvider; 202 private HeaderProvider internalHeaderProvider; 203 private TransportChannelProvider transportChannelProvider; 204 private ApiClock clock; 205 private String endpoint; 206 private String mtlsEndpoint; 207 private String quotaProjectId; 208 @Nullable private WatchdogProvider streamWatchdogProvider; 209 @Nonnull private Duration streamWatchdogCheckInterval; 210 @Nonnull private ApiTracerFactory tracerFactory; 211 private boolean deprecatedExecutorProviderSet; 212 213 /** 214 * Indicate when creating transport whether it is allowed to use mTLS endpoint instead of the 215 * default endpoint. Only the endpoint set by client libraries is allowed. User provided 216 * endpoint should always be used as it is. Client libraries can set it via the {@link 217 * Builder#setSwitchToMtlsEndpointAllowed} method. 218 */ 219 private boolean switchToMtlsEndpointAllowed = false; 220 221 /** Create a builder from a StubSettings object. */ Builder(StubSettings settings)222 protected Builder(StubSettings settings) { 223 this.backgroundExecutorProvider = settings.backgroundExecutorProvider; 224 this.transportChannelProvider = settings.transportChannelProvider; 225 this.credentialsProvider = settings.credentialsProvider; 226 this.headerProvider = settings.headerProvider; 227 this.internalHeaderProvider = settings.internalHeaderProvider; 228 this.clock = settings.clock; 229 this.endpoint = settings.endpoint; 230 this.mtlsEndpoint = settings.mtlsEndpoint; 231 this.switchToMtlsEndpointAllowed = settings.switchToMtlsEndpointAllowed; 232 this.quotaProjectId = settings.quotaProjectId; 233 this.streamWatchdogProvider = settings.streamWatchdogProvider; 234 this.streamWatchdogCheckInterval = settings.streamWatchdogCheckInterval; 235 this.tracerFactory = settings.tracerFactory; 236 this.deprecatedExecutorProviderSet = settings.deprecatedExecutorProviderSet; 237 } 238 239 /** Get Quota Project ID from Client Context * */ getQuotaProjectIdFromClientContext(ClientContext clientContext)240 private static String getQuotaProjectIdFromClientContext(ClientContext clientContext) { 241 if (clientContext.getQuotaProjectId() != null) { 242 return clientContext.getQuotaProjectId(); 243 } 244 if (clientContext.getCredentials() instanceof QuotaProjectIdProvider) { 245 return ((QuotaProjectIdProvider) clientContext.getCredentials()).getQuotaProjectId(); 246 } 247 if (clientContext.getHeaders().containsKey(QUOTA_PROJECT_ID_HEADER_KEY)) { 248 return clientContext.getHeaders().get(QUOTA_PROJECT_ID_HEADER_KEY); 249 } 250 if (clientContext.getInternalHeaders().containsKey(QUOTA_PROJECT_ID_HEADER_KEY)) { 251 return clientContext.getInternalHeaders().get(QUOTA_PROJECT_ID_HEADER_KEY); 252 } 253 return null; 254 } 255 Builder(ClientContext clientContext)256 protected Builder(ClientContext clientContext) { 257 if (clientContext == null) { 258 this.backgroundExecutorProvider = InstantiatingExecutorProvider.newBuilder().build(); 259 this.transportChannelProvider = null; 260 this.credentialsProvider = NoCredentialsProvider.create(); 261 this.headerProvider = new NoHeaderProvider(); 262 this.internalHeaderProvider = new NoHeaderProvider(); 263 this.clock = NanoClock.getDefaultClock(); 264 this.endpoint = null; 265 this.mtlsEndpoint = null; 266 this.quotaProjectId = null; 267 this.streamWatchdogProvider = InstantiatingWatchdogProvider.create(); 268 this.streamWatchdogCheckInterval = Duration.ofSeconds(10); 269 this.tracerFactory = BaseApiTracerFactory.getInstance(); 270 this.deprecatedExecutorProviderSet = false; 271 } else { 272 ExecutorProvider fixedExecutorProvider = 273 FixedExecutorProvider.create(clientContext.getExecutor()); 274 this.deprecatedExecutorProviderSet = true; 275 this.backgroundExecutorProvider = fixedExecutorProvider; 276 this.transportChannelProvider = 277 FixedTransportChannelProvider.create(clientContext.getTransportChannel()); 278 this.credentialsProvider = FixedCredentialsProvider.create(clientContext.getCredentials()); 279 this.headerProvider = FixedHeaderProvider.create(clientContext.getHeaders()); 280 this.internalHeaderProvider = 281 FixedHeaderProvider.create(clientContext.getInternalHeaders()); 282 this.clock = clientContext.getClock(); 283 this.endpoint = clientContext.getEndpoint(); 284 if (this.endpoint != null) { 285 this.mtlsEndpoint = this.endpoint.replace("googleapis.com", "mtls.googleapis.com"); 286 } 287 this.streamWatchdogProvider = 288 FixedWatchdogProvider.create(clientContext.getStreamWatchdog()); 289 this.streamWatchdogCheckInterval = clientContext.getStreamWatchdogCheckInterval(); 290 this.tracerFactory = clientContext.getTracerFactory(); 291 this.quotaProjectId = getQuotaProjectIdFromClientContext(clientContext); 292 } 293 } 294 Builder()295 protected Builder() { 296 this((ClientContext) null); 297 } 298 299 @SuppressWarnings("unchecked") self()300 protected B self() { 301 return (B) this; 302 } 303 304 /** 305 * Sets the ExecutorProvider to use for getting the executor to use for running asynchronous API 306 * call logic (such as retries and long-running operations), and also to pass to the transport 307 * settings if an executor is needed for the transport and it doesn't have its own executor 308 * provider. 309 * 310 * @deprecated Please use {@link #setBackgroundExecutorProvider(ExecutorProvider)} for setting 311 * executor to use for running scheduled API call logic. To set executor for {@link 312 * TransportChannelProvider}, please use {@link 313 * TransportChannelProvider#withExecutor(Executor)} instead. 314 */ 315 @Deprecated setExecutorProvider(ExecutorProvider executorProvider)316 public B setExecutorProvider(ExecutorProvider executorProvider) { 317 // For backward compatibility, this will set backgroundExecutorProvider and mark 318 // deprecatedExecutorProviderSet to true. In ClientContext#create(), if 319 // TransportChannelProvider doesn't have an executor, and deprecatedExecutorProviderSet is 320 // true, backgroundExecutorProvider will be used as TransportChannelProvider's executor. 321 // After this method is deprecated, TransportChannelProvider's executor can only be set with 322 // TransportChannelProvider#withExecutor. 323 this.deprecatedExecutorProviderSet = true; 324 this.backgroundExecutorProvider = executorProvider; 325 return self(); 326 } 327 328 /** 329 * Sets the executor to use for running scheduled API call logic (such as retries and 330 * long-running operations). 331 */ setBackgroundExecutorProvider(ExecutorProvider backgroundExecutorProvider)332 public B setBackgroundExecutorProvider(ExecutorProvider backgroundExecutorProvider) { 333 this.backgroundExecutorProvider = backgroundExecutorProvider; 334 return self(); 335 } 336 337 /** Sets the CredentialsProvider to use for getting the credentials to make calls with. */ setCredentialsProvider(CredentialsProvider credentialsProvider)338 public B setCredentialsProvider(CredentialsProvider credentialsProvider) { 339 this.credentialsProvider = Preconditions.checkNotNull(credentialsProvider); 340 return self(); 341 } 342 343 /** 344 * Sets the HeaderProvider for getting custom static headers for http requests. The header 345 * provider will be called during client construction only once. The headers returned by the 346 * provider will be cached and supplied as is for each request issued by the constructed client. 347 * Some reserved headers can be overridden (e.g. Content-Type) or merged with the default value 348 * (e.g. User-Agent) by the underlying transport layer. 349 */ setHeaderProvider(HeaderProvider headerProvider)350 public B setHeaderProvider(HeaderProvider headerProvider) { 351 this.headerProvider = headerProvider; 352 if (this.quotaProjectId == null 353 && headerProvider.getHeaders().containsKey(QUOTA_PROJECT_ID_HEADER_KEY)) { 354 this.quotaProjectId = headerProvider.getHeaders().get(QUOTA_PROJECT_ID_HEADER_KEY); 355 } 356 return self(); 357 } 358 359 /** 360 * Sets the HeaderProvider for getting internal (library-defined) static headers for http 361 * requests. The header provider will be called during client construction only once. The 362 * headers returned by the provider will be cached and supplied as is for each request issued by 363 * the constructed client. Some reserved headers can be overridden (e.g. Content-Type) or merged 364 * with the default value (e.g. User-Agent) by the underlying transport layer. 365 */ setInternalHeaderProvider(HeaderProvider internalHeaderProvider)366 protected B setInternalHeaderProvider(HeaderProvider internalHeaderProvider) { 367 this.internalHeaderProvider = internalHeaderProvider; 368 if (this.quotaProjectId == null 369 && internalHeaderProvider.getHeaders().containsKey(QUOTA_PROJECT_ID_HEADER_KEY)) { 370 this.quotaProjectId = internalHeaderProvider.getHeaders().get(QUOTA_PROJECT_ID_HEADER_KEY); 371 } 372 return self(); 373 } 374 375 /** 376 * Sets the TransportProvider to use for getting the transport-specific context to make calls 377 * with. 378 */ setTransportChannelProvider(TransportChannelProvider transportChannelProvider)379 public B setTransportChannelProvider(TransportChannelProvider transportChannelProvider) { 380 this.transportChannelProvider = transportChannelProvider; 381 return self(); 382 } 383 384 /** 385 * Sets the {@link WatchdogProvider} to use for streaming RPC. 386 * 387 * <p>This will default to a {@link InstantiatingWatchdogProvider} if it is not set. 388 */ setStreamWatchdogProvider(@ullable WatchdogProvider streamWatchdogProvider)389 public B setStreamWatchdogProvider(@Nullable WatchdogProvider streamWatchdogProvider) { 390 this.streamWatchdogProvider = streamWatchdogProvider; 391 return self(); 392 } 393 394 /** 395 * Sets the clock to use for retry logic. 396 * 397 * <p>This will default to a system clock if it is not set. 398 */ setClock(ApiClock clock)399 public B setClock(ApiClock clock) { 400 this.clock = clock; 401 return self(); 402 } 403 setEndpoint(String endpoint)404 public B setEndpoint(String endpoint) { 405 this.endpoint = endpoint; 406 this.switchToMtlsEndpointAllowed = false; 407 if (this.endpoint != null && this.mtlsEndpoint == null) { 408 this.mtlsEndpoint = this.endpoint.replace("googleapis.com", "mtls.googleapis.com"); 409 } 410 return self(); 411 } 412 setSwitchToMtlsEndpointAllowed(boolean switchToMtlsEndpointAllowed)413 protected B setSwitchToMtlsEndpointAllowed(boolean switchToMtlsEndpointAllowed) { 414 this.switchToMtlsEndpointAllowed = switchToMtlsEndpointAllowed; 415 return self(); 416 } 417 setMtlsEndpoint(String mtlsEndpoint)418 public B setMtlsEndpoint(String mtlsEndpoint) { 419 this.mtlsEndpoint = mtlsEndpoint; 420 return self(); 421 } 422 setQuotaProjectId(String quotaProjectId)423 public B setQuotaProjectId(String quotaProjectId) { 424 this.quotaProjectId = quotaProjectId; 425 return self(); 426 } 427 428 /** 429 * Sets how often the {@link Watchdog} will check ongoing streaming RPCs. Defaults to 10 secs. 430 * Use {@link Duration#ZERO} to disable. 431 */ setStreamWatchdogCheckInterval(@onnull Duration checkInterval)432 public B setStreamWatchdogCheckInterval(@Nonnull Duration checkInterval) { 433 Preconditions.checkNotNull(checkInterval); 434 this.streamWatchdogCheckInterval = checkInterval; 435 return self(); 436 } 437 438 /** 439 * Configures the {@link ApiTracerFactory} that will be used to generate traces. 440 * 441 * @param tracerFactory an instance of {@link ApiTracerFactory} to set. 442 */ 443 @BetaApi("The surface for tracing is not stable yet and may change in the future.") setTracerFactory(@onnull ApiTracerFactory tracerFactory)444 public B setTracerFactory(@Nonnull ApiTracerFactory tracerFactory) { 445 Preconditions.checkNotNull(tracerFactory); 446 this.tracerFactory = tracerFactory; 447 return self(); 448 } 449 450 /** @deprecated Please use {@link #getBackgroundExecutorProvider()}. */ 451 @Deprecated getExecutorProvider()452 public ExecutorProvider getExecutorProvider() { 453 return deprecatedExecutorProviderSet ? backgroundExecutorProvider : null; 454 } 455 456 /** Gets the ExecutorProvider that was previously set on this Builder. */ getBackgroundExecutorProvider()457 public ExecutorProvider getBackgroundExecutorProvider() { 458 return backgroundExecutorProvider; 459 } 460 461 /** Gets the TransportProvider that was previously set on this Builder. */ getTransportChannelProvider()462 public TransportChannelProvider getTransportChannelProvider() { 463 return transportChannelProvider; 464 } 465 466 /** Gets the CredentialsProvider that was previously set on this Builder. */ getCredentialsProvider()467 public CredentialsProvider getCredentialsProvider() { 468 return credentialsProvider; 469 } 470 471 /** Gets the custom HeaderProvider that was previously set on this Builder. */ getHeaderProvider()472 public HeaderProvider getHeaderProvider() { 473 return headerProvider; 474 } 475 476 /** Gets the internal HeaderProvider that was previously set on this Builder. */ getInternalHeaderProvider()477 protected HeaderProvider getInternalHeaderProvider() { 478 return internalHeaderProvider; 479 } 480 481 /** Gets the {@link WatchdogProvider }that was previously set on this Builder. */ 482 @Nullable getStreamWatchdogProvider()483 public WatchdogProvider getStreamWatchdogProvider() { 484 return streamWatchdogProvider; 485 } 486 487 /** Gets the ApiClock that was previously set on this Builder. */ getClock()488 public ApiClock getClock() { 489 return clock; 490 } 491 getEndpoint()492 public String getEndpoint() { 493 return endpoint; 494 } 495 getMtlsEndpoint()496 public String getMtlsEndpoint() { 497 return mtlsEndpoint; 498 } 499 500 /** Gets the QuotaProjectId that was previously set on this Builder. */ getQuotaProjectId()501 public String getQuotaProjectId() { 502 return quotaProjectId; 503 } 504 505 @Nonnull getStreamWatchdogCheckInterval()506 public Duration getStreamWatchdogCheckInterval() { 507 return streamWatchdogCheckInterval; 508 } 509 510 @BetaApi("The surface for tracing is not stable yet and may change in the future.") 511 @Nonnull getTracerFactory()512 public ApiTracerFactory getTracerFactory() { 513 return tracerFactory; 514 } 515 516 /** Applies the given settings updater function to the given method settings builders. */ applyToAllUnaryMethods( Iterable<UnaryCallSettings.Builder<?, ?>> methodSettingsBuilders, ApiFunction<UnaryCallSettings.Builder<?, ?>, Void> settingsUpdater)517 protected static void applyToAllUnaryMethods( 518 Iterable<UnaryCallSettings.Builder<?, ?>> methodSettingsBuilders, 519 ApiFunction<UnaryCallSettings.Builder<?, ?>, Void> settingsUpdater) { 520 for (UnaryCallSettings.Builder<?, ?> settingsBuilder : methodSettingsBuilders) { 521 settingsUpdater.apply(settingsBuilder); 522 } 523 } 524 build()525 public abstract <B extends StubSettings<B>> StubSettings<B> build() throws IOException; 526 527 @Override toString()528 public String toString() { 529 return MoreObjects.toStringHelper(this) 530 .add("backgroundExecutorProvider", backgroundExecutorProvider) 531 .add("transportChannelProvider", transportChannelProvider) 532 .add("credentialsProvider", credentialsProvider) 533 .add("headerProvider", headerProvider) 534 .add("internalHeaderProvider", internalHeaderProvider) 535 .add("clock", clock) 536 .add("endpoint", endpoint) 537 .add("mtlsEndpoint", mtlsEndpoint) 538 .add("switchToMtlsEndpointAllowed", switchToMtlsEndpointAllowed) 539 .add("quotaProjectId", quotaProjectId) 540 .add("streamWatchdogProvider", streamWatchdogProvider) 541 .add("streamWatchdogCheckInterval", streamWatchdogCheckInterval) 542 .add("tracerFactory", tracerFactory) 543 .toString(); 544 } 545 } 546 } 547