From 5a20b1da3753e1f621f9551a7a04a341d06f3959 Mon Sep 17 00:00:00 2001 From: segabriel Date: Sun, 21 Jul 2019 17:46:05 +0300 Subject: [PATCH] Added support generic type for request type --- .../io/scalecube/services/ClassUtils.java | 66 ++++++ .../java/io/scalecube/services/Reflect.java | 67 ++---- .../io/scalecube/services/ServiceCall.java | 2 +- .../java/io/scalecube/services/TypeUtils.java | 200 ++++++++++++++++++ .../services/api/ServiceMessage.java | 20 +- .../services/methods/MethodInfo.java | 8 +- .../api/ServiceMessageDataDecoder.java | 3 +- .../api/ServiceMessageByteBufDataDecoder.java | 3 +- .../services/ServiceCallLocalTest.java | 18 ++ .../services/ServiceCallRemoteTest.java | 18 ++ .../io/scalecube/services/TestRequests.java | 7 + .../services/sut/GreetingService.java | 4 + .../services/sut/GreetingServiceImpl.java | 11 + 13 files changed, 367 insertions(+), 60 deletions(-) create mode 100644 services-api/src/main/java/io/scalecube/services/ClassUtils.java create mode 100644 services-api/src/main/java/io/scalecube/services/TypeUtils.java diff --git a/services-api/src/main/java/io/scalecube/services/ClassUtils.java b/services-api/src/main/java/io/scalecube/services/ClassUtils.java new file mode 100644 index 000000000..b970f3618 --- /dev/null +++ b/services-api/src/main/java/io/scalecube/services/ClassUtils.java @@ -0,0 +1,66 @@ +package io.scalecube.services; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Miscellaneous {@code java.lang.Class} utility methods. + * */ +public class ClassUtils { + + private ClassUtils() { + // Do not instantiate + } + + /** + * Map with primitive wrapper type as key and corresponding primitive type as value, for example: + * Integer.class -> int.class. + */ + private static final Map, Class> primitiveWrapperTypeMap = new IdentityHashMap<>(8); + + /** + * Map with primitive type as key and corresponding wrapper type as value, for example: int.class + * -> Integer.class. + */ + private static final Map, Class> primitiveTypeToWrapperMap = new IdentityHashMap<>(8); + + static { + primitiveWrapperTypeMap.put(Boolean.class, boolean.class); + primitiveWrapperTypeMap.put(Byte.class, byte.class); + primitiveWrapperTypeMap.put(Character.class, char.class); + primitiveWrapperTypeMap.put(Double.class, double.class); + primitiveWrapperTypeMap.put(Float.class, float.class); + primitiveWrapperTypeMap.put(Integer.class, int.class); + primitiveWrapperTypeMap.put(Long.class, long.class); + primitiveWrapperTypeMap.put(Short.class, short.class); + + // Map entry iteration is less expensive to initialize than forEach with lambdas + for (Map.Entry, Class> entry : primitiveWrapperTypeMap.entrySet()) { + primitiveTypeToWrapperMap.put(entry.getValue(), entry.getKey()); + } + } + + /** + * Check if the right-hand side type may be assigned to the left-hand side type, assuming setting + * by reflection. Considers primitive wrapper classes as assignable to the corresponding primitive + * types. + * + * @param lhsType the target type + * @param rhsType the value type that should be assigned to the target type + * @return if the target type is assignable from the value type // * @see TypeUtils#isAssignable + */ + public static boolean isAssignable(Class lhsType, Class rhsType) { + Objects.requireNonNull(lhsType, "Left-hand side type must not be null"); + Objects.requireNonNull(rhsType, "Right-hand side type must not be null"); + if (lhsType.isAssignableFrom(rhsType)) { + return true; + } + if (lhsType.isPrimitive()) { + return lhsType == primitiveWrapperTypeMap.get(rhsType); + } else { + Class resolvedWrapper = primitiveTypeToWrapperMap.get(rhsType); + return resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper); + } + } +} diff --git a/services-api/src/main/java/io/scalecube/services/Reflect.java b/services-api/src/main/java/io/scalecube/services/Reflect.java index 299dcea8a..d1bc36570 100644 --- a/services-api/src/main/java/io/scalecube/services/Reflect.java +++ b/services-api/src/main/java/io/scalecube/services/Reflect.java @@ -47,18 +47,22 @@ public static Type parameterizedReturnType(Method method) { return method.getAnnotation(ResponseType.class).value(); } - Type type = method.getGenericReturnType(); - if (type instanceof ParameterizedType) { - Type actualReturnType = ((ParameterizedType) type).getActualTypeArguments()[0]; + Class methodReturnType = method.getReturnType(); + if (Publisher.class.isAssignableFrom(methodReturnType)) { + Type actualReturnType = + ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0]; if (ServiceMessage.class.equals(actualReturnType)) { return Object.class; } - return actualReturnType; - } else { + } + + if (ServiceMessage.class.equals(methodReturnType)) { return Object.class; } + + return methodReturnType; } /** @@ -89,7 +93,7 @@ public static boolean isReturnTypeServiceMessage(Method method) { * @param method in inspection. * @return type of parameter [0] or void */ - public static Class requestType(Method method) { + public static Type requestType(Method method) { if (method.getParameterTypes().length > 0) { if (method.isAnnotationPresent(RequestType.class)) { return method.getAnnotation(RequestType.class).value(); @@ -97,18 +101,21 @@ public static Class requestType(Method method) { if (method.getParameters()[0].isAnnotationPresent(Principal.class)) { return Void.TYPE; } - - if (method.getGenericParameterTypes()[0] instanceof ParameterizedType) { - try { - return Class.forName(parameterizedRequestType(method).getTypeName()); - } catch (ClassNotFoundException e) { + Class parameterType = method.getParameterTypes()[0]; + Type genericType = method.getGenericParameterTypes()[0]; + if (Publisher.class.isAssignableFrom(parameterType)) { + Type actualRequestType = ((ParameterizedType) genericType).getActualTypeArguments()[0]; + if (ServiceMessage.class.equals(actualRequestType)) { return Object.class; } - } else if (ServiceMessage.class.equals(method.getParameterTypes()[0])) { + return actualRequestType; + } + + if (ServiceMessage.class.equals(parameterType)) { return Object.class; - } else { - return method.getParameterTypes()[0]; } + + return genericType; } } else { return Void.TYPE; @@ -127,22 +134,6 @@ public static boolean isRequestTypeServiceMessage(Method method) { return parameterTypes.length > 0 && ServiceMessage.class.equals(parameterTypes[0]); } - /** - * Util function that returns the parameterizedType of a given object. - * - * @param object to inspect - * @return the parameterized Type of a given object or Object class if unknown. - */ - public static Type parameterizedType(Object object) { - if (object != null) { - Type type = object.getClass().getGenericSuperclass(); - if (type instanceof ParameterizedType) { - return ((ParameterizedType) type).getActualTypeArguments()[0]; - } - } - return Object.class; - } - /** * Parse serviceInterface class and puts available methods annotated by {@link * ServiceMethod} annotation to {@link Method} -> {@link MethodInfo} mapping. @@ -170,22 +161,6 @@ public static Map methodsInfo(Class serviceInterface) { isAuth(method1))))); } - /** - * Util function that returns the parameterized of the request Type of a given object. - * - * @return the parameterized Type of a given object or Object class if unknown. - */ - public static Type parameterizedRequestType(Method method) { - if (method != null && method.getGenericParameterTypes().length > 0) { - Type type = method.getGenericParameterTypes()[0]; - if (type instanceof ParameterizedType) { - return ((ParameterizedType) type).getActualTypeArguments()[0]; - } - } - - return Object.class; - } - /** * Util function to extract service name from service api. * diff --git a/services-api/src/main/java/io/scalecube/services/ServiceCall.java b/services-api/src/main/java/io/scalecube/services/ServiceCall.java index 6fdb27666..1e489999e 100644 --- a/services-api/src/main/java/io/scalecube/services/ServiceCall.java +++ b/services-api/src/main/java/io/scalecube/services/ServiceCall.java @@ -382,7 +382,7 @@ public T api(Class serviceInterface) { final Type returnType = methodInfo.parameterizedReturnType(); final boolean isServiceMessage = methodInfo.isReturnTypeServiceMessage(); - Object request = methodInfo.requestType() == Void.TYPE ? null : params[0]; + Object request = methodInfo.isRequestTypeVoid() ? null : params[0]; switch (methodInfo.communicationMode()) { case FIRE_AND_FORGET: diff --git a/services-api/src/main/java/io/scalecube/services/TypeUtils.java b/services-api/src/main/java/io/scalecube/services/TypeUtils.java new file mode 100644 index 000000000..716efdd51 --- /dev/null +++ b/services-api/src/main/java/io/scalecube/services/TypeUtils.java @@ -0,0 +1,200 @@ +package io.scalecube.services; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.Objects; + +/** Utility to work with Java 5 generic type parameters. */ +public class TypeUtils { + + private TypeUtils() { + // Do not instantiate + } + + /** + * Check if the right-hand side type may be assigned to the left-hand side type following the Java + * generics rules. + * + * @param lhsType the target type + * @param rhsType the value type that should be assigned to the target type + * @return true if rhs is assignable to lhs + */ + public static boolean isAssignable(Type lhsType, Type rhsType) { + Objects.requireNonNull(lhsType, "Left-hand side type must not be null"); + Objects.requireNonNull(rhsType, "Right-hand side type must not be null"); + + // all types are assignable to themselves and to class Object + if (lhsType.equals(rhsType) || Object.class == lhsType) { + return true; + } + + if (lhsType instanceof Class) { + Class lhsClass = (Class) lhsType; + + // just comparing two classes + if (rhsType instanceof Class) { + return ClassUtils.isAssignable(lhsClass, (Class) rhsType); + } + + if (rhsType instanceof ParameterizedType) { + Type rhsRaw = ((ParameterizedType) rhsType).getRawType(); + + // a parameterized type is always assignable to its raw class type + if (rhsRaw instanceof Class) { + return ClassUtils.isAssignable(lhsClass, (Class) rhsRaw); + } + } else if (lhsClass.isArray() && rhsType instanceof GenericArrayType) { + Type rhsComponent = ((GenericArrayType) rhsType).getGenericComponentType(); + + return isAssignable(lhsClass.getComponentType(), rhsComponent); + } + } + + // parameterized types are only assignable to other parameterized types and class types + if (lhsType instanceof ParameterizedType) { + if (rhsType instanceof Class) { + Type lhsRaw = ((ParameterizedType) lhsType).getRawType(); + + if (lhsRaw instanceof Class) { + return ClassUtils.isAssignable((Class) lhsRaw, (Class) rhsType); + } + } else if (rhsType instanceof ParameterizedType) { + return isAssignable((ParameterizedType) lhsType, (ParameterizedType) rhsType); + } + } + + if (lhsType instanceof GenericArrayType) { + Type lhsComponent = ((GenericArrayType) lhsType).getGenericComponentType(); + + if (rhsType instanceof Class) { + Class rhsClass = (Class) rhsType; + + if (rhsClass.isArray()) { + return isAssignable(lhsComponent, rhsClass.getComponentType()); + } + } else if (rhsType instanceof GenericArrayType) { + Type rhsComponent = ((GenericArrayType) rhsType).getGenericComponentType(); + + return isAssignable(lhsComponent, rhsComponent); + } + } + + if (lhsType instanceof WildcardType) { + return isAssignable((WildcardType) lhsType, rhsType); + } + + return false; + } + + private static boolean isAssignable(ParameterizedType lhsType, ParameterizedType rhsType) { + if (lhsType.equals(rhsType)) { + return true; + } + + Type[] lhsTypeArguments = lhsType.getActualTypeArguments(); + Type[] rhsTypeArguments = rhsType.getActualTypeArguments(); + + if (lhsTypeArguments.length != rhsTypeArguments.length) { + return false; + } + + for (int size = lhsTypeArguments.length, i = 0; i < size; ++i) { + Type lhsArg = lhsTypeArguments[i]; + Type rhsArg = rhsTypeArguments[i]; + + if (!lhsArg.equals(rhsArg) + && !(lhsArg instanceof WildcardType && isAssignable((WildcardType) lhsArg, rhsArg))) { + return false; + } + } + + return true; + } + + private static boolean isAssignable(WildcardType lhsType, Type rhsType) { + Type[] lhsUpperBounds = lhsType.getUpperBounds(); + + // supply the implicit upper bound if none are specified + if (lhsUpperBounds.length == 0) { + lhsUpperBounds = new Type[] {Object.class}; + } + + Type[] lhsLowerBounds = lhsType.getLowerBounds(); + + // supply the implicit lower bound if none are specified + if (lhsLowerBounds.length == 0) { + lhsLowerBounds = new Type[] {null}; + } + + if (rhsType instanceof WildcardType) { + // both the upper and lower bounds of the right-hand side must be completely enclosed in the + // upper and lower bounds of the left-hand side. + WildcardType rhsWcType = (WildcardType) rhsType; + Type[] rhsUpperBounds = rhsWcType.getUpperBounds(); + + if (rhsUpperBounds.length == 0) { + rhsUpperBounds = new Type[] {Object.class}; + } + + Type[] rhsLowerBounds = rhsWcType.getLowerBounds(); + + if (rhsLowerBounds.length == 0) { + rhsLowerBounds = new Type[] {null}; + } + + for (Type lhsBound : lhsUpperBounds) { + for (Type rhsBound : rhsUpperBounds) { + if (!isAssignableBound(lhsBound, rhsBound)) { + return false; + } + } + + for (Type rhsBound : rhsLowerBounds) { + if (!isAssignableBound(lhsBound, rhsBound)) { + return false; + } + } + } + + for (Type lhsBound : lhsLowerBounds) { + for (Type rhsBound : rhsUpperBounds) { + if (!isAssignableBound(rhsBound, lhsBound)) { + return false; + } + } + + for (Type rhsBound : rhsLowerBounds) { + if (!isAssignableBound(rhsBound, lhsBound)) { + return false; + } + } + } + } else { + for (Type lhsBound : lhsUpperBounds) { + if (!isAssignableBound(lhsBound, rhsType)) { + return false; + } + } + + for (Type lhsBound : lhsLowerBounds) { + if (!isAssignableBound(rhsType, lhsBound)) { + return false; + } + } + } + + return true; + } + + private static boolean isAssignableBound(Type lhsType, Type rhsType) { + if (rhsType == null) { + return true; + } + if (lhsType == null) { + return false; + } + return isAssignable(lhsType, rhsType); + } +} diff --git a/services-api/src/main/java/io/scalecube/services/api/ServiceMessage.java b/services-api/src/main/java/io/scalecube/services/api/ServiceMessage.java index 7897ca532..231ed0cc5 100644 --- a/services-api/src/main/java/io/scalecube/services/api/ServiceMessage.java +++ b/services-api/src/main/java/io/scalecube/services/api/ServiceMessage.java @@ -1,5 +1,7 @@ package io.scalecube.services.api; +import io.scalecube.services.TypeUtils; +import java.lang.reflect.Type; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -141,18 +143,22 @@ public boolean hasData() { /** * Verify that this message contains data. * - * @param dataClass the expected class of the data + * @param dataType the expected type of the data * @return true if the data is instance of the dataClass */ - public boolean hasData(Class dataClass) { - if (dataClass == null) { + public boolean hasData(Type dataType) { + if (dataType == null) { return false; } - if (dataClass.isPrimitive()) { - return hasData(); - } else { - return dataClass.isInstance(data); + if (dataType instanceof Class) { + Class dataClass = (Class) dataType; + if (dataClass.isPrimitive()) { + return hasData(); + } else { + return dataClass.isInstance(data); + } } + return hasData() && TypeUtils.isAssignable(dataType, data.getClass()); } /** diff --git a/services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java b/services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java index 767d69243..6ac7a4a84 100644 --- a/services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java +++ b/services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java @@ -13,7 +13,7 @@ public final class MethodInfo { private final boolean isReturnTypeServiceMessage; private final CommunicationMode communicationMode; private final int parameterCount; - private final Class requestType; + private final Type requestType; private final boolean isRequestTypeServiceMessage; private final boolean auth; @@ -37,7 +37,7 @@ public MethodInfo( boolean isReturnTypeServiceMessage, CommunicationMode communicationMode, int parameterCount, - Class requestType, + Type requestType, boolean isRequestTypeServiceMessage, boolean auth) { @@ -86,10 +86,10 @@ public boolean isRequestTypeServiceMessage() { } public boolean isRequestTypeVoid() { - return requestType.isAssignableFrom(Void.TYPE); + return Void.TYPE.equals(requestType); } - public Class requestType() { + public Type requestType() { return requestType; } diff --git a/services-api/src/main/java/io/scalecube/services/transport/api/ServiceMessageDataDecoder.java b/services-api/src/main/java/io/scalecube/services/transport/api/ServiceMessageDataDecoder.java index f5fb3239b..1f69842c2 100644 --- a/services-api/src/main/java/io/scalecube/services/transport/api/ServiceMessageDataDecoder.java +++ b/services-api/src/main/java/io/scalecube/services/transport/api/ServiceMessageDataDecoder.java @@ -2,11 +2,12 @@ import io.scalecube.services.api.ServiceMessage; import io.scalecube.utils.ServiceLoaderUtil; +import java.lang.reflect.Type; import java.util.function.BiFunction; @FunctionalInterface public interface ServiceMessageDataDecoder - extends BiFunction, ServiceMessage> { + extends BiFunction { ServiceMessageDataDecoder INSTANCE = ServiceLoaderUtil.findFirst(ServiceMessageDataDecoder.class).orElse(null); diff --git a/services-bytebuf-codec/src/main/java/io/scalecube/services/transport/api/ServiceMessageByteBufDataDecoder.java b/services-bytebuf-codec/src/main/java/io/scalecube/services/transport/api/ServiceMessageByteBufDataDecoder.java index 73256c477..112f4de74 100644 --- a/services-bytebuf-codec/src/main/java/io/scalecube/services/transport/api/ServiceMessageByteBufDataDecoder.java +++ b/services-bytebuf-codec/src/main/java/io/scalecube/services/transport/api/ServiceMessageByteBufDataDecoder.java @@ -1,11 +1,12 @@ package io.scalecube.services.transport.api; import io.scalecube.services.api.ServiceMessage; +import java.lang.reflect.Type; public class ServiceMessageByteBufDataDecoder implements ServiceMessageDataDecoder { @Override - public ServiceMessage apply(ServiceMessage message, Class dataType) { + public ServiceMessage apply(ServiceMessage message, Type dataType) { return ServiceMessageCodec.decodeData(message, dataType); } } diff --git a/services/src/test/java/io/scalecube/services/ServiceCallLocalTest.java b/services/src/test/java/io/scalecube/services/ServiceCallLocalTest.java index 7f7e2ad39..d47b57197 100644 --- a/services/src/test/java/io/scalecube/services/ServiceCallLocalTest.java +++ b/services/src/test/java/io/scalecube/services/ServiceCallLocalTest.java @@ -8,6 +8,7 @@ import static io.scalecube.services.TestRequests.GREETING_REQUEST_TIMEOUT_REQ; import static io.scalecube.services.TestRequests.GREETING_THROWING_VOID_REQ; import static io.scalecube.services.TestRequests.GREETING_VOID_REQ; +import static io.scalecube.services.TestRequests.LIST_GREETING_REQUESTS_REQ; import static io.scalecube.services.TestRequests.NOT_FOUND_REQ; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -15,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import com.fasterxml.jackson.core.type.TypeReference; import io.scalecube.services.api.ServiceMessage; import io.scalecube.services.discovery.ScalecubeServiceDiscovery; import io.scalecube.services.exceptions.ServiceException; @@ -22,7 +24,9 @@ import io.scalecube.services.sut.GreetingResponse; import io.scalecube.services.sut.GreetingServiceImpl; import io.scalecube.services.transport.rsocket.RSocketServiceTransport; +import java.lang.reflect.Type; import java.time.Duration; +import java.util.List; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -166,4 +170,18 @@ public void test_async_greeting_return_string_service_not_found_error_case() thr ex.getMessage(), "No reachable member with such service: " + NOT_FOUND_REQ.qualifier()); } } + + @Test + public void test_async_greetings_return_GreetingResponses() { + Type type = new TypeReference>() {}.getType(); + + // When + Publisher result = provider.call().requestOne(LIST_GREETING_REQUESTS_REQ, type); + + // Then + List greetings = Mono.from(result).block(Duration.ofSeconds(TIMEOUT)).data(); + assertEquals(2, greetings.size()); + assertEquals(" hello to: joe", greetings.get(0).getResult()); + assertEquals(" hello to: alisa", greetings.get(1).getResult()); + } } diff --git a/services/src/test/java/io/scalecube/services/ServiceCallRemoteTest.java b/services/src/test/java/io/scalecube/services/ServiceCallRemoteTest.java index bade6dd98..e652d81b7 100644 --- a/services/src/test/java/io/scalecube/services/ServiceCallRemoteTest.java +++ b/services/src/test/java/io/scalecube/services/ServiceCallRemoteTest.java @@ -9,6 +9,7 @@ import static io.scalecube.services.TestRequests.GREETING_REQUEST_TIMEOUT_REQ; import static io.scalecube.services.TestRequests.GREETING_THROWING_VOID_REQ; import static io.scalecube.services.TestRequests.GREETING_VOID_REQ; +import static io.scalecube.services.TestRequests.LIST_GREETING_REQUESTS_REQ; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -16,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.fasterxml.jackson.core.type.TypeReference; import io.scalecube.services.api.ServiceMessage; import io.scalecube.services.discovery.ScalecubeServiceDiscovery; import io.scalecube.services.discovery.api.ServiceDiscovery; @@ -25,7 +27,9 @@ import io.scalecube.services.sut.QuoteService; import io.scalecube.services.sut.SimpleQuoteService; import io.scalecube.services.transport.rsocket.RSocketServiceTransport; +import java.lang.reflect.Type; import java.time.Duration; +import java.util.List; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -224,6 +228,20 @@ public void test_service_address_lookup_occur_only_after_subscription() { } } + @Test + public void test_remote_async_greetings_return_GreetingResponses() { + Type type = new TypeReference>() {}.getType(); + + // When + Publisher result = gateway.call().requestOne(LIST_GREETING_REQUESTS_REQ, type); + + // Then + List greetings = Mono.from(result).block(Duration.ofSeconds(TIMEOUT)).data(); + assertEquals(2, greetings.size()); + assertEquals(" hello to: joe", greetings.get(0).getResult()); + assertEquals(" hello to: alisa", greetings.get(1).getResult()); + } + private static Microservices gateway() { return Microservices.builder() .discovery(ScalecubeServiceDiscovery::new) diff --git a/services/src/test/java/io/scalecube/services/TestRequests.java b/services/src/test/java/io/scalecube/services/TestRequests.java index 9b525fbaa..036b07d01 100644 --- a/services/src/test/java/io/scalecube/services/TestRequests.java +++ b/services/src/test/java/io/scalecube/services/TestRequests.java @@ -3,6 +3,7 @@ import io.scalecube.services.api.ServiceMessage; import io.scalecube.services.sut.GreetingRequest; import java.time.Duration; +import java.util.Arrays; public interface TestRequests { @@ -79,4 +80,10 @@ public interface TestRequests { ServiceMessage SERVICE_NOT_FOUND = ServiceMessage.builder().qualifier(SERVICE_NAME, "unknown_service").build(); + + ServiceMessage LIST_GREETING_REQUESTS_REQ = + ServiceMessage.builder() + .qualifier(SERVICE_NAME, "listRequests") + .data(Arrays.asList(new GreetingRequest("joe"), new GreetingRequest("alisa"))) + .build(); } diff --git a/services/src/test/java/io/scalecube/services/sut/GreetingService.java b/services/src/test/java/io/scalecube/services/sut/GreetingService.java index 28e3d3a67..186fa175b 100644 --- a/services/src/test/java/io/scalecube/services/sut/GreetingService.java +++ b/services/src/test/java/io/scalecube/services/sut/GreetingService.java @@ -5,6 +5,7 @@ import io.scalecube.services.annotations.Service; import io.scalecube.services.annotations.ServiceMethod; import io.scalecube.services.api.ServiceMessage; +import java.util.List; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -73,4 +74,7 @@ public interface GreetingService { @ServiceMethod Flux greetingFluxEmpty(GreetingRequest request); + + @ServiceMethod + Mono> listRequests(List requests); } diff --git a/services/src/test/java/io/scalecube/services/sut/GreetingServiceImpl.java b/services/src/test/java/io/scalecube/services/sut/GreetingServiceImpl.java index d438aaf78..f58bae732 100644 --- a/services/src/test/java/io/scalecube/services/sut/GreetingServiceImpl.java +++ b/services/src/test/java/io/scalecube/services/sut/GreetingServiceImpl.java @@ -4,6 +4,8 @@ import io.scalecube.services.annotations.Inject; import io.scalecube.services.api.ServiceMessage; import io.scalecube.services.exceptions.UnauthorizedException; +import java.util.List; +import java.util.stream.Collectors; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -149,6 +151,15 @@ public void notifyGreeting() { print("[notifyGreeting] Hello... i am a service and i just notefied"); } + @Override + public Mono> listRequests(List requests) { + print("[listRequest] Hello... i am a service an just received a message:" + requests); + return Mono.just( + requests.stream() + .map(request -> new GreetingResponse(" hello to: " + request.getName())) + .collect(Collectors.toList())); + } + private void print(String message) { if (!ci) { System.out.println(message);