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

io.automatiko.addons.graphql.internal.SecurityAwareBroadcastProcessor Maven / Gradle / Ivy

package io.automatiko.addons.graphql.internal;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Flow.Processor;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import io.automatiko.engine.api.auth.IdentityProvider;
import io.automatiko.engine.api.auth.SecurityPolicy;
import io.automatiko.engine.api.runtime.process.HumanTaskWorkItem;
import io.smallrye.mutiny.helpers.ParameterValidation;
import io.smallrye.mutiny.helpers.Subscriptions;
import io.smallrye.mutiny.operators.AbstractMulti;
import io.smallrye.mutiny.operators.multi.processors.SerializedProcessor;
import io.smallrye.mutiny.operators.multi.processors.UnicastProcessor;
import io.smallrye.mutiny.subscription.BackPressureFailure;
import io.smallrye.mutiny.subscription.MultiSubscriber;

/**
 * Implementation of {@link org.reactivestreams.Processor} that broadcast all subsequently observed items to its current
 * {@link Subscriber}s.
 * 

* This processor does not coordinate back-pressure between different subscribers and between the upstream source and a * subscriber. If an upstream item is received via {@link #onNext(Object)}, if a subscriber is not ready to receive that * item, that subscriber is terminated via a {@link io.smallrye.mutiny.subscription.BackPressureFailure}. *

* The {@code BroadcastProcessor}'s {@link Subscriber}-side consumes items in an unbounded manner. *

* When this {@code BroadcastProcessor} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, late * {@link Subscriber}s only receive the respective terminal event. *

* Unlike the {@link UnicastProcessor}, a {@code BroadcastProcessor} doesn't retain/cache items, therefore, a new * {@code Subscriber} won't receive any past items. *

* Even though {@code BroadcastProcessor} implements the {@link Subscriber} interface, calling {@code onSubscribe} is * not required if the processor is used as a standalone source. However, calling {@code onSubscribe} after * the {@code BroadcastProcessor} has failed or reached completion results in the given {@link Subscription} being * canceled immediately. */ public class SecurityAwareBroadcastProcessor extends AbstractMulti implements Processor { /** * Value indicating that the upstream has been cancelled. */ static final List TERMINATED = new ArrayList<>(0); /** * The array of currently subscribed subscribers. */ final AtomicReference>> subscribers; /** * The failure, write before terminating and read after checking subscribers. */ Throwable failure; /** * Creates a new {@code BroadcastProcessor} * * @param the type of item * @return the new {@code BroadcastProcessor} */ public static SecurityAwareBroadcastProcessor create() { return new SecurityAwareBroadcastProcessor<>(); } /** * Constructs a BroadcastProcessor. */ private SecurityAwareBroadcastProcessor() { subscribers = new AtomicReference<>(new CopyOnWriteArrayList<>()); } public SerializedProcessor serialized() { return new SerializedProcessor<>(this); } /** * Tries to add the given subscriber to the subscribers array atomically * or returns {@code false} if this processor has terminated. * * @param sub the subscriber to add * @return {@code true} if successful, {@code false} if this processor has terminated */ private boolean addSubscription(BroadcastSubscription sub) { List> current = subscribers.get(); if (current == TERMINATED) { return false; } return current.add(sub); } /** * Atomically removes the given subscriber if it is subscribed to this processor. * * @param sub the subscription wrapping a subscriber to remove */ void remove(BroadcastSubscription sub) { List> current = subscribers.get(); if (current == TERMINATED || current.isEmpty()) { return; } current.remove(sub); } @Override public void subscribe(Subscriber s) { } @Override public void subscribe(MultiSubscriber downstream) { BroadcastSubscription subscription = new BroadcastSubscription<>(downstream, this, IdentityProvider.get()); downstream.onSubscribe(subscription); if (addSubscription(subscription)) { // if cancellation happened while a successful add, the remove() didn't work so we need to do it again if (subscription.isCancelled()) { remove(subscription); } } else { Throwable ex = failure; if (ex != null) { downstream.onFailure(ex); } else { downstream.onCompletion(); } } } @Override public void onSubscribe(Subscription subscription) { if (subscribers.get() == TERMINATED) { subscription.cancel(); return; } subscription.request(Long.MAX_VALUE); } @Override public void onNext(T item) { ParameterValidation.nonNullNpe(item, "item"); for (BroadcastSubscription s : subscribers.get()) { s.onNext(item); } } public void onNext(T item, Collection visibleTo) { ParameterValidation.nonNullNpe(item, "item"); for (BroadcastSubscription s : subscribers.get()) { IdentityProvider identityProvider = s.identityProvider(); boolean allowed = visibleTo.isEmpty() || visibleTo.contains(identityProvider.getName()) || visibleTo.stream().anyMatch(i -> identityProvider.getRoles().contains(i)); if (allowed) { s.onNext(item); } } } public void onNext(T item, HumanTaskWorkItem workItem) { ParameterValidation.nonNullNpe(item, "item"); for (BroadcastSubscription s : subscribers.get()) { IdentityProvider identityProvider = s.identityProvider(); boolean allowed = workItem.enforce(SecurityPolicy.of(identityProvider)); if (allowed) { s.onNext(item); } } } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public void onError(Throwable failure) { ParameterValidation.nonNullNpe(failure, "failure"); if (subscribers.get() == TERMINATED) { return; } this.failure = failure; List> andSet = subscribers.getAndSet((List) TERMINATED); for (BroadcastSubscription s : andSet) { s.onError(failure); } } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public void onComplete() { if (subscribers.get() == TERMINATED) { return; } List> andSet = subscribers.getAndSet((List) TERMINATED); for (BroadcastSubscription s : andSet) { s.onComplete(); } } /** * Wraps the actual subscriber, tracks its requests and makes cancellation * to remove itself from the current subscribers array. * * @param the type of item */ static final class BroadcastSubscription implements Subscription { /** * The actual subscriber. **/ private final Subscriber downstream; /** * The parent processor using this subscriber. */ private final SecurityAwareBroadcastProcessor parent; /** * Pending requests. * {@code Long.MIN_VALUE} indicates cancellation. */ private final AtomicLong requests = new AtomicLong(); private final IdentityProvider identityProvider; /** * Constructs a BroadcastSubscription, wraps the actual subscriber and the state. * * @param actual the actual subscriber * @param parent the parent PublishProcessor */ BroadcastSubscription(Subscriber actual, SecurityAwareBroadcastProcessor parent, IdentityProvider identityProvider) { this.downstream = actual; this.parent = parent; this.identityProvider = identityProvider; } public void onNext(T t) { long r = requests.get(); if (r == Long.MIN_VALUE) { return; } if (r != 0L) { downstream.onNext(t); Subscriptions.producedAndHandleAlreadyCancelled(requests, 1); } else { cancel(); downstream.onError(new BackPressureFailure("Could not emit item downstream due to lack of requests")); } } public void onError(Throwable t) { if (requests.get() != Long.MIN_VALUE) { downstream.onError(t); } } public void onComplete() { if (requests.get() != Long.MIN_VALUE) { downstream.onComplete(); } } @Override public void request(long n) { if (n > 0) { Subscriptions.addAndHandledAlreadyCancelled(requests, n); } } @Override public void cancel() { if (requests.getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) { parent.remove(this); } } public boolean isCancelled() { return requests.get() == Long.MIN_VALUE; } public IdentityProvider identityProvider() { return identityProvider; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy