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.0.CR1
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 java.util.function.LongFunction;

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.groups.MultiMerge;
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;
        private final long demand;
        private final boolean encodeAsJsonArray;

        @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;
            this.demand = builder.demand;
            this.encodeAsJsonArray = builder.encodeAsJsonArray;
        }

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

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

        public long getDemand() {
            return demand;
        }

        public boolean encodeAsJsonArray() {
            return encodeAsJsonArray;
        }

        public static class Builder {
            private final Multi multi;
            private final MultivaluedTreeMap headers = new CaseInsensitiveMap<>();
            private Integer status;
            private long demand = 1;
            private boolean encodeAsJsonArray = true;

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

            /**
             * Configure the {@code demand} signaled to the wrapped {@link Multi}, defaults to {@code 1}.
             *
             * 

* A demand of {@code 1} guarantees serial/sequential processing, any higher demand supports * concurrent processing. A demand greater {@code 1}, with concurrent {@link Multi} processing, * does not guarantee element order - this means that elements emitted by the * {@link RestMulti#fromMultiData(Multi) RestMulti.fromMultiData(Multi)} source Multi} * will be produced in a non-deterministic order. * * @see MultiMerge#withConcurrency(int) Multi.createBy().merging().withConcurrency(int) * @see Multi#capDemandsTo(long) * @see Multi#capDemandsUsing(LongFunction) */ public Builder withDemand(long demand) { if (demand <= 0) { throw new IllegalArgumentException("Demand must be greater than zero"); } this.demand = demand; return this; } /** * Configure whether objects produced by the wrapped {@link Multi} are encoded as JSON array elements, which is the * default. * *

* {@code encodeAsJsonArray(false)} produces separate JSON objects. * *

* This property is only used for JSON object results and ignored for SSE and chunked streaming. */ public Builder encodeAsJsonArray(boolean encodeAsJsonArray) { this.encodeAsJsonArray = encodeAsJsonArray; return this; } 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); } } } }