io.scalecube.services.Reflect Maven / Gradle / Ivy
package io.scalecube.services;
import static io.scalecube.services.CommunicationMode.FIRE_AND_FORGET;
import static io.scalecube.services.CommunicationMode.REQUEST_CHANNEL;
import static io.scalecube.services.CommunicationMode.REQUEST_RESPONSE;
import static io.scalecube.services.CommunicationMode.REQUEST_STREAM;
import io.scalecube.services.annotations.RequestType;
import io.scalecube.services.annotations.ResponseType;
import io.scalecube.services.annotations.Service;
import io.scalecube.services.annotations.ServiceMethod;
import io.scalecube.services.annotations.Tag;
import io.scalecube.services.api.Qualifier;
import io.scalecube.services.api.ServiceMessage;
import io.scalecube.services.auth.Auth;
import io.scalecube.services.auth.Principal;
import io.scalecube.services.methods.MethodInfo;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class Reflect {
private Reflect() {
// Do not instantiate
}
/**
* extract parameterized return value of a method.
*
* @param method to extract type from.
* @return the generic type of the return value or object.
*/
public static Type parameterizedReturnType(Method method) {
if (method.isAnnotationPresent(ResponseType.class)) {
return method.getAnnotation(ResponseType.class).value();
}
Type type = method.getGenericReturnType();
if (type instanceof ParameterizedType) {
Type actualReturnType = ((ParameterizedType) type).getActualTypeArguments()[0];
if (ServiceMessage.class.equals(actualReturnType)) {
return Object.class;
}
return actualReturnType;
} else {
return Object.class;
}
}
/**
* Util function to check if return type of method is ServiceMessage.
*
* @param method method to inspect
* @return true if return type of method is ServiceMessage, otherwise false
*/
public static boolean isReturnTypeServiceMessage(Method method) {
Type type = method.getGenericReturnType();
if (type instanceof ParameterizedType) {
Type actualReturnType = ((ParameterizedType) type).getActualTypeArguments()[0];
return ServiceMessage.class.equals(actualReturnType);
}
return false;
}
/**
* Util function returns the the Type of method parameter [0] or Void.Type in case 0 parameters.
* in case the method is annotated with @RequestType this type will always be chosen. if the
* parameter is generic eg. <String> the actual type will be used. in case there is no
* annotation and the type is not generic then return the actual type. in case method accepts
* service message and no RequestType annotation is present then return Object.class
*
* @param method in inspection.
* @return type of parameter [0] or void
*/
public static Class> requestType(Method method) {
if (method.getParameterTypes().length > 0) {
if (method.isAnnotationPresent(RequestType.class)) {
return method.getAnnotation(RequestType.class).value();
} else {
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) {
return Object.class;
}
} else if (ServiceMessage.class.equals(method.getParameterTypes()[0])) {
return Object.class;
} else {
return method.getParameterTypes()[0];
}
}
} else {
return Void.TYPE;
}
}
/**
* Util function to check if the first parameter of method is ServiceMessage.
*
* @param method method to inspect
* @return true if the first parameter of method is ServiceMessage, otherwise false
*/
public static boolean isRequestTypeServiceMessage(Method method) {
Class>[] parameterTypes = method.getParameterTypes();
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.
*
* @param serviceInterface - service interface to be parsed.
* @return - mapping form available service methods of the serviceInterface to their
* descriptions
*/
public static Map methodsInfo(Class> serviceInterface) {
return Collections.unmodifiableMap(
serviceMethods(serviceInterface).values().stream()
.collect(
Collectors.toMap(
Function.identity(),
method1 ->
new MethodInfo(
serviceName(serviceInterface),
methodName(method1),
parameterizedReturnType(method1),
isReturnTypeServiceMessage(method1),
communicationMode(method1),
method1.getParameterCount(),
requestType(method1),
isRequestTypeServiceMessage(method1),
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.
*
* @param serviceInterface with @Service annotation.
* @return service name.
*/
public static String serviceName(Class> serviceInterface) {
// Service name
Service serviceAnnotation = serviceInterface.getAnnotation(Service.class);
if (serviceAnnotation == null) {
throw new IllegalArgumentException(
String.format("Not a service interface: %s", serviceInterface));
}
return serviceAnnotation.value().length() > 0
? serviceAnnotation.value()
: serviceInterface.getName();
}
/**
* Util function to extract service tags from service api.
*
* @param serviceInterface with @Service annotation.
* @return service tags
*/
public static Map serviceTags(Class> serviceInterface) {
return Reflect.transformArrayToMap(serviceInterface.getAnnotationsByType(Tag.class));
}
/**
* Util function to extract service tags from service method api.
*
* @param serviceMethod with @ServiceMethod annotation.
* @return service tags
*/
public static Map serviceMethodTags(Method serviceMethod) {
return Reflect.transformArrayToMap(serviceMethod.getAnnotationsByType(Tag.class));
}
private static Map transformArrayToMap(Tag[] array) {
return array == null || array.length == 0
? Collections.emptyMap()
: Collections.unmodifiableMap(
Arrays.stream(array).collect(Collectors.toMap(Tag::key, Tag::value)));
}
/**
* Util function to get service Method map from service api.
*
* @param serviceInterface with @Service annotation.
* @return service name.
*/
public static Map serviceMethods(Class> serviceInterface) {
Map methods =
Arrays.stream(serviceInterface.getMethods())
.filter(method -> method.isAnnotationPresent(ServiceMethod.class))
.collect(Collectors.toMap(Reflect::methodName, Function.identity()));
return Collections.unmodifiableMap(methods);
}
/**
* Util function to get service interfaces collections from service instance.
*
* @param serviceObject with extends service interface with @Service annotation.
* @return service interface class.
*/
public static Collection> serviceInterfaces(Object serviceObject) {
Class>[] interfaces = serviceObject.getClass().getInterfaces();
return Arrays.stream(interfaces)
.filter(interfaceClass -> interfaceClass.isAnnotationPresent(Service.class))
.collect(Collectors.toList());
}
public static String methodName(Method method) {
ServiceMethod methodAnnotation = method.getAnnotation(ServiceMethod.class);
return methodAnnotation.value().length() > 0 ? methodAnnotation.value() : method.getName();
}
/**
* Handy method to get qualifier String from service's interface and method.
*
* @param serviceInterface service interface to get qualifier for
* @param method service's method to get qualifier for
* @return
*/
public static String qualifier(Class> serviceInterface, Method method) {
return Qualifier.asString(Reflect.serviceName(serviceInterface), Reflect.methodName(method));
}
/**
* Util function to perform basic validation of service message request.
*
* @param method service method.
*/
public static void validateMethodOrThrow(Method method) {
Class> returnType = method.getReturnType();
if (returnType.equals(Void.TYPE)) {
return;
} else if (!Publisher.class.isAssignableFrom(returnType)) {
throw new UnsupportedOperationException("Service method return type can be Publisher only");
}
validateResponseType(method);
validateRequestType(method);
validatePrincipalParameter(method);
if (method.getParameterCount() > 2) {
throw new UnsupportedOperationException("Service method can accept maximum 2 parameters");
}
}
private static void validateResponseType(Method method) {
if (isReturnTypeServiceMessage(method)) {
if (!method.isAnnotationPresent(ResponseType.class)) {
throw new UnsupportedOperationException(
"Return type ServiceMessage cannot be used without @ResponseType method annotation");
} else if (ServiceMessage.class.equals(method.getAnnotation(ResponseType.class).value())) {
throw new UnsupportedOperationException(
"ServiceMessage is not allowed value for @ResponseType");
}
}
}
private static void validateRequestType(Method method) {
if (isRequestTypeServiceMessage(method)) {
if (!method.isAnnotationPresent(RequestType.class)) {
throw new UnsupportedOperationException(
"Request type ServiceMessage cannot be used without @RequestType method annotation");
} else if (ServiceMessage.class.equals(method.getAnnotation(RequestType.class).value())) {
throw new UnsupportedOperationException(
"ServiceMessage is not allowed value for @RequestType");
}
}
}
private static void validatePrincipalParameter(Method method) {
Parameter[] parameters = method.getParameters();
if (!isAuth(method)) {
for (Parameter parameter : parameters) {
if (parameter.isAnnotationPresent(Principal.class)) {
throw new UnsupportedOperationException(
"@Principal can be used only for parameter of @Auth method");
}
}
}
if (method.getParameterCount() == 2) {
if (parameters[0].isAnnotationPresent(Principal.class)) {
throw new UnsupportedOperationException(
"@Principal cannot be the first parameter on method with two parameters");
}
if (!parameters[1].isAnnotationPresent(Principal.class)) {
throw new UnsupportedOperationException(
"The second parameter can be only @Principal (optional)");
}
}
}
/**
* This method is used to get catual {@link CommunicationMode} os service method.
*
* The following modes are supported:
*
*
* - {@link CommunicationMode#REQUEST_CHANNEL} - service has at least one parameter,and the
* first parameter is either of type return type {@link Flux} or {@link Publisher};
*
- {@link CommunicationMode#REQUEST_STREAM} - service's return type is {@link Flux}, and
* parameter is not {@link Flux};
*
- {@link CommunicationMode#REQUEST_RESPONSE} - service's return type is Mono;
*
- {@link CommunicationMode#FIRE_AND_FORGET} - service returns void;
*
*
* @param method - Service method to be analyzed.
* @return - {@link CommunicationMode} of service method. If method does not correspond to any of
* supported modes, throws {@link IllegalArgumentException}
*/
public static CommunicationMode communicationMode(Method method) {
Class> returnType = method.getReturnType();
if (isRequestChannel(method)) {
return REQUEST_CHANNEL;
} else if (returnType.isAssignableFrom(Flux.class)) {
return REQUEST_STREAM;
} else if (returnType.isAssignableFrom(Mono.class)) {
return REQUEST_RESPONSE;
} else if (returnType.isAssignableFrom(Void.TYPE)) {
return FIRE_AND_FORGET;
} else {
throw new IllegalArgumentException(
"Service method is not supported (check return type or parameter type): " + method);
}
}
private static boolean isRequestChannel(Method method) {
Class>[] reqTypes = method.getParameterTypes();
return reqTypes.length > 0
&& (Flux.class.isAssignableFrom(reqTypes[0])
|| Publisher.class.isAssignableFrom(reqTypes[0]));
}
public static void setField(Field field, Object object, Object value)
throws IllegalAccessException {
field.setAccessible(true);
field.set(object, value);
}
public static boolean isService(Class> type) {
return type.isAnnotationPresent(Service.class);
}
public static boolean isAuth(Method method) {
return method.isAnnotationPresent(Auth.class)
|| method.getDeclaringClass().isAnnotationPresent(Auth.class);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy