/* * Copyright 2014 Avanza Bank AB * * 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 com.avanza.astrix.remoting.server; import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.junit.Test; import org.mockito.Mockito; import com.avanza.astrix.core.AstrixBroadcast; import com.avanza.astrix.core.AstrixPartitionedRouting; import com.avanza.astrix.core.AstrixRemoteResult; import com.avanza.astrix.core.AstrixRoutingStrategy; import com.avanza.astrix.core.RemoteResultReducer; import com.avanza.astrix.core.RemoteServiceInvocationException; import com.avanza.astrix.core.ServiceInvocationException; import com.avanza.astrix.core.ServiceUnavailableException; import com.avanza.astrix.core.remoting.Router; import com.avanza.astrix.core.remoting.RoutingKey; import com.avanza.astrix.core.remoting.RoutingStrategy; import com.avanza.astrix.remoting.client.IncompatibleRemoteResultReducerException; import com.avanza.astrix.versioning.core.AstrixObjectSerializer; import rx.Observable; /** * * @author Elias Lindholm (elilin) * */ public class AstrixRemotingTest { @Test public void supportsServiceMethodsWithMultipleArguments() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); TwoArgumentTest impl = new TwoArgumentTest() { @Override public String hello(HelloRequest message, String greeting) { return greeting + message.getMesssage(); } }; remotingDriver.registerServer(TwoArgumentTest.class, impl); TwoArgumentTest testService = remotingDriver.createRemotingProxy(TwoArgumentTest.class); HelloRequest request = new HelloRequest("kalle"); String reply = testService.hello(request, "replyTo-"); assertEquals("replyTo-kalle", reply); } @Test public void routedRequest_throwsExceptionOfNonServiceInvocationExceptionType() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); try { TestService impl = new TestService() { @Override public HelloResponse hello(HelloRequest message) { throw new IllegalArgumentException("Remote service error message"); } }; remotingDriver.registerServer(TestService.class, impl); TestService testService = remotingDriver.createRemotingProxy(TestService.class); testService.hello(new HelloRequest("foo")); fail("Expected remote service exception to be thrown"); } catch (RemoteServiceInvocationException e) { assertEquals(IllegalArgumentException.class.getName(), e.getExceptionType()); assertThat(e.getMessage(), startsWith("Remote service threw exception, see server log for details. [java.lang.IllegalArgumentException: Remote service error message]")); } } @Test public void routedRequest_throwsExceptionOfServiceInvocationType() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); try { TestService impl = new TestService() { @Override public HelloResponse hello(HelloRequest message) { throw new MyCustomServiceException(); } }; remotingDriver.registerServer(TestService.class, impl); TestService testService = remotingDriver.createRemotingProxy(TestService.class); testService.hello(new HelloRequest("foo")); fail("Expected remote service exception to be thrown"); } catch (MyCustomServiceException e) { // Expected } catch (Exception e) { e.printStackTrace(); fail("Excpected exception of type MyCustomServiceException, but was: " + e); } } @Test public void broadcastRequest() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(2); PingService impl = new PingService() { @Override public List<String> ping(String msg) { return Arrays.asList(msg); } }; remotingDriver.registerServerPartition(0, PingService.class, impl); remotingDriver.registerServerPartition(1, PingService.class, impl); PingService broadcastService = remotingDriver.createRemotingProxy(PingService.class); List<String> replys = broadcastService.ping("foo"); assertEquals(2, replys.size()); assertEquals("foo", replys.get(0)); assertEquals("foo", replys.get(1)); } @Test public void partitionedRequest() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(2); CalculatorListService eventPartitionCalculator = new CalculatorListService() { @Override public Integer squareSum(Collection<Integer> nums) { int squareSum = 0; for (int num : nums) { if (num % 2 != 0) { throw new AssertionError("Even Partition should only receive even numbers, but received: " + num); } squareSum += num * num; } return squareSum; } }; CalculatorListService oddPartitionCalculator = new CalculatorListService() { @Override public Integer squareSum(Collection<Integer> nums) { int squareSum = 0; for (int num : nums) { if (num % 2 != 1) { throw new AssertionError("Odd Partition hould only receive odd numbers, but received: " + num); } squareSum += num * num; } return squareSum; } }; remotingDriver.registerServerPartition(0, CalculatorListService.class, eventPartitionCalculator); remotingDriver.registerServerPartition(1, CalculatorListService.class, oddPartitionCalculator); CalculatorListService calculatorService = remotingDriver.createRemotingProxy(CalculatorListService.class); int squareSum = calculatorService.squareSum(Arrays.asList(1, 2, 3, 4, 5)); assertEquals(1 + 4 + 9 + 16 + 25, squareSum); } @Test public void customRoutingRequest() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(2); CustomRoutedCalc evenPartitionCalculator = new CustomRoutedCalc() { @Override public int squareSum(Collection<Integer> nums) { int squareSum = 0; for (int num : nums) { squareSum += num * num; } return squareSum; } }; CustomRoutedCalc oddPartitionCalculator = new CustomRoutedCalc() { @Override public int squareSum(Collection<Integer> nums) { throw new AssertionError("All request should be statically routed to even partition using custom router"); } }; remotingDriver.registerServerPartition(0, CustomRoutedCalc.class, evenPartitionCalculator); remotingDriver.registerServerPartition(1, CustomRoutedCalc.class, oddPartitionCalculator); CustomRoutedCalc calculatorService = remotingDriver.createRemotingProxy(CustomRoutedCalc.class); int squareSum = calculatorService.squareSum(Arrays.asList(1, 2, 3, 4, 5)); assertEquals(1 + 4 + 9 + 16 + 25, squareSum); } @Test public void partitionedRequest_GenericArrayArgument() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(2); PartitionedPingService eventPartitionPing = new PartitionedPingServiceImpl(); PartitionedPingService oddPartitionPing = new PartitionedPingServiceImpl(); remotingDriver.registerServerPartition(0, PartitionedPingService.class, eventPartitionPing); remotingDriver.registerServerPartition(1, PartitionedPingService.class, oddPartitionPing); PartitionedPingService partitionedPing = remotingDriver.createRemotingProxy(PartitionedPingService.class); assertThat(partitionedPing.ping(new double[]{1d, 2d, 3d}), containsInAnyOrder(1d, 2d, 3d)); assertThat(partitionedPing.ping(new int[]{1, 2, 3}), containsInAnyOrder(1, 2, 3)); assertThat(partitionedPing.ping(new float[]{1f, 2f, 3f}), containsInAnyOrder(1f, 2f, 3f)); assertThat(partitionedPing.ping(new short[]{1, 2, 3}), containsInAnyOrder(new Short[]{1, 2, 3})); assertThat(partitionedPing.ping(new long[]{1L, 2L, 3L}), containsInAnyOrder(1L, 2L, 3L)); assertThat(partitionedPing.ping(new String[]{"1", "2", "3"}), containsInAnyOrder("1", "2", "3")); } @Test public void partitionedRequest_voidReturnType() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(2); PartitionedPingServiceImpl evenPartitionPing = new PartitionedPingServiceImpl(); PartitionedPingServiceImpl oddPartitionPing = new PartitionedPingServiceImpl(); remotingDriver.registerServerPartition(0, PartitionedPingService.class, evenPartitionPing); remotingDriver.registerServerPartition(1, PartitionedPingService.class, oddPartitionPing); PartitionedPingService partitionedPing = remotingDriver.createRemotingProxy(PartitionedPingService.class); partitionedPing.pingVoid(new Integer[]{1, 2, 3}); assertArrayEquals(new Integer[]{1, 3}, oddPartitionPing.lastAsyncPingRequest); assertArrayEquals(new Integer[]{2}, evenPartitionPing.lastAsyncPingRequest); } @Test public void asyncPartitionedRequest_voidReturnType() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(2); PartitionedPingServiceImpl evenPartitionPing = new PartitionedPingServiceImpl(); PartitionedPingServiceImpl oddPartitionPing = new PartitionedPingServiceImpl(); remotingDriver.registerServerPartition(0, PartitionedPingService.class, evenPartitionPing); remotingDriver.registerServerPartition(1, PartitionedPingService.class, oddPartitionPing); PartitionedPingServiceAsync partitionedPing = remotingDriver.createRemotingProxy(PartitionedPingServiceAsync.class, PartitionedPingService.class); partitionedPing.pingVoid(new Integer[]{1, 2, 3}).subscribe(); assertArrayEquals(new Integer[]{1, 3}, oddPartitionPing.lastAsyncPingRequest); assertArrayEquals(new Integer[]{2}, evenPartitionPing.lastAsyncPingRequest); } @Test public void partitionedRequest_emptyArgument() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(2); PartitionedPingService evenPartitionPing = Mockito.mock(PartitionedPingService.class); PartitionedPingService oddPartitionPing = Mockito.mock(PartitionedPingService.class); remotingDriver.registerServerPartition(0, PartitionedPingService.class, evenPartitionPing); remotingDriver.registerServerPartition(1, PartitionedPingService.class, oddPartitionPing); PartitionedPingService partitionedPing = remotingDriver.createRemotingProxy(PartitionedPingService.class, PartitionedPingService.class); partitionedPing.pingVoid(new Integer[]{}); Mockito.verifyZeroInteractions(evenPartitionPing, oddPartitionPing); } @Test(expected = RemoteServiceInvocationException.class) public void partitionedRoutingRequest_NonServiceInovcationExcpetion_WrappedInRemoteServiceInvocation() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(2); PartitionedPingService evenPartitionPing = new PartitionedPingServiceImpl() { @Override public List<String> ping(String... nums) { throw new NullPointerException(); }; }; PartitionedPingService oddPartitionPing = new PartitionedPingServiceImpl(); remotingDriver.registerServerPartition(0, PartitionedPingService.class, evenPartitionPing); remotingDriver.registerServerPartition(1, PartitionedPingService.class, oddPartitionPing); PartitionedPingService partitionedPing = remotingDriver.createRemotingProxy(PartitionedPingService.class, PartitionedPingService.class); partitionedPing.ping(new String[]{"1", "2", "3", "4", "5"}); } @Test(expected = MyCustomServiceException.class) public void partitionedRoutingRequest_voidReturnType_throwsException() throws Exception{ AstrixRemotingDriver astrixRemotingDriver = new AstrixRemotingDriver(2); PartitionedPingServiceImpl evenPartition = new PartitionedPingServiceImpl() { @Override public void pingVoid(Integer... nums) { throw new MyCustomServiceException(); } }; PartitionedPingServiceImpl oddPartition = new PartitionedPingServiceImpl(); astrixRemotingDriver.registerServerPartition(0, PartitionedPingService.class, evenPartition); astrixRemotingDriver.registerServerPartition(1,PartitionedPingService.class, oddPartition); PartitionedPingService partitionedService = astrixRemotingDriver.createRemotingProxy(PartitionedPingService.class); partitionedService.pingVoid(1,2,3,4); } @Test public void partitionedRequest_routingOnPropertyOnTargetObject() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(2); CalculatorArrayPojoService evenPartitionCalculator = new CalculatorArrayPojoService() { @Override public Integer squareSum(NumPojo... nums) { int squareSum = 0; for (NumPojo numPojo : nums) { int num = numPojo.getNum(); if (num % 2 != 0) { throw new AssertionError("Even Partition should only receive even numbers, but received: " + num); } squareSum += num * num; } return squareSum; } }; CalculatorArrayPojoService oddPartitionCalculator = new CalculatorArrayPojoService() { @Override public Integer squareSum(NumPojo... nums) { int squareSum = 0; for (NumPojo numPojo : nums) { int num = numPojo.getNum(); if (num % 2 != 1) { throw new AssertionError("Odd Partition hould only receive odd numbers, but received: " + num); } squareSum += num * num; } return squareSum; } }; remotingDriver.registerServerPartition(0, CalculatorArrayPojoService.class, evenPartitionCalculator); remotingDriver.registerServerPartition(1, CalculatorArrayPojoService.class, oddPartitionCalculator); CalculatorArrayPojoService calculatorService = remotingDriver.createRemotingProxy(CalculatorArrayPojoService.class, CalculatorArrayPojoService.class); int squareSum = calculatorService.squareSum(new NumPojo(1), new NumPojo(2), new NumPojo(3), new NumPojo(4), new NumPojo(5)); assertEquals(1 + 4 + 9 + 16 + 25, squareSum); } @Test public void partitionedRequest_routingOnPropertyOnTargetObject_CollectionArgument() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(2); CalculatorListPojoService eventPartitionCalculator = new CalculatorListPojoServiceImpl(); CalculatorListPojoService oddPartitionCalculator = new CalculatorListPojoServiceImpl(); remotingDriver.registerServerPartition(0, CalculatorListPojoService.class, eventPartitionCalculator); remotingDriver.registerServerPartition(1, CalculatorListPojoService.class, oddPartitionCalculator); CalculatorListPojoService calculatorService = remotingDriver.createRemotingProxy(CalculatorListPojoService.class); int squareSum = calculatorService.squareSum(Arrays.asList(new NumPojo(1), new NumPojo(2), new NumPojo(3), new NumPojo(4), new NumPojo(5))); assertEquals(1 + 4 + 9 + 16 + 25, squareSum); } @Test(expected = IllegalArgumentException.class) public void partitionedRequest_routingOnProperty_throwsExceptionForRawTypes() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); remotingDriver.createRemotingProxy(ServiceWithRawListRoutingArgument.class); } @Test(expected = IllegalArgumentException.class) public void partitionedRequest_routingOnProperty_throwsExceptionForMissingMethods() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); remotingDriver.createRemotingProxy(ServiceWithListMissingRoutingPropertyMethod.class); } @Test(expected = IllegalArgumentException.class) public void partitionedService_IncompatibleCollectionType_throwsException() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); remotingDriver.createRemotingProxy(InvalidCollectionTypePartitionedService.class); } @Test(expected = IncompatibleRemoteResultReducerException.class) public void partitionedService_IncompatibleReducer_throwsException() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); remotingDriver.createRemotingProxy(InvalidReducerPartitionedService.class); } @Test public void partitionedService_NonListCollection() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); PartitionedServiceUsingSet sevenPartitionService = new PartitionedServiceUsingSet() { @Override public Set<Integer> ping(Set<Integer> nums) { return nums; } }; remotingDriver.registerServer(PartitionedServiceUsingSet.class, sevenPartitionService); PartitionedServiceUsingSet calculatorService = remotingDriver.createRemotingProxy(PartitionedServiceUsingSet.class); assertEquals(setOf(1,2,3), calculatorService.ping(setOf(1,2,3))); } private static <T> Set<T> setOf(@SuppressWarnings("unchecked") T... values) { return new HashSet<T>(Arrays.asList(values)); } @Test public void broadcastRequest_throwsException() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); try { BroadcastService impl = new BroadcastService() { @Override public String broadcast(BroadcastRequest request) { throw new IllegalArgumentException("Broadcast error message"); } }; remotingDriver.registerServer(BroadcastService.class, impl); BroadcastService broadcastService = remotingDriver.createRemotingProxy(BroadcastService.class); broadcastService.broadcast(new BroadcastRequest("foo")); fail("Expected remote service exception to be thrown"); } catch (RemoteServiceInvocationException e) { assertEquals(IllegalArgumentException.class.getName(), e.getExceptionType()); assertThat(e.getMessage(), startsWith("Remote service threw exception, see server log for details. [java.lang.IllegalArgumentException: Broadcast error message]")); } } @Test public void publishMultipleApis() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); class MultiProvider implements BroadcastService, TestService { @Override public HelloResponse hello(HelloRequest message) { return new HelloResponse("hello " + message.getMesssage()); } @Override @AstrixBroadcast(reducer = BroadcastReducer.class) public String broadcast(BroadcastRequest request) { return "broadcast-" + request.getMesssage(); } } MultiProvider provider = new MultiProvider(); remotingDriver.registerServer(BroadcastService.class, provider); remotingDriver.registerServer(TestService.class, provider); BroadcastService broadcastService = remotingDriver.createRemotingProxy(BroadcastService.class); TestService testService = remotingDriver.createRemotingProxy(TestService.class); assertEquals(provider.hello(new HelloRequest("kalle")), testService.hello(new HelloRequest("kalle"))); assertEquals(provider.broadcast(new BroadcastRequest("kalle")), broadcastService.broadcast(new BroadcastRequest("kalle"))); } @Test(expected = ServiceUnavailableException.class) public void request_NoCorrespondingServiceRegisteredInServiceActivator_throwsServiceUnavailableException() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); TestService missingRemoteService = remotingDriver.createRemotingProxy(TestService.class); missingRemoteService.hello(new HelloRequest("foo")); } @Test public void useObservableVersionOfAService() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); TestService impl = new TestService() { @Override public HelloResponse hello(HelloRequest message) { return new HelloResponse("reply-" + message.getMesssage()); } }; remotingDriver.registerServer(TestService.class, impl); ObservableTestService service = remotingDriver.createRemotingProxy(ObservableTestService.class, TestService.class); Observable<HelloResponse> message = service.hello(new HelloRequest("kalle")); assertEquals("reply-kalle", message.toBlocking().first().getGreeting()); } @Test public void useAsyncVersionOfAService() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); TestService impl = new TestService() { @Override public HelloResponse hello(HelloRequest message) { return new HelloResponse("reply-" + message.getMesssage()); } }; remotingDriver.registerServer(TestService.class, impl); TestServiceAsync service = remotingDriver.createRemotingProxy(TestServiceAsync.class, TestService.class); Future<HelloResponse> response = service.hello(new HelloRequest("kalle")); assertEquals("reply-kalle", response.get().getGreeting()); } @Test(expected = RuntimeException.class) public void ioExceptionThrownDuringDeserializationAreProppagatedAsRuntimeExceptions() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); AstrixObjectSerializer corruptDeserializer = new AstrixObjectSerializer.NoVersioningSupport() { @Override public <T> T deserialize(Object element, Type target, int version) { if (target.equals(HelloResponse.class)) { // simulate failure in deserializing service invocation response throw new IllegalArgumentException("phew.. I/O, huh?"); } return super.deserialize(element, target, version); } }; TestService impl = new TestService() { @Override public HelloResponse hello(HelloRequest message) { return new HelloResponse("reply-" + message.getMesssage()); } }; remotingDriver.registerServer(TestService.class, impl); ObservableTestService service = remotingDriver.createRemotingProxy(ObservableTestService.class, TestService.class, corruptDeserializer); Observable<HelloResponse> message = service.hello(new HelloRequest("kalle")); message.toBlocking().first(); } @Test public void supportServicesThatAcceptAndReturnGenericTypes() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); GenericReturnTypeService impl = new GenericReturnTypeService() { @Override public List<HelloResponse> hello(String routing, List<HelloRequest> greetings) { List<HelloResponse> responses = new ArrayList<>(); for (HelloRequest request : greetings) { responses.add(new HelloResponse("reply-" + request.getMesssage())); } return responses; } }; remotingDriver.registerServer(GenericReturnTypeService.class, impl); GenericReturnTypeService testService = remotingDriver.createRemotingProxy(GenericReturnTypeService.class, GenericReturnTypeService.class); HelloRequest request = new HelloRequest("kalle"); List<HelloResponse> reply = testService.hello("foo-routing", Arrays.<HelloRequest>asList(request)); assertEquals(1, reply.size()); assertEquals("reply-kalle", reply.get(0).getGreeting()); } @Test public void supportServicesThatAcceptAndReturnGenericOnBroadcast() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); BroadcastingGenericReturnTypeService impl = new BroadcastingGenericReturnTypeService() { @Override public List<HelloResponse> hello(List<HelloRequest> greetings) { List<HelloResponse> responses = new ArrayList<>(); for (HelloRequest request : greetings) { responses.add(new HelloResponse("reply-" + request.getMesssage())); } return responses; } }; remotingDriver.registerServer(BroadcastingGenericReturnTypeService.class, impl); BroadcastingGenericReturnTypeService testService = remotingDriver.createRemotingProxy(BroadcastingGenericReturnTypeService.class); HelloRequest request = new HelloRequest("kalle"); List<HelloResponse> reply = testService.hello(Arrays.<HelloRequest>asList(request)); assertEquals(1, reply.size()); assertEquals("reply-kalle", reply.get(0).getGreeting()); } @Test public void supportServicesWithNoArgument() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); NoArgumentService impl = new NoArgumentService() { @Override public List<String> hello() { return Arrays.asList("response"); } }; remotingDriver.registerServer(NoArgumentService.class, impl); NoArgumentService testService = remotingDriver.createRemotingProxy(NoArgumentService.class); List<String> reply = testService.hello(); assertEquals(1, reply.size()); assertEquals("response", reply.get(0)); } @Test public void supportsServicesThatWithVoidReturnType() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); final BlockingQueue<String> receivedRequest = new LinkedBlockingQueue<>(); VoidService impl = new VoidService() { @Override public void hello(String message) { receivedRequest.add(message); } }; remotingDriver.registerServer(VoidService.class, impl); VoidService testService = remotingDriver.createRemotingProxy(VoidService.class); testService.hello("kalle"); String lastReceivedRequest = receivedRequest.poll(1, TimeUnit.SECONDS); assertEquals("kalle", lastReceivedRequest); } @Test public void supportsBroadcastedServicesWithVoidReturnType() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); final BlockingQueue<String> receivedRequest = new LinkedBlockingQueue<>(); BroadcastVoidService impl = new BroadcastVoidService() { @Override public void hello(String message) { receivedRequest.add(message); } }; remotingDriver.registerServer(BroadcastVoidService.class, impl); BroadcastVoidService testService = remotingDriver.createRemotingProxy(BroadcastVoidService.class); testService.hello("kalle"); String lastReceivedRequest = receivedRequest.poll(0, TimeUnit.SECONDS); assertEquals("kalle", lastReceivedRequest); } @Test public void supportsAsyncBroadcastedServicesWithVoidReturnType() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); final BlockingQueue<String> receivedRequest = new LinkedBlockingQueue<>(); BroadcastVoidService impl = new BroadcastVoidService() { @Override public void hello(String message) { receivedRequest.add(message); } }; remotingDriver.registerServer(BroadcastVoidService.class, impl); BroadcastVoidServiceAsync testService = remotingDriver.createRemotingProxy(BroadcastVoidServiceAsync.class, BroadcastVoidService.class); testService.hello("kalle").subscribe(); String lastReceivedRequest = receivedRequest.poll(0, TimeUnit.SECONDS); assertEquals("kalle", lastReceivedRequest); } @Test(expected = MyCustomServiceException.class) public void supports_BroadcastedServicesWithVoidReturnType_throwingExceptions() throws Exception{ AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); BroadcastVoidService impl = new BroadcastVoidService(){ @Override public void hello(String message) { throw new MyCustomServiceException(); } }; remotingDriver.registerServer(BroadcastVoidService.class, impl); BroadcastVoidService voideService = remotingDriver.createRemotingProxy(BroadcastVoidService.class); voideService.hello("test"); } @Test(expected = IllegalArgumentException.class) public void throwsExceptionWhenRegisteringProviderForNonImplementedInterface() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); remotingDriver.registerServer(TestService.class, new Object()); } @Test public void remotingProxiesDoesNotDelegateMethodCallsForMethodsDefinedIn_java_lang_Object() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); remotingDriver.registerServer(VoidService.class, new VoidService() { @Override public void hello(String message) { } @Override public String toString() { return "RemotingServiceToString"; } }); VoidService testService = remotingDriver.createRemotingProxy(VoidService.class); assertEquals("RemotingProxy[" + VoidService.class.getName() + "]", testService.toString()); } @Test public void asyncBroadcastedService() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); remotingDriver.registerServer(BroadcastingGenericReturnTypeService.class, new BroadcastingGenericReturnTypeService() { @Override public List<HelloResponse> hello(List<HelloRequest> greeting) { return Arrays.asList(new HelloResponse(greeting.get(0).getMesssage())); } }); BroadcastingGenericReturnTypeServiceAsync service = remotingDriver.createRemotingProxy(BroadcastingGenericReturnTypeServiceAsync.class, BroadcastingGenericReturnTypeService.class); Future<List<HelloResponse>> resultFuture = service.hello(Arrays.asList(new HelloRequest("foo"))); List<HelloResponse> result = resultFuture.get(); assertEquals(1, result.size()); } @Test(expected = IncompatibleRemoteResultReducerException.class) public void throwsExceptionOnProxyCreationIfRemoteResultReducerDoesNotHaveAMethodSignatureCompatibleWithServiceMethodSignature() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); remotingDriver.createRemotingProxy(BroadcastServiceWithIllegalReducer.class); } @Test(expected = IllegalStateException.class) public void throwsIllegalStateExceptionIfRoutingStrategyReturnsNull() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); remotingDriver.registerServer(VoidService.class, new VoidService() { @Override public void hello(String message) { } }); VoidService voidService = remotingDriver.createRemotingProxy(VoidService.class, VoidService.class, new RoutingStrategy() { @Override public Router create(Method serviceMethod) { return args -> null; } }); voidService.hello("foo"); } @Test public void supportsOptionalReturnType() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); remotingDriver.registerServer(OptionalPing.class, new OptionalPing() { @Override public Optional<String> ping(String message) { return Optional.ofNullable(message); } }); OptionalPing pingService = remotingDriver.createRemotingProxy(OptionalPing.class); assertEquals("foo", pingService.ping("foo").get()); assertEquals(Optional.empty(), pingService.ping(null)); assertEquals("foo", pingService.broadcastPing("foo").get()); assertEquals(Optional.empty(), pingService.broadcastPing(null)); } @Test public void supportsOptionalWithNullReturnValue() throws Exception { AstrixRemotingDriver remotingDriver = new AstrixRemotingDriver(); remotingDriver.registerServer(OptionalPing.class, new OptionalPing() { @Override public Optional<String> ping(String message) { return null; } }); OptionalPing pingService = remotingDriver.createRemotingProxy(OptionalPing.class); assertEquals(null, pingService.ping("foo")); } @SuppressWarnings("serial") public static class HelloRequest implements Serializable { private String messsage; public HelloRequest(String messsage) { this.messsage = messsage; } public HelloRequest() { } public String getMesssage() { return messsage; } public void setMesssage(String messsage) { this.messsage = messsage; } } @SuppressWarnings("serial") public static class BroadcastRequest implements Serializable { private String messsage; public BroadcastRequest(String messsage) { this.messsage = messsage; } public BroadcastRequest() { } public String getMesssage() { return messsage; } public void setMesssage(String messsage) { this.messsage = messsage; } } @SuppressWarnings("serial") public static class HelloResponse implements Serializable { private String greeting; public HelloResponse() { } public HelloResponse(String greeting) { this.greeting = greeting; } public String getGreeting() { return greeting; } public void setGreeting(String greeting) { this.greeting = greeting; } @Override public int hashCode() { return toString().hashCode(); } @Override public String toString() { return this.greeting; } @Override public boolean equals(Object obj) { if (obj == null) return false; if (getClass() != obj.getClass()) return false; return toString().equals(obj.toString()); } } public interface ServiceWithRawListRoutingArgument { @SuppressWarnings("rawtypes") List<Integer> foo(@AstrixPartitionedRouting(routingMethod = "getRoutingKey") List list); } public interface ServiceWithListMissingRoutingPropertyMethod { List<Integer> foo(@AstrixPartitionedRouting(routingMethod = "aMissingMethod") List<NumPojo> list); } interface NoArgumentService { @AstrixBroadcast List<String> hello(); } interface GenericReturnTypeService { List<HelloResponse> hello(String routingKey, List<HelloRequest> greeting); } interface BroadcastingGenericReturnTypeService { @AstrixBroadcast List<HelloResponse> hello(List<HelloRequest> greeting); } interface PingService { @AstrixBroadcast List<String> ping(String msg); } interface CalculatorListService { Integer squareSum(@AstrixPartitionedRouting(reducer = SummingReducer.class) Collection<Integer> nums); } interface CalculatorArrayPojoService { Integer squareSum(@AstrixPartitionedRouting(routingMethod="getNum", reducer = SummingReducer.class) NumPojo... nums); } interface CalculatorListPojoService { Integer squareSum(@AstrixPartitionedRouting(routingMethod="getNum", reducer = SummingReducer.class) List<NumPojo> nums); } static class CalculatorListPojoServiceImpl implements CalculatorListPojoService { @Override public Integer squareSum(List<NumPojo> nums) { int result = 0; for (NumPojo n : nums) { result += n.getNum() * n.getNum(); } return result; } } interface PartitionedPingService { List<Short> ping(@AstrixPartitionedRouting short... nums); List<Integer> ping(@AstrixPartitionedRouting int... nums); List<Long> ping(@AstrixPartitionedRouting long... nums); List<Double> ping(@AstrixPartitionedRouting double... nums); List<Float> ping(@AstrixPartitionedRouting float... nums); List<Boolean> ping(@AstrixPartitionedRouting boolean... nums); List<String> ping(@AstrixPartitionedRouting String... nums); void pingVoid(@AstrixPartitionedRouting Integer... nums); } interface PartitionedPingServiceAsync { Observable<Void> pingVoid(@AstrixPartitionedRouting Integer... nums); } public static class NumPojo implements Serializable { private static final long serialVersionUID = 1L; private int num; public NumPojo(int num) { this.num = num; } public int getNum() { return num; } @Override public int hashCode() { // Routes all arguments to same partition return 1; } } interface InvalidCollectionTypePartitionedService { Integer squareSum(@AstrixPartitionedRouting(reducer = SummingReducer.class) Set<Integer> nums); } interface InvalidReducerPartitionedService { int squareSum(@AstrixPartitionedRouting(collectionFactory = HashSet.class) Set<Integer> nums); } interface PartitionedServiceUsingSet { Set<Integer> ping(@AstrixPartitionedRouting(reducer = SetReducer.class, collectionFactory = HashSet.class) Set<Integer> nums); } public static class SetReducer<T> implements RemoteResultReducer<Set<T>> { @Override public Set<T> reduce(List<AstrixRemoteResult<Set<T>>> results) { Set<T> result = new HashSet<>(); for (AstrixRemoteResult<Set<T>> remoteResult : results) { result.addAll(remoteResult.getResult()); } return result; } } public static class PartitionedPingServiceImpl implements PartitionedPingService { Integer[] lastAsyncPingRequest; @Override public List<Short> ping(short... nums) { List<Short> result = new ArrayList<>(); for (short s : nums) { result.add(s); } return result; } @Override public List<Integer> ping(int... nums) { List<Integer> result = new ArrayList<>(); for (int s : nums) { result.add(s); } return result; } @Override public List<Long> ping(long... nums) { List<Long> result = new ArrayList<>(); for (long s : nums) { result.add(s); } return result; } @Override public List<Double> ping(double... nums) { List<Double> result = new ArrayList<>(); for (double s : nums) { result.add(s); } return result; } @Override public List<Float> ping(float... nums) { List<Float> result = new ArrayList<>(); for (float s : nums) { result.add(s); } return result; } @Override public List<Boolean> ping(boolean... nums) { List<Boolean> result = new ArrayList<>(); for (boolean s : nums) { result.add(s); } return result; } @Override public List<String> ping(String... nums) { List<String> result = new ArrayList<>(); for (String s : nums) { result.add(s); } return result; } @Override public void pingVoid(Integer... nums) { this.lastAsyncPingRequest = nums; } } public static class SummingReducer implements RemoteResultReducer<Integer> { @Override public Integer reduce(List<AstrixRemoteResult<Integer>> results) { int sum = 0; for (AstrixRemoteResult<Integer> result : results) { sum += result.getResult(); } return sum; } } interface BroadcastingGenericReturnTypeServiceAsync { @AstrixBroadcast Future<List<HelloResponse>> hello(List<HelloRequest> greeting); } interface TestService { HelloResponse hello(HelloRequest message); } interface TwoArgumentTest { String hello(HelloRequest message, String greeting); } public static class StaticRouting implements RoutingStrategy { @Override public Router create(Method serviceMethod) { return new Router() { @Override public RoutingKey getRoutingKey(Object[] args) throws Exception { return RoutingKey.create(0); } }; } } interface CustomRoutedCalc { @AstrixRoutingStrategy(StaticRouting.class) int squareSum(Collection<Integer> nums); } interface VoidService { void hello(String message); } interface OptionalPing { Optional<String> ping(String message); @AstrixBroadcast(reducer = FirstNonEmpty.class) default Optional<String> broadcastPing(String message) { return ping(message); } } public static class FirstNonEmpty implements RemoteResultReducer<Optional<String>> { @Override public Optional<String> reduce(List<AstrixRemoteResult<Optional<String>>> result) { for (AstrixRemoteResult<Optional<String>> r : result) { if (r.getResult().isPresent()) { return r.getResult(); } } return Optional.empty(); } } interface BroadcastVoidService { @AstrixBroadcast void hello(String message); } interface BroadcastVoidServiceAsync { @AstrixBroadcast Observable<Void> hello(String message); } interface ObservableTestService { Observable<HelloResponse> hello(HelloRequest message); } interface TestServiceAsync { Future<HelloResponse> hello(HelloRequest message); } interface BroadcastService { @AstrixBroadcast(reducer = GenericReducer.class) String broadcast(BroadcastRequest request); } interface BroadcastServiceWithIllegalReducer { @AstrixBroadcast(reducer = StringToStringReducer.class) Future<String> broadcast(BroadcastRequest request); } public static class BroadcastReducer implements RemoteResultReducer<String> { @Override public String reduce(List<AstrixRemoteResult<String>> result) { return result.get(0).getResult(); // Only one 'partition' } } public static class GenericReducer<T> implements RemoteResultReducer<T> { @Override public T reduce(List<AstrixRemoteResult<T>> result) { return result.get(0).getResult(); } } public static class StringToStringReducer implements RemoteResultReducer<String> { @Override public String reduce(List<AstrixRemoteResult<String>> result) { return null; // Never invoked, } } public static class DateToDateReducer implements RemoteResultReducer<Date> { @Override public Date reduce(List<AstrixRemoteResult<Date>> result) { return null; // Never invoked, } } public static class MyCustomServiceException extends ServiceInvocationException { private static final long serialVersionUID = 1L; public MyCustomServiceException() { super("my-custom-message"); } public MyCustomServiceException(String msg) { super(msg); } @Override public ServiceInvocationException recreateOnClientSide() { return new MyCustomServiceException(getMessage()); } } }