All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.scalecube.services.ServiceCall Maven / Gradle / Ivy

package io.scalecube.services;

import io.scalecube.services.api.ErrorData;
import io.scalecube.services.api.ServiceMessage;
import io.scalecube.services.codec.ServiceMessageDataCodec;
import io.scalecube.services.exceptions.ExceptionProcessor;
import io.scalecube.services.exceptions.ServiceUnavailableException;
import io.scalecube.services.metrics.Metrics;
import io.scalecube.services.routing.Router;
import io.scalecube.services.transport.LocalServiceDispatchers;
import io.scalecube.services.transport.api.ServiceMethodDispatcher;
import io.scalecube.services.transport.client.api.ClientTransport;
import io.scalecube.transport.Address;

import com.codahale.metrics.Timer;
import com.google.common.reflect.Reflection;

import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class ServiceCall {

  private static final Logger LOGGER = LoggerFactory.getLogger(ServiceCall.class);

  private final ClientTransport transport;
  private final LocalServiceDispatchers localServices;

  public ServiceCall(ClientTransport transport, LocalServiceDispatchers localServices) {
    this.transport = transport;
    this.localServices = localServices;
  }

  public Call call() {
    return new Call(this.transport, this.localServices);
  }

  public static class Call {

    private Router router;
    private Metrics metrics;
    private Timer latency;
    private ClientTransport transport;
    private ServiceMessageDataCodec messageDataCodec;
    private LocalServiceDispatchers localServices;

    public Call(ClientTransport transport, LocalServiceDispatchers localServices) {
      this.transport = transport;
      this.messageDataCodec = new ServiceMessageDataCodec();
      this.localServices = localServices;
    }

    public Call router(Router router) {
      this.router = router;
      return this;
    }

    public Call metrics(Metrics metrics) {
      this.metrics = metrics;
      this.latency = Metrics.timer(this.metrics, ServiceCall.class.getName(), "invoke");
      return this;
    }

    /**
     * Invoke a request message and invoke a service by a given service name and method name. expected headers in *
     * request: ServiceHeaders.SERVICE_REQUEST the logical name of the service. ServiceHeaders.METHOD the method name to
     * invoke message uses the router to select the target endpoint service instance in the cluster. Throws Exception
     * in* case of an error or TimeoutException if no response if a given duration.
     *
     * @param request request with given headers.
     * @return CompletableFuture with service call dispatching result.
     */
    public Publisher requestOne(final ServiceMessage request) {
      Class responseType = request.responseType() != null ? request.responseType() : Object.class;
      return requestOne(request, responseType);
    }

    @SuppressWarnings("unchecked")
    public Publisher requestOne(final ServiceMessage request, final Class returnType) {
      Messages.validate().serviceRequest(request);
      String qualifier = request.qualifier();

      if (localServices.contains(qualifier)) {
        ServiceMethodDispatcher dispatcher = localServices.getDispatcher(qualifier);
        return dispatcher.requestResponse(request).onErrorMap(ExceptionProcessor::mapException);
      } else {
        ServiceReference serviceReference =
            router.route(request).orElseThrow(() -> noReachableMemberException(request));

        Address address =
            Address.create(serviceReference.host(), serviceReference.port());

        return transport.create(address)
            .requestResponse(request)
            .map(message -> {
              if (ExceptionProcessor.isError(message)) {
                throw ExceptionProcessor.toException(messageDataCodec.decode(message, ErrorData.class));
              } else {
                return messageDataCodec.decode(message, returnType);
              }
            });
      }
    }

    /**
     * Issues fire-and-rorget request.
     *
     * @param request request to send.
     * @return Mono of type Void.
     */
    @SuppressWarnings("unchecked")
    public Mono oneWay(ServiceMessage request) {
      Messages.validate().serviceRequest(request);
      String qualifier = request.qualifier();

      if (localServices.contains(qualifier)) {
        ServiceMethodDispatcher dispatcher = localServices.getDispatcher(qualifier);
        return dispatcher.fireAndForget(request)
            .onErrorMap(ExceptionProcessor::mapException)
            .map(message -> null);
      } else {
        ServiceReference serviceReference =
            router.route(request).orElseThrow(() -> noReachableMemberException(request));

        Address address =
            Address.create(serviceReference.host(), serviceReference.port());

        return transport.create(address).fireAndForget(request);
      }
    }

    /**
     * sending subscription request message to a service that returns Observable.
     *
     * @param request containing subscription data.
     * @return rx.Observable for the specific stream.
     */
    @SuppressWarnings("unchecked")
    public Publisher requestMany(ServiceMessage request) {
      Messages.validate().serviceRequest(request);
      String qualifier = request.qualifier();

      if (localServices.contains(qualifier)) {
        ServiceMethodDispatcher dispatcher = localServices.getDispatcher(qualifier);
        return dispatcher.requestStream(request).onErrorMap(ExceptionProcessor::mapException);
      } else {
        Class responseType =
            request.responseType() != null ? request.responseType() : Object.class;

        ServiceReference serviceReference =
            router.route(request).orElseThrow(() -> noReachableMemberException(request));

        Address address =
            Address.create(serviceReference.host(), serviceReference.port());

        return transport.create(address)
            .requestStream(request)
            .map(message -> {
              if (ExceptionProcessor.isError(message)) {
                throw ExceptionProcessor.toException(messageDataCodec.decode(message, ErrorData.class));
              } else {
                return messageDataCodec.decode(message, responseType);
              }
            });
      }
    }

    /**
     * Create proxy creates a java generic proxy instance by a given service interface.
     *
     * @param serviceInterface Service Interface type.
     * @return newly created service proxy object.
     */
    public  T api(final Class serviceInterface) {

      final Call serviceCall = this;

      return Reflection.newProxy(serviceInterface, (proxy, method, args) -> {

        Object check = objectToStringEqualsHashCode(method.getName(), serviceInterface, args);
        if (check != null) {
          return check; // toString, hashCode was invoked.
        }

        Metrics.mark(serviceInterface, metrics, method, "request");
        Class parameterizedReturnType = Reflect.parameterizedReturnType(method);
        Class returnType = method.getReturnType();
        final ServiceMessage reqMsg = ServiceMessage.builder()
            .qualifier(Reflect.serviceName(serviceInterface), method.getName())
            .data(method.getParameterCount() != 0 ? args[0] : null)
            .build();

        if (returnType.isAssignableFrom(Mono.class) && parameterizedReturnType.isAssignableFrom(Void.class)) {
          // noinspection unchecked
          return serviceCall.oneWay(reqMsg);

        } else if (returnType.isAssignableFrom(Mono.class)) {
          // noinspection unchecked
          return Mono.from(serviceCall.requestOne(reqMsg, parameterizedReturnType))
              .map(message -> messageDataCodec.decode(message, parameterizedReturnType))
              .transform(mono -> parameterizedReturnType.equals(ServiceMessage.class) ? mono
                  : mono.map(ServiceMessage::data));

        } else if (returnType.isAssignableFrom(Flux.class)) {
          // noinspection unchecked
          return Flux.from(serviceCall.requestMany(reqMsg))
              .map(message -> messageDataCodec.decode(message, parameterizedReturnType))
              .transform(flux -> parameterizedReturnType.equals(ServiceMessage.class) ? flux
                  : flux.map(ServiceMessage::data));

        } else {
          throw new IllegalArgumentException("Return type is not supported on method: " + method);
        }
      });
    }

    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());
    }

    private Object objectToStringEqualsHashCode(String method, Class serviceInterface, Object... args) {
      if (method.equals("hashCode")) {
        return serviceInterface.hashCode();
      } else if (method.equals("equals")) {
        return serviceInterface.equals(args[0]);
      } else if (method.equals("toString")) {
        return serviceInterface.toString();
      } else {
        return null;
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy