Search in sources :

Example 1 with NatsClient

use of io.micronaut.nats.annotation.NatsClient in project micronaut-nats by micronaut-projects.

the class NatsIntroductionAdvice method intercept.

@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
    if (context.hasAnnotation(NatsClient.class)) {
        StaticPublisherState publisherState = publisherCache.get(context.getExecutableMethod(), method -> {
            if (!method.findAnnotation(NatsClient.class).isPresent()) {
                throw new IllegalStateException("No @NatsClient annotation present on method: " + method);
            }
            Optional<String> subject = method.findAnnotation(Subject.class).flatMap(AnnotationValue::stringValue);
            String connection = method.findAnnotation(NatsConnection.class).flatMap(conn -> conn.get("connection", String.class)).orElse(NatsConnection.DEFAULT_CONNECTION);
            Argument<?> bodyArgument = findBodyArgument(method).orElseThrow(() -> new NatsClientException("No valid message body argument found for method: " + method));
            Headers methodHeaders = new Headers();
            List<AnnotationValue<MessageHeader>> headerAnnotations = method.getAnnotationValuesByType(MessageHeader.class);
            // set the values in the class first so methods can override
            Collections.reverse(headerAnnotations);
            headerAnnotations.forEach(header -> {
                String name = header.stringValue("name").orElse(null);
                String value = header.stringValue().orElse(null);
                if (StringUtils.isNotEmpty(name) && StringUtils.isNotEmpty(value)) {
                    methodHeaders.put(name, value);
                }
            });
            NatsMessageSerDes<?> serDes = serDesRegistry.findSerdes(bodyArgument).orElseThrow(() -> new NatsClientException(String.format("Could not find a serializer for the body argument of type [%s]", bodyArgument.getType().getName())));
            ReactivePublisher reactivePublisher;
            try {
                reactivePublisher = beanContext.getBean(ReactivePublisher.class, Qualifiers.byName(connection));
            } catch (Throwable e) {
                throw new NatsClientException(String.format("Failed to retrieve a publisher named [%s] to publish messages", connection), e);
            }
            return new StaticPublisherState(subject.orElse(null), bodyArgument, methodHeaders, method.getReturnType(), connection, serDes, reactivePublisher);
        });
        NatsMessage.Builder builder = NatsMessage.builder();
        Headers headers = publisherState.getHeaders();
        Argument[] arguments = context.getArguments();
        Map<String, Object> parameterValues = context.getParameterValueMap();
        for (Argument argument : arguments) {
            AnnotationValue<MessageHeader> headerAnn = argument.getAnnotation(MessageHeader.class);
            boolean headersObject = argument.getType() == Headers.class;
            if (headerAnn != null) {
                Map.Entry<String, List<String>> entry = getNameAndValue(argument, headerAnn, parameterValues);
                String name = entry.getKey();
                List<String> value = entry.getValue();
                headers.put(name, value);
            } else if (headersObject) {
                Headers dynamicHeaders = (Headers) parameterValues.get(argument.getName());
                dynamicHeaders.forEach(headers::put);
            }
        }
        if (!headers.isEmpty()) {
            builder.headers(headers);
        }
        Object body = parameterValues.get(publisherState.getBodyArgument().getName());
        byte[] converted = publisherState.getSerDes().serialize(body);
        builder = builder.data(converted);
        String subject = publisherState.getSubject().orElse(findSubjectKey(context).orElse(null));
        builder = builder.subject(subject);
        if (subject == null) {
            throw new IllegalStateException("No @Subject annotation present on method: " + context.getExecutableMethod());
        }
        Message message = builder.build();
        ReactivePublisher reactivePublisher = publisherState.getReactivePublisher();
        InterceptedMethod interceptedMethod = InterceptedMethod.of(context);
        try {
            boolean rpc = !interceptedMethod.returnTypeValue().isVoid();
            Mono<?> reactive;
            if (rpc) {
                reactive = Mono.from(reactivePublisher.publishAndReply(message)).flatMap(response -> {
                    Object deserialized = deserialize(response, publisherState.getDataType(), publisherState.getDataType());
                    if (deserialized == null) {
                        return Mono.empty();
                    } else {
                        return Mono.just(deserialized);
                    }
                });
                if (interceptedMethod.resultType() == InterceptedMethod.ResultType.SYNCHRONOUS) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Publish is an RPC call. Blocking until a response is received.", context);
                    }
                } else {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Publish is an RPC call. Publisher will complete when a response is received.", context);
                    }
                    reactive = reactive.subscribeOn(scheduler);
                }
            } else {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Sending the message.", context);
                }
                reactive = Mono.from(reactivePublisher.publish(message)).onErrorMap(throwable -> new NatsClientException(String.format("Failed to publish a message with subject: [%s]", subject), throwable, Collections.singletonList(message)));
            }
            switch(interceptedMethod.resultType()) {
                case PUBLISHER:
                    return interceptedMethod.handleResult(reactive);
                case COMPLETION_STAGE:
                    CompletableFuture<Object> future = new CompletableFuture<>();
                    reactive.subscribe(new Subscriber<Object>() {

                        Object value = null;

                        @Override
                        public void onSubscribe(Subscription s) {
                            s.request(1);
                        }

                        @Override
                        public void onNext(Object o) {
                            value = o;
                        }

                        @Override
                        public void onError(Throwable t) {
                            future.completeExceptionally(t);
                        }

                        @Override
                        public void onComplete() {
                            future.complete(value);
                        }
                    });
                    return interceptedMethod.handleResult(future);
                case SYNCHRONOUS:
                    return interceptedMethod.handleResult(reactive.block());
                default:
                    return interceptedMethod.unsupported();
            }
        } catch (Exception e) {
            return interceptedMethod.handleException(e);
        }
    } else {
        return context.proceed();
    }
}
Also used : Arrays(java.util.Arrays) BeanContext(io.micronaut.context.BeanContext) NatsConnection(io.micronaut.nats.annotation.NatsConnection) LoggerFactory(org.slf4j.LoggerFactory) MessageHeader(io.micronaut.messaging.annotation.MessageHeader) CompletableFuture(java.util.concurrent.CompletableFuture) NatsMessageSerDes(io.micronaut.nats.serdes.NatsMessageSerDes) Caffeine(io.micronaut.caffeine.cache.Caffeine) Scheduler(reactor.core.scheduler.Scheduler) InterceptedMethod(io.micronaut.aop.InterceptedMethod) ExecutableMethod(io.micronaut.inject.ExecutableMethod) Message(io.nats.client.Message) InterceptorBean(io.micronaut.aop.InterceptorBean) TaskExecutors(io.micronaut.scheduling.TaskExecutors) NatsMessageSerDesRegistry(io.micronaut.nats.serdes.NatsMessageSerDesRegistry) MethodInterceptor(io.micronaut.aop.MethodInterceptor) NatsClientException(io.micronaut.nats.exception.NatsClientException) Map(java.util.Map) Schedulers(reactor.core.scheduler.Schedulers) Argument(io.micronaut.core.type.Argument) ConversionService(io.micronaut.core.convert.ConversionService) Subscriber(org.reactivestreams.Subscriber) ExecutorService(java.util.concurrent.ExecutorService) Logger(org.slf4j.Logger) Headers(io.nats.client.impl.Headers) Qualifiers(io.micronaut.inject.qualifiers.Qualifiers) NatsMessage(io.nats.client.impl.NatsMessage) Singleton(jakarta.inject.Singleton) Mono(reactor.core.publisher.Mono) Objects(java.util.Objects) StringUtils(io.micronaut.core.util.StringUtils) AbstractMap(java.util.AbstractMap) List(java.util.List) MethodInvocationContext(io.micronaut.aop.MethodInvocationContext) AnnotationValue(io.micronaut.core.annotation.AnnotationValue) Subscription(org.reactivestreams.Subscription) Subject(io.micronaut.nats.annotation.Subject) Optional(java.util.Optional) Cache(io.micronaut.caffeine.cache.Cache) NatsClient(io.micronaut.nats.annotation.NatsClient) Collections(java.util.Collections) MessageBody(io.micronaut.messaging.annotation.MessageBody) ReactivePublisher(io.micronaut.nats.reactive.ReactivePublisher) Named(jakarta.inject.Named) Argument(io.micronaut.core.type.Argument) Message(io.nats.client.Message) NatsMessage(io.nats.client.impl.NatsMessage) Headers(io.nats.client.impl.Headers) CompletableFuture(java.util.concurrent.CompletableFuture) ReactivePublisher(io.micronaut.nats.reactive.ReactivePublisher) NatsMessage(io.nats.client.impl.NatsMessage) List(java.util.List) Subscription(org.reactivestreams.Subscription) NatsClientException(io.micronaut.nats.exception.NatsClientException) InterceptedMethod(io.micronaut.aop.InterceptedMethod) Subject(io.micronaut.nats.annotation.Subject) NatsClientException(io.micronaut.nats.exception.NatsClientException) AnnotationValue(io.micronaut.core.annotation.AnnotationValue) MessageHeader(io.micronaut.messaging.annotation.MessageHeader) Map(java.util.Map) AbstractMap(java.util.AbstractMap)

Aggregations

InterceptedMethod (io.micronaut.aop.InterceptedMethod)1 InterceptorBean (io.micronaut.aop.InterceptorBean)1 MethodInterceptor (io.micronaut.aop.MethodInterceptor)1 MethodInvocationContext (io.micronaut.aop.MethodInvocationContext)1 Cache (io.micronaut.caffeine.cache.Cache)1 Caffeine (io.micronaut.caffeine.cache.Caffeine)1 BeanContext (io.micronaut.context.BeanContext)1 AnnotationValue (io.micronaut.core.annotation.AnnotationValue)1 ConversionService (io.micronaut.core.convert.ConversionService)1 Argument (io.micronaut.core.type.Argument)1 StringUtils (io.micronaut.core.util.StringUtils)1 ExecutableMethod (io.micronaut.inject.ExecutableMethod)1 Qualifiers (io.micronaut.inject.qualifiers.Qualifiers)1 MessageBody (io.micronaut.messaging.annotation.MessageBody)1 MessageHeader (io.micronaut.messaging.annotation.MessageHeader)1 NatsClient (io.micronaut.nats.annotation.NatsClient)1 NatsConnection (io.micronaut.nats.annotation.NatsConnection)1 Subject (io.micronaut.nats.annotation.Subject)1 NatsClientException (io.micronaut.nats.exception.NatsClientException)1 ReactivePublisher (io.micronaut.nats.reactive.ReactivePublisher)1