diff --git a/core/src/main/java/io/grpc/InternalManagedChannelProvider.java b/core/src/main/java/io/grpc/InternalManagedChannelProvider.java new file mode 100644 index 0000000000..94f9c281ec --- /dev/null +++ b/core/src/main/java/io/grpc/InternalManagedChannelProvider.java @@ -0,0 +1,22 @@ +/* + * Copyright 2018, gRPC Authors All rights reserved. + * + * 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; + +public final class InternalManagedChannelProvider { + public static final Iterable> HARDCODED_CLASSES = + ManagedChannelProvider.HARDCODED_CLASSES; +} diff --git a/core/src/main/java/io/grpc/ManagedChannelProvider.java b/core/src/main/java/io/grpc/ManagedChannelProvider.java index e3bcc3e797..b56b31471a 100644 --- a/core/src/main/java/io/grpc/ManagedChannelProvider.java +++ b/core/src/main/java/io/grpc/ManagedChannelProvider.java @@ -17,12 +17,10 @@ package io.grpc; import com.google.common.annotations.VisibleForTesting; +import io.grpc.ServiceProviders.PriorityAccessor; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; +import java.util.Iterator; import java.util.List; -import java.util.ServiceConfigurationError; -import java.util.ServiceLoader; /** * Provider of managed channels for transport agnostic consumption. @@ -33,86 +31,24 @@ import java.util.ServiceLoader; */ @Internal public abstract class ManagedChannelProvider { - private static final ManagedChannelProvider provider - = load(ManagedChannelProvider.class.getClassLoader()); - @VisibleForTesting - static ManagedChannelProvider load(ClassLoader classLoader) { - Iterable candidates; - if (isAndroid()) { - candidates = getCandidatesViaHardCoded(); - } else { - candidates = getCandidatesViaServiceLoader(classLoader); - } - List list = new ArrayList(); - for (ManagedChannelProvider current : candidates) { - if (!current.isAvailable()) { - continue; - } - list.add(current); - } - if (list.isEmpty()) { - return null; - } else { - return Collections.max(list, new Comparator() { + 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 int compare(ManagedChannelProvider f1, ManagedChannelProvider f2) { - return f1.priority() - f2.priority(); + public boolean isAvailable(ManagedChannelProvider provider) { + return provider.isAvailable(); + } + + @Override + public int getPriority(ManagedChannelProvider provider) { + return provider.priority(); } }); - } - } - - /** - * Loads service providers for the {@link ManagedChannelProvider} service using - * {@link ServiceLoader}. - */ - @VisibleForTesting - public static Iterable getCandidatesViaServiceLoader( - ClassLoader classLoader) { - Iterable i - = ServiceLoader.load(ManagedChannelProvider.class, classLoader); - // Attempt to load using the context class loader and ServiceLoader. - // This allows frameworks like http://aries.apache.org/modules/spi-fly.html to plug in. - if (!i.iterator().hasNext()) { - i = ServiceLoader.load(ManagedChannelProvider.class); - } - return i; - } - - /** - * Load providers from a hard-coded list. This avoids using getResource(), which has performance - * problems on Android (see https://github.com/grpc/grpc-java/issues/2037). Any provider that may - * be used on Android is free to be added here. - */ - @VisibleForTesting - public static Iterable getCandidatesViaHardCoded() { - // 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(create(Class.forName("io.grpc.okhttp.OkHttpChannelProvider"))); - } catch (ClassNotFoundException ex) { - // ignore - } - try { - list.add(create(Class.forName("io.grpc.netty.NettyChannelProvider"))); - } catch (ClassNotFoundException ex) { - // ignore - } - return list; - } - - @VisibleForTesting - static ManagedChannelProvider create(Class rawClass) { - try { - return rawClass.asSubclass(ManagedChannelProvider.class).getConstructor().newInstance(); - } catch (Throwable t) { - throw new ServiceConfigurationError( - "Provider " + rawClass.getName() + " could not be instantiated: " + t, t); - } - } /** * Returns the ClassLoader-wide default channel. @@ -127,21 +63,6 @@ public abstract class ManagedChannelProvider { return provider; } - /** - * Returns whether current platform is Android. - */ - protected static boolean isAndroid() { - try { - // Specify a class loader instead of null because we may be running under Robolectric - Class.forName("android.app.Application", /*initialize=*/ false, - ManagedChannelProvider.class.getClassLoader()); - return true; - } catch (Exception e) { - // If Application isn't loaded, it might as well not be Android. - return false; - } - } - /** * Whether this provider is available for use, taking the current environment into consideration. * If {@code false}, no other methods are safe to be called. @@ -176,4 +97,22 @@ 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/core/src/main/java/io/grpc/ServerProvider.java b/core/src/main/java/io/grpc/ServerProvider.java index 5b5144c98c..060be56420 100644 --- a/core/src/main/java/io/grpc/ServerProvider.java +++ b/core/src/main/java/io/grpc/ServerProvider.java @@ -16,9 +16,9 @@ package io.grpc; -import com.google.common.annotations.VisibleForTesting; import io.grpc.ManagedChannelProvider.ProviderNotFoundException; -import java.util.ServiceLoader; +import io.grpc.ServiceProviders.PriorityAccessor; +import java.util.Collections; /** * Provider of servers for transport agnostic consumption. @@ -29,32 +29,21 @@ import java.util.ServiceLoader; */ @Internal public abstract class ServerProvider { - private static final ServerProvider provider = - load(ServerProvider.class.getClassLoader()); + private static final ServerProvider provider = ServiceProviders.load( + ServerProvider.class, + Collections.>emptyList(), + ServerProvider.class.getClassLoader(), + new PriorityAccessor() { + @Override + public boolean isAvailable(ServerProvider provider) { + return provider.isAvailable(); + } - @VisibleForTesting - static final ServerProvider load(ClassLoader cl) { - ServiceLoader providers = ServiceLoader.load(ServerProvider.class, cl); - - // Attempt to load using the context class loader and ServiceLoader. - // This allows frameworks like http://aries.apache.org/modules/spi-fly.html to plug in. - if (!providers.iterator().hasNext()) { - providers = ServiceLoader.load(ServerProvider.class); - } - - ServerProvider best = null; - - for (ServerProvider current : providers) { - if (!current.isAvailable()) { - continue; - } else if (best == null) { - best = current; - } else if (current.priority() > best.priority()) { - best = current; - } - } - return best; - } + @Override + public int getPriority(ServerProvider provider) { + return provider.priority(); + } + }); /** * Returns the ClassLoader-wide default server. diff --git a/core/src/test/java/io/grpc/ManagedChannelProviderTest.java b/core/src/test/java/io/grpc/ManagedChannelProviderTest.java index a759b8f3a1..817243219a 100644 --- a/core/src/test/java/io/grpc/ManagedChannelProviderTest.java +++ b/core/src/test/java/io/grpc/ManagedChannelProviderTest.java @@ -16,16 +16,9 @@ package io.grpc; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ServiceConfigurationError; -import java.util.regex.Pattern; +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; @@ -33,151 +26,21 @@ import org.junit.runners.JUnit4; /** Unit tests for {@link ManagedChannelProvider}. */ @RunWith(JUnit4.class) public class ManagedChannelProviderTest { - private final String serviceFile = "META-INF/services/io.grpc.ManagedChannelProvider"; - - @Test(expected = ManagedChannelProvider.ProviderNotFoundException.class) - public void noProvider() { - ManagedChannelProvider.provider(); - } - - @Test - public void multipleProvider() { - ClassLoader cl = new ReplacingClassLoader(getClass().getClassLoader(), serviceFile, - "io/grpc/ManagedChannelProviderTest-multipleProvider.txt"); - assertSame(Available7Provider.class, ManagedChannelProvider.load(cl).getClass()); - } - - @Test - public void unavailableProvider() { - ClassLoader cl = new ReplacingClassLoader(getClass().getClassLoader(), serviceFile, - "io/grpc/ManagedChannelProviderTest-unavailableProvider.txt"); - assertNull(ManagedChannelProvider.load(cl)); - } @Test public void getCandidatesViaHardCoded_triesToLoadClasses() throws Exception { - ClassLoader cl = getClass().getClassLoader(); - final RuntimeException toThrow = new RuntimeException(); - cl = new ClassLoader(cl) { - @Override - public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - if (name.startsWith("io.grpc.netty.") || name.startsWith("io.grpc.okhttp.")) { - throw toThrow; - } else { - return super.loadClass(name, resolve); - } - } - }; - cl = new StaticTestingClassLoader(cl, Pattern.compile("io\\.grpc\\.[^.]*")); - try { - invokeGetCandidatesViaHardCoded(cl); - fail("Expected exception"); - } catch (RuntimeException ex) { - assertSame(toThrow, ex); - } + ServiceProvidersTestUtil.testHardcodedClasses( + HardcodedClassesCallable.class.getName(), + getClass().getClassLoader(), + ImmutableSet.of( + "io.grpc.okhttp.OkHttpChannelProvider", + "io.grpc.netty.NettyChannelProvider")); } - @Test - public void getCandidatesViaHardCoded_ignoresMissingClasses() throws Exception { - ClassLoader cl = getClass().getClassLoader(); - cl = new ClassLoader(cl) { - @Override - public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - if (name.startsWith("io.grpc.netty.") || name.startsWith("io.grpc.okhttp.")) { - throw new ClassNotFoundException(); - } else { - return super.loadClass(name, resolve); - } - } - }; - cl = new StaticTestingClassLoader(cl, Pattern.compile("io\\.grpc\\.[^.]*")); - Iterable i = invokeGetCandidatesViaHardCoded(cl); - assertFalse("Iterator should be empty", i.iterator().hasNext()); - } - - @Test - public void create_throwsErrorOnMisconfiguration() throws Exception { - class PrivateClass {} - - try { - ManagedChannelProvider.create(PrivateClass.class); - fail("Expected exception"); - } catch (ServiceConfigurationError e) { - assertTrue("Expected ClassCastException cause: " + e.getCause(), - e.getCause() instanceof ClassCastException); - } - } - - private static Iterable invokeGetCandidatesViaHardCoded(ClassLoader cl) throws Exception { - // An error before the invoke likely means there is a bug in the test - Class klass = Class.forName(ManagedChannelProvider.class.getName(), true, cl); - Method getCandidatesViaHardCoded = klass.getMethod("getCandidatesViaHardCoded"); - try { - return (Iterable) getCandidatesViaHardCoded.invoke(null); - } catch (InvocationTargetException ex) { - if (ex.getCause() instanceof Exception) { - throw (Exception) ex.getCause(); - } - throw ex; - } - } - - 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; - } - + public static final class HardcodedClassesCallable implements Callable>> { @Override - protected boolean isAvailable() { - return isAvailable; - } - - @Override - protected int priority() { - return priority; - } - - @Override - protected ManagedChannelBuilder builderForAddress(String host, int port) { - throw new UnsupportedOperationException(); - } - - @Override - protected ManagedChannelBuilder builderForTarget(String target) { - throw new UnsupportedOperationException(); - } - } - - public static class Available0Provider extends BaseProvider { - public Available0Provider() { - super(true, 0); - } - } - - public static class Available5Provider extends BaseProvider { - public Available5Provider() { - super(true, 5); - } - } - - public static class Available7Provider extends BaseProvider { - public Available7Provider() { - super(true, 7); - } - } - - public static class UnavailableProvider extends BaseProvider { - public UnavailableProvider() { - super(false, 10); - } - - @Override - protected int priority() { - throw new RuntimeException("purposefully broken"); + public Iterator> call() { + return ManagedChannelProvider.HARDCODED_CLASSES.iterator(); } } } diff --git a/core/src/test/java/io/grpc/ServerProviderTest.java b/core/src/test/java/io/grpc/ServerProviderTest.java deleted file mode 100644 index 39384b7086..0000000000 --- a/core/src/test/java/io/grpc/ServerProviderTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2015, gRPC Authors All rights reserved. - * - * 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 org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Unit tests for {@link ServerProvider}. */ -@RunWith(JUnit4.class) -public class ServerProviderTest { - private final String serviceFile = "META-INF/services/io.grpc.ServerProvider"; - - @Test(expected = ManagedChannelProvider.ProviderNotFoundException.class) - public void noProvider() { - ServerProvider.provider(); - } - - @Test - public void multipleProvider() { - ClassLoader cl = new ReplacingClassLoader( - getClass().getClassLoader(), serviceFile, - "io/grpc/ServerProviderTest-multipleProvider.txt"); - assertSame(Available7Provider.class, ServerProvider.load(cl).getClass()); - } - - @Test - public void unavailableProvider() { - ClassLoader cl = new ReplacingClassLoader( - getClass().getClassLoader(), serviceFile, - "io/grpc/ServerProviderTest-unavailableProvider.txt"); - assertNull(ServerProvider.load(cl)); - } - - @Test - public void contextClassLoaderProvider() { - ClassLoader ccl = Thread.currentThread().getContextClassLoader(); - try { - ClassLoader cl = new ReplacingClassLoader( - getClass().getClassLoader(), serviceFile, - "io/grpc/ServerProviderTest-empty.txt"); - - // test that the context classloader is used as fallback - ClassLoader rcll = new ReplacingClassLoader( - getClass().getClassLoader(), serviceFile, - "io/grpc/ServerProviderTest-multipleProvider.txt"); - Thread.currentThread().setContextClassLoader(rcll); - assertSame(Available7Provider.class, ServerProvider.load(cl).getClass()); - } finally { - Thread.currentThread().setContextClassLoader(ccl); - } - } - - private static class BaseProvider extends ServerProvider { - 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 ServerBuilder builderForPort(int port) { - throw new UnsupportedOperationException(); - } - } - - public static class Available0Provider extends BaseProvider { - public Available0Provider() { - super(true, 0); - } - } - - public static class Available5Provider extends BaseProvider { - public Available5Provider() { - super(true, 5); - } - } - - public static class Available7Provider extends BaseProvider { - public Available7Provider() { - super(true, 7); - } - } - - public static class UnavailableProvider extends BaseProvider { - public UnavailableProvider() { - super(false, 10); - } - - @Override - protected int priority() { - throw new RuntimeException("purposefully broken"); - } - } -} diff --git a/core/src/test/resources/io/grpc/ManagedChannelProviderTest-multipleProvider.txt b/core/src/test/resources/io/grpc/ManagedChannelProviderTest-multipleProvider.txt deleted file mode 100644 index e048430544..0000000000 --- a/core/src/test/resources/io/grpc/ManagedChannelProviderTest-multipleProvider.txt +++ /dev/null @@ -1,3 +0,0 @@ -io.grpc.ManagedChannelProviderTest$Available5Provider -io.grpc.ManagedChannelProviderTest$Available7Provider -io.grpc.ManagedChannelProviderTest$Available0Provider diff --git a/core/src/test/resources/io/grpc/ManagedChannelProviderTest-unavailableProvider.txt b/core/src/test/resources/io/grpc/ManagedChannelProviderTest-unavailableProvider.txt deleted file mode 100644 index b24a346532..0000000000 --- a/core/src/test/resources/io/grpc/ManagedChannelProviderTest-unavailableProvider.txt +++ /dev/null @@ -1 +0,0 @@ -io.grpc.ManagedChannelProviderTest$UnavailableProvider diff --git a/core/src/test/resources/io/grpc/ServerProviderTest-empty.txt b/core/src/test/resources/io/grpc/ServerProviderTest-empty.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/core/src/test/resources/io/grpc/ServerProviderTest-multipleProvider.txt b/core/src/test/resources/io/grpc/ServerProviderTest-multipleProvider.txt deleted file mode 100644 index ba9aadacce..0000000000 --- a/core/src/test/resources/io/grpc/ServerProviderTest-multipleProvider.txt +++ /dev/null @@ -1,3 +0,0 @@ -io.grpc.ServerProviderTest$Available5Provider -io.grpc.ServerProviderTest$Available7Provider -io.grpc.ServerProviderTest$Available0Provider diff --git a/core/src/test/resources/io/grpc/ServerProviderTest-unavailableProvider.txt b/core/src/test/resources/io/grpc/ServerProviderTest-unavailableProvider.txt deleted file mode 100644 index 0076908359..0000000000 --- a/core/src/test/resources/io/grpc/ServerProviderTest-unavailableProvider.txt +++ /dev/null @@ -1 +0,0 @@ -io.grpc.ServerProviderTest$UnavailableProvider diff --git a/netty/src/test/java/io/grpc/netty/NettyChannelProviderTest.java b/netty/src/test/java/io/grpc/netty/NettyChannelProviderTest.java index 5968a2e1b6..00beb78466 100644 --- a/netty/src/test/java/io/grpc/netty/NettyChannelProviderTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyChannelProviderTest.java @@ -21,6 +21,8 @@ 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 org.junit.Test; import org.junit.runner.RunWith; @@ -34,7 +36,8 @@ public class NettyChannelProviderTest { @Test public void provided() { for (ManagedChannelProvider current - : ManagedChannelProvider.getCandidatesViaServiceLoader(getClass().getClassLoader())) { + : InternalServiceProviders.getCandidatesViaServiceLoader( + ManagedChannelProvider.class, getClass().getClassLoader())) { if (current instanceof NettyChannelProvider) { return; } @@ -44,7 +47,8 @@ public class NettyChannelProviderTest { @Test public void providedHardCoded() { - for (ManagedChannelProvider current : ManagedChannelProvider.getCandidatesViaHardCoded()) { + for (ManagedChannelProvider current : InternalServiceProviders.getCandidatesViaHardCoded( + ManagedChannelProvider.class, InternalManagedChannelProvider.HARDCODED_CLASSES)) { if (current instanceof NettyChannelProvider) { return; } diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelProvider.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelProvider.java index 4c87b7f0f1..7fb888d1e0 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelProvider.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelProvider.java @@ -17,6 +17,7 @@ package io.grpc.okhttp; import io.grpc.Internal; +import io.grpc.InternalServiceProviders; import io.grpc.ManagedChannelProvider; import io.grpc.internal.GrpcUtil; @@ -33,7 +34,8 @@ public final class OkHttpChannelProvider extends ManagedChannelProvider { @Override public int priority() { - return (GrpcUtil.IS_RESTRICTED_APPENGINE || isAndroid()) ? 8 : 3; + return (GrpcUtil.IS_RESTRICTED_APPENGINE + || InternalServiceProviders.isAndroid(getClass().getClassLoader())) ? 8 : 3; } @Override diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelProviderTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelProviderTest.java index 6a2ef7f37e..94e48dcc9b 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelProviderTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelProviderTest.java @@ -20,6 +20,8 @@ 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 org.junit.Test; import org.junit.runner.RunWith; @@ -33,7 +35,8 @@ public class OkHttpChannelProviderTest { @Test public void provided() { for (ManagedChannelProvider current - : ManagedChannelProvider.getCandidatesViaServiceLoader(getClass().getClassLoader())) { + : InternalServiceProviders.getCandidatesViaServiceLoader( + ManagedChannelProvider.class, getClass().getClassLoader())) { if (current instanceof OkHttpChannelProvider) { return; } @@ -43,7 +46,8 @@ public class OkHttpChannelProviderTest { @Test public void providedHardCoded() { - for (ManagedChannelProvider current : ManagedChannelProvider.getCandidatesViaHardCoded()) { + for (ManagedChannelProvider current : InternalServiceProviders.getCandidatesViaHardCoded( + ManagedChannelProvider.class, InternalManagedChannelProvider.HARDCODED_CLASSES)) { if (current instanceof OkHttpChannelProvider) { return; }