/* * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package reactor.core.publisher; import java.util.Objects; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Function; import java.util.stream.Stream; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import reactor.core.Exceptions; import reactor.core.Scannable; /** * Maps the upstream value into a single {@code true} or {@code false} value * provided by a generated Publisher for that input value and emits the input value if * the inner Publisher returned {@code true}. * <p> * Only the first item emitted by the inner Publisher's are considered. If * the inner Publisher is empty, no resulting item is generated for that input value. * * @param <T> the input value type * @author Simon Baslé */ class MonoFilterWhen<T> extends MonoSource<T, T> { final Function<? super T, ? extends Publisher<Boolean>> asyncPredicate; MonoFilterWhen(Mono<T> source, Function<? super T, ? extends Publisher<Boolean>> asyncPredicate) { super(source); this.asyncPredicate = asyncPredicate; } @Override public void subscribe(Subscriber<? super T> s) { source.subscribe(new MonoFilterWhenSubscriber<>(s, asyncPredicate)); } static final class MonoFilterWhenSubscriber<T> extends Operators.MonoSubscriber<T, T> { /* Implementation notes on state transitions: * This subscriber runs through a few possible state transitions, that are * expressed through the signal methods rather than an explicit state variable, * as they are simple enough (states suffixed with a * correspond to a terminal * signal downstream): * - SUBSCRIPTION -> EMPTY | VALUED | EARLY ERROR * - EMPTY -> COMPLETE * - VALUED -> FILTERING | EARLY ERROR * - EARLY ERROR* * - FILTERING -> FEMPTY | FERROR | FVALUED * - FEMPTY -> COMPLETE * - FERROR* * - FVALUED -> ON NEXT + COMPLETE | COMPLETE * - COMPLETE* */ final Function<? super T, ? extends Publisher<Boolean>> asyncPredicate; //this is only touched by onNext and read by onComplete, so no need for volatile boolean sourceValued; Subscription upstream; volatile FilterWhenInner asyncFilter; static final AtomicReferenceFieldUpdater<MonoFilterWhenSubscriber, FilterWhenInner> ASYNC_FILTER = AtomicReferenceFieldUpdater.newUpdater(MonoFilterWhenSubscriber.class, FilterWhenInner.class, "asyncFilter"); static final FilterWhenInner INNER_CANCELLED = new FilterWhenInner(null, false); MonoFilterWhenSubscriber(Subscriber<? super T> actual, Function<? super T, ? extends Publisher<Boolean>> asyncPredicate) { super(actual); this.asyncPredicate = asyncPredicate; } @Override public void onSubscribe(Subscription s) { if (Operators.validate(upstream, s)) { upstream = s; actual.onSubscribe(this); s.request(Long.MAX_VALUE); } } @SuppressWarnings("unchecked") @Override public void onNext(T t) { //we assume the source is a Mono, so only one onNext will ever happen sourceValued = true; setValue(t); Publisher<Boolean> p; try { p = Objects.requireNonNull(asyncPredicate.apply(t), "The asyncPredicate returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); super.onError(ex); return; } if (p instanceof Callable) { Boolean u; try { u = ((Callable<Boolean>) p).call(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); super.onError(ex); return; } if (u != null && u) { complete(t); } else { actual.onComplete(); } } else { FilterWhenInner inner = new FilterWhenInner(this, !(p instanceof Mono)); if (ASYNC_FILTER.compareAndSet(this, null, inner)) { p.subscribe(inner); } } } @Override public void onComplete() { if (!sourceValued) { //there was no value, we can complete empty super.onComplete(); } //otherwise just wait for the inner filter to apply, rather than complete too soon } /* implementation note on onError: * if the source errored, we can propagate that directly since there * was no chance for an inner subscriber to have been triggered * (the source being a Mono). So we can just have the parent's behavior * of calling actual.onError(t) for onError. */ @Override public void cancel() { if (super.state != CANCELLED) { super.cancel(); upstream.cancel(); cancelInner(); } } void cancelInner() { FilterWhenInner a = asyncFilter; if (a != INNER_CANCELLED) { a = ASYNC_FILTER.getAndSet(this, INNER_CANCELLED); if (a != null && a != INNER_CANCELLED) { a.cancel(); } } } void innerResult(Boolean item) { if (item != null && item) { //will reset the value with itself, but using parent's `value` saves a field complete(value); } else { super.onComplete(); } } void innerError(Throwable ex) { //if the inner subscriber (the filter one) errors, then we can //always propagate that error directly, as it means that the source Mono //was at least valued rather than in error. super.onError(ex); } @Override public Object scan(Attr key) { switch (key) { case PARENT: return upstream; case TERMINATED: return asyncFilter != null ? asyncFilter.scan(Attr.TERMINATED) : super.scan(Attr.TERMINATED); default: //CANCELLED, PREFETCH return super.scan(key); } } @Override public Stream<? extends Scannable> inners() { FilterWhenInner c = asyncFilter; return c == null ? Stream.empty() : Stream.of(c); } } static final class FilterWhenInner implements InnerConsumer<Boolean> { final MonoFilterWhenSubscriber<?> parent; /** should the filter publisher be cancelled once we received the first value? */ final boolean cancelOnNext; boolean done; volatile Subscription sub; static final AtomicReferenceFieldUpdater<FilterWhenInner, Subscription> SUB = AtomicReferenceFieldUpdater.newUpdater(FilterWhenInner.class, Subscription.class, "sub"); FilterWhenInner(MonoFilterWhenSubscriber<?> parent, boolean cancelOnNext) { this.parent = parent; this.cancelOnNext = cancelOnNext; } @Override public void onSubscribe(Subscription s) { if (Operators.setOnce(SUB, this, s)) { s.request(Long.MAX_VALUE); } } @Override public void onNext(Boolean t) { if (!done) { if (cancelOnNext) { sub.cancel(); } done = true; parent.innerResult(t); } } @Override public void onError(Throwable t) { if (!done) { done = true; parent.innerError(t); } else { Operators.onErrorDropped(t); } } @Override public void onComplete() { if (!done) { //the filter publisher was empty done = true; parent.innerResult(null); //will trigger actual.onComplete() } } void cancel() { Operators.terminate(SUB, this); } @Override public Object scan(Attr key) { switch(key) { case PARENT: return parent; case ACTUAL: return sub; case CANCELLED: return sub == Operators.cancelledSubscription(); case TERMINATED: return done; case PREFETCH: return Integer.MAX_VALUE; case REQUESTED_FROM_DOWNSTREAM: return done ? 0 : 1; default: return null; } } } }