/* * 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.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import reactor.core.publisher.Operators; import org.springframework.util.Assert; /** * Abstract base class for {@code Publisher} implementations that bridge between * event-listener read APIs and Reactive Streams. * * <p>Specifically a base class for reading from the HTTP request body with * Servlet 3.1 non-blocking I/O and Undertow XNIO as well as handling incoming * WebSocket messages with standard Java WebSocket (JSR-356), Jetty, and * Undertow. * * @author Arjen Poutsma * @author Violeta Georgieva * @author Rossen Stoyanchev * @since 5.0 */ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> { protected final Log logger = LogFactory.getLog(getClass()); private final AtomicReference<State> state = new AtomicReference<>(State.UNSUBSCRIBED); private volatile long demand; @SuppressWarnings("rawtypes") private static final AtomicLongFieldUpdater<AbstractListenerReadPublisher> DEMAND_FIELD_UPDATER = AtomicLongFieldUpdater.newUpdater(AbstractListenerReadPublisher.class, "demand"); private Subscriber<? super T> subscriber; // Publisher implementation... @Override public void subscribe(Subscriber<? super T> subscriber) { if (this.logger.isTraceEnabled()) { this.logger.trace(this.state + " subscribe: " + subscriber); } this.state.get().subscribe(this, subscriber); } // Listener delegation methods... /** * Listeners can call this to notify when reading is possible. */ public final void onDataAvailable() { if (this.logger.isTraceEnabled()) { this.logger.trace(this.state + " onDataAvailable"); } this.state.get().onDataAvailable(this); } /** * Listeners can call this to notify when all data has been read. */ public void onAllDataRead() { if (this.logger.isTraceEnabled()) { this.logger.trace(this.state + " onAllDataRead"); } this.state.get().onAllDataRead(this); } /** * Listeners can call this to notify when a read error has occurred. */ public final void onError(Throwable t) { if (this.logger.isTraceEnabled()) { this.logger.trace(this.state + " onError: " + t); } this.state.get().onError(this, t); } protected abstract void checkOnDataAvailable(); /** * Reads a data from the input, if possible. * @return the data that was read; or {@code null} */ protected abstract T read() throws IOException; /** * Read and publish data from the input. Continue till there is no more * demand or there is no more data to be read. * @return {@code true} if there is more demand; {@code false} otherwise */ private boolean readAndPublish() throws IOException { long r; while ((r = demand) > 0) { T data = read(); if (data != null) { if (r != Long.MAX_VALUE) { DEMAND_FIELD_UPDATER.addAndGet(this, -1L); } this.subscriber.onNext(data); } else { return true; } } return false; } private boolean changeState(State oldState, State newState) { return this.state.compareAndSet(oldState, newState); } private static final class ReadSubscription implements Subscription { private final AbstractListenerReadPublisher<?> publisher; public ReadSubscription(AbstractListenerReadPublisher<?> publisher) { this.publisher = publisher; } @Override public final void request(long n) { if (this.publisher.logger.isTraceEnabled()) { this.publisher.logger.trace(state() + " request: " + n); } state().request(this.publisher, n); } @Override public final void cancel() { if (this.publisher.logger.isTraceEnabled()) { this.publisher.logger.trace(state() + " cancel"); } state().cancel(this.publisher); } private State state() { return this.publisher.state.get(); } } /** * Represents a state for the {@link Publisher} to be in. The following figure * indicate the four different states that exist, and the relationships between them. * * <pre> * UNSUBSCRIBED * | * v * NO_DEMAND -------------------> DEMAND * | ^ ^ | * | | | | * | --------- READING <----- | * | | | * | v | * ------------> COMPLETED <--------- * </pre> * Refer to the individual states for more information. */ private enum State { /** * The initial unsubscribed state. Will respond to {@link * #subscribe(AbstractListenerReadPublisher, Subscriber)} by * changing state to {@link #NO_DEMAND}. */ UNSUBSCRIBED { @Override <T> void subscribe(AbstractListenerReadPublisher<T> publisher, Subscriber<? super T> subscriber) { Assert.notNull(publisher, "Publisher must not be null"); Assert.notNull(subscriber, "Subscriber must not be null"); if (publisher.changeState(this, NO_DEMAND)) { Subscription subscription = new ReadSubscription(publisher); publisher.subscriber = subscriber; subscriber.onSubscribe(subscription); } else { throw new IllegalStateException(toString()); } } }, /** * State that gets entered when there is no demand. Responds to {@link * #request(AbstractListenerReadPublisher, long)} by increasing the demand, * changing state to {@link #DEMAND} and will check whether there * is data available for reading. */ NO_DEMAND { @Override <T> void request(AbstractListenerReadPublisher<T> publisher, long n) { if (Operators.checkRequest(n, publisher.subscriber)) { Operators.addAndGet(DEMAND_FIELD_UPDATER, publisher, n); if (publisher.changeState(this, DEMAND)) { publisher.checkOnDataAvailable(); } } } }, /** * State that gets entered when there is demand. Responds to * {@link #onDataAvailable(AbstractListenerReadPublisher)} by * reading the available data. The state will be changed to * {@link #NO_DEMAND} if there is no demand. */ DEMAND { @Override <T> void request(AbstractListenerReadPublisher<T> publisher, long n) { if (Operators.checkRequest(n, publisher.subscriber)) { Operators.addAndGet(DEMAND_FIELD_UPDATER, publisher, n); } } @Override <T> void onDataAvailable(AbstractListenerReadPublisher<T> publisher) { if (publisher.changeState(this, READING)) { try { boolean demandAvailable = publisher.readAndPublish(); if (demandAvailable) { publisher.changeState(READING, DEMAND); publisher.checkOnDataAvailable(); } else { publisher.changeState(READING, NO_DEMAND); } } catch (IOException ex) { publisher.onError(ex); } } } }, READING { @Override <T> void request(AbstractListenerReadPublisher<T> publisher, long n) { if (Operators.checkRequest(n, publisher.subscriber)) { Operators.addAndGet(DEMAND_FIELD_UPDATER, publisher, n); } } }, /** * The terminal completed state. Does not respond to any events. */ COMPLETED { @Override <T> void request(AbstractListenerReadPublisher<T> publisher, long n) { // ignore } @Override <T> void cancel(AbstractListenerReadPublisher<T> publisher) { // ignore } @Override <T> void onAllDataRead(AbstractListenerReadPublisher<T> publisher) { // ignore } @Override <T> void onError(AbstractListenerReadPublisher<T> publisher, Throwable t) { // ignore } }; <T> void subscribe(AbstractListenerReadPublisher<T> publisher, Subscriber<? super T> subscriber) { throw new IllegalStateException(toString()); } <T> void request(AbstractListenerReadPublisher<T> publisher, long n) { throw new IllegalStateException(toString()); } <T> void cancel(AbstractListenerReadPublisher<T> publisher) { publisher.changeState(this, COMPLETED); } <T> void onDataAvailable(AbstractListenerReadPublisher<T> publisher) { // ignore } <T> void onAllDataRead(AbstractListenerReadPublisher<T> publisher) { if (publisher.changeState(this, COMPLETED)) { if (publisher.subscriber != null) { publisher.subscriber.onComplete(); } } } <T> void onError(AbstractListenerReadPublisher<T> publisher, Throwable t) { if (publisher.changeState(this, COMPLETED)) { if (publisher.subscriber != null) { publisher.subscriber.onError(t); } } } } }