inprocess,core: add ManagedChannelBuilder and ServerBuilder factory hiders

* inprocess,core: add ManagedChannelBuilder and ServerBuilder factory hiders

Because the factory for Channels and Servers resides on the builder
itself, it is easy for subclasses to accidentally inherit the
factory.  This causes confusion, because calling a static method on
a specific class may result in a different class.

This change adds hiding static factories to each builder, and a test
to enforce that each subclass hides the factory.  The test lives in
the interop tests, because it has a classpath dependency on all the
existing transports.

Minor note: the test scans the classpath using a Beta Guava API.
The test can be disabled if the API goes away.
This commit is contained in:
Carl Mastrangelo 2017-09-06 15:21:17 -07:00 committed by GitHub
parent ddd31d6799
commit 53f56a32f1
6 changed files with 142 additions and 0 deletions

View File

@ -36,6 +36,20 @@ public abstract class ForwardingChannelBuilder<T extends ForwardingChannelBuilde
*/
protected ForwardingChannelBuilder() {}
/**
* This method serves to force sub classes to "hide" this static factory.
*/
public static ManagedChannelBuilder<?> forAddress(String name, int port) {
throw new UnsupportedOperationException("Subclass failed to hide static factory");
}
/**
* This method serves to force sub classes to "hide" this static factory.
*/
public static ManagedChannelBuilder<?> forTarget(String target) {
throw new UnsupportedOperationException("Subclass failed to hide static factory");
}
/**
* Returns the delegated {@code ManagedChannelBuilder}.
*/

View File

@ -48,6 +48,20 @@ public final class InProcessChannelBuilder extends
return new InProcessChannelBuilder(name);
}
/**
* Always fails. Call {@link #forName} instead.
*/
public static InProcessChannelBuilder forTarget(String target) {
throw new UnsupportedOperationException("call forName() instead");
}
/**
* Always fails. Call {@link #forName} instead.
*/
public static InProcessChannelBuilder forAddress(String name, int port) {
throw new UnsupportedOperationException("call forName() instead");
}
private final String name;
private InProcessChannelBuilder(String name) {

View File

@ -68,6 +68,13 @@ public final class InProcessServerBuilder
return new InProcessServerBuilder(name);
}
/**
* Always fails. Call {@link #forName} instead.
*/
public static InProcessServerBuilder forPort(int port) {
throw new UnsupportedOperationException("call forName() instead");
}
private final String name;
private InProcessServerBuilder(String name) {

View File

@ -56,6 +56,14 @@ public abstract class AbstractManagedChannelImplBuilder
<T extends AbstractManagedChannelImplBuilder<T>> extends ManagedChannelBuilder<T> {
private static final String DIRECT_ADDRESS_SCHEME = "directaddress";
public static ManagedChannelBuilder<?> forAddress(String name, int port) {
throw new UnsupportedOperationException("Subclass failed to hide static factory");
}
public static ManagedChannelBuilder<?> forTarget(String target) {
throw new UnsupportedOperationException("Subclass failed to hide static factory");
}
/**
* An idle timeout larger than this would disable idle mode.
*/

View File

@ -51,6 +51,10 @@ import javax.annotation.Nullable;
public abstract class AbstractServerImplBuilder<T extends AbstractServerImplBuilder<T>>
extends ServerBuilder<T> {
public static ServerBuilder<?> forPort(int port) {
throw new UnsupportedOperationException("Subclass failed to hide static factory");
}
private static final ObjectPool<? extends Executor> DEFAULT_EXECUTOR_POOL =
SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
private static final HandlerRegistry DEFAULT_FALLBACK_REGISTRY = new HandlerRegistry() {

View File

@ -0,0 +1,95 @@
/*
* Copyright 2017, 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.assertSame;
import static org.junit.Assert.assertTrue;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;
import com.google.common.truth.Truth;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
/**
* Tests that Channel and Server builders properly hide the static constructors.
*/
@RunWith(Parameterized.class)
public class ChannelAndServerBuilderTest {
@Parameter
public Class<?> builderClass;
/**
* Javadoc.
*/
@Parameters(name = "class={0}")
public static Collection<Object[]> params() throws Exception {
ClassLoader loader = ChannelAndServerBuilderTest.class.getClassLoader();
List<Object[]> classes = new ArrayList<Object[]>();
for (ClassInfo classInfo : ClassPath.from(loader).getTopLevelClassesRecursive("io.grpc")) {
Class<?> clazz = Class.forName(classInfo.getName(), false /*initialize*/, loader);
if (ServerBuilder.class.isAssignableFrom(clazz) && clazz != ServerBuilder.class) {
classes.add(new Object[]{clazz});
} else if (ManagedChannelBuilder.class.isAssignableFrom(clazz)
&& clazz != ManagedChannelBuilder.class ) {
classes.add(new Object[]{clazz});
}
}
Truth.assertWithMessage("Unable to find any builder classes").that(classes).isNotEmpty();
return classes;
}
@Test
public void serverBuilderHidesMethod_forPort() throws Exception {
Assume.assumeTrue(ServerBuilder.class.isAssignableFrom(builderClass));
Method method = builderClass.getMethod("forPort", int.class);
assertTrue(Modifier.isStatic(method.getModifiers()));
assertTrue(ServerBuilder.class.isAssignableFrom(method.getReturnType()));
assertSame(builderClass, method.getDeclaringClass());
}
@Test
public void channelBuilderHidesMethod_forAddress() throws Exception {
Assume.assumeTrue(ManagedChannelBuilder.class.isAssignableFrom(builderClass));
Method method = builderClass.getMethod("forAddress", String.class, int.class);
assertTrue(Modifier.isStatic(method.getModifiers()));
assertTrue(ManagedChannelBuilder.class.isAssignableFrom(method.getReturnType()));
assertSame(builderClass, method.getDeclaringClass());
}
@Test
public void channelBuilderHidesMethod_forTarget() throws Exception {
Assume.assumeTrue(ManagedChannelBuilder.class.isAssignableFrom(builderClass));
Method method = builderClass.getMethod("forTarget", String.class);
assertTrue(Modifier.isStatic(method.getModifiers()));
assertTrue(ManagedChannelBuilder.class.isAssignableFrom(method.getReturnType()));
assertSame(builderClass, method.getDeclaringClass());
}
}