Swap Server from Builder to HandlerRegistry.
The idea is that Server would be provided a HandlerRegistry at construction time.
This has a simplistic implementation of HandlerRegistry. If we like the API, then I can implement a lock-less version as we find need.
Most classes are still under Server so that it is obvious what changes were made. Moving things out of Server would be a separate CL.
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=73334671
diff --git a/core/src/main/java/com/google/net/stubby/HandlerRegistry.java b/core/src/main/java/com/google/net/stubby/HandlerRegistry.java
new file mode 100644
index 0000000..33ad884
--- /dev/null
+++ b/core/src/main/java/com/google/net/stubby/HandlerRegistry.java
@@ -0,0 +1,34 @@
+package com.google.net.stubby;
+
+import com.google.net.stubby.Server.MethodDefinition;
+import com.google.net.stubby.Server.ServiceDefinition;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/** Registry of services and their methods for dispatching incoming calls. */
+@ThreadSafe
+public abstract class HandlerRegistry {
+ /** Lookup full method name, starting with '/'. Returns {@code null} if method not found. */
+ @Nullable
+ public abstract Method lookupMethod(String methodName);
+
+ /** A method definition and its parent's service definition. */
+ public static final class Method {
+ private final ServiceDefinition serviceDef;
+ private final MethodDefinition methodDef;
+
+ public Method(ServiceDefinition serviceDef, MethodDefinition methodDef) {
+ this.serviceDef = serviceDef;
+ this.methodDef = methodDef;
+ }
+
+ public ServiceDefinition getServiceDefinition() {
+ return serviceDef;
+ }
+
+ public MethodDefinition getMethodDefinition() {
+ return methodDef;
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/net/stubby/MutableHandlerRegistry.java b/core/src/main/java/com/google/net/stubby/MutableHandlerRegistry.java
new file mode 100644
index 0000000..855777a
--- /dev/null
+++ b/core/src/main/java/com/google/net/stubby/MutableHandlerRegistry.java
@@ -0,0 +1,20 @@
+package com.google.net.stubby;
+
+import com.google.net.stubby.Server.ServiceDefinition;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/** Mutable registry of services and their methods for dispatching incoming calls. */
+@ThreadSafe
+public abstract class MutableHandlerRegistry extends HandlerRegistry {
+ /**
+ * Returns {@code null}, or previous service if {@code service} replaced an existing service.
+ */
+ @Nullable
+ public abstract ServiceDefinition addService(ServiceDefinition service);
+
+ /** Returns {@code false} if {@code service} was not registered. */
+ @Nullable
+ public abstract boolean removeService(ServiceDefinition service);
+}
diff --git a/core/src/main/java/com/google/net/stubby/MutableHandlerRegistryImpl.java b/core/src/main/java/com/google/net/stubby/MutableHandlerRegistryImpl.java
new file mode 100644
index 0000000..f6d686e
--- /dev/null
+++ b/core/src/main/java/com/google/net/stubby/MutableHandlerRegistryImpl.java
@@ -0,0 +1,52 @@
+package com.google.net.stubby;
+
+import com.google.net.stubby.Server.MethodDefinition;
+import com.google.net.stubby.Server.ServiceDefinition;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/** Mutable registry implementation of services and their methods for dispatching incoming calls. */
+@ThreadSafe
+public final class MutableHandlerRegistryImpl extends MutableHandlerRegistry {
+ private final ConcurrentMap<String, ServiceDefinition> services
+ = new ConcurrentHashMap<String, ServiceDefinition>();
+
+ @Override
+ @Nullable
+ public ServiceDefinition addService(ServiceDefinition service) {
+ return services.put(service.getName(), service);
+ }
+
+ @Override
+ public boolean removeService(ServiceDefinition service) {
+ return services.remove(service.getName(), service);
+ }
+
+ @Override
+ @Nullable
+ public Method lookupMethod(String methodName) {
+ methodName = methodName.replace('.', '/');
+ if (!methodName.startsWith("/")) {
+ return null;
+ }
+ methodName = methodName.substring(1);
+ int index = methodName.lastIndexOf("/");
+ if (index == -1) {
+ return null;
+ }
+ ServiceDefinition service = services.get(methodName.substring(0, index));
+ if (service == null) {
+ return null;
+ }
+ MethodDefinition method = service.getMethod(methodName.substring(index + 1));
+ if (method == null) {
+ return null;
+ }
+ return new Method(service, method);
+ }
+}
diff --git a/core/src/main/java/com/google/net/stubby/Server.java b/core/src/main/java/com/google/net/stubby/Server.java
index 5b6a85c..1ace9fb 100644
--- a/core/src/main/java/com/google/net/stubby/Server.java
+++ b/core/src/main/java/com/google/net/stubby/Server.java
@@ -1,10 +1,14 @@
package com.google.net.stubby;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.Service;
import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
@@ -15,24 +19,21 @@
*/
@ThreadSafe
public interface Server extends Service {
- /** Builder that Servers are expected to provide for constructing new instances. */
- abstract class Builder {
- public abstract Builder addService(ServiceDef service);
- public abstract Server build();
- }
-
/** Definition of a service to be exposed via a Server. */
- final class ServiceDef {
- public static ServiceDef.Builder builder(String serviceName) {
- return new ServiceDef.Builder(serviceName);
+ final class ServiceDefinition {
+ public static ServiceDefinition.Builder builder(String serviceName) {
+ return new ServiceDefinition.Builder(serviceName);
}
private final String name;
- private final ImmutableList<MethodDef> methods;
+ private final ImmutableList<MethodDefinition> methods;
+ private final ImmutableMap<String, MethodDefinition> methodLookup;
- private ServiceDef(String name, ImmutableList<MethodDef> methods) {
+ private ServiceDefinition(String name, ImmutableList<MethodDefinition> methods,
+ Map<String, MethodDefinition> methodLookup) {
this.name = name;
this.methods = methods;
+ this.methodLookup = ImmutableMap.copyOf(methodLookup);
}
/** Simple name of the service. It is not an absolute path. */
@@ -40,14 +41,20 @@
return name;
}
- public ImmutableList<MethodDef> getMethods() {
+ public ImmutableList<MethodDefinition> getMethods() {
return methods;
}
+ public MethodDefinition getMethod(String name) {
+ return methodLookup.get(name);
+ }
+
/** Builder for constructing Service instances. */
public static final class Builder {
private final String serviceName;
- private final ImmutableList.Builder<MethodDef> methods = ImmutableList.builder();
+ private final ImmutableList.Builder<MethodDefinition> methods = ImmutableList.builder();
+ private final Map<String, MethodDefinition> methodLookup
+ = new HashMap<String, MethodDefinition>();
private Builder(String serviceName) {
this.serviceName = serviceName;
@@ -63,29 +70,36 @@
*/
public <ReqT, RespT> Builder addMethod(String name, Marshaller<ReqT> requestMarshaller,
Marshaller<RespT> responseMarshaller, CallHandler<ReqT, RespT> handler) {
- methods.add(
- new MethodDef<ReqT, RespT>(name, requestMarshaller, responseMarshaller, handler));
+ Preconditions.checkNotNull(name, "name must not be null");
+ if (methodLookup.containsKey(name)) {
+ throw new IllegalStateException("Method by same name already registered");
+ }
+ MethodDefinition def = new MethodDefinition<ReqT, RespT>(name,
+ Preconditions.checkNotNull(requestMarshaller, "requestMarshaller must not be null"),
+ Preconditions.checkNotNull(responseMarshaller, "responseMarshaller must not be null"),
+ Preconditions.checkNotNull(handler, "handler must not be null"));
+ methodLookup.put(name, def);
+ methods.add(def);
return this;
}
- /** Construct new ServiceDef. */
- public ServiceDef build() {
- return new ServiceDef(serviceName, methods.build());
+ /** Construct new ServiceDefinition. */
+ public ServiceDefinition build() {
+ return new ServiceDefinition(serviceName, methods.build(), methodLookup);
}
}
}
/** Definition of a method supported by a service. */
- final class MethodDef<RequestT, ResponseT> {
+ final class MethodDefinition<RequestT, ResponseT> {
private final String name;
private final Marshaller<RequestT> requestMarshaller;
private final Marshaller<ResponseT> responseMarshaller;
private final CallHandler<RequestT, ResponseT> handler;
- // MethodDef has no way of public creation, because all parameters are required. A builder
- // wouldn't have any methods other than build(). addMethod() can be overriden if we ever need to
- // extend what MethodDef contains or if we add a Builder or similar.
- private MethodDef(String name, Marshaller<RequestT> requestMarshaller,
+ // MethodDefinition has no form of public construction. It is only created within the context of
+ // a ServiceDefinition.Builder.
+ private MethodDefinition(String name, Marshaller<RequestT> requestMarshaller,
Marshaller<ResponseT> responseMarshaller, CallHandler<RequestT, ResponseT> handler) {
this.name = name;
this.requestMarshaller = requestMarshaller;
@@ -115,24 +129,47 @@
}
/**
- * Class to begin processing incoming RPCs. Advanced applications and generated code implement
+ * Interface for intercepting incoming RPCs before the handler receives them.
+ */
+ @ThreadSafe
+ interface Interceptor {
+ /**
+ * Intercept a new call. General semantics of {@link Server.CallHandler#startCall} apply. {@code
+ * next} may only be called once. Returned listener must not be {@code null}.
+ *
+ * <p>If the implementation throws an exception, {@code call} will be closed with an error.
+ * Implementations must not throw an exception if they started processing that may use {@code
+ * call} on another thread.
+ *
+ * @param method metadata concerning the call
+ * @param call object for responding
+ * @param next next processor in the interceptor chain
+ * @return listener for processing incoming messages for {@code call}
+ */
+ <ReqT, RespT> Server.Call.Listener<ReqT> interceptCall(MethodDescriptor<ReqT, RespT> method,
+ Server.Call<ReqT, RespT> call, CallHandler<ReqT, RespT> next);
+ }
+
+ /**
+ * Interface to begin processing incoming RPCs. Advanced applications and generated code implement
* this interface to implement service methods.
*/
@ThreadSafe
interface CallHandler<ReqT, RespT> {
/**
- * Produce a listener for the incoming call. Implementations are free to call methods on {@code
- * call} before this method has returned.
+ * Produce a non-{@code null} listener for the incoming call. Implementations are free to call
+ * methods on {@code call} before this method has returned.
*
- * <p>If the implementation throws an exception or returns {@code null}, {@code call} will be
- * closed with an error.
+ * <p>If the implementation throws an exception, {@code call} will be closed with an error.
+ * Implementations must not throw an exception if they started processing that may use {@code
+ * call} on another thread.
*
- * @param call object for responding
* @param method metadata concerning the call
+ * @param call object for responding
* @return listener for processing incoming messages for {@code call}
*/
- Server.Call.Listener<ReqT> startCall(Server.Call<ReqT, RespT> call,
- MethodDescriptor<ReqT, RespT> method);
+ Server.Call.Listener<ReqT> startCall(MethodDescriptor<ReqT, RespT> method,
+ Server.Call<ReqT, RespT> call);
}
/**
diff --git a/core/src/main/java/com/google/net/stubby/ServerInterceptors.java b/core/src/main/java/com/google/net/stubby/ServerInterceptors.java
new file mode 100644
index 0000000..1b82328
--- /dev/null
+++ b/core/src/main/java/com/google/net/stubby/ServerInterceptors.java
@@ -0,0 +1,94 @@
+package com.google.net.stubby;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.net.stubby.Server.CallHandler;
+import com.google.net.stubby.Server.Interceptor;
+import com.google.net.stubby.Server.MethodDefinition;
+import com.google.net.stubby.Server.ServiceDefinition;
+
+import java.util.List;
+import java.util.Iterator;
+
+/** Utility class for {@link Server.Interceptor}s. */
+public class ServerInterceptors {
+ // Prevent instantiation
+ private ServerInterceptors() {}
+
+ /**
+ * Create a new {@code ServiceDefinition} whose {@link Server.CallHandler}s will call {@code
+ * interceptors} before calling the pre-existing {@code CallHandler}.
+ */
+ public static ServiceDefinition intercept(ServiceDefinition serviceDef,
+ List<Interceptor> interceptors) {
+ Preconditions.checkNotNull(serviceDef);
+ List<Interceptor> immutableInterceptors = ImmutableList.copyOf(interceptors);
+ if (immutableInterceptors.isEmpty()) {
+ return serviceDef;
+ }
+ ServiceDefinition.Builder serviceDefBuilder = ServiceDefinition.builder(serviceDef.getName());
+ for (MethodDefinition<?, ?> method : serviceDef.getMethods()) {
+ wrapAndAddMethod(serviceDefBuilder, method, immutableInterceptors);
+ }
+ return serviceDefBuilder.build();
+ }
+
+ private static <ReqT, RespT> void wrapAndAddMethod(ServiceDefinition.Builder serviceDefBuilder,
+ MethodDefinition<ReqT, RespT> method, List<Interceptor> interceptors) {
+ CallHandler<ReqT, RespT> callHandler
+ = InterceptCallHandler.create(interceptors, method.getCallHandler());
+ serviceDefBuilder.addMethod(method.getName(), method.getRequestMarshaller(),
+ method.getResponseMarshaller(), callHandler);
+ }
+
+ private static class InterceptCallHandler<ReqT, RespT> implements CallHandler<ReqT, RespT> {
+ public static <ReqT, RespT> InterceptCallHandler<ReqT, RespT> create(
+ List<Interceptor> interceptors, CallHandler<ReqT, RespT> callHandler) {
+ return new InterceptCallHandler<ReqT, RespT>(interceptors, callHandler);
+ }
+
+ private final List<Interceptor> interceptors;
+ private final CallHandler<ReqT, RespT> callHandler;
+
+ private InterceptCallHandler(List<Interceptor> interceptors,
+ CallHandler<ReqT, RespT> callHandler) {
+ this.interceptors = interceptors;
+ this.callHandler = callHandler;
+ }
+
+ @Override
+ public Server.Call.Listener<ReqT> startCall(MethodDescriptor<ReqT, RespT> method,
+ Server.Call<ReqT, RespT> call) {
+ return ProcessInterceptorsCallHandler.create(interceptors.iterator(), callHandler)
+ .startCall(method, call);
+ }
+ }
+
+ private static class ProcessInterceptorsCallHandler<ReqT, RespT>
+ implements CallHandler<ReqT, RespT> {
+ public static <ReqT, RespT> ProcessInterceptorsCallHandler<ReqT, RespT> create(
+ Iterator<Interceptor> interceptors, CallHandler<ReqT, RespT> callHandler) {
+ return new ProcessInterceptorsCallHandler<ReqT, RespT>(interceptors, callHandler);
+ }
+
+ private Iterator<Interceptor> interceptors;
+ private final CallHandler<ReqT, RespT> callHandler;
+
+ private ProcessInterceptorsCallHandler(Iterator<Interceptor> interceptors,
+ CallHandler<ReqT, RespT> callHandler) {
+ this.interceptors = interceptors;
+ this.callHandler = callHandler;
+ }
+
+ @Override
+ public Server.Call.Listener<ReqT> startCall(MethodDescriptor<ReqT, RespT> method,
+ Server.Call<ReqT, RespT> call) {
+ if (interceptors != null && interceptors.hasNext()) {
+ return interceptors.next().interceptCall(method, call, this);
+ } else {
+ interceptors = null;
+ return callHandler.startCall(method, call);
+ }
+ }
+ }
+}
diff --git a/core/src/test/java/com/google/net/stubby/MutableHandlerRegistryImplTest.java b/core/src/test/java/com/google/net/stubby/MutableHandlerRegistryImplTest.java
new file mode 100644
index 0000000..ff42f7c
--- /dev/null
+++ b/core/src/test/java/com/google/net/stubby/MutableHandlerRegistryImplTest.java
@@ -0,0 +1,147 @@
+package com.google.net.stubby;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import com.google.net.stubby.Server.CallHandler;
+import com.google.net.stubby.Server.MethodDefinition;
+import com.google.net.stubby.Server.ServiceDefinition;
+import com.google.net.stubby.HandlerRegistry.Method;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+/** Unit tests for {@link MutableHandlerRegistryImpl}. */
+@RunWith(JUnit4.class)
+public class MutableHandlerRegistryImplTest {
+ private MutableHandlerRegistry registry = new MutableHandlerRegistryImpl();
+ private Marshaller<String> requestMarshaller = mock(Marshaller.class);
+ private Marshaller<Integer> responseMarshaller = mock(Marshaller.class);
+ private CallHandler<String, Integer> handler = mock(CallHandler.class);
+ private ServiceDefinition basicServiceDefinition = ServiceDefinition.builder("basic")
+ .addMethod("flow", requestMarshaller, responseMarshaller, handler).build();
+ private MethodDefinition flowMethodDefinition = basicServiceDefinition.getMethods().get(0);
+ private ServiceDefinition multiServiceDefinition = ServiceDefinition.builder("multi")
+ .addMethod("couple", requestMarshaller, responseMarshaller, handler)
+ .addMethod("few", requestMarshaller, responseMarshaller, handler).build();
+ private MethodDefinition coupleMethodDefinition = multiServiceDefinition.getMethod("couple");
+ private MethodDefinition fewMethodDefinition = multiServiceDefinition.getMethod("few");
+
+ @After
+ public void makeSureMocksUnused() {
+ Mockito.verifyZeroInteractions(requestMarshaller);
+ Mockito.verifyZeroInteractions(responseMarshaller);
+ Mockito.verifyZeroInteractions(handler);
+ }
+
+ @Test
+ public void simpleLookup() {
+ assertNull(registry.addService(basicServiceDefinition));
+ Method method = registry.lookupMethod("/basic.flow");
+ assertSame(flowMethodDefinition, method.getMethodDefinition());
+ assertSame(basicServiceDefinition, method.getServiceDefinition());
+ method = registry.lookupMethod("/basic.flow");
+ assertSame(flowMethodDefinition, method.getMethodDefinition());
+ assertSame(basicServiceDefinition, method.getServiceDefinition());
+ method = registry.lookupMethod("/basic/flow");
+ assertSame(flowMethodDefinition, method.getMethodDefinition());
+ assertSame(basicServiceDefinition, method.getServiceDefinition());
+
+ assertNull(registry.lookupMethod("basic.flow"));
+ assertNull(registry.lookupMethod("/basic.basic"));
+ assertNull(registry.lookupMethod("/flow.flow"));
+ assertNull(registry.lookupMethod("/completely.random"));
+ }
+
+ @Test
+ public void multiServiceLookup() {
+ assertNull(registry.addService(basicServiceDefinition));
+ assertNull(registry.addService(multiServiceDefinition));
+
+ Method method = registry.lookupMethod("/basic.flow");
+ assertSame(flowMethodDefinition, method.getMethodDefinition());
+ assertSame(basicServiceDefinition, method.getServiceDefinition());
+ method = registry.lookupMethod("/multi.couple");
+ assertSame(coupleMethodDefinition, method.getMethodDefinition());
+ assertSame(multiServiceDefinition, method.getServiceDefinition());
+ method = registry.lookupMethod("/multi.few");
+ assertSame(fewMethodDefinition, method.getMethodDefinition());
+ assertSame(multiServiceDefinition, method.getServiceDefinition());
+ }
+
+ @Test
+ public void removeAndLookup() {
+ assertNull(registry.addService(multiServiceDefinition));
+ assertNotNull(registry.lookupMethod("/multi.couple"));
+ assertNotNull(registry.lookupMethod("/multi.few"));
+ assertTrue(registry.removeService(multiServiceDefinition));
+ assertNull(registry.lookupMethod("/multi.couple"));
+ assertNull(registry.lookupMethod("/multi.few"));
+ }
+
+ @Test
+ public void replaceAndLookup() {
+ assertNull(registry.addService(basicServiceDefinition));
+ assertNotNull(registry.lookupMethod("/basic.flow"));
+ ServiceDefinition replaceServiceDefinition = ServiceDefinition.builder("basic")
+ .addMethod("another", requestMarshaller, responseMarshaller, handler).build();
+ MethodDefinition anotherMethodDefinition = replaceServiceDefinition.getMethods().get(0);
+ assertSame(basicServiceDefinition, registry.addService(replaceServiceDefinition));
+
+ assertNull(registry.lookupMethod("/basic.flow"));
+ Method method = registry.lookupMethod("/basic.another");
+ assertSame(anotherMethodDefinition, method.getMethodDefinition());
+ assertSame(replaceServiceDefinition, method.getServiceDefinition());
+ }
+
+ @Test
+ public void removeSameSucceeds() {
+ assertNull(registry.addService(basicServiceDefinition));
+ assertTrue(registry.removeService(basicServiceDefinition));
+ }
+
+ @Test
+ public void doubleRemoveFails() {
+ assertNull(registry.addService(basicServiceDefinition));
+ assertTrue(registry.removeService(basicServiceDefinition));
+ assertFalse(registry.removeService(basicServiceDefinition));
+ }
+
+ @Test
+ public void removeMissingFails() {
+ assertFalse(registry.removeService(basicServiceDefinition));
+ }
+
+ @Test
+ public void removeMissingNameConflictFails() {
+ assertNull(registry.addService(basicServiceDefinition));
+ assertFalse(registry.removeService(ServiceDefinition.builder("basic").build()));
+ }
+
+ @Test
+ public void initialAddReturnsNull() {
+ assertNull(registry.addService(basicServiceDefinition));
+ assertNull(registry.addService(multiServiceDefinition));
+ }
+
+ @Test
+ public void addAfterRemoveReturnsNull() {
+ assertNull(registry.addService(basicServiceDefinition));
+ assertTrue(registry.removeService(basicServiceDefinition));
+ assertNull(registry.addService(basicServiceDefinition));
+ }
+
+ @Test
+ public void addReturnsPrevious() {
+ assertNull(registry.addService(basicServiceDefinition));
+ assertSame(basicServiceDefinition,
+ registry.addService(ServiceDefinition.builder("basic").build()));
+ }
+}
diff --git a/core/src/test/java/com/google/net/stubby/ServerInterceptorsTest.java b/core/src/test/java/com/google/net/stubby/ServerInterceptorsTest.java
new file mode 100644
index 0000000..bc356d3
--- /dev/null
+++ b/core/src/test/java/com/google/net/stubby/ServerInterceptorsTest.java
@@ -0,0 +1,187 @@
+package com.google.net.stubby;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import com.google.net.stubby.Server.Interceptor;
+import com.google.net.stubby.Server.Call;
+import com.google.net.stubby.Server.CallHandler;
+import com.google.net.stubby.Server.MethodDefinition;
+import com.google.net.stubby.Server.ServiceDefinition;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Unit tests for {@link ServerInterceptors}. */
+@RunWith(JUnit4.class)
+public class ServerInterceptorsTest {
+ private Marshaller<String> requestMarshaller = mock(Marshaller.class);
+ private Marshaller<Integer> responseMarshaller = mock(Marshaller.class);
+ private CallHandler<String, Integer> handler = mock(CallHandler.class);
+ private Call.Listener<String> listener = mock(Call.Listener.class);
+ private MethodDescriptor<String, Integer> methodDescriptor = mock(MethodDescriptor.class);
+ private Call<String, Integer> call = mock(Call.class);
+ private ServiceDefinition serviceDefinition = ServiceDefinition.builder("basic")
+ .addMethod("flow", requestMarshaller, responseMarshaller, handler).build();
+
+ @Before
+ public void setUp() {
+ Mockito.when(handler.startCall(
+ Mockito.<MethodDescriptor<String, Integer>>any(), Mockito.<Call<String, Integer>>any()))
+ .thenReturn(listener);
+ }
+
+ @After
+ public void makeSureExpectedMocksUnused() {
+ verifyZeroInteractions(requestMarshaller);
+ verifyZeroInteractions(responseMarshaller);
+ verifyZeroInteractions(listener);
+ verifyZeroInteractions(methodDescriptor);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void npeForNullServiceDefinition() {
+ ServerInterceptors.intercept(null, Arrays.<Interceptor>asList());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void npeForNullInterceptorList() {
+ ServerInterceptors.intercept(serviceDefinition, null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void npeForNullInterceptor() {
+ ServerInterceptors.intercept(serviceDefinition, Arrays.asList((Interceptor) null));
+ }
+
+ @Test
+ public void noop() {
+ assertSame(serviceDefinition,
+ ServerInterceptors.intercept(serviceDefinition, Arrays.<Interceptor>asList()));
+ }
+
+ @Test
+ public void multipleInvocationsOfHandler() {
+ Interceptor interceptor = Mockito.spy(new NoopInterceptor());
+ ServiceDefinition intercepted
+ = ServerInterceptors.intercept(serviceDefinition, Arrays.asList(interceptor));
+ assertSame(listener,
+ intercepted.getMethods().get(0).getCallHandler().startCall(methodDescriptor, call));
+ verify(interceptor).interceptCall(same(methodDescriptor), same(call), anyCallHandler());
+ verify(handler).startCall(methodDescriptor, call);
+ verifyNoMoreInteractions(interceptor, handler);
+
+ assertSame(listener,
+ intercepted.getMethods().get(0).getCallHandler().startCall(methodDescriptor, call));
+ verify(interceptor, times(2))
+ .interceptCall(same(methodDescriptor), same(call), anyCallHandler());
+ verify(handler, times(2)).startCall(methodDescriptor, call);
+ verifyNoMoreInteractions(interceptor, handler);
+ }
+
+ @Test
+ public void correctHandlerCalled() {
+ CallHandler<String, Integer> handler2 = Mockito.mock(CallHandler.class);
+ serviceDefinition = ServiceDefinition.builder("basic")
+ .addMethod("flow", requestMarshaller, responseMarshaller, handler)
+ .addMethod("flow2", requestMarshaller, responseMarshaller, handler2).build();
+ ServiceDefinition intercepted = ServerInterceptors.intercept(
+ serviceDefinition, Arrays.<Interceptor>asList(new NoopInterceptor()));
+ intercepted.getMethod("flow").getCallHandler().startCall(methodDescriptor, call);
+ verify(handler).startCall(methodDescriptor, call);
+ verifyNoMoreInteractions(handler);
+ verifyZeroInteractions(handler2);
+
+ intercepted.getMethod("flow2").getCallHandler().startCall(methodDescriptor, call);
+ verify(handler2).startCall(methodDescriptor, call);
+ verifyNoMoreInteractions(handler);
+ verifyNoMoreInteractions(handler2);
+ }
+
+ @Test
+ public void ordered() {
+ final List<String> order = new ArrayList<String>();
+ handler = new CallHandler<String, Integer>() {
+ @Override
+ public Call.Listener<String> startCall(MethodDescriptor<String, Integer> method,
+ Call<String, Integer> call) {
+ order.add("handler");
+ return listener;
+ }
+ };
+ Interceptor interceptor1 = new Interceptor() {
+ @Override
+ public <ReqT, RespT> Call.Listener<ReqT> interceptCall(
+ MethodDescriptor<ReqT, RespT> method, Call<ReqT, RespT> call,
+ CallHandler<ReqT, RespT> next) {
+ order.add("i1");
+ return next.startCall(method, call);
+ }
+ };
+ Interceptor interceptor2 = new Interceptor() {
+ @Override
+ public <ReqT, RespT> Call.Listener<ReqT> interceptCall(
+ MethodDescriptor<ReqT, RespT> method, Call<ReqT, RespT> call,
+ CallHandler<ReqT, RespT> next) {
+ order.add("i2");
+ return next.startCall(method, call);
+ }
+ };
+ ServiceDefinition serviceDefinition = ServiceDefinition.builder("basic")
+ .addMethod("flow", requestMarshaller, responseMarshaller, handler).build();
+ ServiceDefinition intercepted = ServerInterceptors.intercept(
+ serviceDefinition, Arrays.asList(interceptor1, interceptor2));
+ assertSame(listener,
+ intercepted.getMethods().get(0).getCallHandler().startCall(methodDescriptor, call));
+ assertEquals(Arrays.asList("i1", "i2", "handler"), order);
+ }
+
+ @Test
+ public void argumentsPassed() {
+ final MethodDescriptor<String, Integer> method2 = mock(MethodDescriptor.class);
+ final Call<String, Integer> call2 = mock(Call.class);
+ final Call.Listener<String> listener2 = mock(Call.Listener.class);
+ Interceptor interceptor = new Interceptor() {
+ @Override
+ public <ReqT, RespT> Call.Listener<ReqT> interceptCall(
+ MethodDescriptor<ReqT, RespT> method, Call<ReqT, RespT> call,
+ CallHandler<ReqT, RespT> next) {
+ assertSame(method, methodDescriptor);
+ assertSame(call, ServerInterceptorsTest.this.call);
+ assertSame(listener, next.startCall((MethodDescriptor) method2, (Call) call2));
+ return (Call.Listener) listener2;
+ }
+ };
+ ServiceDefinition intercepted = ServerInterceptors.intercept(
+ serviceDefinition, Arrays.asList(interceptor));
+ assertSame(listener2,
+ intercepted.getMethods().get(0).getCallHandler().startCall(methodDescriptor, call));
+ verify(handler).startCall(method2, call2);
+ }
+
+ private CallHandler<String, Integer> anyCallHandler() {
+ return Mockito.<CallHandler<String, Integer>>any();
+ }
+
+ private static class NoopInterceptor implements Interceptor {
+ @Override
+ public <ReqT, RespT> Call.Listener<ReqT> interceptCall(MethodDescriptor<ReqT, RespT> method,
+ Call<ReqT, RespT> call, CallHandler<ReqT, RespT> next) {
+ return next.startCall(method, call);
+ }
+ }
+}