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

org.jboss.resteasy.reactive.RestMulti Maven / Gradle / Ivy

There is a newer version: 3.17.5
Show newest version
package org.jboss.resteasy.reactive;

import static io.smallrye.mutiny.helpers.ParameterValidation.MAPPER_RETURNED_NULL;
import static io.smallrye.mutiny.helpers.ParameterValidation.nonNull;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Flow;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

import org.jboss.resteasy.reactive.common.util.CaseInsensitiveMap;
import org.jboss.resteasy.reactive.common.util.MultivaluedTreeMap;

import io.smallrye.mutiny.Context;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.helpers.EmptyUniSubscription;
import io.smallrye.mutiny.helpers.Subscriptions;
import io.smallrye.mutiny.infrastructure.Infrastructure;
import io.smallrye.mutiny.operators.AbstractMulti;
import io.smallrye.mutiny.operators.AbstractUni;
import io.smallrye.mutiny.subscription.ContextSupport;
import io.smallrye.mutiny.subscription.MultiSubscriber;
import io.smallrye.mutiny.subscription.UniSubscriber;
import io.smallrye.mutiny.subscription.UniSubscription;

/**
 * A wrapper around {@link Multi} that gives resource methods a way to specify the HTTP status code and HTTP headers
 * when streaming a result.
 */
@SuppressWarnings("ReactiveStreamsUnusedPublisher")
public abstract class RestMulti extends AbstractMulti {

    public abstract Integer getStatus();

    public abstract Map> getHeaders();

    public static  RestMulti.SyncRestMulti.Builder fromMultiData(Multi multi) {
        return new RestMulti.SyncRestMulti.Builder<>(multi);
    }

    public static  RestMulti fromUniResponse(Uni uni,
            Function> dataExtractor) {
        return fromUniResponse(uni, dataExtractor, null, null);
    }

    public static  RestMulti fromUniResponse(Uni uni,
            Function> dataExtractor,
            Function>> headersExtractor) {
        return fromUniResponse(uni, dataExtractor, headersExtractor, null);
    }

    public static  RestMulti fromUniResponse(Uni uni,
            Function> dataExtractor,
            Function>> headersExtractor,
            Function statusExtractor) {
        Function> actualDataExtractor = Infrastructure
                .decorate(nonNull(dataExtractor, "dataExtractor"));
        return (RestMulti) Infrastructure.onMultiCreation(new AsyncRestMulti<>(uni, actualDataExtractor,
                headersExtractor, statusExtractor));
    }

    public static class SyncRestMulti extends RestMulti {

        private final Multi multi;
        private final Integer status;
        private final MultivaluedTreeMap headers;

        @Override
        public void subscribe(MultiSubscriber subscriber) {
            multi.subscribe(Infrastructure.onMultiSubscription(multi, subscriber));
        }

        private SyncRestMulti(Builder builder) {
            this.multi = builder.multi;
            this.status = builder.status;
            this.headers = builder.headers;
        }

        @Override
        public Integer getStatus() {
            return status;
        }

        @Override
        public Map> getHeaders() {
            return headers;
        }

        public static class Builder {
            private final Multi multi;

            private Integer status;

            private final MultivaluedTreeMap headers = new CaseInsensitiveMap<>();

            private Builder(Multi multi) {
                this.multi = Objects.requireNonNull(multi, "multi cannot be null");
            }

            public Builder status(int status) {
                this.status = status;
                return this;
            }

            public Builder header(String name, String value) {
                if (value == null) {
                    headers.remove(name);
                    return this;
                }
                headers.add(name, value);
                return this;
            }

            public RestMulti build() {
                return new SyncRestMulti<>(this);
            }
        }
    }

    // Copied from: io.smallrye.mutiny.operators.uni.UniOnItemTransformToUni while adding header and status extraction
    public static class AsyncRestMulti extends RestMulti {

        private final Function> dataExtractor;
        private final Function statusExtractor;
        private final Function>> headersExtractor;
        private final AtomicReference status;
        private final AtomicReference>> headers;
        private final Uni upstream;

        public  AsyncRestMulti(Uni upstream,
                Function> dataExtractor,
                Function>> headersExtractor,
                Function statusExtractor) {
            this.upstream = upstream;
            this.dataExtractor = dataExtractor;
            this.statusExtractor = statusExtractor;
            this.headersExtractor = headersExtractor;
            this.status = new AtomicReference<>(null);
            this.headers = new AtomicReference<>(Collections.emptyMap());
        }

        @Override
        public void subscribe(MultiSubscriber subscriber) {
            if (subscriber == null) {
                throw new NullPointerException("The subscriber must not be `null`");
            }
            AbstractUni.subscribe(upstream, new FlatMapPublisherSubscriber<>(subscriber, dataExtractor, statusExtractor, status,
                    headersExtractor, headers));
        }

        @Override
        public Integer getStatus() {
            return status.get();
        }

        @Override
        public Map> getHeaders() {
            return headers.get();
        }

        static final class FlatMapPublisherSubscriber
                implements Flow.Subscriber, UniSubscriber, Flow.Subscription, ContextSupport {

            private final AtomicReference secondUpstream;
            private final AtomicReference firstUpstream;
            private final Flow.Subscriber downstream;
            private final Function> dataExtractor;
            private final Function statusExtractor;
            private final AtomicReference status;
            private final Function>> headersExtractor;
            private final AtomicReference>> headers;
            private final AtomicLong requested = new AtomicLong();

            public FlatMapPublisherSubscriber(Flow.Subscriber downstream,
                    Function> dataExtractor,
                    Function statusExtractor,
                    AtomicReference status,
                    Function>> headersExtractor,
                    AtomicReference>> headers) {
                this.downstream = downstream;
                this.dataExtractor = dataExtractor;
                this.statusExtractor = statusExtractor;
                this.status = status;
                this.headersExtractor = headersExtractor;
                this.headers = headers;
                this.firstUpstream = new AtomicReference<>();
                this.secondUpstream = new AtomicReference<>();
            }

            @Override
            public void onNext(O item) {
                downstream.onNext(item);
            }

            @Override
            public void onError(Throwable failure) {
                downstream.onError(failure);
            }

            @Override
            public void onComplete() {
                downstream.onComplete();
            }

            @Override
            public void request(long n) {
                Subscriptions.requestIfNotNullOrAccumulate(secondUpstream, requested, n);
            }

            @Override
            public void cancel() {
                UniSubscription subscription = firstUpstream.getAndSet(EmptyUniSubscription.CANCELLED);
                if (subscription != null && subscription != EmptyUniSubscription.CANCELLED) {
                    subscription.cancel();
                }
                Subscriptions.cancel(secondUpstream);
            }

            @Override
            public Context context() {
                if (downstream instanceof ContextSupport) {
                    return ((ContextSupport) downstream).context();
                } else {
                    return Context.empty();
                }
            }

            /**
             * Called when we get the subscription from the upstream UNI
             *
             * @param subscription the subscription allowing to cancel the computation.
             */
            @Override
            public void onSubscribe(UniSubscription subscription) {
                if (firstUpstream.compareAndSet(null, subscription)) {
                    downstream.onSubscribe(this);
                }
            }

            /**
             * Called after we produced the {@link Flow.Publisher} and subscribe on it.
             *
             * @param subscription the subscription from the produced {@link Flow.Publisher}
             */
            @Override
            public void onSubscribe(Flow.Subscription subscription) {
                if (secondUpstream.compareAndSet(null, subscription)) {
                    long r = requested.getAndSet(0L);
                    if (r != 0L) {
                        subscription.request(r);
                    }
                }
            }

            @Override
            public void onItem(I item) {
                Multi publisher;

                try {
                    publisher = dataExtractor.apply(item);
                    if (publisher == null) {
                        throw new NullPointerException(MAPPER_RETURNED_NULL);
                    }
                    if (headersExtractor != null) {
                        headers.set(headersExtractor.apply(item));
                    }
                    if (statusExtractor != null) {
                        status.set(statusExtractor.apply(item));
                    }
                } catch (Throwable ex) {
                    downstream.onError(ex);
                    return;
                }

                publisher.subscribe(this);
            }

            @Override
            public void onFailure(Throwable failure) {
                downstream.onError(failure);
            }
        }

    }

}