Search in sources :

Example 1 with CompletionAwareSubscriber

use of io.micronaut.core.async.subscriber.CompletionAwareSubscriber in project micronaut-core by micronaut-projects.

the class RoutingInBoundHandler method buildSubscriber.

private Subscriber<Object> buildSubscriber(NettyHttpRequest<?> request, RouteMatch<?> finalRoute, MonoSink<RouteMatch<?>> emitter) {
    boolean isFormData = request.isFormOrMultipartData();
    if (isFormData) {
        return new CompletionAwareSubscriber<Object>() {

            final boolean alwaysAddContent = request.isFormData();

            RouteMatch<?> routeMatch = finalRoute;

            final AtomicBoolean executed = new AtomicBoolean(false);

            final AtomicLong pressureRequested = new AtomicLong(0);

            final ConcurrentHashMap<String, Sinks.Many<Object>> subjectsByDataName = new ConcurrentHashMap<>();

            final Collection<Sinks.Many<Object>> downstreamSubscribers = Collections.synchronizedList(new ArrayList<>());

            final ConcurrentHashMap<IdentityWrapper, HttpDataReference> dataReferences = new ConcurrentHashMap<>();

            final ConversionService conversionService = ConversionService.SHARED;

            Subscription s;

            final LongConsumer onRequest = num -> pressureRequested.updateAndGet(p -> {
                long newVal = p - num;
                if (newVal < 0) {
                    s.request(num - p);
                    return 0;
                } else {
                    return newVal;
                }
            });

            Flux processFlowable(Sinks.Many<Object> many, HttpDataReference dataReference, boolean controlsFlow) {
                Flux flux = many.asFlux();
                if (controlsFlow) {
                    flux = flux.doOnRequest(onRequest);
                }
                return flux.doAfterTerminate(() -> {
                    if (controlsFlow) {
                        dataReference.destroy();
                    }
                });
            }

            @Override
            protected void doOnSubscribe(Subscription subscription) {
                this.s = subscription;
                subscription.request(1);
            }

            @Override
            protected void doOnNext(Object message) {
                boolean executed = this.executed.get();
                if (message instanceof ByteBufHolder) {
                    if (message instanceof HttpData) {
                        HttpData data = (HttpData) message;
                        if (LOG.isTraceEnabled()) {
                            LOG.trace("Received HTTP Data for request [{}]: {}", request, message);
                        }
                        String name = data.getName();
                        Optional<Argument<?>> requiredInput = routeMatch.getRequiredInput(name);
                        if (requiredInput.isPresent()) {
                            Argument<?> argument = requiredInput.get();
                            Supplier<Object> value;
                            boolean isPublisher = Publishers.isConvertibleToPublisher(argument.getType());
                            boolean chunkedProcessing = false;
                            if (isPublisher) {
                                HttpDataReference dataReference = dataReferences.computeIfAbsent(new IdentityWrapper(data), key -> new HttpDataReference(data));
                                Argument typeVariable;
                                if (StreamingFileUpload.class.isAssignableFrom(argument.getType())) {
                                    typeVariable = ARGUMENT_PART_DATA;
                                } else {
                                    typeVariable = argument.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
                                }
                                Class typeVariableType = typeVariable.getType();
                                Sinks.Many<Object> namedSubject = subjectsByDataName.computeIfAbsent(name, key -> makeDownstreamUnicastProcessor());
                                chunkedProcessing = PartData.class.equals(typeVariableType) || Publishers.isConvertibleToPublisher(typeVariableType) || ClassUtils.isJavaLangType(typeVariableType);
                                if (Publishers.isConvertibleToPublisher(typeVariableType)) {
                                    boolean streamingFileUpload = StreamingFileUpload.class.isAssignableFrom(typeVariableType);
                                    if (streamingFileUpload) {
                                        typeVariable = ARGUMENT_PART_DATA;
                                    } else {
                                        typeVariable = typeVariable.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
                                    }
                                    dataReference.subject.getAndUpdate(subject -> {
                                        if (subject == null) {
                                            Sinks.Many<Object> childSubject = makeDownstreamUnicastProcessor();
                                            Flux flowable = processFlowable(childSubject, dataReference, true);
                                            if (streamingFileUpload && data instanceof FileUpload) {
                                                namedSubject.tryEmitNext(new NettyStreamingFileUpload((FileUpload) data, serverConfiguration.getMultipart(), getIoExecutor(), (Flux<PartData>) flowable));
                                            } else {
                                                namedSubject.tryEmitNext(flowable);
                                            }
                                            return childSubject;
                                        }
                                        return subject;
                                    });
                                }
                                Sinks.Many<Object> subject;
                                final Sinks.Many<Object> ds = dataReference.subject.get();
                                if (ds != null) {
                                    subject = ds;
                                } else {
                                    subject = namedSubject;
                                }
                                Object part = data;
                                if (chunkedProcessing) {
                                    HttpDataReference.Component component;
                                    try {
                                        component = dataReference.addComponent();
                                        if (component == null) {
                                            s.request(1);
                                            return;
                                        }
                                    } catch (IOException e) {
                                        subject.tryEmitError(e);
                                        s.cancel();
                                        return;
                                    }
                                    part = new NettyPartData(dataReference, component);
                                }
                                if (data instanceof FileUpload && StreamingFileUpload.class.isAssignableFrom(argument.getType())) {
                                    dataReference.upload.getAndUpdate(upload -> {
                                        if (upload == null) {
                                            return new NettyStreamingFileUpload((FileUpload) data, serverConfiguration.getMultipart(), getIoExecutor(), (Flux<PartData>) processFlowable(subject, dataReference, true));
                                        }
                                        return upload;
                                    });
                                }
                                Optional<?> converted = conversionService.convert(part, typeVariable);
                                converted.ifPresent(subject::tryEmitNext);
                                if (data.isCompleted() && chunkedProcessing) {
                                    subject.tryEmitComplete();
                                }
                                value = () -> {
                                    StreamingFileUpload upload = dataReference.upload.get();
                                    if (upload != null) {
                                        return upload;
                                    } else {
                                        return processFlowable(namedSubject, dataReference, dataReference.subject.get() == null);
                                    }
                                };
                            } else {
                                if (data instanceof Attribute && !data.isCompleted()) {
                                    request.addContent(data);
                                    s.request(1);
                                    return;
                                } else {
                                    value = () -> {
                                        if (data.refCnt() > 0) {
                                            return data;
                                        } else {
                                            return null;
                                        }
                                    };
                                }
                            }
                            if (!executed) {
                                String argumentName = argument.getName();
                                if (!routeMatch.isSatisfied(argumentName)) {
                                    Object fulfillParamter = value.get();
                                    routeMatch = routeMatch.fulfill(Collections.singletonMap(argumentName, fulfillParamter));
                                    // the data to the request ensures it is cleaned up after the route completes.
                                    if (!alwaysAddContent && fulfillParamter instanceof ByteBufHolder) {
                                        request.addContent((ByteBufHolder) fulfillParamter);
                                    }
                                }
                                if (isPublisher && chunkedProcessing) {
                                    // accounting for the previous request
                                    pressureRequested.incrementAndGet();
                                }
                                if (routeMatch.isExecutable() || message instanceof LastHttpContent) {
                                    executeRoute();
                                    executed = true;
                                }
                            }
                            if (alwaysAddContent) {
                                request.addContent(data);
                            }
                            if (!executed || !chunkedProcessing) {
                                s.request(1);
                            }
                        } else {
                            request.addContent(data);
                            s.request(1);
                        }
                    } else {
                        request.addContent((ByteBufHolder) message);
                        s.request(1);
                    }
                } else {
                    ((NettyHttpRequest) request).setBody(message);
                    s.request(1);
                }
            }

            @Override
            protected void doOnError(Throwable t) {
                s.cancel();
                for (Sinks.Many<Object> subject : downstreamSubscribers) {
                    subject.tryEmitError(t);
                }
                emitter.error(t);
            }

            @Override
            protected void doOnComplete() {
                for (Sinks.Many<Object> subject : downstreamSubscribers) {
                    // subjects will ignore the onComplete if they're already done
                    subject.tryEmitComplete();
                }
                executeRoute();
            }

            private Sinks.Many<Object> makeDownstreamUnicastProcessor() {
                Sinks.Many<Object> processor = Sinks.many().unicast().onBackpressureBuffer();
                downstreamSubscribers.add(processor);
                return processor;
            }

            private void executeRoute() {
                if (executed.compareAndSet(false, true)) {
                    emitter.success(routeMatch);
                }
            }
        };
    } else {
        return new CompletionAwareSubscriber<Object>() {

            private Subscription s;

            private RouteMatch<?> routeMatch = finalRoute;

            private AtomicBoolean executed = new AtomicBoolean(false);

            @Override
            protected void doOnSubscribe(Subscription subscription) {
                this.s = subscription;
                subscription.request(1);
            }

            @Override
            protected void doOnNext(Object message) {
                if (message instanceof ByteBufHolder) {
                    request.addContent((ByteBufHolder) message);
                    s.request(1);
                } else {
                    ((NettyHttpRequest) request).setBody(message);
                    s.request(1);
                }
            }

            @Override
            protected void doOnError(Throwable t) {
                s.cancel();
                emitter.error(t);
            }

            @Override
            protected void doOnComplete() {
                if (executed.compareAndSet(false, true)) {
                    emitter.success(routeMatch);
                }
            }
        };
    }
}
Also used : Publishers(io.micronaut.core.async.publisher.Publishers) HttpRequestTerminatedEvent(io.micronaut.http.context.event.HttpRequestTerminatedEvent) HttpHeaders(io.micronaut.http.HttpHeaders) Internal(io.micronaut.core.annotation.Internal) NettyStreamedFileCustomizableResponseType(io.micronaut.http.server.netty.types.files.NettyStreamedFileCustomizableResponseType) ByteBufHolder(io.netty.buffer.ByteBufHolder) HttpStatus(io.micronaut.http.HttpStatus) IdleState(io.netty.handler.timeout.IdleState) NettySystemFileCustomizableResponseType(io.micronaut.http.server.netty.types.files.NettySystemFileCustomizableResponseType) NettyPartData(io.micronaut.http.server.netty.multipart.NettyPartData) TextPlainCodec(io.micronaut.runtime.http.codec.TextPlainCodec) IdleStateEvent(io.netty.handler.timeout.IdleStateEvent) Set(java.util.Set) RequestArgumentSatisfier(io.micronaut.http.server.binding.RequestArgumentSatisfier) SSLException(javax.net.ssl.SSLException) RouteInfo(io.micronaut.web.router.RouteInfo) Writable(io.micronaut.core.io.Writable) Body(io.micronaut.http.annotation.Body) DefaultFullHttpResponse(io.netty.handler.codec.http.DefaultFullHttpResponse) Http2Error(io.netty.handler.codec.http2.Http2Error) InternalServerException(io.micronaut.http.server.exceptions.InternalServerException) RouteMatch(io.micronaut.web.router.RouteMatch) NettyByteBufferFactory(io.micronaut.buffer.netty.NettyByteBufferFactory) NettyMutableHttpResponse(io.micronaut.http.netty.NettyMutableHttpResponse) MonoSink(reactor.core.publisher.MonoSink) Supplier(java.util.function.Supplier) ArrayList(java.util.ArrayList) UriRouteMatch(io.micronaut.web.router.UriRouteMatch) HttpData(io.netty.handler.codec.http.multipart.HttpData) Nullable(io.micronaut.core.annotation.Nullable) DuplicateRouteException(io.micronaut.web.router.exceptions.DuplicateRouteException) BiConsumer(java.util.function.BiConsumer) ByteBuffer(io.micronaut.core.io.buffer.ByteBuffer) Argument(io.micronaut.core.type.Argument) HttpRequest(io.micronaut.http.HttpRequest) ConversionService(io.micronaut.core.convert.ConversionService) ServerRequestContext(io.micronaut.http.context.ServerRequestContext) ErrorResponseProcessor(io.micronaut.http.server.exceptions.response.ErrorResponseProcessor) MediaTypeCodecRegistry(io.micronaut.http.codec.MediaTypeCodecRegistry) HttpContent(io.netty.handler.codec.http.HttpContent) FileUpload(io.netty.handler.codec.http.multipart.FileUpload) ClosedChannelException(java.nio.channels.ClosedChannelException) Publisher(org.reactivestreams.Publisher) Mono(reactor.core.publisher.Mono) IOException(java.io.IOException) NettyHttpResponseBuilder(io.micronaut.http.netty.NettyHttpResponseBuilder) File(java.io.File) AbstractNettyHttpRequest(io.micronaut.http.netty.AbstractNettyHttpRequest) DefaultHttpContent(io.netty.handler.codec.http.DefaultHttpContent) Flux(reactor.core.publisher.Flux) AtomicLong(java.util.concurrent.atomic.AtomicLong) Paths(java.nio.file.Paths) SimpleChannelInboundHandler(io.netty.channel.SimpleChannelInboundHandler) HttpHeaderNames(io.netty.handler.codec.http.HttpHeaderNames) Future(io.netty.util.concurrent.Future) RouteExecutor(io.micronaut.http.server.RouteExecutor) Sinks(reactor.core.publisher.Sinks) StreamingFileUpload(io.micronaut.http.multipart.StreamingFileUpload) URL(java.net.URL) URISyntaxException(java.net.URISyntaxException) LoggerFactory(org.slf4j.LoggerFactory) NettyHttpServerConfiguration(io.micronaut.http.server.netty.configuration.NettyHttpServerConfiguration) FileCustomizableResponseType(io.micronaut.http.server.types.files.FileCustomizableResponseType) Unpooled(io.netty.buffer.Unpooled) CompletionAwareSubscriber(io.micronaut.core.async.subscriber.CompletionAwareSubscriber) PartData(io.micronaut.http.multipart.PartData) NettyCustomizableResponseTypeHandler(io.micronaut.http.server.netty.types.NettyCustomizableResponseTypeHandler) StaticResourceResolver(io.micronaut.web.router.resource.StaticResourceResolver) MediaType(io.micronaut.http.MediaType) Http2Exception(io.netty.handler.codec.http2.Http2Exception) ReferenceCounted(io.micronaut.core.io.buffer.ReferenceCounted) HttpResponse(io.micronaut.http.HttpResponse) ClassUtils(io.micronaut.core.reflect.ClassUtils) Collection(java.util.Collection) MethodBasedRouteMatch(io.micronaut.web.router.MethodBasedRouteMatch) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) MutableHttpResponse(io.micronaut.http.MutableHttpResponse) MutableHttpHeaders(io.micronaut.http.MutableHttpHeaders) NettyCustomizableResponseTypeHandlerRegistry(io.micronaut.http.server.netty.types.NettyCustomizableResponseTypeHandlerRegistry) TooLongFrameException(io.netty.handler.codec.TooLongFrameException) HttpResponseStatus(io.netty.handler.codec.http.HttpResponseStatus) Collectors(java.util.stream.Collectors) ByteBufOutputStream(io.netty.buffer.ByteBufOutputStream) Attribute(io.netty.handler.codec.http.multipart.Attribute) StreamedHttpRequest(io.micronaut.http.netty.stream.StreamedHttpRequest) DecoderResult(io.netty.handler.codec.DecoderResult) List(java.util.List) DefaultHttpHeaders(io.netty.handler.codec.http.DefaultHttpHeaders) Optional(java.util.Optional) HttpAttributes(io.micronaut.http.HttpAttributes) Pattern(java.util.regex.Pattern) HttpVersion(io.netty.handler.codec.http.HttpVersion) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) AtomicReference(java.util.concurrent.atomic.AtomicReference) Sharable(io.netty.channel.ChannelHandler.Sharable) LastHttpContent(io.netty.handler.codec.http.LastHttpContent) HashSet(java.util.HashSet) ErrorContext(io.micronaut.http.server.exceptions.response.ErrorContext) ChannelHandlerContext(io.netty.channel.ChannelHandlerContext) ByteBuf(io.netty.buffer.ByteBuf) Subscriber(org.reactivestreams.Subscriber) ExecutorService(java.util.concurrent.ExecutorService) HttpMethod(io.micronaut.http.HttpMethod) MediaTypeCodec(io.micronaut.http.codec.MediaTypeCodec) Logger(org.slf4j.Logger) HttpHeaderValues(io.netty.handler.codec.http.HttpHeaderValues) ApplicationEventPublisher(io.micronaut.context.event.ApplicationEventPublisher) GenericFutureListener(io.netty.util.concurrent.GenericFutureListener) NettyStreamingFileUpload(io.micronaut.http.server.netty.multipart.NettyStreamingFileUpload) LongConsumer(java.util.function.LongConsumer) NonNull(io.micronaut.core.annotation.NonNull) CollectionUtils(io.micronaut.core.util.CollectionUtils) Subscription(org.reactivestreams.Subscription) Router(io.micronaut.web.router.Router) Collections(java.util.Collections) JsonSubscriber(io.micronaut.http.netty.stream.JsonSubscriber) Argument(io.micronaut.core.type.Argument) Attribute(io.netty.handler.codec.http.multipart.Attribute) ArrayList(java.util.ArrayList) CompletionAwareSubscriber(io.micronaut.core.async.subscriber.CompletionAwareSubscriber) ConversionService(io.micronaut.core.convert.ConversionService) HttpData(io.netty.handler.codec.http.multipart.HttpData) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) Subscription(org.reactivestreams.Subscription) FileUpload(io.netty.handler.codec.http.multipart.FileUpload) StreamingFileUpload(io.micronaut.http.multipart.StreamingFileUpload) NettyStreamingFileUpload(io.micronaut.http.server.netty.multipart.NettyStreamingFileUpload) Sinks(reactor.core.publisher.Sinks) Flux(reactor.core.publisher.Flux) StreamingFileUpload(io.micronaut.http.multipart.StreamingFileUpload) NettyStreamingFileUpload(io.micronaut.http.server.netty.multipart.NettyStreamingFileUpload) IOException(java.io.IOException) LastHttpContent(io.netty.handler.codec.http.LastHttpContent) RouteMatch(io.micronaut.web.router.RouteMatch) UriRouteMatch(io.micronaut.web.router.UriRouteMatch) MethodBasedRouteMatch(io.micronaut.web.router.MethodBasedRouteMatch) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) AtomicLong(java.util.concurrent.atomic.AtomicLong) LongConsumer(java.util.function.LongConsumer) NettyPartData(io.micronaut.http.server.netty.multipart.NettyPartData) NettyPartData(io.micronaut.http.server.netty.multipart.NettyPartData) PartData(io.micronaut.http.multipart.PartData) Collection(java.util.Collection) ByteBufHolder(io.netty.buffer.ByteBufHolder) NettyStreamingFileUpload(io.micronaut.http.server.netty.multipart.NettyStreamingFileUpload) AbstractNettyHttpRequest(io.micronaut.http.netty.AbstractNettyHttpRequest)

Example 2 with CompletionAwareSubscriber

use of io.micronaut.core.async.subscriber.CompletionAwareSubscriber in project micronaut-core by micronaut-projects.

the class JsonContentProcessor method doOnSubscribe.

@Override
protected void doOnSubscribe(Subscription subscription, Subscriber<? super JsonNode> subscriber) {
    if (parentSubscription == null) {
        return;
    }
    boolean streamArray = false;
    boolean isJsonStream = nettyHttpRequest.getContentType().map(mediaType -> mediaType.equals(MediaType.APPLICATION_JSON_STREAM_TYPE)).orElse(false);
    if (subscriber instanceof TypedSubscriber) {
        TypedSubscriber typedSubscriber = (TypedSubscriber) subscriber;
        Argument typeArgument = typedSubscriber.getTypeArgument();
        Class targetType = typeArgument.getType();
        if (Publishers.isConvertibleToPublisher(targetType) && !Publishers.isSingle(targetType)) {
            Optional<Argument<?>> genericArgument = typeArgument.getFirstTypeVariable();
            if (genericArgument.isPresent() && !Iterable.class.isAssignableFrom(genericArgument.get().getType()) && !isJsonStream) {
                // if the generic argument is not a iterable type them stream the array into the publisher
                streamArray = true;
            }
        }
    }
    this.jacksonProcessor = jsonMapper.createReactiveParser(p -> {
    }, streamArray);
    this.jacksonProcessor.subscribe(new CompletionAwareSubscriber<JsonNode>() {

        @Override
        protected void doOnSubscribe(Subscription jsonSubscription) {
            Subscription childSubscription = new Subscription() {

                boolean first = true;

                @Override
                public synchronized void request(long n) {
                    // find a better way in the future
                    if (first) {
                        jsonSubscription.request(n < Long.MAX_VALUE ? n + 1 : n);
                        parentSubscription.request(n < Long.MAX_VALUE ? n + 1 : n);
                    } else {
                        jsonSubscription.request(n);
                        parentSubscription.request(n);
                    }
                }

                @Override
                public synchronized void cancel() {
                    jsonSubscription.cancel();
                    parentSubscription.cancel();
                }
            };
            subscriber.onSubscribe(childSubscription);
        }

        @Override
        protected void doOnNext(JsonNode message) {
            subscriber.onNext(message);
        }

        @Override
        protected void doOnError(Throwable t) {
            subscriber.onError(t);
        }

        @Override
        protected void doOnComplete() {
            subscriber.onComplete();
        }
    });
    jacksonProcessor.onSubscribe(subscription);
}
Also used : Publishers(io.micronaut.core.async.publisher.Publishers) JsonNode(io.micronaut.json.tree.JsonNode) HttpServerConfiguration(io.micronaut.http.server.HttpServerConfiguration) Processor(org.reactivestreams.Processor) Internal(io.micronaut.core.annotation.Internal) ByteBufHolder(io.netty.buffer.ByteBufHolder) ByteBufUtil(io.netty.buffer.ByteBufUtil) CompletionAwareSubscriber(io.micronaut.core.async.subscriber.CompletionAwareSubscriber) ByteBuf(io.netty.buffer.ByteBuf) TypedSubscriber(io.micronaut.core.async.subscriber.TypedSubscriber) MediaType(io.micronaut.http.MediaType) ReferenceCountUtil(io.netty.util.ReferenceCountUtil) Subscription(org.reactivestreams.Subscription) JsonMapper(io.micronaut.json.JsonMapper) AbstractHttpContentProcessor(io.micronaut.http.server.netty.AbstractHttpContentProcessor) Optional(java.util.Optional) Argument(io.micronaut.core.type.Argument) Subscriber(org.reactivestreams.Subscriber) NettyHttpRequest(io.micronaut.http.server.netty.NettyHttpRequest) Argument(io.micronaut.core.type.Argument) JsonNode(io.micronaut.json.tree.JsonNode) Subscription(org.reactivestreams.Subscription) TypedSubscriber(io.micronaut.core.async.subscriber.TypedSubscriber)

Example 3 with CompletionAwareSubscriber

use of io.micronaut.core.async.subscriber.CompletionAwareSubscriber in project micronaut-core by micronaut-projects.

the class HttpClientIntroductionAdvice method intercept.

/**
 * Interceptor to apply headers, cookies, parameter and body arguements.
 *
 * @param context The context
 * @return httpClient or future
 */
@Nullable
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
    if (!context.hasStereotype(Client.class)) {
        throw new IllegalStateException("Client advice called from type that is not annotated with @Client: " + context);
    }
    final AnnotationMetadata annotationMetadata = context.getAnnotationMetadata();
    Class<?> declaringType = context.getDeclaringType();
    if (Closeable.class == declaringType || AutoCloseable.class == declaringType) {
        clientFactory.disposeClient(annotationMetadata);
        return null;
    }
    Optional<Class<? extends Annotation>> httpMethodMapping = context.getAnnotationTypeByStereotype(HttpMethodMapping.class);
    HttpClient httpClient = clientFactory.getClient(annotationMetadata);
    if (context.hasStereotype(HttpMethodMapping.class) && httpClient != null) {
        AnnotationValue<HttpMethodMapping> mapping = context.getAnnotation(HttpMethodMapping.class);
        String uri = mapping.getRequiredValue(String.class);
        if (StringUtils.isEmpty(uri)) {
            uri = "/" + context.getMethodName();
        }
        Class<? extends Annotation> annotationType = httpMethodMapping.get();
        HttpMethod httpMethod = HttpMethod.parse(annotationType.getSimpleName().toUpperCase(Locale.ENGLISH));
        String httpMethodName = context.stringValue(CustomHttpMethod.class, "method").orElse(httpMethod.name());
        MutableHttpRequest<?> request = HttpRequest.create(httpMethod, "", httpMethodName);
        UriMatchTemplate uriTemplate = UriMatchTemplate.of("");
        if (!(uri.length() == 1 && uri.charAt(0) == '/')) {
            uriTemplate = uriTemplate.nest(uri);
        }
        Map<String, Object> pathParams = new HashMap<>();
        Map<String, List<String>> queryParams = new LinkedHashMap<>();
        ClientRequestUriContext uriContext = new ClientRequestUriContext(uriTemplate, pathParams, queryParams);
        List<Argument> bodyArguments = new ArrayList<>();
        List<String> uriVariables = uriTemplate.getVariableNames();
        Map<String, MutableArgumentValue<?>> parameters = context.getParameters();
        ClientArgumentRequestBinder<Object> defaultBinder = (ctx, uriCtx, value, req) -> {
            Argument<?> argument = ctx.getArgument();
            if (uriCtx.getUriTemplate().getVariableNames().contains(argument.getName())) {
                String name = argument.getAnnotationMetadata().stringValue(Bindable.class).orElse(argument.getName());
                // Convert and put as path param
                if (argument.getAnnotationMetadata().hasStereotype(Format.class)) {
                    ConversionService.SHARED.convert(value, ConversionContext.STRING.with(argument.getAnnotationMetadata())).ifPresent(v -> pathParams.put(name, v));
                } else {
                    pathParams.put(name, value);
                }
            } else {
                bodyArguments.add(ctx.getArgument());
            }
        };
        // Apply all the method binders
        List<Class<? extends Annotation>> methodBinderTypes = context.getAnnotationTypesByStereotype(Bindable.class);
        // @Version is not a bindable, so it needs to looked for separately
        methodBinderTypes.addAll(context.getAnnotationTypesByStereotype(Version.class));
        if (!CollectionUtils.isEmpty(methodBinderTypes)) {
            for (Class<? extends Annotation> binderType : methodBinderTypes) {
                binderRegistry.findAnnotatedBinder(binderType).ifPresent(b -> b.bind(context, uriContext, request));
            }
        }
        InterceptedMethod interceptedMethod = InterceptedMethod.of(context);
        // Apply all the argument binders
        Argument[] arguments = context.getArguments();
        if (arguments.length > 0) {
            for (Argument argument : arguments) {
                Object definedValue = getValue(argument, context, parameters);
                if (definedValue != null) {
                    final ClientArgumentRequestBinder<Object> binder = (ClientArgumentRequestBinder<Object>) binderRegistry.findArgumentBinder((Argument<Object>) argument).orElse(defaultBinder);
                    ArgumentConversionContext conversionContext = ConversionContext.of(argument);
                    binder.bind(conversionContext, uriContext, definedValue, request);
                    if (conversionContext.hasErrors()) {
                        return interceptedMethod.handleException(new ConversionErrorException(argument, conversionContext.getLastError().get()));
                    }
                }
            }
        }
        Object body = request.getBody().orElse(null);
        if (body == null && !bodyArguments.isEmpty()) {
            Map<String, Object> bodyMap = new LinkedHashMap<>();
            for (Argument bodyArgument : bodyArguments) {
                String argumentName = bodyArgument.getName();
                MutableArgumentValue<?> value = parameters.get(argumentName);
                bodyMap.put(argumentName, value.getValue());
            }
            body = bodyMap;
            request.body(body);
        }
        boolean variableSatisfied = uriVariables.isEmpty() || pathParams.keySet().containsAll(uriVariables);
        if (body != null && !variableSatisfied) {
            if (body instanceof Map) {
                for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) body).entrySet()) {
                    String k = entry.getKey().toString();
                    Object v = entry.getValue();
                    if (v != null) {
                        pathParams.putIfAbsent(k, v);
                    }
                }
            } else {
                BeanMap<Object> beanMap = BeanMap.of(body);
                for (Map.Entry<String, Object> entry : beanMap.entrySet()) {
                    String k = entry.getKey();
                    Object v = entry.getValue();
                    if (v != null) {
                        pathParams.putIfAbsent(k, v);
                    }
                }
            }
        }
        if (!HttpMethod.permitsRequestBody(httpMethod)) {
            // If a binder set the body and the method does not permit it, reset to null
            request.body(null);
            body = null;
        }
        uri = uriTemplate.expand(pathParams);
        // Remove all the pathParams that have already been used.
        // Other path parameters are added to query
        uriVariables.forEach(pathParams::remove);
        addParametersToQuery(pathParams, uriContext);
        // The original query can be added by getting it from the request.getUri() and appending
        request.uri(URI.create(appendQuery(uri, uriContext.getQueryParameters())));
        if (body != null && !request.getContentType().isPresent()) {
            MediaType[] contentTypes = MediaType.of(context.stringValues(Produces.class));
            if (ArrayUtils.isEmpty(contentTypes)) {
                contentTypes = DEFAULT_ACCEPT_TYPES;
            }
            if (ArrayUtils.isNotEmpty(contentTypes)) {
                request.contentType(contentTypes[0]);
            }
        }
        request.setAttribute(HttpAttributes.INVOCATION_CONTEXT, context);
        // Set the URI template used to make the request for tracing purposes
        request.setAttribute(HttpAttributes.URI_TEMPLATE, resolveTemplate(annotationMetadata, uriTemplate.toString()));
        String serviceId = getClientId(annotationMetadata);
        Argument<?> errorType = annotationMetadata.classValue(Client.class, "errorType").map((Function<Class, Argument>) Argument::of).orElse(HttpClient.DEFAULT_ERROR_TYPE);
        request.setAttribute(HttpAttributes.SERVICE_ID, serviceId);
        final MediaType[] acceptTypes;
        Collection<MediaType> accept = request.accept();
        if (accept.isEmpty()) {
            String[] consumesMediaType = context.stringValues(Consumes.class);
            if (ArrayUtils.isEmpty(consumesMediaType)) {
                acceptTypes = DEFAULT_ACCEPT_TYPES;
            } else {
                acceptTypes = MediaType.of(consumesMediaType);
            }
            request.accept(acceptTypes);
        } else {
            acceptTypes = accept.toArray(MediaType.EMPTY_ARRAY);
        }
        ReturnType<?> returnType = context.getReturnType();
        try {
            Argument<?> valueType = interceptedMethod.returnTypeValue();
            Class<?> reactiveValueType = valueType.getType();
            switch(interceptedMethod.resultType()) {
                case PUBLISHER:
                    boolean isSingle = returnType.isSingleResult() || returnType.isCompletable() || HttpResponse.class.isAssignableFrom(reactiveValueType) || HttpStatus.class == reactiveValueType;
                    Publisher<?> publisher;
                    if (!isSingle && httpClient instanceof StreamingHttpClient) {
                        publisher = httpClientResponseStreamingPublisher((StreamingHttpClient) httpClient, acceptTypes, request, errorType, valueType);
                    } else {
                        publisher = httpClientResponsePublisher(httpClient, request, returnType, errorType, valueType);
                    }
                    Object finalPublisher = interceptedMethod.handleResult(publisher);
                    for (ReactiveClientResultTransformer transformer : transformers) {
                        finalPublisher = transformer.transform(finalPublisher);
                    }
                    return finalPublisher;
                case COMPLETION_STAGE:
                    Publisher<?> csPublisher = httpClientResponsePublisher(httpClient, request, returnType, errorType, valueType);
                    CompletableFuture<Object> future = new CompletableFuture<>();
                    csPublisher.subscribe(new CompletionAwareSubscriber<Object>() {

                        AtomicReference<Object> reference = new AtomicReference<>();

                        @Override
                        protected void doOnSubscribe(Subscription subscription) {
                            subscription.request(1);
                        }

                        @Override
                        protected void doOnNext(Object message) {
                            if (Void.class != reactiveValueType) {
                                reference.set(message);
                            }
                        }

                        @Override
                        protected void doOnError(Throwable t) {
                            if (t instanceof HttpClientResponseException) {
                                HttpClientResponseException e = (HttpClientResponseException) t;
                                if (e.getStatus() == HttpStatus.NOT_FOUND) {
                                    if (reactiveValueType == Optional.class) {
                                        future.complete(Optional.empty());
                                    } else if (HttpResponse.class.isAssignableFrom(reactiveValueType)) {
                                        future.complete(e.getResponse());
                                    } else {
                                        future.complete(null);
                                    }
                                    return;
                                }
                            }
                            if (LOG.isErrorEnabled()) {
                                LOG.error("Client [" + declaringType.getName() + "] received HTTP error response: " + t.getMessage(), t);
                            }
                            future.completeExceptionally(t);
                        }

                        @Override
                        protected void doOnComplete() {
                            future.complete(reference.get());
                        }
                    });
                    return interceptedMethod.handleResult(future);
                case SYNCHRONOUS:
                    Class<?> javaReturnType = returnType.getType();
                    BlockingHttpClient blockingHttpClient = httpClient.toBlocking();
                    if (void.class == javaReturnType || httpMethod == HttpMethod.HEAD) {
                        request.getHeaders().remove(HttpHeaders.ACCEPT);
                    }
                    if (HttpResponse.class.isAssignableFrom(javaReturnType)) {
                        return handleBlockingCall(javaReturnType, () -> blockingHttpClient.exchange(request, returnType.asArgument().getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT), errorType));
                    } else if (void.class == javaReturnType) {
                        return handleBlockingCall(javaReturnType, () -> blockingHttpClient.exchange(request, null, errorType));
                    } else {
                        return handleBlockingCall(javaReturnType, () -> blockingHttpClient.retrieve(request, returnType.asArgument(), errorType));
                    }
                default:
                    return interceptedMethod.unsupported();
            }
        } catch (Exception e) {
            return interceptedMethod.handleException(e);
        }
    }
    // try other introduction advice
    return context.proceed();
}
Also used : Publishers(io.micronaut.core.async.publisher.Publishers) Bindable(io.micronaut.core.bind.annotation.Bindable) Arrays(java.util.Arrays) Version(io.micronaut.core.version.annotation.Version) CustomHttpMethod(io.micronaut.http.annotation.CustomHttpMethod) Produces(io.micronaut.http.annotation.Produces) ArrayUtils(io.micronaut.core.util.ArrayUtils) LoggerFactory(org.slf4j.LoggerFactory) HttpHeaders(io.micronaut.http.HttpHeaders) SseClient(io.micronaut.http.client.sse.SseClient) Internal(io.micronaut.core.annotation.Internal) Event(io.micronaut.http.sse.Event) CompletionAwareSubscriber(io.micronaut.core.async.subscriber.CompletionAwareSubscriber) HttpStatus(io.micronaut.http.HttpStatus) MediaType(io.micronaut.http.MediaType) Locale(java.util.Locale) Map(java.util.Map) ClientArgumentRequestBinder(io.micronaut.http.client.bind.ClientArgumentRequestBinder) HttpResponse(io.micronaut.http.HttpResponse) HttpClientResponseException(io.micronaut.http.client.exceptions.HttpClientResponseException) URI(java.net.URI) HttpClient(io.micronaut.http.client.HttpClient) JsonMediaTypeCodec(io.micronaut.json.codec.JsonMediaTypeCodec) MutableArgumentValue(io.micronaut.core.type.MutableArgumentValue) Collection(java.util.Collection) Singleton(jakarta.inject.Singleton) HttpClientBinderRegistry(io.micronaut.http.client.bind.HttpClientBinderRegistry) StringUtils(io.micronaut.core.util.StringUtils) Format(io.micronaut.core.convert.format.Format) List(java.util.List) MethodInvocationContext(io.micronaut.aop.MethodInvocationContext) AnnotationValue(io.micronaut.core.annotation.AnnotationValue) Annotation(java.lang.annotation.Annotation) Optional(java.util.Optional) HttpAttributes(io.micronaut.http.HttpAttributes) StreamingHttpClient(io.micronaut.http.client.StreamingHttpClient) MutableHttpRequest(io.micronaut.http.MutableHttpRequest) HttpClientRegistry(io.micronaut.http.client.HttpClientRegistry) UriBuilder(io.micronaut.http.uri.UriBuilder) ArgumentConversionContext(io.micronaut.core.convert.ArgumentConversionContext) HttpMethodMapping(io.micronaut.http.annotation.HttpMethodMapping) ReactiveClientResultTransformer(io.micronaut.http.client.ReactiveClientResultTransformer) ConversionErrorException(io.micronaut.core.convert.exceptions.ConversionErrorException) UriMatchTemplate(io.micronaut.http.uri.UriMatchTemplate) HashMap(java.util.HashMap) CompletableFuture(java.util.concurrent.CompletableFuture) Client(io.micronaut.http.client.annotation.Client) InterceptedMethod(io.micronaut.aop.InterceptedMethod) AtomicReference(java.util.concurrent.atomic.AtomicReference) Function(java.util.function.Function) Supplier(java.util.function.Supplier) BeanMap(io.micronaut.core.beans.BeanMap) ArrayList(java.util.ArrayList) LinkedHashMap(java.util.LinkedHashMap) BlockingHttpClient(io.micronaut.http.client.BlockingHttpClient) MethodInterceptor(io.micronaut.aop.MethodInterceptor) BootstrapContextCompatible(io.micronaut.context.annotation.BootstrapContextCompatible) Nullable(io.micronaut.core.annotation.Nullable) ByteBuffer(io.micronaut.core.io.buffer.ByteBuffer) ReturnType(io.micronaut.core.type.ReturnType) Argument(io.micronaut.core.type.Argument) HttpRequest(io.micronaut.http.HttpRequest) ClientRequestUriContext(io.micronaut.http.client.bind.ClientRequestUriContext) ConversionService(io.micronaut.core.convert.ConversionService) ConfigurationException(io.micronaut.context.exceptions.ConfigurationException) HttpMethod(io.micronaut.http.HttpMethod) Logger(org.slf4j.Logger) Publisher(org.reactivestreams.Publisher) ConversionContext(io.micronaut.core.convert.ConversionContext) CollectionUtils(io.micronaut.core.util.CollectionUtils) Consumes(io.micronaut.http.annotation.Consumes) Subscription(org.reactivestreams.Subscription) Closeable(java.io.Closeable) AnnotationMetadata(io.micronaut.core.annotation.AnnotationMetadata) Collections(java.util.Collections) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) Closeable(java.io.Closeable) ArrayList(java.util.ArrayList) CustomHttpMethod(io.micronaut.http.annotation.CustomHttpMethod) AnnotationMetadata(io.micronaut.core.annotation.AnnotationMetadata) LinkedHashMap(java.util.LinkedHashMap) Version(io.micronaut.core.version.annotation.Version) MediaType(io.micronaut.http.MediaType) List(java.util.List) ArrayList(java.util.ArrayList) ClientArgumentRequestBinder(io.micronaut.http.client.bind.ClientArgumentRequestBinder) SseClient(io.micronaut.http.client.sse.SseClient) HttpClient(io.micronaut.http.client.HttpClient) StreamingHttpClient(io.micronaut.http.client.StreamingHttpClient) Client(io.micronaut.http.client.annotation.Client) BlockingHttpClient(io.micronaut.http.client.BlockingHttpClient) Subscription(org.reactivestreams.Subscription) Optional(java.util.Optional) HttpStatus(io.micronaut.http.HttpStatus) BlockingHttpClient(io.micronaut.http.client.BlockingHttpClient) ClientRequestUriContext(io.micronaut.http.client.bind.ClientRequestUriContext) Produces(io.micronaut.http.annotation.Produces) Map(java.util.Map) HashMap(java.util.HashMap) BeanMap(io.micronaut.core.beans.BeanMap) LinkedHashMap(java.util.LinkedHashMap) CustomHttpMethod(io.micronaut.http.annotation.CustomHttpMethod) HttpMethod(io.micronaut.http.HttpMethod) StreamingHttpClient(io.micronaut.http.client.StreamingHttpClient) Argument(io.micronaut.core.type.Argument) ConversionErrorException(io.micronaut.core.convert.exceptions.ConversionErrorException) UriMatchTemplate(io.micronaut.http.uri.UriMatchTemplate) Function(java.util.function.Function) CompletableFuture(java.util.concurrent.CompletableFuture) Format(io.micronaut.core.convert.format.Format) ArgumentConversionContext(io.micronaut.core.convert.ArgumentConversionContext) ReactiveClientResultTransformer(io.micronaut.http.client.ReactiveClientResultTransformer) InterceptedMethod(io.micronaut.aop.InterceptedMethod) HttpMethodMapping(io.micronaut.http.annotation.HttpMethodMapping) MutableArgumentValue(io.micronaut.core.type.MutableArgumentValue) AtomicReference(java.util.concurrent.atomic.AtomicReference) Annotation(java.lang.annotation.Annotation) HttpClientResponseException(io.micronaut.http.client.exceptions.HttpClientResponseException) ConversionErrorException(io.micronaut.core.convert.exceptions.ConversionErrorException) ConfigurationException(io.micronaut.context.exceptions.ConfigurationException) HttpClientResponseException(io.micronaut.http.client.exceptions.HttpClientResponseException) HttpClient(io.micronaut.http.client.HttpClient) StreamingHttpClient(io.micronaut.http.client.StreamingHttpClient) BlockingHttpClient(io.micronaut.http.client.BlockingHttpClient) Nullable(io.micronaut.core.annotation.Nullable)

Example 4 with CompletionAwareSubscriber

use of io.micronaut.core.async.subscriber.CompletionAwareSubscriber in project micronaut-core by micronaut-projects.

the class Publishers method mapOrSupplyEmpty.

/**
 * Map the result from a publisher using the given mapper or supply empty value.
 *
 * @param publisher The publisher
 * @param mapOrSupplyEmpty    The mapOrSupplyEmpty
 * @param <T>       The generic type
 * @param <R>       The result type
 * @since 2.5.0
 * @return The mapped publisher
 */
public static <T, R> Publisher<R> mapOrSupplyEmpty(Publisher<T> publisher, MapOrSupplyEmpty<T, R> mapOrSupplyEmpty) {
    return (MicronautPublisher<R>) actual -> publisher.subscribe(new CompletionAwareSubscriber<T>() {

        AtomicBoolean resultPresent = new AtomicBoolean();

        @Override
        protected void doOnSubscribe(Subscription subscription) {
            actual.onSubscribe(subscription);
        }

        @Override
        protected void doOnNext(T message) {
            try {
                R result = Objects.requireNonNull(mapOrSupplyEmpty.map(message), "The mapper returned a null value.");
                actual.onNext(result);
                resultPresent.set(true);
            } catch (Throwable e) {
                onError(e);
            }
        }

        @Override
        protected void doOnError(Throwable t) {
            actual.onError(t);
        }

        @Override
        protected void doOnComplete() {
            if (!resultPresent.get()) {
                actual.onNext(mapOrSupplyEmpty.supplyEmpty());
            }
            actual.onComplete();
        }
    });
}
Also used : AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) CompletionAwareSubscriber(io.micronaut.core.async.subscriber.CompletionAwareSubscriber) Subscription(org.reactivestreams.Subscription)

Aggregations

CompletionAwareSubscriber (io.micronaut.core.async.subscriber.CompletionAwareSubscriber)4 Internal (io.micronaut.core.annotation.Internal)3 Publishers (io.micronaut.core.async.publisher.Publishers)3 Argument (io.micronaut.core.type.Argument)3 MediaType (io.micronaut.http.MediaType)3 Subscription (org.reactivestreams.Subscription)3 Nullable (io.micronaut.core.annotation.Nullable)2 ConversionService (io.micronaut.core.convert.ConversionService)2 ByteBuffer (io.micronaut.core.io.buffer.ByteBuffer)2 CollectionUtils (io.micronaut.core.util.CollectionUtils)2 HttpAttributes (io.micronaut.http.HttpAttributes)2 HttpHeaders (io.micronaut.http.HttpHeaders)2 HttpMethod (io.micronaut.http.HttpMethod)2 HttpRequest (io.micronaut.http.HttpRequest)2 HttpResponse (io.micronaut.http.HttpResponse)2 HttpStatus (io.micronaut.http.HttpStatus)2 Optional (java.util.Optional)2 InterceptedMethod (io.micronaut.aop.InterceptedMethod)1 MethodInterceptor (io.micronaut.aop.MethodInterceptor)1 MethodInvocationContext (io.micronaut.aop.MethodInvocationContext)1