
io.scalecube.services.methods.ServiceMethodInvoker Maven / Gradle / Ivy
package io.scalecube.services.methods;
import io.scalecube.services.api.ServiceMessage;
import io.scalecube.services.exceptions.BadRequestException;
import io.scalecube.services.exceptions.ServiceProviderErrorMapper;
import io.scalecube.services.transport.api.ServiceMessageDataDecoder;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Invoker of service method. Prepares service message request before call as well as doing some
* handling of a product of the service call.
*/
public final class ServiceMethodInvoker {
private final Method method;
private final Object service;
private final MethodInfo methodInfo;
private final ServiceProviderErrorMapper errorMapper;
private final ServiceMessageDataDecoder dataDecoder;
/**
* Constructs a service method invoker out of real service object instance and method info.
*
* @param method service method
* @param service service instance
* @param methodInfo method information
* @param errorMapper error mapper
* @param dataDecoder data decoder
*/
public ServiceMethodInvoker(
Method method,
Object service,
MethodInfo methodInfo,
ServiceProviderErrorMapper errorMapper,
ServiceMessageDataDecoder dataDecoder) {
this.method = method;
this.service = service;
this.methodInfo = methodInfo;
this.errorMapper = errorMapper;
this.dataDecoder = dataDecoder;
}
/**
* Invokes service method with single response.
*
* @param message request service message
* @return mono of service message
*/
public Mono invokeOne(ServiceMessage message) {
return Mono.defer(() -> Mono.from(invoke(toRequest(message))))
.map(this::toResponse)
.onErrorResume(throwable -> Mono.just(errorMapper.toMessage(throwable)));
}
/**
* Invokes service method with message stream response.
*
* @param message request service message
* @return flux of service messages
*/
public Flux invokeMany(ServiceMessage message) {
return Flux.defer(() -> Flux.from(invoke(toRequest(message))))
.map(this::toResponse)
.onErrorResume(throwable -> Flux.just(errorMapper.toMessage(throwable)));
}
/**
* Invokes service method with bidirectional communication.
*
* @param publisher request service message
* @return flux of service messages
*/
public Flux invokeBidirectional(Publisher publisher) {
return Flux.from(publisher)
.map(this::toRequest)
.transform(this::invoke)
.map(this::toResponse)
.onErrorResume(throwable -> Flux.just(errorMapper.toMessage(throwable)));
}
private Publisher> invoke(Object arguments) {
Publisher> result = null;
Throwable throwable = null;
try {
if (method.getParameterCount() == 0) {
result = (Publisher>) method.invoke(service);
} else {
result = (Publisher>) method.invoke(service, arguments);
}
if (result == null) {
result = Mono.empty();
}
} catch (InvocationTargetException ex) {
throwable = Optional.ofNullable(ex.getCause()).orElse(ex);
} catch (Throwable ex) {
throwable = ex;
}
return throwable != null ? Mono.error(throwable) : result;
}
private Object toRequest(ServiceMessage message) {
ServiceMessage request = dataDecoder.apply(message, methodInfo.requestType());
if (!methodInfo.isRequestTypeVoid()
&& !methodInfo.isRequestTypeServiceMessage()
&& !request.hasData(methodInfo.requestType())) {
Optional> dataOptional = Optional.ofNullable(request.data());
Class> clazz = dataOptional.map(Object::getClass).orElse(null);
throw new BadRequestException(
String.format(
"Expected service request data of type: %s, but received: %s",
methodInfo.requestType(), clazz));
}
return methodInfo.isRequestTypeServiceMessage() ? request : request.data();
}
private ServiceMessage toResponse(Object response) {
return (response instanceof ServiceMessage)
? (ServiceMessage) response
: ServiceMessage.builder().qualifier(methodInfo.qualifier()).data(response).build();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy