diff --git a/api/src/main/java/io/grpc/ChannelCredentials.java b/api/src/main/java/io/grpc/ChannelCredentials.java
new file mode 100644
index 0000000000..a601fb1867
--- /dev/null
+++ b/api/src/main/java/io/grpc/ChannelCredentials.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020 The gRPC Authors
+ *
+ * Licensed 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 io.grpc;
+
+/**
+ * Represents a security configuration to be used for channels. There is no generic mechanism for
+ * processing arbitrary {@code ChannelCredentials}; the consumer of the credential (the channel)
+ * must support each implementation explicitly and separately. Consumers are not required to support
+ * all types or even all possible configurations for types that are partially supported, but they
+ * must at least fully support {@link ChoiceChannelCredentials}.
+ *
+ *
A {@code ChannelCredential} provides client identity and authenticates the server. This is
+ * different from {@link CallCredentials}, which only provides client identity. They can also
+ * influence types of encryption used and similar security configuration.
+ *
+ *
The concrete credential type should not be relevant to most users of the API and may be an
+ * implementation decision. Users should generally use the {@code ChannelCredentials} type for
+ * variables instead of the concrete type. Freshly-constructed credentials should be returned as
+ * {@code ChannelCredentials} instead of a concrete type to encourage this pattern. Concrete types
+ * would only be used after {@code instanceof} checks (which must consider
+ * {@code ChoiceChannelCredentials}!).
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7479")
+public abstract class ChannelCredentials {}
diff --git a/api/src/main/java/io/grpc/ChoiceChannelCredentials.java b/api/src/main/java/io/grpc/ChoiceChannelCredentials.java
new file mode 100644
index 0000000000..2fe00da86e
--- /dev/null
+++ b/api/src/main/java/io/grpc/ChoiceChannelCredentials.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2020 The gRPC Authors
+ *
+ * Licensed 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 io.grpc;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides a list of {@link ChannelCredentials}, where any one may be used. The credentials are in
+ * preference order.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7479")
+public final class ChoiceChannelCredentials extends ChannelCredentials {
+ /**
+ * Constructs with the provided {@code creds} as options, with preferred credentials first.
+ *
+ * @throws IllegalArgumentException if no creds are provided
+ */
+ public static ChannelCredentials create(ChannelCredentials... creds) {
+ if (creds.length == 0) {
+ throw new IllegalArgumentException("At least one credential is required");
+ }
+ return new ChoiceChannelCredentials(creds);
+ }
+
+ private final List creds;
+
+ private ChoiceChannelCredentials(ChannelCredentials... creds) {
+ for (ChannelCredentials cred : creds) {
+ if (cred == null) {
+ throw new NullPointerException();
+ }
+ }
+ this.creds = Collections.unmodifiableList(new ArrayList<>(Arrays.asList(creds)));
+ }
+
+ /** Non-empty list of credentials, in preference order. */
+ public List getCredentialsList() {
+ return creds;
+ }
+}
diff --git a/api/src/main/java/io/grpc/CompositeCallCredentials.java b/api/src/main/java/io/grpc/CompositeCallCredentials.java
new file mode 100644
index 0000000000..1b3e5a52d5
--- /dev/null
+++ b/api/src/main/java/io/grpc/CompositeCallCredentials.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2020 The gRPC Authors
+ *
+ * Licensed 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 io.grpc;
+
+import com.google.common.base.Preconditions;
+import java.util.concurrent.Executor;
+
+/**
+ * Uses multiple {@code CallCredentials} as if they were one. If the first credential fails, the
+ * second will not be used. Both must succeed to allow the RPC.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7479")
+public final class CompositeCallCredentials extends CallCredentials {
+ private final CallCredentials credentials1;
+ private final CallCredentials credentials2;
+
+ public CompositeCallCredentials(CallCredentials creds1, CallCredentials creds2) {
+ this.credentials1 = Preconditions.checkNotNull(creds1, "creds1");
+ this.credentials2 = Preconditions.checkNotNull(creds2, "creds2");
+ }
+
+ @Override
+ public void applyRequestMetadata(
+ RequestInfo requestInfo, Executor appExecutor, MetadataApplier applier) {
+ credentials1.applyRequestMetadata(requestInfo, appExecutor,
+ new WrappingMetadataApplier(requestInfo, appExecutor, applier, Context.current()));
+ }
+
+ @Override
+ public void thisUsesUnstableApi() {}
+
+ private final class WrappingMetadataApplier extends MetadataApplier {
+ private final RequestInfo requestInfo;
+ private final Executor appExecutor;
+ private final MetadataApplier delegate;
+ private final Context context;
+
+ public WrappingMetadataApplier(
+ RequestInfo requestInfo, Executor appExecutor, MetadataApplier delegate, Context context) {
+ this.requestInfo = requestInfo;
+ this.appExecutor = appExecutor;
+ this.delegate = Preconditions.checkNotNull(delegate, "delegate");
+ this.context = Preconditions.checkNotNull(context, "context");
+ }
+
+ @Override
+ public void apply(Metadata headers) {
+ Preconditions.checkNotNull(headers, "headers");
+ Context previous = context.attach();
+ try {
+ credentials2.applyRequestMetadata(
+ requestInfo, appExecutor, new CombiningMetadataApplier(delegate, headers));
+ } finally {
+ context.detach(previous);
+ }
+ }
+
+ @Override
+ public void fail(Status status) {
+ delegate.fail(status);
+ }
+ }
+
+ private static final class CombiningMetadataApplier extends MetadataApplier {
+ private final MetadataApplier delegate;
+ private final Metadata firstHeaders;
+
+ public CombiningMetadataApplier(MetadataApplier delegate, Metadata firstHeaders) {
+ this.delegate = delegate;
+ this.firstHeaders = firstHeaders;
+ }
+
+ @Override
+ public void apply(Metadata headers) {
+ Preconditions.checkNotNull(headers, "headers");
+ Metadata combined = new Metadata();
+ combined.merge(firstHeaders);
+ combined.merge(headers);
+ delegate.apply(combined);
+ }
+
+ @Override
+ public void fail(Status status) {
+ delegate.fail(status);
+ }
+ }
+}
diff --git a/api/src/main/java/io/grpc/CompositeChannelCredentials.java b/api/src/main/java/io/grpc/CompositeChannelCredentials.java
new file mode 100644
index 0000000000..a3819fccee
--- /dev/null
+++ b/api/src/main/java/io/grpc/CompositeChannelCredentials.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2020 The gRPC Authors
+ *
+ * Licensed 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 io.grpc;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * {@code ChannelCredentials} which use per-RPC {@link CallCredentials}. If the {@code
+ * ChannelCredentials} has multiple {@code CallCredentials} (e.g., a composite credential inside a
+ * composite credential), then all of the {@code CallCredentials} should be used; one {@code
+ * CallCredentials} does not override another.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7479")
+public final class CompositeChannelCredentials extends ChannelCredentials {
+ public static ChannelCredentials create(
+ ChannelCredentials channelCreds, CallCredentials callCreds) {
+ return new CompositeChannelCredentials(channelCreds, callCreds);
+ }
+
+ private final ChannelCredentials channelCredentials;
+ private final CallCredentials callCredentials;
+
+ private CompositeChannelCredentials(ChannelCredentials channelCreds, CallCredentials callCreds) {
+ this.channelCredentials = Preconditions.checkNotNull(channelCreds, "channelCreds");
+ this.callCredentials = Preconditions.checkNotNull(callCreds, "callCreds");
+ }
+
+ public ChannelCredentials getChannelCredentials() {
+ return channelCredentials;
+ }
+
+ public CallCredentials getCallCredentials() {
+ return callCredentials;
+ }
+}
diff --git a/api/src/main/java/io/grpc/Grpc.java b/api/src/main/java/io/grpc/Grpc.java
index 53ff28be35..e5fb4a112d 100644
--- a/api/src/main/java/io/grpc/Grpc.java
+++ b/api/src/main/java/io/grpc/Grpc.java
@@ -20,6 +20,8 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.SocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
import javax.net.ssl.SSLSession;
/**
@@ -62,4 +64,64 @@ public final class Grpc {
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface TransportAttr {}
+
+ /**
+ * Creates a channel builder with a target string and credentials. The target can be either a
+ * valid {@link NameResolver}-compliant URI, or an authority string.
+ *
+ * A {@code NameResolver}-compliant URI is an absolute hierarchical URI as defined by {@link
+ * java.net.URI}. Example URIs:
+ *
+ * - {@code "dns:///foo.googleapis.com:8080"}
+ * - {@code "dns:///foo.googleapis.com"}
+ * - {@code "dns:///%5B2001:db8:85a3:8d3:1319:8a2e:370:7348%5D:443"}
+ * - {@code "dns://8.8.8.8/foo.googleapis.com:8080"}
+ * - {@code "dns://8.8.8.8/foo.googleapis.com"}
+ * - {@code "zookeeper://zk.example.com:9900/example_service"}
+ *
+ *
+ * An authority string will be converted to a {@code NameResolver}-compliant URI, which has
+ * the scheme from the name resolver with the highest priority (e.g. {@code "dns"}),
+ * no authority, and the original authority string as its path after properly escaped.
+ * We recommend libraries to specify the schema explicitly if it is known, since libraries cannot
+ * know which NameResolver will be default during runtime.
+ * Example authority strings:
+ *
+ * - {@code "localhost"}
+ * - {@code "127.0.0.1"}
+ * - {@code "localhost:8080"}
+ * - {@code "foo.googleapis.com:8080"}
+ * - {@code "127.0.0.1:8080"}
+ * - {@code "[2001:db8:85a3:8d3:1319:8a2e:370:7348]"}
+ * - {@code "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443"}
+ *
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/7479")
+ public static ManagedChannelBuilder> newChannelBuilder(
+ String target, ChannelCredentials creds) {
+ return ManagedChannelRegistry.getDefaultRegistry().newChannelBuilder(target, creds);
+ }
+
+ /**
+ * Creates a channel builder from a host, port, and credentials. The host and port are combined to
+ * form an authority string and then passed to {@link #newChannelBuilder(String,
+ * ChannelCredentials)}. IPv6 addresses are properly surrounded by square brackets ("[]").
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/7479")
+ public static ManagedChannelBuilder> newChannelBuilderForAddress(
+ String host, int port, ChannelCredentials creds) {
+ return newChannelBuilder(authorityFromHostAndPort(host, port), creds);
+ }
+
+ /**
+ * Combine a host and port into an authority string.
+ */
+ // A copy of GrpcUtil.authorityFromHostAndPort
+ private static String authorityFromHostAndPort(String host, int port) {
+ try {
+ return new URI(null, null, host, port, null, null, null).getAuthority();
+ } catch (URISyntaxException ex) {
+ throw new IllegalArgumentException("Invalid host or port: " + host + " " + port, ex);
+ }
+ }
}
diff --git a/api/src/main/java/io/grpc/InsecureChannelCredentials.java b/api/src/main/java/io/grpc/InsecureChannelCredentials.java
new file mode 100644
index 0000000000..9f67b0b249
--- /dev/null
+++ b/api/src/main/java/io/grpc/InsecureChannelCredentials.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2020 The gRPC Authors
+ *
+ * Licensed 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 io.grpc;
+
+/** No client identity, authentication, or encryption is to be used. */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7479")
+public final class InsecureChannelCredentials extends ChannelCredentials {
+ public static ChannelCredentials create() {
+ return new InsecureChannelCredentials();
+ }
+
+ private InsecureChannelCredentials() {}
+}
diff --git a/api/src/main/java/io/grpc/ManagedChannelBuilder.java b/api/src/main/java/io/grpc/ManagedChannelBuilder.java
index e0c87d3071..6513083a5a 100644
--- a/api/src/main/java/io/grpc/ManagedChannelBuilder.java
+++ b/api/src/main/java/io/grpc/ManagedChannelBuilder.java
@@ -182,6 +182,7 @@ public abstract class ManagedChannelBuilder>
* not perform HTTP/1.1 upgrades.
*
* @return this
+ * @throws IllegalStateException if ChannelCredentials were provided when constructing the builder
* @throws UnsupportedOperationException if plaintext mode is not supported.
* @since 1.11.0
*/
@@ -193,6 +194,7 @@ public abstract class ManagedChannelBuilder>
* Makes the client use TLS.
*
* @return this
+ * @throws IllegalStateException if ChannelCredentials were provided when constructing the builder
* @throws UnsupportedOperationException if transport security is not supported.
* @since 1.9.0
*/
diff --git a/api/src/main/java/io/grpc/ManagedChannelProvider.java b/api/src/main/java/io/grpc/ManagedChannelProvider.java
index 76a8d6707b..f57340d9ba 100644
--- a/api/src/main/java/io/grpc/ManagedChannelProvider.java
+++ b/api/src/main/java/io/grpc/ManagedChannelProvider.java
@@ -16,11 +16,7 @@
package io.grpc;
-import com.google.common.annotations.VisibleForTesting;
-import io.grpc.ServiceProviders.PriorityAccessor;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
+import com.google.common.base.Preconditions;
/**
* Provider of managed channels for transport agnostic consumption.
@@ -36,31 +32,13 @@ import java.util.List;
*/
@Internal
public abstract class ManagedChannelProvider {
- @VisibleForTesting
- static final Iterable> HARDCODED_CLASSES = new HardcodedClasses();
-
- private static final ManagedChannelProvider provider = ServiceProviders.load(
- ManagedChannelProvider.class,
- HARDCODED_CLASSES,
- ManagedChannelProvider.class.getClassLoader(),
- new PriorityAccessor() {
- @Override
- public boolean isAvailable(ManagedChannelProvider provider) {
- return provider.isAvailable();
- }
-
- @Override
- public int getPriority(ManagedChannelProvider provider) {
- return provider.priority();
- }
- });
-
/**
* Returns the ClassLoader-wide default channel.
*
* @throws ProviderNotFoundException if no provider is available
*/
public static ManagedChannelProvider provider() {
+ ManagedChannelProvider provider = ManagedChannelRegistry.getDefaultRegistry().provider();
if (provider == null) {
throw new ProviderNotFoundException("No functional channel service provider found. "
+ "Try adding a dependency on the grpc-okhttp, grpc-netty, or grpc-netty-shaded "
@@ -93,6 +71,40 @@ public abstract class ManagedChannelProvider {
*/
protected abstract ManagedChannelBuilder> builderForTarget(String target);
+ /**
+ * Creates a new builder with the given target URI and credentials. Returns an error-string result
+ * if unable to understand the credentials.
+ */
+ protected NewChannelBuilderResult newChannelBuilder(String target, ChannelCredentials creds) {
+ return NewChannelBuilderResult.error("ChannelCredentials are unsupported");
+ }
+
+ public static final class NewChannelBuilderResult {
+ private final ManagedChannelBuilder> channelBuilder;
+ private final String error;
+
+ private NewChannelBuilderResult(ManagedChannelBuilder> channelBuilder, String error) {
+ this.channelBuilder = channelBuilder;
+ this.error = error;
+ }
+
+ public static NewChannelBuilderResult channelBuilder(ManagedChannelBuilder> builder) {
+ return new NewChannelBuilderResult(Preconditions.checkNotNull(builder), null);
+ }
+
+ public static NewChannelBuilderResult error(String error) {
+ return new NewChannelBuilderResult(null, Preconditions.checkNotNull(error));
+ }
+
+ public ManagedChannelBuilder> getChannelBuilder() {
+ return channelBuilder;
+ }
+
+ public String getError() {
+ return error;
+ }
+ }
+
/**
* Thrown when no suitable {@link ManagedChannelProvider} objects can be found.
*/
@@ -103,22 +115,4 @@ public abstract class ManagedChannelProvider {
super(msg);
}
}
-
- private static final class HardcodedClasses implements Iterable> {
- @Override
- public Iterator> iterator() {
- List> list = new ArrayList<>();
- try {
- list.add(Class.forName("io.grpc.okhttp.OkHttpChannelProvider"));
- } catch (ClassNotFoundException ex) {
- // ignore
- }
- try {
- list.add(Class.forName("io.grpc.netty.NettyChannelProvider"));
- } catch (ClassNotFoundException ex) {
- // ignore
- }
- return list.iterator();
- }
- }
}
diff --git a/api/src/main/java/io/grpc/ManagedChannelRegistry.java b/api/src/main/java/io/grpc/ManagedChannelRegistry.java
new file mode 100644
index 0000000000..b13d47a90d
--- /dev/null
+++ b/api/src/main/java/io/grpc/ManagedChannelRegistry.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2020 The gRPC Authors
+ *
+ * Licensed 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 io.grpc;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Registry of {@link ManagedChannelProvider}s. The {@link #getDefaultRegistry default instance}
+ * loads providers at runtime through the Java service provider mechanism.
+ *
+ * @since 1.32.0
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7479")
+@ThreadSafe
+public final class ManagedChannelRegistry {
+ private static final Logger logger = Logger.getLogger(ManagedChannelRegistry.class.getName());
+ private static ManagedChannelRegistry instance;
+
+ @GuardedBy("this")
+ private final LinkedHashSet allProviders = new LinkedHashSet<>();
+ /** Immutable, sorted version of {@code allProviders}. Is replaced instead of mutating. */
+ @GuardedBy("this")
+ private List effectiveProviders = Collections.emptyList();
+
+ /**
+ * Register a provider.
+ *
+ * If the provider's {@link ManagedChannelProvider#isAvailable isAvailable()} returns
+ * {@code false}, this method will throw {@link IllegalArgumentException}.
+ *
+ *
Providers will be used in priority order. In case of ties, providers are used in
+ * registration order.
+ */
+ public synchronized void register(ManagedChannelProvider provider) {
+ addProvider(provider);
+ refreshProviders();
+ }
+
+ private synchronized void addProvider(ManagedChannelProvider provider) {
+ Preconditions.checkArgument(provider.isAvailable(), "isAvailable() returned false");
+ allProviders.add(provider);
+ }
+
+ /**
+ * Deregisters a provider. No-op if the provider is not in the registry.
+ *
+ * @param provider the provider that was added to the register via {@link #register}.
+ */
+ public synchronized void deregister(ManagedChannelProvider provider) {
+ allProviders.remove(provider);
+ refreshProviders();
+ }
+
+ private synchronized void refreshProviders() {
+ List providers = new ArrayList<>(allProviders);
+ // Sort descending based on priority.
+ // sort() must be stable, as we prefer first-registered providers
+ Collections.sort(providers, Collections.reverseOrder(new Comparator() {
+ @Override
+ public int compare(ManagedChannelProvider o1, ManagedChannelProvider o2) {
+ return o1.priority() - o2.priority();
+ }
+ }));
+ effectiveProviders = Collections.unmodifiableList(providers);
+ }
+
+ /**
+ * Returns the default registry that loads providers via the Java service loader mechanism.
+ */
+ public static synchronized ManagedChannelRegistry getDefaultRegistry() {
+ if (instance == null) {
+ List providerList = ServiceProviders.loadAll(
+ ManagedChannelProvider.class,
+ getHardCodedClasses(),
+ ManagedChannelProvider.class.getClassLoader(),
+ new ManagedChannelPriorityAccessor());
+ instance = new ManagedChannelRegistry();
+ for (ManagedChannelProvider provider : providerList) {
+ logger.fine("Service loader found " + provider);
+ if (provider.isAvailable()) {
+ instance.addProvider(provider);
+ }
+ }
+ instance.refreshProviders();
+ }
+ return instance;
+ }
+
+ /**
+ * Returns effective providers, in priority order.
+ */
+ @VisibleForTesting
+ synchronized List providers() {
+ return effectiveProviders;
+ }
+
+ // For emulating ManagedChannelProvider.provider()
+ ManagedChannelProvider provider() {
+ List providers = providers();
+ return providers.isEmpty() ? null : providers.get(0);
+ }
+
+ @VisibleForTesting
+ static List> getHardCodedClasses() {
+ // Class.forName(String) is used to remove the need for ProGuard configuration. Note that
+ // ProGuard does not detect usages of Class.forName(String, boolean, ClassLoader):
+ // https://sourceforge.net/p/proguard/bugs/418/
+ List> list = new ArrayList<>();
+ try {
+ list.add(Class.forName("io.grpc.okhttp.OkHttpChannelProvider"));
+ } catch (ClassNotFoundException e) {
+ logger.log(Level.FINE, "Unable to find OkHttpChannelProvider", e);
+ }
+ try {
+ list.add(Class.forName("io.grpc.netty.NettyChannelProvider"));
+ } catch (ClassNotFoundException e) {
+ logger.log(Level.FINE, "Unable to find NettyChannelProvider", e);
+ }
+ return Collections.unmodifiableList(list);
+ }
+
+ ManagedChannelBuilder> newChannelBuilder(String target, ChannelCredentials creds) {
+ List providers = providers();
+ if (providers.isEmpty()) {
+ throw new ProviderNotFoundException("No functional channel service provider found. "
+ + "Try adding a dependency on the grpc-okhttp, grpc-netty, or grpc-netty-shaded "
+ + "artifact");
+ }
+ StringBuilder error = new StringBuilder();
+ for (ManagedChannelProvider provider : providers()) {
+ ManagedChannelProvider.NewChannelBuilderResult result
+ = provider.newChannelBuilder(target, creds);
+ if (result.getChannelBuilder() != null) {
+ return result.getChannelBuilder();
+ }
+ error.append("; ");
+ error.append(provider.getClass().getName());
+ error.append(": ");
+ error.append(result.getError());
+ }
+ throw new ProviderNotFoundException(error.substring(2));
+ }
+
+ private static final class ManagedChannelPriorityAccessor
+ implements ServiceProviders.PriorityAccessor {
+ @Override
+ public boolean isAvailable(ManagedChannelProvider provider) {
+ return provider.isAvailable();
+ }
+
+ @Override
+ public int getPriority(ManagedChannelProvider provider) {
+ return provider.priority();
+ }
+ }
+
+ /** Thrown when no suitable {@link ManagedChannelProvider} objects can be found. */
+ public static final class ProviderNotFoundException extends RuntimeException {
+ private static final long serialVersionUID = 1;
+
+ public ProviderNotFoundException(String msg) {
+ super(msg);
+ }
+ }
+}
diff --git a/api/src/main/java/io/grpc/TlsChannelCredentials.java b/api/src/main/java/io/grpc/TlsChannelCredentials.java
new file mode 100644
index 0000000000..91f57391cd
--- /dev/null
+++ b/api/src/main/java/io/grpc/TlsChannelCredentials.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2020 The gRPC Authors
+ *
+ * Licensed 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 io.grpc;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * TLS credentials, providing server authentication and encryption. Consumers of this credential
+ * must verify they understand the configuration via the {@link #incomprehensible
+ * incomprehensible()} method. Unless overridden by a {@code Feature}, server verification should
+ * use customary default root certificates.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7479")
+public final class TlsChannelCredentials extends ChannelCredentials {
+ /** Use TLS with its defaults. */
+ public static ChannelCredentials create() {
+ return newBuilder().build();
+ }
+
+ private final boolean fakeFeature;
+
+ TlsChannelCredentials(Builder builder) {
+ fakeFeature = builder.fakeFeature;
+ }
+
+ /**
+ * Returns an empty set if this credential can be adequately understood via
+ * the features listed, otherwise returns a hint of features that are lacking
+ * to understand the configuration to be used for manual debugging.
+ *
+ * An "understood" feature does not imply the caller is able to fully
+ * handle the feature. It simply means the caller understands the feature
+ * enough to use the appropriate APIs to read the configuration. The caller
+ * may support just a subset of a feature, in which case the caller would
+ * need to look at the configuration to determine if only the supported
+ * subset is used.
+ *
+ *
This method may not be as simple as a set difference. There may be
+ * multiple features that can independently satisfy a piece of configuration.
+ * If the configuration is incomprehensible, all such features would be
+ * returned, even though only one may be necessary.
+ *
+ *
An empty set does not imply that the credentials are fully understood.
+ * There may be optional configuration that can be ignored if not understood.
+ *
+ *
Since {@code Feature} is an {@code enum}, {@code understoodFeatures}
+ * should generally be an {@link java.util.EnumSet}. {@code
+ * understoodFeatures} will not be modified.
+ *
+ * @param understoodFeatures the features understood by the caller
+ * @return empty set if the caller can adequately understand the configuration
+ */
+ public Set incomprehensible(Set understoodFeatures) {
+ Set incomprehensible = EnumSet.noneOf(Feature.class);
+ if (fakeFeature) {
+ requiredFeature(understoodFeatures, incomprehensible, Feature.FAKE);
+ }
+ return Collections.unmodifiableSet(incomprehensible);
+ }
+
+ private static void requiredFeature(
+ Set understoodFeatures, Set incomprehensible, Feature feature) {
+ if (!understoodFeatures.contains(feature)) {
+ incomprehensible.add(feature);
+ }
+ }
+
+ /**
+ * Features to understand TLS configuration. Additional enum values may be added in the future.
+ */
+ public enum Feature {
+ /**
+ * A feature that no consumer should understand. It should be used for unit testing to confirm
+ * a call to {@link #incomprehensible incomprehensible()} is implemented properly.
+ */
+ FAKE,
+ ;
+ }
+
+ /** Creates a builder for changing default configuration. */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /** Builder for {@link TlsChannelCredentials}. */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/7479")
+ public static final class Builder {
+ private boolean fakeFeature;
+
+ private Builder() {}
+
+ /**
+ * Requires {@link Feature#FAKE} to be understood. For use in testing consumers of this
+ * credential.
+ */
+ public Builder requireFakeFeature() {
+ fakeFeature = true;
+ return this;
+ }
+
+ /** Construct the credentials. */
+ public ChannelCredentials build() {
+ return new TlsChannelCredentials(this);
+ }
+ }
+}
diff --git a/api/src/test/java/io/grpc/CompositeCallCredentialsTest.java b/api/src/test/java/io/grpc/CompositeCallCredentialsTest.java
new file mode 100644
index 0000000000..4e606c4166
--- /dev/null
+++ b/api/src/test/java/io/grpc/CompositeCallCredentialsTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2020 The gRPC Authors
+ *
+ * Licensed 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 io.grpc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.concurrent.Executor;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class CompositeCallCredentialsTest {
+ private static final Metadata.Key METADATA_KEY =
+ Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER);
+
+ private CompositeCallCredentials callCredentials;
+ private CallCredentials.RequestInfo requestInfo;
+ private Executor appExecutor;
+ private FakeMetadataApplier applier = new FakeMetadataApplier();
+
+ @Test
+ public void applyRequestMetadata_firstFails() {
+ Status failure = Status.UNAVAILABLE.withDescription("expected");
+ callCredentials = new CompositeCallCredentials(
+ new FakeCallCredentials(failure),
+ new FakeCallCredentials(createMetadata(METADATA_KEY, "value2")));
+ callCredentials.applyRequestMetadata(requestInfo, appExecutor, applier);
+ assertThat(applier.status).isSameInstanceAs(failure);
+ }
+
+ @Test
+ public void applyRequestMetadata_secondFails() {
+ Status failure = Status.UNAVAILABLE.withDescription("expected");
+ callCredentials = new CompositeCallCredentials(
+ new FakeCallCredentials(createMetadata(METADATA_KEY, "value1")),
+ new FakeCallCredentials(failure));
+ callCredentials.applyRequestMetadata(requestInfo, appExecutor, applier);
+ assertThat(applier.status).isSameInstanceAs(failure);
+ }
+
+ @Test
+ public void applyRequestMetadata_bothSucceed() {
+ callCredentials = new CompositeCallCredentials(
+ new FakeCallCredentials(createMetadata(METADATA_KEY, "value1")),
+ new FakeCallCredentials(createMetadata(METADATA_KEY, "value2")));
+ callCredentials.applyRequestMetadata(requestInfo, appExecutor, applier);
+ assertThat(applier.headers).isNotNull();
+ assertThat(applier.headers.getAll(METADATA_KEY)).containsExactly("value1", "value2");
+ }
+
+ private static Metadata createMetadata(Metadata.Key key, String value) {
+ Metadata metadata = new Metadata();
+ metadata.put(key, value);
+ return metadata;
+ }
+
+ private static class FakeMetadataApplier extends CallCredentials.MetadataApplier {
+ private Metadata headers;
+ private Status status;
+
+ @Override public void apply(Metadata headers) {
+ assertThat(this.headers).isNull();
+ assertThat(this.status).isNull();
+ this.headers = headers;
+ }
+
+ @Override public void fail(Status status) {
+ assertThat(this.headers).isNull();
+ assertThat(this.status).isNull();
+ this.status = status;
+ }
+ }
+
+ private static class FakeCallCredentials extends CallCredentials {
+ private final Metadata headers;
+ private final Status status;
+
+ public FakeCallCredentials(Metadata headers) {
+ this.headers = headers;
+ this.status = null;
+ }
+
+ public FakeCallCredentials(Status status) {
+ this.headers = null;
+ this.status = status;
+ }
+
+ @Override public void applyRequestMetadata(
+ CallCredentials.RequestInfo requestInfo, Executor appExecutor,
+ CallCredentials.MetadataApplier applier) {
+ if (headers != null) {
+ applier.apply(headers);
+ } else {
+ applier.fail(status);
+ }
+ }
+
+ @Override public void thisUsesUnstableApi() {}
+ }
+}
diff --git a/api/src/test/java/io/grpc/ManagedChannelProviderTest.java b/api/src/test/java/io/grpc/ManagedChannelProviderTest.java
deleted file mode 100644
index edb4a701eb..0000000000
--- a/api/src/test/java/io/grpc/ManagedChannelProviderTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2015 The gRPC Authors
- *
- * Licensed 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 io.grpc;
-
-import com.google.common.collect.ImmutableSet;
-import java.util.Iterator;
-import java.util.concurrent.Callable;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@link ManagedChannelProvider}. */
-@RunWith(JUnit4.class)
-public class ManagedChannelProviderTest {
-
- @Test
- public void getCandidatesViaHardCoded_triesToLoadClasses() throws Exception {
- ServiceProvidersTestUtil.testHardcodedClasses(
- HardcodedClassesCallable.class.getName(),
- getClass().getClassLoader(),
- ImmutableSet.of(
- "io.grpc.okhttp.OkHttpChannelProvider",
- "io.grpc.netty.NettyChannelProvider"));
- }
-
- public static final class HardcodedClassesCallable implements Callable>> {
- @Override
- public Iterator> call() {
- return ManagedChannelProvider.HARDCODED_CLASSES.iterator();
- }
- }
-}
diff --git a/api/src/main/java/io/grpc/InternalManagedChannelProvider.java b/api/src/test/java/io/grpc/ManagedChannelRegistryAccessor.java
similarity index 64%
rename from api/src/main/java/io/grpc/InternalManagedChannelProvider.java
rename to api/src/test/java/io/grpc/ManagedChannelRegistryAccessor.java
index 37fd52a7a5..5c65c8e532 100644
--- a/api/src/main/java/io/grpc/InternalManagedChannelProvider.java
+++ b/api/src/test/java/io/grpc/ManagedChannelRegistryAccessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 The gRPC Authors
+ * Copyright 2020 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,11 @@
package io.grpc;
-public final class InternalManagedChannelProvider {
- public static final Iterable> HARDCODED_CLASSES =
- ManagedChannelProvider.HARDCODED_CLASSES;
+/** Accesses test-only methods of {@link ManagedChannelRegistry}. */
+public final class ManagedChannelRegistryAccessor {
+ private ManagedChannelRegistryAccessor() {}
+
+ public static Iterable> getHardCodedClasses() {
+ return ManagedChannelRegistry.getHardCodedClasses();
+ }
}
diff --git a/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java b/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java
new file mode 100644
index 0000000000..68d666ff10
--- /dev/null
+++ b/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2020 The gRPC Authors
+ *
+ * Licensed 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 io.grpc;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ManagedChannelRegistry}. */
+@RunWith(JUnit4.class)
+public class ManagedChannelRegistryTest {
+ private String target = "testing123";
+ private ChannelCredentials creds = new ChannelCredentials() {};
+
+ @Test
+ public void register_unavilableProviderThrows() {
+ ManagedChannelRegistry reg = new ManagedChannelRegistry();
+ try {
+ reg.register(new BaseProvider(false, 5));
+ fail("Should throw");
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessageThat().contains("isAvailable() returned false");
+ }
+ assertThat(reg.providers()).isEmpty();
+ }
+
+ @Test
+ public void deregister() {
+ ManagedChannelRegistry reg = new ManagedChannelRegistry();
+ ManagedChannelProvider p1 = new BaseProvider(true, 5);
+ ManagedChannelProvider p2 = new BaseProvider(true, 5);
+ ManagedChannelProvider p3 = new BaseProvider(true, 5);
+ reg.register(p1);
+ reg.register(p2);
+ reg.register(p3);
+ assertThat(reg.providers()).containsExactly(p1, p2, p3).inOrder();
+ reg.deregister(p2);
+ assertThat(reg.providers()).containsExactly(p1, p3).inOrder();
+ }
+
+ @Test
+ public void provider_sorted() {
+ ManagedChannelRegistry reg = new ManagedChannelRegistry();
+ ManagedChannelProvider p1 = new BaseProvider(true, 5);
+ ManagedChannelProvider p2 = new BaseProvider(true, 3);
+ ManagedChannelProvider p3 = new BaseProvider(true, 8);
+ ManagedChannelProvider p4 = new BaseProvider(true, 3);
+ ManagedChannelProvider p5 = new BaseProvider(true, 8);
+ reg.register(p1);
+ reg.register(p2);
+ reg.register(p3);
+ reg.register(p4);
+ reg.register(p5);
+ assertThat(reg.providers()).containsExactly(p3, p5, p1, p2, p4).inOrder();
+ }
+
+ @Test
+ public void getProvider_noProvider() {
+ assertThat(new ManagedChannelRegistry().provider()).isNull();
+ }
+
+ @Test
+ public void newChannelBuilder_providerReturnsError() {
+ final String errorString = "brisking";
+ class ErrorProvider extends BaseProvider {
+ ErrorProvider() {
+ super(true, 5);
+ }
+
+ @Override
+ public NewChannelBuilderResult newChannelBuilder(
+ String passedTarget, ChannelCredentials passedCreds) {
+ assertThat(passedTarget).isSameInstanceAs(target);
+ assertThat(passedCreds).isSameInstanceAs(creds);
+ return NewChannelBuilderResult.error(errorString);
+ }
+ }
+
+ ManagedChannelRegistry registry = new ManagedChannelRegistry();
+ registry.register(new ErrorProvider());
+ try {
+ registry.newChannelBuilder(target, creds);
+ fail("expected exception");
+ } catch (ManagedChannelRegistry.ProviderNotFoundException ex) {
+ assertThat(ex).hasMessageThat().contains(errorString);
+ assertThat(ex).hasMessageThat().contains(ErrorProvider.class.getName());
+ }
+ }
+
+ @Test
+ public void newChannelBuilder_providerReturnsNonNull() {
+ ManagedChannelRegistry registry = new ManagedChannelRegistry();
+ registry.register(new BaseProvider(true, 5) {
+ @Override
+ public NewChannelBuilderResult newChannelBuilder(
+ String passedTarget, ChannelCredentials passedCreds) {
+ return NewChannelBuilderResult.error("dodging");
+ }
+ });
+ class MockChannelBuilder extends ForwardingChannelBuilder {
+ @Override public ManagedChannelBuilder> delegate() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ final ManagedChannelBuilder> mcb = new MockChannelBuilder();
+ registry.register(new BaseProvider(true, 4) {
+ @Override
+ public NewChannelBuilderResult newChannelBuilder(
+ String passedTarget, ChannelCredentials passedCreds) {
+ return NewChannelBuilderResult.channelBuilder(mcb);
+ }
+ });
+ registry.register(new BaseProvider(true, 3) {
+ @Override
+ public NewChannelBuilderResult newChannelBuilder(
+ String passedTarget, ChannelCredentials passedCreds) {
+ fail("Should not be called");
+ throw new AssertionError();
+ }
+ });
+ assertThat(registry.newChannelBuilder(target, creds)).isSameInstanceAs(mcb);
+ }
+
+ @Test
+ public void newChannelBuilder_noProvider() {
+ ManagedChannelRegistry registry = new ManagedChannelRegistry();
+ try {
+ registry.newChannelBuilder(target, creds);
+ fail("expected exception");
+ } catch (ManagedChannelRegistry.ProviderNotFoundException ex) {
+ assertThat(ex).hasMessageThat().contains("No functional channel service provider found");
+ assertThat(ex).hasMessageThat().contains("grpc-netty");
+ }
+ }
+
+ private static class BaseProvider extends ManagedChannelProvider {
+ private final boolean isAvailable;
+ private final int priority;
+
+ public BaseProvider(boolean isAvailable, int priority) {
+ this.isAvailable = isAvailable;
+ this.priority = priority;
+ }
+
+ @Override
+ protected boolean isAvailable() {
+ return isAvailable;
+ }
+
+ @Override
+ protected int priority() {
+ return priority;
+ }
+
+ @Override
+ protected ManagedChannelBuilder> builderForAddress(String name, int port) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected ManagedChannelBuilder> builderForTarget(String target) {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java
index d3cdf8db2f..bc2fb51794 100644
--- a/core/src/main/java/io/grpc/internal/GrpcUtil.java
+++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java
@@ -508,6 +508,7 @@ public final class GrpcUtil {
/**
* Combine a host and port into an authority string.
*/
+ // There is a copy of this method in io.grpc.Grpc
public static String authorityFromHostAndPort(String host, int port) {
try {
return new URI(null, null, host, port, null, null, null).getAuthority();
diff --git a/netty/build.gradle b/netty/build.gradle
index 629dc823b3..20b35eb36d 100644
--- a/netty/build.gradle
+++ b/netty/build.gradle
@@ -24,6 +24,7 @@ dependencies {
// Tests depend on base class defined by core module.
testImplementation project(':grpc-core').sourceSets.test.output,
+ project(':grpc-api').sourceSets.test.output,
project(':grpc-testing'),
project(':grpc-testing-proto')
testRuntimeOnly libraries.netty_tcnative,
diff --git a/netty/src/test/java/io/grpc/netty/NettyChannelProviderTest.java b/netty/src/test/java/io/grpc/netty/NettyChannelProviderTest.java
index 26dd6796a3..40bfeb2802 100644
--- a/netty/src/test/java/io/grpc/netty/NettyChannelProviderTest.java
+++ b/netty/src/test/java/io/grpc/netty/NettyChannelProviderTest.java
@@ -21,9 +21,9 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import io.grpc.InternalManagedChannelProvider;
import io.grpc.InternalServiceProviders;
import io.grpc.ManagedChannelProvider;
+import io.grpc.ManagedChannelRegistryAccessor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -47,9 +47,8 @@ public class NettyChannelProviderTest {
@Test
public void providedHardCoded() {
- for (ManagedChannelProvider current : InternalServiceProviders.getCandidatesViaHardCoded(
- ManagedChannelProvider.class, InternalManagedChannelProvider.HARDCODED_CLASSES)) {
- if (current instanceof NettyChannelProvider) {
+ for (Class> current : ManagedChannelRegistryAccessor.getHardCodedClasses()) {
+ if (current == NettyChannelProvider.class) {
return;
}
}
diff --git a/okhttp/build.gradle b/okhttp/build.gradle
index 0c23c3b9f8..f7d8c62284 100644
--- a/okhttp/build.gradle
+++ b/okhttp/build.gradle
@@ -21,6 +21,7 @@ dependencies {
perfmarkDependency 'implementation'
// Tests depend on base class defined by core module.
testImplementation project(':grpc-core').sourceSets.test.output,
+ project(':grpc-api').sourceSets.test.output,
project(':grpc-testing'),
project(':grpc-netty')
signature "org.codehaus.mojo.signature:java17:1.0@signature"
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelProviderTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelProviderTest.java
index 53007762dd..3069eb1bda 100644
--- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelProviderTest.java
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelProviderTest.java
@@ -20,9 +20,9 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import io.grpc.InternalManagedChannelProvider;
import io.grpc.InternalServiceProviders;
import io.grpc.ManagedChannelProvider;
+import io.grpc.ManagedChannelRegistryAccessor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -46,9 +46,8 @@ public class OkHttpChannelProviderTest {
@Test
public void providedHardCoded() {
- for (ManagedChannelProvider current : InternalServiceProviders.getCandidatesViaHardCoded(
- ManagedChannelProvider.class, InternalManagedChannelProvider.HARDCODED_CLASSES)) {
- if (current instanceof OkHttpChannelProvider) {
+ for (Class> current : ManagedChannelRegistryAccessor.getHardCodedClasses()) {
+ if (current == OkHttpChannelProvider.class) {
return;
}
}