Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.scalecube.services.ServiceCall Maven / Gradle / Ivy
package io.scalecube.services;
import static java.util.Objects.requireNonNull;
import io.scalecube.net.Address;
import io.scalecube.services.api.ErrorData;
import io.scalecube.services.api.ServiceMessage;
import io.scalecube.services.exceptions.DefaultErrorMapper;
import io.scalecube.services.exceptions.ServiceClientErrorMapper;
import io.scalecube.services.exceptions.ServiceUnavailableException;
import io.scalecube.services.methods.MethodInfo;
import io.scalecube.services.methods.ServiceMethodRegistry;
import io.scalecube.services.registry.api.ServiceRegistry;
import io.scalecube.services.routing.Router;
import io.scalecube.services.routing.Routers;
import io.scalecube.services.transport.api.ClientTransport;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Exceptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class ServiceCall {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceCall.class);
private static final ServiceMessage UNEXPECTED_EMPTY_RESPONSE =
ServiceMessage.error(503, 503, "Unexpected empty response");
private ClientTransport transport;
private ServiceMethodRegistry methodRegistry;
private ServiceRegistry serviceRegistry;
private Router router;
private ServiceClientErrorMapper errorMapper = DefaultErrorMapper.INSTANCE;
private Consumer requestReleaser =
req -> {
// no-op
};
private Map credentials = Collections.emptyMap();
/** Default constructor. */
public ServiceCall() {}
private ServiceCall(ServiceCall other) {
this.transport = other.transport;
this.methodRegistry = other.methodRegistry;
this.serviceRegistry = other.serviceRegistry;
this.router = other.router;
this.errorMapper = other.errorMapper;
}
/**
* Creates new {@link ServiceCall}'s definition with a given client transport.
*
* @param clientTransport client transport.
* @return new {@link ServiceCall} instance.
*/
public ServiceCall transport(ClientTransport clientTransport) {
ServiceCall target = new ServiceCall(this);
target.transport = clientTransport;
return target;
}
/**
* Creates new {@link ServiceCall}'s definition with a given service registry.
*
* @param serviceRegistry service registry.
* @return new {@link ServiceCall} instance.
*/
public ServiceCall serviceRegistry(ServiceRegistry serviceRegistry) {
ServiceCall target = new ServiceCall(this);
target.serviceRegistry = serviceRegistry;
return target;
}
/**
* Creates new {@link ServiceCall}'s definition with a given method registry.
*
* @param methodRegistry method registry.
* @return new {@link ServiceCall} instance.
*/
public ServiceCall methodRegistry(ServiceMethodRegistry methodRegistry) {
ServiceCall target = new ServiceCall(this);
target.methodRegistry = methodRegistry;
return target;
}
/**
* Creates new {@link ServiceCall}'s definition with a given router.
*
* @param routerType given class of the router.
* @return new {@link ServiceCall} instance.
*/
public ServiceCall router(Class extends Router> routerType) {
ServiceCall target = new ServiceCall(this);
target.router = Routers.getRouter(routerType);
return target;
}
/**
* Creates new {@link ServiceCall}'s definition with a given router.
*
* @param router given.
* @return new {@link ServiceCall} instance.
*/
public ServiceCall router(Router router) {
ServiceCall target = new ServiceCall(this);
target.router = router;
return target;
}
/**
* Creates new {@link ServiceCall}'s definition with a given error mapper.
*
* @param errorMapper given.
* @return new {@link ServiceCall} instance.
*/
public ServiceCall errorMapper(ServiceClientErrorMapper errorMapper) {
ServiceCall target = new ServiceCall(this);
target.errorMapper = errorMapper;
return target;
}
/**
* Creates new {@link ServiceCall}'s definition with a given requestReleaser.
*
* @param requestReleaser given.
* @return new {@link ServiceCall} instance.
*/
public ServiceCall requestReleaser(Consumer requestReleaser) {
ServiceCall target = new ServiceCall(this);
target.requestReleaser = requestReleaser;
return target;
}
/**
* Creates new {@link ServiceCall}'s definition with a given credentials.
*
* @param credentials given.
* @return new {@link ServiceCall} instance.
*/
public ServiceCall credentials(Map credentials) {
ServiceCall target = new ServiceCall(this);
target.credentials = credentials;
return target;
}
/**
* Issues fire-and-forget request.
*
* @param request request message to send.
* @return mono publisher completing normally or with error.
*/
public Mono oneWay(ServiceMessage request) {
return Mono.defer(() -> requestOne(request, Void.class).then());
}
/**
* Issues fire-and-forget request.
*
* @param request request message to send.
* @param address of remote target service to invoke.
* @return mono publisher completing normally or with error.
*/
public Mono oneWay(ServiceMessage request, Address address) {
return Mono.defer(() -> requestOne(request, Void.class, address).then());
}
/**
* Issues request-and-reply request.
*
* @param request request message to send.
* @return mono publisher completing with single response message or with error.
*/
public Mono requestOne(ServiceMessage request) {
return requestOne(request, null);
}
/**
* Issues request-and-reply request.
*
* @param request request message to send.
* @param responseType type of response.
* @return mono publisher completing with single response message or with error.
*/
public Mono requestOne(ServiceMessage request, Type responseType) {
return Mono.defer(
() -> {
String qualifier = request.qualifier();
if (methodRegistry != null
&& methodRegistry.containsInvoker(qualifier)) { // local service
return methodRegistry
.getInvoker(request.qualifier())
.invokeOne(request, requestReleaser)
.map(this::throwIfError);
} else {
return addressLookup(request)
.flatMap(address -> requestOne(request, responseType, address)); // remote service
}
});
}
/**
* Given an address issues request-and-reply request to a remote address.
*
* @param request request message to send.
* @param responseType type of response.
* @param address of remote target service to invoke.
* @return mono publisher completing with single response message or with error.
*/
public Mono requestOne(
ServiceMessage request, Type responseType, Address address) {
return Mono.defer(
() -> {
requireNonNull(address, "requestOne address parameter is required and must not be null");
requireNonNull(transport, "transport is required and must not be null");
return transport
.create(address)
.requestResponse(request, responseType)
.map(this::throwIfError);
});
}
/**
* Issues request to service which returns stream of service messages back.
*
* @param request request message to send.
* @return flux publisher of service responses.
*/
public Flux requestMany(ServiceMessage request) {
return requestMany(request, null);
}
/**
* Issues request to service which returns stream of service messages back.
*
* @param request request with given headers.
* @param responseType type of responses.
* @return flux publisher of service responses.
*/
public Flux requestMany(ServiceMessage request, Type responseType) {
return Flux.defer(
() -> {
String qualifier = request.qualifier();
if (methodRegistry != null
&& methodRegistry.containsInvoker(qualifier)) { // local service
return methodRegistry
.getInvoker(request.qualifier())
.invokeMany(request, requestReleaser)
.map(this::throwIfError);
} else {
return addressLookup(request)
.flatMapMany(
address -> requestMany(request, responseType, address)); // remote service
}
});
}
/**
* Given an address issues request to remote service which returns stream of service messages
* back.
*
* @param request request with given headers.
* @param responseType type of responses.
* @param address of remote target service to invoke.
* @return flux publisher of service responses.
*/
public Flux requestMany(
ServiceMessage request, Type responseType, Address address) {
return Flux.defer(
() -> {
requireNonNull(address, "requestMany address parameter is required and must not be null");
requireNonNull(transport, "transport is required and must not be null");
return transport
.create(address)
.requestStream(request, responseType)
.map(this::throwIfError);
});
}
/**
* Issues stream of service requests to service which returns stream of service messages back.
*
* @param publisher of service requests.
* @return flux publisher of service responses.
*/
public Flux requestBidirectional(Publisher publisher) {
return requestBidirectional(publisher, null);
}
/**
* Issues stream of service requests to service which returns stream of service messages back.
*
* @param publisher of service requests.
* @param responseType type of responses.
* @return flux publisher of service responses.
*/
public Flux requestBidirectional(
Publisher publisher, Type responseType) {
return Flux.from(publisher)
.switchOnFirst(
(first, messages) -> {
if (first.hasValue()) {
ServiceMessage request = first.get();
String qualifier = request.qualifier();
if (methodRegistry != null
&& methodRegistry.containsInvoker(qualifier)) { // local service
return methodRegistry
.getInvoker(qualifier)
.invokeBidirectional(messages, requestReleaser)
.map(this::throwIfError);
} else {
// remote service
return addressLookup(request)
.flatMapMany(
address -> requestBidirectional(messages, responseType, address));
}
}
return messages;
});
}
/**
* Given an address issues stream of service requests to service which returns stream of service
* messages back.
*
* @param publisher of service requests.
* @param responseType type of responses.
* @param address of remote target service to invoke.
* @return flux publisher of service responses.
*/
public Flux requestBidirectional(
Publisher publisher, Type responseType, Address address) {
return Flux.defer(
() -> {
requireNonNull(
address, "requestBidirectional address parameter is required and must not be null");
requireNonNull(transport, "transport is required and must not be null");
return transport
.create(address)
.requestChannel(publisher, responseType)
.map(this::throwIfError);
});
}
/**
* Create proxy creates a java generic proxy instance by a given service interface.
*
* @param serviceInterface Service Interface type.
* @return newly created service proxy object.
*/
@SuppressWarnings("unchecked")
public T api(Class serviceInterface) {
final ServiceCall serviceCall = this;
final Map genericReturnTypes = Reflect.methodsInfo(serviceInterface);
// noinspection unchecked,Convert2Lambda
return (T)
Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class[] {serviceInterface},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] params) {
Optional check =
toStringOrEqualsOrHashCode(method.getName(), serviceInterface, params);
if (check.isPresent()) {
return check.get(); // toString, hashCode was invoked.
}
final MethodInfo methodInfo = genericReturnTypes.get(method);
final Type returnType = methodInfo.parameterizedReturnType();
final boolean isServiceMessage = methodInfo.isReturnTypeServiceMessage();
Object request = methodInfo.requestType() == Void.TYPE ? null : params[0];
switch (methodInfo.communicationMode()) {
case FIRE_AND_FORGET:
return serviceCall.oneWay(toServiceMessage(methodInfo, request));
case REQUEST_RESPONSE:
return serviceCall
.requestOne(toServiceMessage(methodInfo, request), returnType)
.transform(asMono(isServiceMessage));
case REQUEST_STREAM:
return serviceCall
.requestMany(toServiceMessage(methodInfo, request), returnType)
.transform(asFlux(isServiceMessage));
case REQUEST_CHANNEL:
// this is REQUEST_CHANNEL so it means params[0] must
// be a publisher - its safe to cast.
return serviceCall
.requestBidirectional(
Flux.from((Publisher) request)
.map(data -> toServiceMessage(methodInfo, data)),
returnType)
.transform(asFlux(isServiceMessage));
default:
throw new IllegalArgumentException(
"Communication mode is not supported: " + method);
}
}
});
}
private Mono addressLookup(ServiceMessage request) {
Callable callable =
() ->
router
.route(serviceRegistry, request)
.map(ServiceReference::address)
.orElseThrow(() -> noReachableMemberException(request));
return Mono.fromCallable(callable).doOnError(th -> applyRequestReleaser(request));
}
private ServiceMessage toServiceMessage(MethodInfo methodInfo, Object request) {
if (request instanceof ServiceMessage) {
return ServiceMessage.from((ServiceMessage) request)
.qualifier(methodInfo.serviceName(), methodInfo.methodName())
.headers(credentials)
.build();
}
return ServiceMessage.builder()
.qualifier(methodInfo.serviceName(), methodInfo.methodName())
.headers(credentials)
.data(request)
.build();
}
private ServiceUnavailableException noReachableMemberException(ServiceMessage request) {
LOGGER.error(
"Failed to invoke service, "
+ "No reachable member with such service definition [{}], args [{}]",
request.qualifier(),
request);
return new ServiceUnavailableException(
"No reachable member with such service: " + request.qualifier());
}
/**
* check and handle toString or equals or hashcode method where invoked.
*
* @param method that was invoked.
* @param serviceInterface for a given service interface.
* @param args parameters that where invoked.
* @return Optional object as result of to string equals or hashCode result or absent if none of
* these where invoked.
*/
private Optional toStringOrEqualsOrHashCode(
String method, Class> serviceInterface, Object... args) {
switch (method) {
case "toString":
return Optional.of(serviceInterface.toString());
case "equals":
return Optional.of(serviceInterface.equals(args[0]));
case "hashCode":
return Optional.of(serviceInterface.hashCode());
default:
return Optional.empty();
}
}
private Function, Flux> asFlux(boolean isReturnTypeServiceMessage) {
return flux -> isReturnTypeServiceMessage ? flux.cast(Object.class) : flux.map(msgToResp());
}
private Function, Mono> asMono(boolean isReturnTypeServiceMessage) {
return mono -> isReturnTypeServiceMessage ? mono.cast(Object.class) : mono.map(msgToResp());
}
private Function msgToResp() {
return sm -> sm.hasData() ? sm.data() : UNEXPECTED_EMPTY_RESPONSE;
}
private ServiceMessage throwIfError(ServiceMessage message) {
if (message.isError() && message.hasData(ErrorData.class)) {
throw Exceptions.propagate(errorMapper.toError(message));
}
return message;
}
private void applyRequestReleaser(ServiceMessage request) {
if (request.data() != null) {
requestReleaser.accept(request.data());
}
}
}