diff --git a/api/src/main/java/io/grpc/ForwardingServerBuilder.java b/api/src/main/java/io/grpc/ForwardingServerBuilder.java new file mode 100644 index 0000000000..4e1d5a1baf --- /dev/null +++ b/api/src/main/java/io/grpc/ForwardingServerBuilder.java @@ -0,0 +1,169 @@ +/* + * 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.MoreObjects; +import java.io.File; +import java.io.InputStream; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + +/** + * A {@link ServerBuilder} that delegates all its builder method to another builder by default. + * + * @param The type of the subclass extending this abstract class. + * @since 1.33.0 + */ +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7393") +public abstract class ForwardingServerBuilder> + extends ServerBuilder { + + /** The default constructor. */ + protected ForwardingServerBuilder() {} + + /** + * This method serves to force sub classes to "hide" this static factory. + */ + public static ServerBuilder forPort(int port) { + throw new UnsupportedOperationException("Subclass failed to hide static factory"); + } + + /** + * Returns the delegated {@code ServerBuilder}. + */ + protected abstract ServerBuilder delegate(); + + @Override + public T directExecutor() { + delegate().directExecutor(); + return thisT(); + } + + @Override + public T executor(@Nullable Executor executor) { + delegate().executor(executor); + return thisT(); + } + + @Override + public T addService(ServerServiceDefinition service) { + delegate().addService(service); + return thisT(); + } + + @Override + public T addService(BindableService bindableService) { + delegate().addService(bindableService); + return thisT(); + } + + @Override + public T intercept(ServerInterceptor interceptor) { + delegate().intercept(interceptor); + return thisT(); + } + + @Override + public T addTransportFilter(ServerTransportFilter filter) { + delegate().addTransportFilter(filter); + return thisT(); + } + + @Override + public T addStreamTracerFactory(ServerStreamTracer.Factory factory) { + delegate().addStreamTracerFactory(factory); + return thisT(); + } + + @Override + public T fallbackHandlerRegistry(@Nullable HandlerRegistry fallbackRegistry) { + delegate().fallbackHandlerRegistry(fallbackRegistry); + return thisT(); + } + + @Override + public T useTransportSecurity(File certChain, File privateKey) { + delegate().useTransportSecurity(certChain, privateKey); + return thisT(); + } + + @Override + public T useTransportSecurity(InputStream certChain, InputStream privateKey) { + delegate().useTransportSecurity(certChain, privateKey); + return thisT(); + } + + @Override + public T decompressorRegistry(@Nullable DecompressorRegistry registry) { + delegate().decompressorRegistry(registry); + return thisT(); + } + + @Override + public T compressorRegistry(@Nullable CompressorRegistry registry) { + delegate().compressorRegistry(registry); + return thisT(); + } + + @Override + public T handshakeTimeout(long timeout, TimeUnit unit) { + delegate().handshakeTimeout(timeout, unit); + return thisT(); + } + + @Override + public T maxInboundMessageSize(int bytes) { + delegate().maxInboundMessageSize(bytes); + return thisT(); + } + + @Override + public T maxInboundMetadataSize(int bytes) { + delegate().maxInboundMetadataSize(bytes); + return thisT(); + } + + @Override + public T setBinaryLog(BinaryLog binaryLog) { + delegate().setBinaryLog(binaryLog); + return thisT(); + } + + /** + * Returns the {@link Server} built by the delegate by default. Overriding method can return + * different value. + */ + @Override + public Server build() { + return delegate().build(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString(); + } + + /** + * Returns the correctly typed version of the builder. + */ + protected final T thisT() { + @SuppressWarnings("unchecked") + T thisT = (T) this; + return thisT; + } +} diff --git a/api/src/test/java/io/grpc/ForwardingServerBuilderTest.java b/api/src/test/java/io/grpc/ForwardingServerBuilderTest.java new file mode 100644 index 0000000000..6a8c1c115a --- /dev/null +++ b/api/src/test/java/io/grpc/ForwardingServerBuilderTest.java @@ -0,0 +1,83 @@ +/* + * 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.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import com.google.common.base.Defaults; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collections; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link ForwardingServerBuilder}. + */ +@RunWith(JUnit4.class) +public class ForwardingServerBuilderTest { + private final ServerBuilder mockDelegate = mock(ServerBuilder.class); + private final ForwardingServerBuilder testServerBuilder = new TestBuilder(); + + private final class TestBuilder extends ForwardingServerBuilder { + @Override + protected ServerBuilder delegate() { + return mockDelegate; + } + } + + @Test + public void allMethodsForwarded() throws Exception { + ForwardingTestUtil.testMethodsForwarded( + ServerBuilder.class, + mockDelegate, + testServerBuilder, + Collections.emptyList()); + } + + @Test + public void allBuilderMethodsReturnThis() throws Exception { + for (Method method : ServerBuilder.class.getDeclaredMethods()) { + if (Modifier.isStatic(method.getModifiers()) || Modifier.isPrivate(method.getModifiers())) { + continue; + } + if (method.getName().equals("build")) { + continue; + } + Class[] argTypes = method.getParameterTypes(); + Object[] args = new Object[argTypes.length]; + for (int i = 0; i < argTypes.length; i++) { + args[i] = Defaults.defaultValue(argTypes[i]); + } + + Object returnedValue = method.invoke(testServerBuilder, args); + + assertThat(returnedValue).isSameInstanceAs(testServerBuilder); + } + } + + @Test + public void buildReturnsDelegateBuildByDefault() { + Server server = mock(Server.class); + doReturn(server).when(mockDelegate).build(); + + assertThat(testServerBuilder.build()).isSameInstanceAs(server); + } +} diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/TransportBenchmark.java b/benchmarks/src/jmh/java/io/grpc/benchmarks/TransportBenchmark.java index ec4646bff4..b673657cb3 100644 --- a/benchmarks/src/jmh/java/io/grpc/benchmarks/TransportBenchmark.java +++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/TransportBenchmark.java @@ -22,6 +22,7 @@ import com.google.protobuf.ByteString; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.Server; +import io.grpc.ServerBuilder; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.benchmarks.proto.BenchmarkServiceGrpc; @@ -31,7 +32,6 @@ import io.grpc.benchmarks.proto.Messages.SimpleResponse; import io.grpc.benchmarks.qps.AsyncServer; import io.grpc.inprocess.InProcessChannelBuilder; import io.grpc.inprocess.InProcessServerBuilder; -import io.grpc.internal.AbstractServerImplBuilder; import io.grpc.netty.NegotiationType; import io.grpc.netty.NettyChannelBuilder; import io.grpc.netty.NettyServerBuilder; @@ -80,7 +80,7 @@ public class TransportBenchmark { @Setup public void setUp() throws Exception { - AbstractServerImplBuilder serverBuilder; + ServerBuilder serverBuilder; ManagedChannelBuilder channelBuilder; switch (transport) { case INPROCESS: diff --git a/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java b/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java index 21a1becc28..c68076cc4c 100644 --- a/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java +++ b/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java @@ -283,7 +283,7 @@ public abstract class AbstractServerImplBuilder { + private final ClientTransportServersBuilder clientTransportServersBuilder; + + /** + * An interface to provide to provide transport specific information for the server. This method + * is meant for Transport implementors and should not be used by normal users. + */ + public interface ClientTransportServersBuilder { + List buildClientTransportServers( + List streamTracerFactories); + } + + /** + * Creates a new server builder with given transport servers provider. + */ + public ServerImplBuilder(ClientTransportServersBuilder clientTransportServersBuilder) { + this.clientTransportServersBuilder = Preconditions + .checkNotNull(clientTransportServersBuilder, "clientTransportServersBuilder"); + } + + @Override + protected List buildTransportServers( + List streamTracerFactories) { + return clientTransportServersBuilder.buildClientTransportServers(streamTracerFactories); + } + + @Override + public void setDeadlineTicker(Deadline.Ticker ticker) { + super.setDeadlineTicker(ticker); + } + + @Override + public void setTracingEnabled(boolean value) { + super.setTracingEnabled(value); + } + + @Override + public void setStatsEnabled(boolean value) { + super.setStatsEnabled(value); + } + + @Override + public void setStatsRecordStartedRpcs(boolean value) { + super.setStatsRecordStartedRpcs(value); + } + + @Override + public void setStatsRecordFinishedRpcs(boolean value) { + super.setStatsRecordFinishedRpcs(value); + } + + @Override + public void setStatsRecordRealTimeMetrics(boolean value) { + super.setStatsRecordRealTimeMetrics(value); + } + + @Override + public InternalChannelz getChannelz() { + return super.getChannelz(); + } + + @Override + public ObjectPool getExecutorPool() { + return super.getExecutorPool(); + } + + @Override + public ServerImplBuilder useTransportSecurity(File certChain, File privateKey) { + throw new UnsupportedOperationException("TLS not supported in ServerImplBuilder"); + } + + public static ServerBuilder forPort(int port) { + throw new UnsupportedOperationException("ClientTransportServersBuilder is required"); + } +} diff --git a/core/src/test/java/io/grpc/internal/ServerImplBuilderTest.java b/core/src/test/java/io/grpc/internal/ServerImplBuilderTest.java new file mode 100644 index 0000000000..3b5e3c7d8c --- /dev/null +++ b/core/src/test/java/io/grpc/internal/ServerImplBuilderTest.java @@ -0,0 +1,64 @@ +/* + * 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.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import io.grpc.ServerStreamTracer; +import io.grpc.internal.ServerImplBuilder.ClientTransportServersBuilder; +import java.util.List; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Unit tests for {@link ServerImplBuilder}. */ +@RunWith(JUnit4.class) +public class ServerImplBuilderTest { + @Rule public final MockitoRule mocks = MockitoJUnit.rule(); + + @Mock private ClientTransportServersBuilder mockClientTransportServersBuilder; + @Mock private List mockServerStreamTracerFactories; + @Mock private List mockInternalServers; + private ServerImplBuilder builder; + + @Before + public void setUp() throws Exception { + builder = new ServerImplBuilder(mockClientTransportServersBuilder); + } + + @Test + public void buildTransportServers() { + doReturn(mockInternalServers).when(mockClientTransportServersBuilder) + .buildClientTransportServers(ArgumentMatchers.anyList()); + + List servers = builder + .buildTransportServers(mockServerStreamTracerFactories); + assertEquals(mockInternalServers, servers); + assertNotNull(servers); + verify(mockClientTransportServersBuilder) + .buildClientTransportServers(mockServerStreamTracerFactories); + } +}