From 395fae59f0b5ae40c91e8fcc6640dc7b62703f50 Mon Sep 17 00:00:00 2001 From: DNVindhya Date: Thu, 9 Jun 2022 16:27:25 -0700 Subject: [PATCH] api: Add GlobalInterceptors API (#9235) * added GlobalInterceptors API --- .../main/java/io/grpc/GlobalInterceptors.java | 102 ++++++++++ .../io/grpc/InternalGlobalInterceptors.java | 46 +++++ .../java/io/grpc/GlobalInterceptorsTest.java | 184 ++++++++++++++++++ 3 files changed, 332 insertions(+) create mode 100644 api/src/main/java/io/grpc/GlobalInterceptors.java create mode 100644 api/src/main/java/io/grpc/InternalGlobalInterceptors.java create mode 100644 api/src/test/java/io/grpc/GlobalInterceptorsTest.java diff --git a/api/src/main/java/io/grpc/GlobalInterceptors.java b/api/src/main/java/io/grpc/GlobalInterceptors.java new file mode 100644 index 0000000000..c237843731 --- /dev/null +++ b/api/src/main/java/io/grpc/GlobalInterceptors.java @@ -0,0 +1,102 @@ +/* + * Copyright 2022 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.Collections; +import java.util.List; + +/** The collection of global interceptors and global server stream tracers. */ +@Internal +final class GlobalInterceptors { + private static List clientInterceptors = Collections.emptyList(); + private static List serverInterceptors = Collections.emptyList(); + private static List serverStreamTracerFactories = + Collections.emptyList(); + private static boolean isGlobalInterceptorsTracersSet; + private static boolean isGlobalInterceptorsTracersGet; + + // Prevent instantiation + private GlobalInterceptors() {} + + /** + * Sets the list of global interceptors and global server stream tracers. + * + *

If {@code setInterceptorsTracers()} is called again, this method will throw {@link + * IllegalStateException}. + * + *

It is only safe to call early. This method throws {@link IllegalStateException} after any of + * the get calls [{@link #getClientInterceptors()}, {@link #getServerInterceptors()} or {@link + * #getServerStreamTracerFactories()}] has been called, in order to limit changes to the result of + * {@code setInterceptorsTracers()}. + * + * @param clientInterceptorList list of {@link ClientInterceptor} that make up global Client + * Interceptors. + * @param serverInterceptorList list of {@link ServerInterceptor} that make up global Server + * Interceptors. + * @param serverStreamTracerFactoryList list of {@link ServerStreamTracer.Factory} that make up + * global ServerStreamTracer factories. + */ + static synchronized void setInterceptorsTracers( + List clientInterceptorList, + List serverInterceptorList, + List serverStreamTracerFactoryList) { + if (isGlobalInterceptorsTracersGet) { + throw new IllegalStateException("Set cannot be called after any get call"); + } + if (isGlobalInterceptorsTracersSet) { + throw new IllegalStateException("Global interceptors and tracers are already set"); + } + + if (clientInterceptorList != null) { + clientInterceptors = Collections.unmodifiableList(new ArrayList<>(clientInterceptorList)); + } + + if (serverInterceptorList != null) { + serverInterceptors = Collections.unmodifiableList(new ArrayList<>(serverInterceptorList)); + } + + if (serverStreamTracerFactoryList != null) { + serverStreamTracerFactories = + Collections.unmodifiableList(new ArrayList<>(serverStreamTracerFactoryList)); + } + isGlobalInterceptorsTracersSet = true; + } + + /** + * Returns the list of global {@link ClientInterceptor}. If not set, this returns am empty list. + */ + static synchronized List getClientInterceptors() { + isGlobalInterceptorsTracersGet = true; + return clientInterceptors; + } + + /** Returns list of global {@link ServerInterceptor}. If not set, this returns an empty list. */ + static synchronized List getServerInterceptors() { + isGlobalInterceptorsTracersGet = true; + return serverInterceptors; + } + + /** + * Returns list of global {@link ServerStreamTracer.Factory}. If not set, this returns an empty + * list. + */ + static synchronized List getServerStreamTracerFactories() { + isGlobalInterceptorsTracersGet = true; + return serverStreamTracerFactories; + } +} diff --git a/api/src/main/java/io/grpc/InternalGlobalInterceptors.java b/api/src/main/java/io/grpc/InternalGlobalInterceptors.java new file mode 100644 index 0000000000..db0ff6e2ce --- /dev/null +++ b/api/src/main/java/io/grpc/InternalGlobalInterceptors.java @@ -0,0 +1,46 @@ +/* + * Copyright 2022 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.List; + +/** Accessor to internal methods of {@link GlobalInterceptors}. */ +@Internal +public final class InternalGlobalInterceptors { + + public static void setInterceptorsTracers( + List clientInterceptorList, + List serverInterceptorList, + List serverStreamTracerFactoryList) { + GlobalInterceptors.setInterceptorsTracers( + clientInterceptorList, serverInterceptorList, serverStreamTracerFactoryList); + } + + public static List getClientInterceptors() { + return GlobalInterceptors.getClientInterceptors(); + } + + public static List getServerInterceptors() { + return GlobalInterceptors.getServerInterceptors(); + } + + public static List getServerStreamTracerFactories() { + return GlobalInterceptors.getServerStreamTracerFactories(); + } + + private InternalGlobalInterceptors() {} +} diff --git a/api/src/test/java/io/grpc/GlobalInterceptorsTest.java b/api/src/test/java/io/grpc/GlobalInterceptorsTest.java new file mode 100644 index 0000000000..c679f4b10a --- /dev/null +++ b/api/src/test/java/io/grpc/GlobalInterceptorsTest.java @@ -0,0 +1,184 @@ +/* + * Copyright 2022 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import org.junit.Test; + +public class GlobalInterceptorsTest { + + private final StaticTestingClassLoader classLoader = + new StaticTestingClassLoader( + getClass().getClassLoader(), Pattern.compile("io\\.grpc\\.[^.]+")); + + @Test + public void setInterceptorsTracers() throws Exception { + Class runnable = classLoader.loadClass(StaticTestingClassLoaderSet.class.getName()); + ((Runnable) runnable.getDeclaredConstructor().newInstance()).run(); + } + + @Test + public void setGlobalInterceptorsTracers_twice() throws Exception { + Class runnable = classLoader.loadClass(StaticTestingClassLoaderSetTwice.class.getName()); + ((Runnable) runnable.getDeclaredConstructor().newInstance()).run(); + } + + @Test + public void getBeforeSet_clientInterceptors() throws Exception { + Class runnable = + classLoader.loadClass( + StaticTestingClassLoaderGetBeforeSetClientInterceptor.class.getName()); + ((Runnable) runnable.getDeclaredConstructor().newInstance()).run(); + } + + @Test + public void getBeforeSet_serverInterceptors() throws Exception { + Class runnable = + classLoader.loadClass( + StaticTestingClassLoaderGetBeforeSetServerInterceptor.class.getName()); + ((Runnable) runnable.getDeclaredConstructor().newInstance()).run(); + } + + @Test + public void getBeforeSet_serverStreamTracerFactories() throws Exception { + Class runnable = + classLoader.loadClass( + StaticTestingClassLoaderGetBeforeSetServerStreamTracerFactory.class.getName()); + ((Runnable) runnable.getDeclaredConstructor().newInstance()).run(); + } + + // UsedReflectively + public static final class StaticTestingClassLoaderSet implements Runnable { + @Override + public void run() { + List clientInterceptorList = + new ArrayList<>(Arrays.asList(new NoopClientInterceptor())); + List serverInterceptorList = + new ArrayList<>(Arrays.asList(new NoopServerInterceptor())); + List serverStreamTracerFactoryList = + new ArrayList<>( + Arrays.asList( + new NoopServerStreamTracerFactory(), new NoopServerStreamTracerFactory())); + + GlobalInterceptors.setInterceptorsTracers( + clientInterceptorList, serverInterceptorList, serverStreamTracerFactoryList); + + assertThat(GlobalInterceptors.getClientInterceptors()).isEqualTo(clientInterceptorList); + assertThat(GlobalInterceptors.getServerInterceptors()).isEqualTo(serverInterceptorList); + assertThat(GlobalInterceptors.getServerStreamTracerFactories()) + .isEqualTo(serverStreamTracerFactoryList); + } + } + + public static final class StaticTestingClassLoaderSetTwice implements Runnable { + @Override + public void run() { + GlobalInterceptors.setInterceptorsTracers( + new ArrayList<>(Arrays.asList(new NoopClientInterceptor())), + null, + new ArrayList<>(Arrays.asList(new NoopServerStreamTracerFactory()))); + try { + GlobalInterceptors.setInterceptorsTracers( + null, new ArrayList<>(Arrays.asList(new NoopServerInterceptor())), null); + fail("should have failed for calling setGlobalInterceptorsTracers() again"); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().isEqualTo("Global interceptors and tracers are already set"); + } + } + } + + public static final class StaticTestingClassLoaderGetBeforeSetClientInterceptor + implements Runnable { + @Override + public void run() { + List clientInterceptors = GlobalInterceptors.getClientInterceptors(); + assertThat(clientInterceptors).isEmpty(); + + try { + GlobalInterceptors.setInterceptorsTracers( + new ArrayList<>(Arrays.asList(new NoopClientInterceptor())), null, null); + fail("should have failed for invoking set call after get is already called"); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().isEqualTo("Set cannot be called after any get call"); + } + } + } + + public static final class StaticTestingClassLoaderGetBeforeSetServerInterceptor + implements Runnable { + @Override + public void run() { + List serverInterceptors = GlobalInterceptors.getServerInterceptors(); + assertThat(serverInterceptors).isEmpty(); + + try { + GlobalInterceptors.setInterceptorsTracers( + null, new ArrayList<>(Arrays.asList(new NoopServerInterceptor())), null); + fail("should have failed for invoking set call after get is already called"); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().isEqualTo("Set cannot be called after any get call"); + } + } + } + + public static final class StaticTestingClassLoaderGetBeforeSetServerStreamTracerFactory + implements Runnable { + @Override + public void run() { + List serverStreamTracerFactories = + GlobalInterceptors.getServerStreamTracerFactories(); + assertThat(serverStreamTracerFactories).isEmpty(); + + try { + GlobalInterceptors.setInterceptorsTracers( + null, null, new ArrayList<>(Arrays.asList(new NoopServerStreamTracerFactory()))); + fail("should have failed for invoking set call after get is already called"); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().isEqualTo("Set cannot be called after any get call"); + } + } + } + + private static class NoopClientInterceptor implements ClientInterceptor { + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + return next.newCall(method, callOptions); + } + } + + private static class NoopServerInterceptor implements ServerInterceptor { + @Override + public ServerCall.Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + return next.startCall(call, headers); + } + } + + private static class NoopServerStreamTracerFactory extends ServerStreamTracer.Factory { + @Override + public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata headers) { + throw new UnsupportedOperationException(); + } + } +}