• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20172

11 Feb 2026 12:26PM UTC coverage: 88.71% (+0.003%) from 88.707%
#20172

push

github

jdcormie
api: Use io.grpc.Uri for target parsing in ManagedChannelRegistry

35405 of 39911 relevant lines covered (88.71%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.78
/../core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java
1
/*
2
 * Copyright 2020 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.internal;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21
import static io.grpc.internal.UriWrapper.wrap;
22

23
import com.google.common.annotations.VisibleForTesting;
24
import com.google.common.base.Preconditions;
25
import com.google.common.util.concurrent.MoreExecutors;
26
import com.google.errorprone.annotations.DoNotCall;
27
import io.grpc.Attributes;
28
import io.grpc.BinaryLog;
29
import io.grpc.CallCredentials;
30
import io.grpc.CallOptions;
31
import io.grpc.Channel;
32
import io.grpc.ChannelCredentials;
33
import io.grpc.ClientCall;
34
import io.grpc.ClientInterceptor;
35
import io.grpc.ClientTransportFilter;
36
import io.grpc.CompressorRegistry;
37
import io.grpc.DecompressorRegistry;
38
import io.grpc.EquivalentAddressGroup;
39
import io.grpc.InternalChannelz;
40
import io.grpc.InternalConfiguratorRegistry;
41
import io.grpc.InternalFeatureFlags;
42
import io.grpc.ManagedChannel;
43
import io.grpc.ManagedChannelBuilder;
44
import io.grpc.MethodDescriptor;
45
import io.grpc.MetricSink;
46
import io.grpc.NameResolver;
47
import io.grpc.NameResolverProvider;
48
import io.grpc.NameResolverRegistry;
49
import io.grpc.ProxyDetector;
50
import io.grpc.StatusOr;
51
import io.grpc.Uri;
52
import java.lang.reflect.InvocationTargetException;
53
import java.lang.reflect.Method;
54
import java.net.SocketAddress;
55
import java.net.URI;
56
import java.net.URISyntaxException;
57
import java.util.ArrayList;
58
import java.util.Arrays;
59
import java.util.Collection;
60
import java.util.Collections;
61
import java.util.IdentityHashMap;
62
import java.util.LinkedHashMap;
63
import java.util.List;
64
import java.util.Map;
65
import java.util.concurrent.Executor;
66
import java.util.concurrent.TimeUnit;
67
import java.util.logging.Level;
68
import java.util.logging.Logger;
69
import java.util.regex.Pattern;
70
import javax.annotation.Nullable;
71

72
/**
73
 * Default managed channel builder, for usage in Transport implementations.
74
 */
75
public final class ManagedChannelImplBuilder
76
    extends ManagedChannelBuilder<ManagedChannelImplBuilder> {
77
  private static final String DIRECT_ADDRESS_SCHEME = "directaddress";
78

79
  private static final Logger log = Logger.getLogger(ManagedChannelImplBuilder.class.getName());
1✔
80

81
  @DoNotCall("ClientTransportFactoryBuilder is required, use a constructor")
82
  public static ManagedChannelBuilder<?> forAddress(String name, int port) {
83
    throw new UnsupportedOperationException(
×
84
        "ClientTransportFactoryBuilder is required, use a constructor");
85
  }
86

87
  @DoNotCall("ClientTransportFactoryBuilder is required, use a constructor")
88
  public static ManagedChannelBuilder<?> forTarget(String target) {
89
    throw new UnsupportedOperationException(
×
90
        "ClientTransportFactoryBuilder is required, use a constructor");
91
  }
92

93
  /**
94
   * An idle timeout larger than this would disable idle mode.
95
   */
96
  @VisibleForTesting
97
  static final long IDLE_MODE_MAX_TIMEOUT_DAYS = 30;
98

99
  /**
100
   * The default idle timeout.
101
   */
102
  @VisibleForTesting
103
  static final long IDLE_MODE_DEFAULT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(30);
1✔
104

105
  /**
106
   * An idle timeout smaller than this would be capped to it.
107
   */
108
  static final long IDLE_MODE_MIN_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1);
1✔
109

110
  private static final ObjectPool<? extends Executor> DEFAULT_EXECUTOR_POOL =
1✔
111
      SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
1✔
112

113
  private static final DecompressorRegistry DEFAULT_DECOMPRESSOR_REGISTRY =
114
      DecompressorRegistry.getDefaultInstance();
1✔
115

116
  private static final CompressorRegistry DEFAULT_COMPRESSOR_REGISTRY =
117
      CompressorRegistry.getDefaultInstance();
1✔
118

119
  private static final long DEFAULT_RETRY_BUFFER_SIZE_IN_BYTES = 1L << 24;  // 16M
120
  private static final long DEFAULT_PER_RPC_BUFFER_LIMIT_IN_BYTES = 1L << 20; // 1M
121

122
  // Matching this pattern means the target string is a URI target or at least intended to be one.
123
  // A URI target must be an absolute hierarchical URI.
124
  // From RFC 2396: scheme = alpha *( alpha | digit | "+" | "-" | "." )
125
  @VisibleForTesting
126
  static final Pattern URI_PATTERN = Pattern.compile("[a-zA-Z][a-zA-Z0-9+.-]*:/.*");
1✔
127

128
  private static final Method GET_CLIENT_INTERCEPTOR_METHOD;
129

130
  static {
131
    Method getClientInterceptorMethod = null;
1✔
132
    try {
133
      Class<?> censusStatsAccessor =
1✔
134
          Class.forName("io.grpc.census.InternalCensusStatsAccessor");
1✔
135
      getClientInterceptorMethod =
1✔
136
          censusStatsAccessor.getDeclaredMethod(
1✔
137
              "getClientInterceptor",
138
              boolean.class,
139
              boolean.class,
140
              boolean.class,
141
              boolean.class);
142
    } catch (ClassNotFoundException e) {
1✔
143
      // Replace these separate catch statements with multicatch when Android min-API >= 19
144
      log.log(Level.FINE, "Unable to apply census stats", e);
1✔
145
    } catch (NoSuchMethodException e) {
×
146
      log.log(Level.FINE, "Unable to apply census stats", e);
×
147
    }
1✔
148
    GET_CLIENT_INTERCEPTOR_METHOD = getClientInterceptorMethod;
1✔
149
  }
1✔
150

151

152
  ObjectPool<? extends Executor> executorPool = DEFAULT_EXECUTOR_POOL;
1✔
153

154
  ObjectPool<? extends Executor> offloadExecutorPool = DEFAULT_EXECUTOR_POOL;
1✔
155

156
  private final List<ClientInterceptor> interceptors = new ArrayList<>();
1✔
157
  NameResolverRegistry nameResolverRegistry = NameResolverRegistry.getDefaultRegistry();
1✔
158

159
  final List<ClientTransportFilter> transportFilters = new ArrayList<>();
1✔
160

161
  final String target;
162
  @Nullable
163
  final ChannelCredentials channelCredentials;
164
  @Nullable
165
  final CallCredentials callCredentials;
166
  @Nullable
167
  IdentityHashMap<NameResolver.Args.Key<?>, Object> nameResolverCustomArgs;
168

169
  @Nullable
170
  private final SocketAddress directServerAddress;
171

172
  @Nullable
173
  String userAgent;
174

175
  @Nullable
176
  String authorityOverride;
177

178
  String defaultLbPolicy = GrpcUtil.DEFAULT_LB_POLICY;
1✔
179

180
  boolean fullStreamDecompression;
181

182
  DecompressorRegistry decompressorRegistry = DEFAULT_DECOMPRESSOR_REGISTRY;
1✔
183

184
  CompressorRegistry compressorRegistry = DEFAULT_COMPRESSOR_REGISTRY;
1✔
185

186
  long idleTimeoutMillis = IDLE_MODE_DEFAULT_TIMEOUT_MILLIS;
1✔
187

188
  int maxRetryAttempts = 5;
1✔
189
  int maxHedgedAttempts = 5;
1✔
190
  long retryBufferSize = DEFAULT_RETRY_BUFFER_SIZE_IN_BYTES;
1✔
191
  long perRpcBufferLimit = DEFAULT_PER_RPC_BUFFER_LIMIT_IN_BYTES;
1✔
192
  boolean retryEnabled = true;
1✔
193

194
  InternalChannelz channelz = InternalChannelz.instance();
1✔
195
  int maxTraceEvents;
196

197
  @Nullable
198
  Map<String, ?> defaultServiceConfig;
199
  boolean lookUpServiceConfig = true;
1✔
200

201
  @Nullable
202
  BinaryLog binlog;
203

204
  @Nullable
205
  ProxyDetector proxyDetector;
206

207
  private boolean authorityCheckerDisabled;
208
  private boolean statsEnabled = true;
1✔
209
  private boolean recordStartedRpcs = true;
1✔
210
  private boolean recordFinishedRpcs = true;
1✔
211
  private boolean recordRealTimeMetrics = false;
1✔
212
  private boolean recordRetryMetrics = true;
1✔
213
  private boolean tracingEnabled = true;
1✔
214
  List<MetricSink> metricSinks = new ArrayList<>();
1✔
215

216
  /**
217
   * An interface for Transport implementors to provide the {@link ClientTransportFactory}
218
   * appropriate for the channel.
219
   */
220
  public interface ClientTransportFactoryBuilder {
221
    ClientTransportFactory buildClientTransportFactory();
222
  }
223

224
  /**
225
   * Convenience ClientTransportFactoryBuilder, throws UnsupportedOperationException().
226
   */
227
  public static class UnsupportedClientTransportFactoryBuilder implements
1✔
228
      ClientTransportFactoryBuilder {
229
    @Override
230
    public ClientTransportFactory buildClientTransportFactory() {
231
      throw new UnsupportedOperationException();
×
232
    }
233
  }
234

235
  /**
236
   * An interface for Transport implementors to provide a default port to {@link
237
   * io.grpc.NameResolver} for use in cases where the target string doesn't include a port. The
238
   * default implementation returns {@link GrpcUtil#DEFAULT_PORT_SSL}.
239
   */
240
  public interface ChannelBuilderDefaultPortProvider {
241
    int getDefaultPort();
242
  }
243

244
  /**
245
   * Default implementation of {@link ChannelBuilderDefaultPortProvider} that returns a fixed port.
246
   */
247
  public static final class FixedPortProvider implements ChannelBuilderDefaultPortProvider {
248
    private final int port;
249

250
    public FixedPortProvider(int port) {
1✔
251
      this.port = port;
1✔
252
    }
1✔
253

254
    @Override
255
    public int getDefaultPort() {
256
      return port;
1✔
257
    }
258
  }
259

260
  private static final class ManagedChannelDefaultPortProvider implements
261
      ChannelBuilderDefaultPortProvider {
262
    @Override
263
    public int getDefaultPort() {
264
      return GrpcUtil.DEFAULT_PORT_SSL;
1✔
265
    }
266
  }
267

268
  private final ClientTransportFactoryBuilder clientTransportFactoryBuilder;
269
  private final ChannelBuilderDefaultPortProvider channelBuilderDefaultPortProvider;
270

271
  /**
272
   * Creates a new managed channel builder with a target string, which can be either a valid {@link
273
   * io.grpc.NameResolver}-compliant URI, or an authority string. Transport implementors must
274
   * provide client transport factory builder, and may set custom channel default port provider.
275
   */
276
  public ManagedChannelImplBuilder(String target,
277
      ClientTransportFactoryBuilder clientTransportFactoryBuilder,
278
      @Nullable ChannelBuilderDefaultPortProvider channelBuilderDefaultPortProvider) {
279
    this(target, null, null, clientTransportFactoryBuilder, channelBuilderDefaultPortProvider);
1✔
280
  }
1✔
281

282
  /**
283
   * Creates a new managed channel builder with a target string, which can be either a valid {@link
284
   * io.grpc.NameResolver}-compliant URI, or an authority string. Transport implementors must
285
   * provide client transport factory builder, and may set custom channel default port provider.
286
   *
287
   * @param channelCreds The ChannelCredentials provided by the user. These may be used when
288
   *     creating derivative channels.
289
   */
290
  public ManagedChannelImplBuilder(
291
      String target, @Nullable ChannelCredentials channelCreds, @Nullable CallCredentials callCreds,
292
      ClientTransportFactoryBuilder clientTransportFactoryBuilder,
293
      @Nullable ChannelBuilderDefaultPortProvider channelBuilderDefaultPortProvider) {
1✔
294
    this.target = checkNotNull(target, "target");
1✔
295
    this.channelCredentials = channelCreds;
1✔
296
    this.callCredentials = callCreds;
1✔
297
    this.clientTransportFactoryBuilder = checkNotNull(clientTransportFactoryBuilder,
1✔
298
        "clientTransportFactoryBuilder");
299
    this.directServerAddress = null;
1✔
300

301
    if (channelBuilderDefaultPortProvider != null) {
1✔
302
      this.channelBuilderDefaultPortProvider = channelBuilderDefaultPortProvider;
1✔
303
    } else {
304
      this.channelBuilderDefaultPortProvider = new ManagedChannelDefaultPortProvider();
1✔
305
    }
306
    // TODO(dnvindhya): Move configurator to all the individual builders
307
    InternalConfiguratorRegistry.configureChannelBuilder(this);
1✔
308
  }
1✔
309

310
  /**
311
   * Returns a target string for the SocketAddress. It is only used as a placeholder, because
312
   * DirectAddressNameResolverProvider will not actually try to use it. However, it must be a valid
313
   * URI.
314
   */
315
  @VisibleForTesting
316
  static String makeTargetStringForDirectAddress(SocketAddress address) {
317
    try {
318
      return new URI(DIRECT_ADDRESS_SCHEME, "", "/" + address, null).toString();
1✔
319
    } catch (URISyntaxException e) {
×
320
      // It should not happen.
321
      throw new RuntimeException(e);
×
322
    }
323
  }
324

325
  /**
326
   * Creates a new managed channel builder with the given server address, authority string of the
327
   * channel. Transport implementors must provide client transport factory builder, and may set
328
   * custom channel default port provider.
329
   */
330
  public ManagedChannelImplBuilder(SocketAddress directServerAddress, String authority,
331
      ClientTransportFactoryBuilder clientTransportFactoryBuilder,
332
      @Nullable ChannelBuilderDefaultPortProvider channelBuilderDefaultPortProvider) {
333
    this(directServerAddress, authority, null, null, clientTransportFactoryBuilder,
1✔
334
        channelBuilderDefaultPortProvider);
335
  }
1✔
336

337
  /**
338
   * Creates a new managed channel builder with the given server address, authority string of the
339
   * channel. Transport implementors must provide client transport factory builder, and may set
340
   * custom channel default port provider.
341
   * 
342
   * @param channelCreds The ChannelCredentials provided by the user. These may be used when
343
   *     creating derivative channels.
344
   */
345
  public ManagedChannelImplBuilder(SocketAddress directServerAddress, String authority,
346
      @Nullable ChannelCredentials channelCreds, @Nullable CallCredentials callCreds,
347
      ClientTransportFactoryBuilder clientTransportFactoryBuilder,
348
      @Nullable ChannelBuilderDefaultPortProvider channelBuilderDefaultPortProvider) {
1✔
349
    this.target = makeTargetStringForDirectAddress(directServerAddress);
1✔
350
    this.channelCredentials = channelCreds;
1✔
351
    this.callCredentials = callCreds;
1✔
352
    this.clientTransportFactoryBuilder = checkNotNull(clientTransportFactoryBuilder,
1✔
353
        "clientTransportFactoryBuilder");
354
    this.directServerAddress = directServerAddress;
1✔
355
    NameResolverRegistry reg = new NameResolverRegistry();
1✔
356
    reg.register(new DirectAddressNameResolverProvider(directServerAddress,
1✔
357
        authority));
358
    this.nameResolverRegistry = reg;
1✔
359

360
    if (channelBuilderDefaultPortProvider != null) {
1✔
361
      this.channelBuilderDefaultPortProvider = channelBuilderDefaultPortProvider;
1✔
362
    } else {
363
      this.channelBuilderDefaultPortProvider = new ManagedChannelDefaultPortProvider();
1✔
364
    }
365
    // TODO(dnvindhya): Move configurator to all the individual builders
366
    InternalConfiguratorRegistry.configureChannelBuilder(this);
1✔
367
  }
1✔
368

369
  @Override
370
  public ManagedChannelImplBuilder directExecutor() {
371
    return executor(MoreExecutors.directExecutor());
1✔
372
  }
373

374
  @Override
375
  public ManagedChannelImplBuilder executor(Executor executor) {
376
    if (executor != null) {
1✔
377
      this.executorPool = new FixedObjectPool<>(executor);
1✔
378
    } else {
379
      this.executorPool = DEFAULT_EXECUTOR_POOL;
1✔
380
    }
381
    return this;
1✔
382
  }
383

384
  @Override
385
  public ManagedChannelImplBuilder offloadExecutor(Executor executor) {
386
    if (executor != null) {
1✔
387
      this.offloadExecutorPool = new FixedObjectPool<>(executor);
1✔
388
    } else {
389
      this.offloadExecutorPool = DEFAULT_EXECUTOR_POOL;
1✔
390
    }
391
    return this;
1✔
392
  }
393

394
  @Override
395
  public ManagedChannelImplBuilder intercept(List<ClientInterceptor> interceptors) {
396
    this.interceptors.addAll(interceptors);
1✔
397
    return this;
1✔
398
  }
399

400
  @Override
401
  public ManagedChannelImplBuilder intercept(ClientInterceptor... interceptors) {
402
    return intercept(Arrays.asList(interceptors));
1✔
403
  }
404

405
  @Override
406
  protected ManagedChannelImplBuilder interceptWithTarget(InterceptorFactory factory) {
407
    // Add a placeholder instance to the interceptor list, and replace it with a real instance
408
    // during build().
409
    this.interceptors.add(new InterceptorFactoryWrapper(factory));
1✔
410
    return this;
1✔
411
  }
412

413
  @Override
414
  public ManagedChannelImplBuilder addTransportFilter(ClientTransportFilter hook) {
415
    transportFilters.add(checkNotNull(hook, "transport filter"));
1✔
416
    return this;
1✔
417
  }
418

419
  @Deprecated
420
  @Override
421
  public ManagedChannelImplBuilder nameResolverFactory(NameResolver.Factory resolverFactory) {
422
    Preconditions.checkState(directServerAddress == null,
1✔
423
        "directServerAddress is set (%s), which forbids the use of NameResolverFactory",
424
        directServerAddress);
425
    if (resolverFactory != null) {
1✔
426
      NameResolverRegistry reg = new NameResolverRegistry();
1✔
427
      if (resolverFactory instanceof NameResolverProvider) {
1✔
428
        reg.register((NameResolverProvider) resolverFactory);
1✔
429
      } else {
430
        reg.register(new NameResolverFactoryToProviderFacade(resolverFactory));
1✔
431
      }
432
      this.nameResolverRegistry = reg;
1✔
433
    } else {
1✔
434
      this.nameResolverRegistry = NameResolverRegistry.getDefaultRegistry();
1✔
435
    }
436
    return this;
1✔
437
  }
438

439
  ManagedChannelImplBuilder nameResolverRegistry(NameResolverRegistry resolverRegistry) {
440
    this.nameResolverRegistry = resolverRegistry;
1✔
441
    return this;
1✔
442
  }
443

444
  @Override
445
  public ManagedChannelImplBuilder defaultLoadBalancingPolicy(String policy) {
446
    Preconditions.checkState(directServerAddress == null,
1✔
447
        "directServerAddress is set (%s), which forbids the use of load-balancing policy",
448
        directServerAddress);
449
    Preconditions.checkArgument(policy != null, "policy cannot be null");
1✔
450
    this.defaultLbPolicy = policy;
1✔
451
    return this;
1✔
452
  }
453

454
  @Override
455
  public ManagedChannelImplBuilder decompressorRegistry(DecompressorRegistry registry) {
456
    if (registry != null) {
1✔
457
      this.decompressorRegistry = registry;
1✔
458
    } else {
459
      this.decompressorRegistry = DEFAULT_DECOMPRESSOR_REGISTRY;
1✔
460
    }
461
    return this;
1✔
462
  }
463

464
  @Override
465
  public ManagedChannelImplBuilder compressorRegistry(CompressorRegistry registry) {
466
    if (registry != null) {
1✔
467
      this.compressorRegistry = registry;
1✔
468
    } else {
469
      this.compressorRegistry = DEFAULT_COMPRESSOR_REGISTRY;
1✔
470
    }
471
    return this;
1✔
472
  }
473

474
  @Override
475
  public ManagedChannelImplBuilder userAgent(@Nullable String userAgent) {
476
    this.userAgent = userAgent;
1✔
477
    return this;
1✔
478
  }
479

480
  @Override
481
  public ManagedChannelImplBuilder overrideAuthority(String authority) {
482
    this.authorityOverride = checkAuthority(authority);
1✔
483
    return this;
1✔
484
  }
485

486
  @Override
487
  public ManagedChannelImplBuilder idleTimeout(long value, TimeUnit unit) {
488
    checkArgument(value > 0, "idle timeout is %s, but must be positive", value);
1✔
489
    // We convert to the largest unit to avoid overflow
490
    if (unit.toDays(value) >= IDLE_MODE_MAX_TIMEOUT_DAYS) {
1✔
491
      // This disables idle mode
492
      this.idleTimeoutMillis = ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE;
1✔
493
    } else {
494
      this.idleTimeoutMillis = Math.max(unit.toMillis(value), IDLE_MODE_MIN_TIMEOUT_MILLIS);
1✔
495
    }
496
    return this;
1✔
497
  }
498

499
  @Override
500
  public ManagedChannelImplBuilder maxRetryAttempts(int maxRetryAttempts) {
501
    this.maxRetryAttempts = maxRetryAttempts;
1✔
502
    return this;
1✔
503
  }
504

505
  @Override
506
  public ManagedChannelImplBuilder maxHedgedAttempts(int maxHedgedAttempts) {
507
    this.maxHedgedAttempts = maxHedgedAttempts;
1✔
508
    return this;
1✔
509
  }
510

511
  @Override
512
  public ManagedChannelImplBuilder retryBufferSize(long bytes) {
513
    checkArgument(bytes > 0L, "retry buffer size must be positive");
1✔
514
    retryBufferSize = bytes;
1✔
515
    return this;
1✔
516
  }
517

518
  @Override
519
  public ManagedChannelImplBuilder perRpcBufferLimit(long bytes) {
520
    checkArgument(bytes > 0L, "per RPC buffer limit must be positive");
1✔
521
    perRpcBufferLimit = bytes;
1✔
522
    return this;
1✔
523
  }
524

525
  @Override
526
  public ManagedChannelImplBuilder disableRetry() {
527
    retryEnabled = false;
1✔
528
    return this;
1✔
529
  }
530

531
  @Override
532
  public ManagedChannelImplBuilder enableRetry() {
533
    retryEnabled = true;
1✔
534
    return this;
1✔
535
  }
536

537
  @Override
538
  public ManagedChannelImplBuilder setBinaryLog(BinaryLog binlog) {
539
    this.binlog = binlog;
×
540
    return this;
×
541
  }
542

543
  @Override
544
  public ManagedChannelImplBuilder maxTraceEvents(int maxTraceEvents) {
545
    checkArgument(maxTraceEvents >= 0, "maxTraceEvents must be non-negative");
1✔
546
    this.maxTraceEvents = maxTraceEvents;
1✔
547
    return this;
1✔
548
  }
549

550
  @Override
551
  public ManagedChannelImplBuilder proxyDetector(@Nullable ProxyDetector proxyDetector) {
552
    this.proxyDetector = proxyDetector;
1✔
553
    return this;
1✔
554
  }
555

556
  @Override
557
  public ManagedChannelImplBuilder defaultServiceConfig(@Nullable Map<String, ?> serviceConfig) {
558
    // TODO(notcarl): use real parsing
559
    defaultServiceConfig = checkMapEntryTypes(serviceConfig);
1✔
560
    return this;
1✔
561
  }
562

563
  @Nullable
564
  private static Map<String, ?> checkMapEntryTypes(@Nullable Map<?, ?> map) {
565
    if (map == null) {
1✔
566
      return null;
×
567
    }
568
    // Not using ImmutableMap.Builder because of extra guava dependency for Android.
569
    Map<String, Object> parsedMap = new LinkedHashMap<>();
1✔
570
    for (Map.Entry<?, ?> entry : map.entrySet()) {
1✔
571
      checkArgument(
1✔
572
          entry.getKey() instanceof String,
1✔
573
          "The key of the entry '%s' is not of String type", entry);
574

575
      String key = (String) entry.getKey();
1✔
576
      Object value = entry.getValue();
1✔
577
      if (value == null) {
1✔
578
        parsedMap.put(key, null);
1✔
579
      } else if (value instanceof Map) {
1✔
580
        parsedMap.put(key, checkMapEntryTypes((Map<?, ?>) value));
1✔
581
      } else if (value instanceof List) {
1✔
582
        parsedMap.put(key, checkListEntryTypes((List<?>) value));
1✔
583
      } else if (value instanceof String) {
1✔
584
        parsedMap.put(key, value);
1✔
585
      } else if (value instanceof Double) {
1✔
586
        parsedMap.put(key, value);
1✔
587
      } else if (value instanceof Boolean) {
1✔
588
        parsedMap.put(key, value);
1✔
589
      } else {
590
        throw new IllegalArgumentException(
1✔
591
            "The value of the map entry '" + entry + "' is of type '" + value.getClass()
1✔
592
                + "', which is not supported");
593
      }
594
    }
1✔
595
    return Collections.unmodifiableMap(parsedMap);
1✔
596
  }
597

598
  private static List<?> checkListEntryTypes(List<?> list) {
599
    List<Object> parsedList = new ArrayList<>(list.size());
1✔
600
    for (Object value : list) {
1✔
601
      if (value == null) {
1✔
602
        parsedList.add(null);
1✔
603
      } else if (value instanceof Map) {
1✔
604
        parsedList.add(checkMapEntryTypes((Map<?, ?>) value));
1✔
605
      } else if (value instanceof List) {
1✔
606
        parsedList.add(checkListEntryTypes((List<?>) value));
×
607
      } else if (value instanceof String) {
1✔
608
        parsedList.add(value);
1✔
609
      } else if (value instanceof Double) {
1✔
610
        parsedList.add(value);
1✔
611
      } else if (value instanceof Boolean) {
1✔
612
        parsedList.add(value);
1✔
613
      } else {
614
        throw new IllegalArgumentException(
×
615
            "The entry '" + value + "' is of type '" + value.getClass()
×
616
                + "', which is not supported");
617
      }
618
    }
1✔
619
    return Collections.unmodifiableList(parsedList);
1✔
620
  }
621

622
  @Override
623
  public <X> ManagedChannelImplBuilder setNameResolverArg(NameResolver.Args.Key<X> key, X value) {
624
    if (nameResolverCustomArgs == null) {
1✔
625
      nameResolverCustomArgs = new IdentityHashMap<>();
1✔
626
    }
627
    nameResolverCustomArgs.put(checkNotNull(key, "key"), checkNotNull(value, "value"));
1✔
628
    return this;
1✔
629
  }
630

631
  @SuppressWarnings("unchecked") // This cast is safe because of setNameResolverArg()'s signature.
632
  void copyAllNameResolverCustomArgsTo(NameResolver.Args.Builder dest) {
633
    if (nameResolverCustomArgs != null) {
1✔
634
      for (Map.Entry<NameResolver.Args.Key<?>, Object> entry : nameResolverCustomArgs.entrySet()) {
1✔
635
        dest.setArg((NameResolver.Args.Key<Object>) entry.getKey(), entry.getValue());
1✔
636
      }
1✔
637
    }
638
  }
1✔
639

640
  @Override
641
  public ManagedChannelImplBuilder disableServiceConfigLookUp() {
642
    this.lookUpServiceConfig = false;
1✔
643
    return this;
1✔
644
  }
645

646
  /**
647
   * Disable or enable stats features. Enabled by default.
648
   *
649
   * <p>For the current release, calling {@code setStatsEnabled(true)} may have a side effect that
650
   * disables retry.
651
   */
652
  public void setStatsEnabled(boolean value) {
653
    statsEnabled = value;
1✔
654
  }
1✔
655

656
  /**
657
   * Disable or enable stats recording for RPC upstarts.  Effective only if {@link
658
   * #setStatsEnabled} is set to true.  Enabled by default.
659
   */
660
  public void setStatsRecordStartedRpcs(boolean value) {
661
    recordStartedRpcs = value;
1✔
662
  }
1✔
663

664
  /**
665
   * Disable or enable stats recording for RPC completions.  Effective only if {@link
666
   * #setStatsEnabled} is set to true.  Enabled by default.
667
   */
668
  public void setStatsRecordFinishedRpcs(boolean value) {
669
    recordFinishedRpcs = value;
1✔
670
  }
1✔
671

672
  /**
673
   * Disable or enable real-time metrics recording.  Effective only if {@link #setStatsEnabled} is
674
   * set to true.  Disabled by default.
675
   */
676
  public void setStatsRecordRealTimeMetrics(boolean value) {
677
    recordRealTimeMetrics = value;
×
678
  }
×
679
  
680
  public void setStatsRecordRetryMetrics(boolean value) {
681
    recordRetryMetrics = value;
1✔
682
  }
1✔
683

684
  /**
685
   * Disable or enable tracing features.  Enabled by default.
686
   */
687
  public void setTracingEnabled(boolean value) {
688
    tracingEnabled = value;
1✔
689
  }
1✔
690

691
  /**
692
   * Verifies the authority is valid.
693
   */
694
  @VisibleForTesting
695
  String checkAuthority(String authority) {
696
    if (authorityCheckerDisabled) {
1✔
697
      return authority;
1✔
698
    }
699
    return GrpcUtil.checkAuthority(authority);
1✔
700
  }
701

702
  /** Disable the check whether the authority is valid. */
703
  public ManagedChannelImplBuilder disableCheckAuthority() {
704
    authorityCheckerDisabled = true;
1✔
705
    return this;
1✔
706
  }
707

708
  /** Enable previously disabled authority check. */
709
  public ManagedChannelImplBuilder enableCheckAuthority() {
710
    authorityCheckerDisabled = false;
1✔
711
    return this;
1✔
712
  }
713

714
  @Override
715
  protected ManagedChannelImplBuilder addMetricSink(MetricSink metricSink) {
716
    metricSinks.add(checkNotNull(metricSink, "metric sink"));
1✔
717
    return this;
1✔
718
  }
719

720
  @Override
721
  public ManagedChannel build() {
722
    ClientTransportFactory clientTransportFactory =
1✔
723
        clientTransportFactoryBuilder.buildClientTransportFactory();
1✔
724
    ResolvedNameResolver resolvedResolver =
725
        InternalFeatureFlags.getRfc3986UrisEnabled()
1✔
726
            ? getNameResolverProviderRfc3986(target, nameResolverRegistry)
1✔
727
            : getNameResolverProvider(target, nameResolverRegistry);
1✔
728
    resolvedResolver.checkAddressTypes(clientTransportFactory.getSupportedSocketAddressTypes());
1✔
729
    return new ManagedChannelOrphanWrapper(new ManagedChannelImpl(
1✔
730
        this,
731
        clientTransportFactory,
732
        resolvedResolver.targetUri,
733
        resolvedResolver.provider,
734
        new ExponentialBackoffPolicy.Provider(),
735
        SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR),
1✔
736
        GrpcUtil.STOPWATCH_SUPPLIER,
737
        getEffectiveInterceptors(resolvedResolver.targetUri.toString()),
1✔
738
        TimeProvider.SYSTEM_TIME_PROVIDER));
739
  }
740

741
  // Temporarily disable retry when stats or tracing is enabled to avoid breakage, until we know
742
  // what should be the desired behavior for retry + stats/tracing.
743
  // TODO(zdapeng): FIX IT
744
  @VisibleForTesting
745
  List<ClientInterceptor> getEffectiveInterceptors(String computedTarget) {
746
    List<ClientInterceptor> effectiveInterceptors = new ArrayList<>(this.interceptors.size());
1✔
747
    for (ClientInterceptor interceptor : this.interceptors) {
1✔
748
      if (interceptor instanceof InterceptorFactoryWrapper) {
1✔
749
        InterceptorFactory factory = ((InterceptorFactoryWrapper) interceptor).factory;
1✔
750
        interceptor = factory.newInterceptor(computedTarget);
1✔
751
        if (interceptor == null) {
1✔
752
          throw new NullPointerException("Factory returned null interceptor: " + factory);
×
753
        }
754
      }
755
      effectiveInterceptors.add(interceptor);
1✔
756
    }
1✔
757

758
    boolean disableImplicitCensus = InternalConfiguratorRegistry.wasSetConfiguratorsCalled();
1✔
759
    if (disableImplicitCensus) {
1✔
760
      return effectiveInterceptors;
×
761
    }
762
    if (statsEnabled) {
1✔
763
      ClientInterceptor statsInterceptor = null;
1✔
764

765
      if (GET_CLIENT_INTERCEPTOR_METHOD != null) {
1✔
766
        try {
767
          statsInterceptor =
1✔
768
              (ClientInterceptor) GET_CLIENT_INTERCEPTOR_METHOD
769
              .invoke(
1✔
770
                null,
771
                recordStartedRpcs,
1✔
772
                recordFinishedRpcs,
1✔
773
                recordRealTimeMetrics,
1✔
774
                recordRetryMetrics);
1✔
775
        } catch (IllegalAccessException e) {
×
776
          log.log(Level.FINE, "Unable to apply census stats", e);
×
777
        } catch (InvocationTargetException e) {
×
778
          log.log(Level.FINE, "Unable to apply census stats", e);
×
779
        }
1✔
780
      }
781

782
      if (statsInterceptor != null) {
1✔
783
        // First interceptor runs last (see ClientInterceptors.intercept()), so that no
784
        // other interceptor can override the tracer factory we set in CallOptions.
785
        effectiveInterceptors.add(0, statsInterceptor);
1✔
786
      }
787
    }
788
    if (tracingEnabled) {
1✔
789
      ClientInterceptor tracingInterceptor = null;
1✔
790
      try {
791
        Class<?> censusTracingAccessor =
1✔
792
            Class.forName("io.grpc.census.InternalCensusTracingAccessor");
1✔
793
        Method getClientInterceptroMethod =
1✔
794
            censusTracingAccessor.getDeclaredMethod("getClientInterceptor");
1✔
795
        tracingInterceptor = (ClientInterceptor) getClientInterceptroMethod.invoke(null);
1✔
796
      } catch (ClassNotFoundException e) {
1✔
797
        // Replace these separate catch statements with multicatch when Android min-API >= 19
798
        log.log(Level.FINE, "Unable to apply census stats", e);
1✔
799
      } catch (NoSuchMethodException e) {
×
800
        log.log(Level.FINE, "Unable to apply census stats", e);
×
801
      } catch (IllegalAccessException e) {
×
802
        log.log(Level.FINE, "Unable to apply census stats", e);
×
803
      } catch (InvocationTargetException e) {
×
804
        log.log(Level.FINE, "Unable to apply census stats", e);
×
805
      }
1✔
806
      if (tracingInterceptor != null) {
1✔
807
        effectiveInterceptors.add(0, tracingInterceptor);
1✔
808
      }
809
    }
810
    return effectiveInterceptors;
1✔
811
  }
812

813
  /**
814
   * Returns a default port to {@link NameResolver} for use in cases where the target string doesn't
815
   * include a port. The default implementation returns {@link GrpcUtil#DEFAULT_PORT_SSL}.
816
   */
817
  int getDefaultPort() {
818
    return channelBuilderDefaultPortProvider.getDefaultPort();
1✔
819
  }
820

821
  @VisibleForTesting
822
  static class ResolvedNameResolver {
823
    public final UriWrapper targetUri;
824
    public final NameResolverProvider provider;
825

826
    public ResolvedNameResolver(UriWrapper targetUri, NameResolverProvider provider) {
1✔
827
      this.targetUri = checkNotNull(targetUri, "targetUri");
1✔
828
      this.provider = checkNotNull(provider, "provider");
1✔
829
    }
1✔
830

831
    void checkAddressTypes(
832
        Collection<Class<? extends SocketAddress>> channelTransportSocketAddressTypes) {
833
      if (channelTransportSocketAddressTypes != null) {
1✔
834
        Collection<Class<? extends SocketAddress>> nameResolverSocketAddressTypes =
1✔
835
            provider.getProducedSocketAddressTypes();
1✔
836
        if (!channelTransportSocketAddressTypes.containsAll(nameResolverSocketAddressTypes)) {
1✔
837
          throw new IllegalArgumentException(
1✔
838
              String.format(
1✔
839
                  "Address types of NameResolver '%s' for '%s' not supported by transport",
840
                  provider.getDefaultScheme(), targetUri));
1✔
841
        }
842
      }
843
    }
1✔
844
  }
845

846
  @VisibleForTesting
847
  static ResolvedNameResolver getNameResolverProvider(
848
      String target, NameResolverRegistry nameResolverRegistry) {
849
    // Finding a NameResolver. Try using the target string as the URI. If that fails, try prepending
850
    // "dns:///".
851
    NameResolverProvider provider = null;
1✔
852
    URI targetUri = null;
1✔
853
    StringBuilder uriSyntaxErrors = new StringBuilder();
1✔
854
    try {
855
      targetUri = new URI(target);
1✔
856
    } catch (URISyntaxException e) {
1✔
857
      // Can happen with ip addresses like "[::1]:1234" or 127.0.0.1:1234.
858
      uriSyntaxErrors.append(e.getMessage());
1✔
859
    }
1✔
860
    if (targetUri != null) {
1✔
861
      // For "localhost:8080" this would likely cause provider to be null, because "localhost" is
862
      // parsed as the scheme. Will hit the next case and try "dns:///localhost:8080".
863
      provider = nameResolverRegistry.getProviderForScheme(targetUri.getScheme());
1✔
864
    }
865

866
    if (provider == null && !URI_PATTERN.matcher(target).matches()) {
1✔
867
      // It doesn't look like a URI target. Maybe it's an authority string. Try with the default
868
      // scheme from the registry.
869
      try {
870
        targetUri = new URI(nameResolverRegistry.getDefaultScheme(), "", "/" + target, null);
1✔
871
      } catch (URISyntaxException e) {
×
872
        // Should not be possible.
873
        throw new IllegalArgumentException(e);
×
874
      }
1✔
875
      provider = nameResolverRegistry.getProviderForScheme(targetUri.getScheme());
1✔
876
    }
877

878
    if (provider == null) {
1✔
879
      throw new IllegalArgumentException(String.format(
1✔
880
          "Could not find a NameResolverProvider for %s%s",
881
          target, uriSyntaxErrors.length() > 0 ? " (" + uriSyntaxErrors + ")" : ""));
1✔
882
    }
883

884
    return new ResolvedNameResolver(wrap(targetUri), provider);
1✔
885
  }
886

887
  @VisibleForTesting
888
  static ResolvedNameResolver getNameResolverProviderRfc3986(
889
      String target, NameResolverRegistry nameResolverRegistry) {
890
    // Finding a NameResolver. Try using the target string as the URI. If that fails, try prepending
891
    // "dns:///".
892
    NameResolverProvider provider = null;
1✔
893
    Uri targetUri = null;
1✔
894
    StringBuilder uriSyntaxErrors = new StringBuilder();
1✔
895
    try {
896
      targetUri = Uri.parse(target);
1✔
897
    } catch (URISyntaxException e) {
1✔
898
      // Can happen with ip addresses like "[::1]:1234" or 127.0.0.1:1234.
899
      uriSyntaxErrors.append(e.getMessage());
1✔
900
    }
1✔
901
    if (targetUri != null) {
1✔
902
      // For "localhost:8080" this would likely cause provider to be null, because "localhost" is
903
      // parsed as the scheme. Will hit the next case and try "dns:///localhost:8080".
904
      provider = nameResolverRegistry.getProviderForScheme(targetUri.getScheme());
1✔
905
    }
906

907
    if (provider == null && !URI_PATTERN.matcher(target).matches()) {
1✔
908
      // It doesn't look like a URI target. Maybe it's an authority string. Try with the default
909
      // scheme from the registry.
910
      targetUri =
911
          Uri.newBuilder()
1✔
912
              .setScheme(nameResolverRegistry.getDefaultScheme())
1✔
913
              .setHost("")
1✔
914
              .setPath("/" + target)
1✔
915
              .build();
1✔
916
      provider = nameResolverRegistry.getProviderForScheme(targetUri.getScheme());
1✔
917
    }
918

919
    if (provider == null) {
1✔
920
      throw new IllegalArgumentException(
1✔
921
          String.format(
1✔
922
              "Could not find a NameResolverProvider for %s%s",
923
              target, uriSyntaxErrors.length() > 0 ? " (" + uriSyntaxErrors + ")" : ""));
1✔
924
    }
925

926
    return new ResolvedNameResolver(wrap(targetUri), provider);
1✔
927
  }
928

929
  private static class DirectAddressNameResolverProvider extends NameResolverProvider {
930
    final SocketAddress address;
931
    final String authority;
932
    final Collection<Class<? extends SocketAddress>> producedSocketAddressTypes;
933

934
    DirectAddressNameResolverProvider(SocketAddress address, String authority) {
1✔
935
      this.address = address;
1✔
936
      this.authority = authority;
1✔
937
      this.producedSocketAddressTypes
1✔
938
          = Collections.singleton(address.getClass());
1✔
939
    }
1✔
940

941
    @Override
942
    public NameResolver newNameResolver(URI notUsedUri, NameResolver.Args args) {
943
      return new NameResolver() {
1✔
944
        @Override
945
        public String getServiceAuthority() {
946
          return authority;
1✔
947
        }
948

949
        @Override
950
        public void start(Listener2 listener) {
951
          listener.onResult2(
1✔
952
              ResolutionResult.newBuilder()
1✔
953
                  .setAddressesOrError(
1✔
954
                      StatusOr.fromValue(
1✔
955
                          Collections.singletonList(new EquivalentAddressGroup(address))))
1✔
956
                  .setAttributes(Attributes.EMPTY)
1✔
957
                  .build());
1✔
958
        }
1✔
959

960
        @Override
961
        public void shutdown() {}
1✔
962
      };
963
    }
964

965
    @Override
966
    public String getDefaultScheme() {
967
      return DIRECT_ADDRESS_SCHEME;
1✔
968
    }
969

970
    @Override
971
    protected boolean isAvailable() {
972
      return true;
1✔
973
    }
974

975
    @Override
976
    protected int priority() {
977
      return 5;
1✔
978
    }
979

980
    @Override
981
    public Collection<Class<? extends SocketAddress>> getProducedSocketAddressTypes() {
982
      return producedSocketAddressTypes;
1✔
983
    }
984
  }
985

986
  private static final class InterceptorFactoryWrapper implements ClientInterceptor {
987
    final InterceptorFactory factory;
988

989
    public InterceptorFactoryWrapper(InterceptorFactory factory) {
1✔
990
      this.factory = checkNotNull(factory, "factory");
1✔
991
    }
1✔
992

993
    @Override
994
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
995
        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
996
      throw new AssertionError("Should have been replaced with real instance");
×
997
    }
998
  }
999

1000
  /**
1001
   * Returns the internal offload executor pool for offloading tasks.
1002
   */
1003
  public ObjectPool<? extends Executor> getOffloadExecutorPool() {
1004
    return this.offloadExecutorPool;
1✔
1005
  }
1006
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc