/*
* Copyright 2002-2016 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 org.springframework.http.server.reactive;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.Exceptions;
import reactor.core.publisher.MonoSource;
import reactor.core.publisher.Operators;
import org.springframework.util.Assert;
/**
* Given a write function that accepts a source {@code Publisher<T>} to write
* with and returns {@code Publisher<Void>} for the result, this operator helps
* to defer the invocation of the write function, until we know if the source
* publisher will begin publishing without an error. If the first emission is
* an error, the write function is bypassed, and the error is sent directly
* through the result publisher. Otherwise the write function is invoked.
*
* @author Rossen Stoyanchev
* @author Stephane Maldini
* @since 5.0
*/
public class ChannelSendOperator<T> extends MonoSource<T, Void> {
private final Function<Publisher<T>, Publisher<Void>> writeFunction;
public ChannelSendOperator(Publisher<? extends T> source, Function<Publisher<T>, Publisher<Void>> writeFunction) {
super(source);
this.writeFunction = writeFunction;
}
@Override
public void subscribe(Subscriber<? super Void> s) {
this.source.subscribe(new WriteWithBarrier(s));
}
@SuppressWarnings("deprecation")
private class WriteWithBarrier extends SubscriberAdapter<T, Void> implements Publisher<T> {
/**
* We've at at least one emission, we've called the write function, the write
* subscriber has subscribed and cached signals have been emitted to it.
* We're now simply passing data through to the write subscriber.
**/
private boolean readyToWrite = false;
/** No emission from upstream yet */
private boolean beforeFirstEmission = true;
/** Cached signal before readyToWrite */
private T item;
/** Cached 1st/2nd signal before readyToWrite */
private Throwable error;
/** Cached 1st/2nd signal before readyToWrite */
private boolean completed = false;
/** The actual writeSubscriber vs the downstream completion subscriber */
private Subscriber<? super T> writeSubscriber;
public WriteWithBarrier(Subscriber<? super Void> subscriber) {
super(subscriber);
}
@Override
protected void doOnSubscribe(Subscription subscription) {
super.doOnSubscribe(subscription);
super.upstream().request(1); // bypass doRequest
}
@Override
public void doNext(T item) {
if (this.readyToWrite) {
this.writeSubscriber.onNext(item);
return;
}
synchronized (this) {
if (this.readyToWrite) {
this.writeSubscriber.onNext(item);
}
else if (this.beforeFirstEmission) {
this.item = item;
this.beforeFirstEmission = false;
writeFunction.apply(this).subscribe(new DownstreamBridge(downstream()));
}
else {
subscription.cancel();
downstream().onError(new IllegalStateException("Unexpected item."));
}
}
}
@Override
public void doError(Throwable ex) {
if (this.readyToWrite) {
this.writeSubscriber.onError(ex);
return;
}
synchronized (this) {
if (this.readyToWrite) {
this.writeSubscriber.onError(ex);
}
else if (this.beforeFirstEmission) {
this.beforeFirstEmission = false;
downstream().onError(ex);
}
else {
this.error = ex;
}
}
}
@Override
public void doComplete() {
if (this.readyToWrite) {
this.writeSubscriber.onComplete();
return;
}
synchronized (this) {
if (this.readyToWrite) {
this.writeSubscriber.onComplete();
}
else if (this.beforeFirstEmission) {
this.completed = true;
this.beforeFirstEmission = false;
writeFunction.apply(this).subscribe(new DownstreamBridge(downstream()));
}
else {
this.completed = true;
}
}
}
@Override
public void subscribe(Subscriber<? super T> writeSubscriber) {
synchronized (this) {
Assert.isNull(this.writeSubscriber, "Only one writeSubscriber supported");
this.writeSubscriber = writeSubscriber;
if (this.error != null || this.completed) {
this.writeSubscriber.onSubscribe(Operators.emptySubscription());
emitCachedSignals();
}
else {
this.writeSubscriber.onSubscribe(this);
}
}
}
/**
* Emit cached signals to the write subscriber.
* @return true if no more signals expected
*/
private boolean emitCachedSignals() {
if (this.item != null) {
this.writeSubscriber.onNext(this.item);
}
if (this.error != null) {
this.writeSubscriber.onError(this.error);
return true;
}
if (this.completed) {
this.writeSubscriber.onComplete();
return true;
}
return false;
}
@Override
protected void doRequest(long n) {
if (readyToWrite) {
super.doRequest(n);
return;
}
synchronized (this) {
if (this.writeSubscriber != null) {
readyToWrite = true;
if (emitCachedSignals()) {
return;
}
n--;
if (n == 0) {
return;
}
super.doRequest(n);
}
}
}
}
// TODO Remove this copy of Reactor 3.0.x Operators.SubscriberAdapter
private static class SubscriberAdapter<I, O> implements Subscriber<I>, Subscription {
protected final Subscriber<? super O> subscriber;
protected Subscription subscription;
public SubscriberAdapter(Subscriber<? super O> subscriber) {
this.subscriber = subscriber;
}
public Subscriber<? super O> downstream() {
return subscriber;
}
@Override
public final void cancel() {
try {
doCancel();
} catch (Throwable throwable) {
doOnSubscriberError(Operators.onOperatorError(subscription, throwable));
}
}
@Override
public final void onComplete() {
try {
doComplete();
} catch (Throwable throwable) {
doOnSubscriberError(Operators.onOperatorError(throwable));
}
}
@Override
public final void onError(Throwable t) {
if (t == null) {
throw Exceptions.argumentIsNullException();
}
doError(t);
}
@Override
public final void onNext(I i) {
if (i == null) {
throw Exceptions.argumentIsNullException();
}
try {
doNext(i);
}
catch (Throwable throwable) {
doOnSubscriberError(Operators.onOperatorError(subscription, throwable, i));
}
}
@Override
public final void onSubscribe(Subscription s) {
if (Operators.validate(subscription, s)) {
try {
subscription = s;
doOnSubscribe(s);
}
catch (Throwable throwable) {
doOnSubscriberError(Operators.onOperatorError(s, throwable));
}
}
}
@Override
public final void request(long n) {
try {
Operators.checkRequest(n);
doRequest(n);
} catch (Throwable throwable) {
doCancel();
doOnSubscriberError(Operators.onOperatorError(throwable));
}
}
@Override
public String toString() {
return getClass().getSimpleName();
}
/**
* Hook for further processing of onSubscribe's Subscription.
* @param subscription the subscription to optionally process
*/
protected void doOnSubscribe(Subscription subscription) {
subscriber.onSubscribe(this);
}
public Subscription upstream() {
return subscription;
}
@SuppressWarnings("unchecked")
protected void doNext(I i) {
subscriber.onNext((O) i);
}
protected void doError(Throwable throwable) {
subscriber.onError(throwable);
}
protected void doOnSubscriberError(Throwable throwable){
subscriber.onError(throwable);
}
protected void doComplete() {
subscriber.onComplete();
}
protected void doRequest(long n) {
Subscription s = this.subscription;
if (s != null) {
s.request(n);
}
}
protected void doCancel() {
Subscription s = this.subscription;
if (s != null) {
this.subscription = null;
s.cancel();
}
}
}
private class DownstreamBridge implements Subscriber<Void> {
private final Subscriber<? super Void> downstream;
public DownstreamBridge(Subscriber<? super Void> downstream) {
this.downstream = downstream;
}
@Override
public void onSubscribe(Subscription subscription) {
subscription.request(Long.MAX_VALUE);
}
@Override
public void onNext(Void aVoid) {
}
@Override
public void onError(Throwable ex) {
this.downstream.onError(ex);
}
@Override
public void onComplete() {
this.downstream.onComplete();
}
}
}