/* * Copyright 2017 the original author or authors. * * 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 ratpack.exec.internal; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import ratpack.func.Action; import ratpack.func.Block; import ratpack.stream.TransformablePublisher; import ratpack.util.Exceptions; import java.util.concurrent.atomic.AtomicBoolean; public class ExecutionBoundPublisher<T> implements TransformablePublisher<T> { private final Publisher<T> publisher; private final Action<? super T> disposer; public ExecutionBoundPublisher(Publisher<T> publisher, Action<? super T> disposer) { this.publisher = publisher; this.disposer = disposer; } @Override public void subscribe(Subscriber<? super T> subscriber) { DefaultExecution execution = DefaultExecution.require(); execution.delimitStream(subscriber::onError, continuation -> publisher.subscribe(new Subscriber<T>() { private Subscription subscription; private final AtomicBoolean cancelled = new AtomicBoolean(); private final AtomicBoolean pendingCancelSignal = new AtomicBoolean(true); private boolean dispatch(Block block) { if (cancelled.get()) { return false; } else if (execution.isBound()) { try { block.execute(); return true; } catch (Exception e) { throw Exceptions.uncheck(e); // really should not happen } } else { return continuation.event(block); } } @Override public void onSubscribe(final Subscription subscription) { this.subscription = subscription; dispatch(() -> subscriber.onSubscribe(new Subscription() { @Override public void request(long n) { dispatch(() -> subscription.request(n)); } @Override public void cancel() { if (cancelled.compareAndSet(false, true)) { if (execution.isBound()) { subscription.cancel(); continuation.complete(Block.noop()); } else { pendingCancelSignal.set(true); continuation.complete(() -> { if (pendingCancelSignal.compareAndSet(true, false)) { subscription.cancel(); } }); } } } }) ); } @Override public void onNext(final T element) { boolean added = dispatch(() -> { if (cancelled.get()) { dispose(element); } else { subscriber.onNext(element); } }); if (!added) { dispose(element); if (cancelled.get()) { if (execution.isBound() && pendingCancelSignal.compareAndSet(true, false)) { subscription.cancel(); continuation.complete(Block.noop()); } } } } private void dispose(T element) { try { disposer.execute(element); } catch (Exception e) { DefaultExecution.LOGGER.warn("Exception raised disposing stream item will be ignored - ", e); } } @Override public void onComplete() { continuation.complete(() -> { if (!cancelled.get()) { subscriber.onComplete(); } }); } @Override public void onError(final Throwable cause) { if (!cancelled.get()) { continuation.complete(() -> subscriber.onError(cause)); } } }) ); } }