/*
* Copyright 2002-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 org.springframework.http.server.reactive;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Processor;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.springframework.util.Assert;
/**
* Abstract base class for {@code Processor} implementations that bridge between
* event-listener write APIs and Reactive Streams.
*
* <p>Specifically a base class for writing to the HTTP response body with
* Servlet 3.1 non-blocking I/O and Undertow XNIO as well for writing WebSocket
* messages through the Java WebSocket API (JSR-356), Jetty, and Undertow.
*
* @author Arjen Poutsma
* @author Violeta Georgieva
* @author Rossen Stoyanchev
* @since 5.0
*/
public abstract class AbstractListenerWriteProcessor<T> implements Processor<T, Void> {
protected final Log logger = LogFactory.getLog(getClass());
private final WriteResultPublisher resultPublisher = new WriteResultPublisher();
private final AtomicReference<State> state = new AtomicReference<>(State.UNSUBSCRIBED);
protected volatile T currentData;
private volatile boolean subscriberCompleted;
private Subscription subscription;
// Subscriber implementation...
@Override
public final void onSubscribe(Subscription subscription) {
if (logger.isTraceEnabled()) {
logger.trace(this.state + " onSubscribe: " + subscription);
}
this.state.get().onSubscribe(this, subscription);
}
@Override
public final void onNext(T data) {
if (logger.isTraceEnabled()) {
logger.trace(this.state + " onNext: " + data);
}
this.state.get().onNext(this, data);
}
@Override
public final void onError(Throwable t) {
if (logger.isTraceEnabled()) {
logger.trace(this.state + " onError: " + t);
}
this.state.get().onError(this, t);
}
@Override
public final void onComplete() {
if (logger.isTraceEnabled()) {
logger.trace(this.state + " onComplete");
}
this.state.get().onComplete(this);
}
// Publisher implementation...
@Override
public final void subscribe(Subscriber<? super Void> subscriber) {
this.resultPublisher.subscribe(subscriber);
}
// Listener delegation methods...
/**
* Listeners can call this to notify when writing is possible.
*/
public final void onWritePossible() {
this.state.get().onWritePossible(this);
}
/**
* Listeners can call this method to cancel further writing.
*/
public void cancel() {
if (this.subscription != null) {
this.subscription.cancel();
}
}
/**
* Called when a data item is received via {@link Subscriber#onNext(Object)}
*/
protected void receiveData(T data) {
if (this.currentData != null) {
throw new IllegalStateException("Current data not processed yet: " + this.currentData);
}
this.currentData = data;
}
/**
* Called when the current received data item can be released.
*/
protected abstract void releaseData();
/**
* Whether the given data item contains any actual data to be processed.
*/
protected abstract boolean isDataEmpty(T data);
/**
* Whether writing is possible.
*/
protected abstract boolean isWritePossible();
/**
* Writes the given data to the output.
* @param data the data to write
* @return whether the data was fully written (true)and new data can be
* requested or otherwise (false)
*/
protected abstract boolean write(T data) throws IOException;
/**
* Suspend writing. Defaults to no-op.
*/
protected void suspendWriting() {
}
/**
* Invoked when writing is complete. Defaults to no-op.
*/
protected void writingComplete() {
}
private boolean changeState(State oldState, State newState) {
return this.state.compareAndSet(oldState, newState);
}
private void writeIfPossible() {
if (isWritePossible()) {
onWritePossible();
}
}
/**
* Represents a state for the {@link Subscriber} to be in. The following figure
* indicate the four different states that exist, and the relationships between them.
*
* <pre>
* UNSUBSCRIBED
* |
* v
* REQUESTED -------------------> RECEIVED
* ^ ^
* | |
* --------- WRITING <-----
* |
* v
* COMPLETED
* </pre>
* Refer to the individual states for more information.
*/
private enum State {
/**
* The initial unsubscribed state. Will respond to {@code onSubscribe} by
* requesting 1 data from the subscription, and change state to {@link
* #REQUESTED}.
*/
UNSUBSCRIBED {
@Override
public <T> void onSubscribe(AbstractListenerWriteProcessor<T> processor, Subscription subscription) {
Assert.notNull(subscription, "Subscription must not be null");
if (processor.changeState(this, REQUESTED)) {
processor.subscription = subscription;
subscription.request(1);
}
else {
super.onSubscribe(processor, subscription);
}
}
},
/**
* State that gets entered after a data has been
* {@linkplain Subscription#request(long) requested}. Responds to {@code onNext}
* by changing state to {@link #RECEIVED}, and responds to {@code onComplete} by
* changing state to {@link #COMPLETED}.
*/
REQUESTED {
@Override
public <T> void onNext(AbstractListenerWriteProcessor<T> processor, T data) {
if (processor.isDataEmpty(data)) {
processor.subscription.request(1);
}
else {
processor.receiveData(data);
if (processor.changeState(this, RECEIVED)) {
processor.writeIfPossible();
}
}
}
@Override
public <T> void onComplete(AbstractListenerWriteProcessor<T> processor) {
if (processor.changeState(this, COMPLETED)) {
processor.writingComplete();
processor.resultPublisher.publishComplete();
}
}
},
/**
* State that gets entered after a data has been
* {@linkplain Subscriber#onNext(Object) received}. Responds to
* {@code onWritePossible} by writing the current data and changes
* the state to {@link #WRITING}. If it can be written completely,
* changes the state to either {@link #REQUESTED} if the subscription
* has not been completed; or {@link #COMPLETED} if it has. If it cannot
* be written completely the state will be changed to {@code #RECEIVED}.
*/
RECEIVED {
@Override
public <T> void onWritePossible(AbstractListenerWriteProcessor<T> processor) {
if (processor.changeState(this, WRITING)) {
T data = processor.currentData;
try {
boolean writeCompleted = processor.write(data);
if (writeCompleted) {
processor.releaseData();
if (!processor.subscriberCompleted) {
processor.changeState(WRITING, REQUESTED);
processor.suspendWriting();
processor.subscription.request(1);
}
else {
processor.changeState(WRITING, COMPLETED);
processor.writingComplete();
processor.resultPublisher.publishComplete();
}
}
else {
processor.changeState(WRITING, RECEIVED);
processor.writeIfPossible();
}
}
catch (IOException ex) {
processor.cancel();
processor.onError(ex);
}
}
}
@Override
public <T> void onComplete(AbstractListenerWriteProcessor<T> processor) {
processor.subscriberCompleted = true;
}
},
/**
* State that gets entered after a writing of the current data has been
* {@code onWritePossible started}.
*/
WRITING {
@Override
public <T> void onComplete(AbstractListenerWriteProcessor<T> processor) {
processor.subscriberCompleted = true;
}
},
/**
* The terminal completed state. Does not respond to any events.
*/
COMPLETED {
@Override
public <T> void onNext(AbstractListenerWriteProcessor<T> processor, T data) {
// ignore
}
@Override
public <T> void onError(AbstractListenerWriteProcessor<T> processor, Throwable ex) {
// ignore
}
@Override
public <T> void onComplete(AbstractListenerWriteProcessor<T> processor) {
// ignore
}
};
public <T> void onSubscribe(AbstractListenerWriteProcessor<T> processor, Subscription subscription) {
subscription.cancel();
}
public <T> void onNext(AbstractListenerWriteProcessor<T> processor, T data) {
throw new IllegalStateException(toString());
}
public <T> void onError(AbstractListenerWriteProcessor<T> processor, Throwable ex) {
if (processor.changeState(this, COMPLETED)) {
processor.writingComplete();
processor.resultPublisher.publishError(ex);
}
}
public <T> void onComplete(AbstractListenerWriteProcessor<T> processor) {
throw new IllegalStateException(toString());
}
public <T> void onWritePossible(AbstractListenerWriteProcessor<T> processor) {
// ignore
}
}
}