org.springframework.http.server.reactive.AbstractRequestBodyPublisher.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.http.server.reactive.AbstractRequestBodyPublisher.java

Source

/*
 * 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.io.IOException;
import java.nio.channels.Channel;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ReadListener;

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.core.io.buffer.DataBuffer;

/**
 * Abstract base class for {@code Publisher} implementations that bridge between
 * event-listener APIs and Reactive Streams. Specifically, base class for the
 * Servlet 3.1 and Undertow support.
 *
 * @author Arjen Poutsma
 * @since 5.0
 * @see ServletServerHttpRequest
 * @see UndertowHttpHandlerAdapter
 */
abstract class AbstractRequestBodyPublisher implements Publisher<DataBuffer> {

    protected final Log logger = LogFactory.getLog(getClass());

    private final AtomicReference<State> state = new AtomicReference<>(State.UNSUBSCRIBED);

    private final AtomicLong demand = new AtomicLong();

    private Subscriber<? super DataBuffer> subscriber;

    @Override
    public void subscribe(Subscriber<? super DataBuffer> subscriber) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(this.state + " subscribe: " + subscriber);
        }
        this.state.get().subscribe(this, subscriber);
    }

    /**
     * Called via a listener interface to indicate that reading is possible.
     * @see ReadListener#onDataAvailable()
     * @see org.xnio.ChannelListener#handleEvent(Channel)
     */
    protected final void onDataAvailable() {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(this.state + " onDataAvailable");
        }
        this.state.get().onDataAvailable(this);
    }

    /**
     * Called via a listener interface to indicate that all data has been read.
     * @see ReadListener#onAllDataRead()
     * @see org.xnio.ChannelListener#handleEvent(Channel)
     */
    protected final void onAllDataRead() {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(this.state + " onAllDataRead");
        }
        this.state.get().onAllDataRead(this);
    }

    /**
     * Called by a listener interface to indicate that as error has occured.
     * @param t the error
     * @see ReadListener#onError(Throwable)
     */
    protected final void onError(Throwable t) {
        if (this.logger.isErrorEnabled()) {
            this.logger.error(this.state + " onError: " + t, t);
        }
        this.state.get().onError(this, t);
    }

    /**
     * Reads and publishes data buffers from the input. Continues till either there is no
     * more demand, or till there is no more data to be read.
     * @return {@code true} if there is more demand; {@code false} otherwise
     */
    private boolean readAndPublish() throws IOException {
        while (hasDemand()) {
            DataBuffer dataBuffer = read();
            if (dataBuffer != null) {
                getAndSub(this.demand, 1L);
                this.subscriber.onNext(dataBuffer);
            } else {
                return true;
            }
        }
        return false;
    }

    /**
     * Concurrent substraction bound to 0 and Long.MAX_VALUE.
     * Any concurrent write will "happen" before this operation.
     *
     * @param sequence current atomic to update
     * @param toSub    delta to sub
     * @return value before subscription, 0 or Long.MAX_VALUE
     */
    private static long getAndSub(AtomicLong sequence, long toSub) {
        long r;
        long u;
        do {
            r = sequence.get();
            if (r == 0 || r == Long.MAX_VALUE) {
                return r;
            }
            u = Operators.subOrZero(r, toSub);
        } while (!sequence.compareAndSet(r, u));

        return r;
    }

    protected abstract void checkOnDataAvailable();

    /**
     * Reads a data buffer from the input, if possible. Returns {@code null} if a buffer
     * could not be read.
     * @return the data buffer that was read; or {@code null}
     */
    protected abstract DataBuffer read() throws IOException;

    private boolean hasDemand() {
        return (this.demand.get() > 0);
    }

    private boolean changeState(State oldState, State newState) {
        return this.state.compareAndSet(oldState, newState);
    }

    private static final class RequestBodySubscription implements Subscription {

        private final AbstractRequestBodyPublisher publisher;

        public RequestBodySubscription(AbstractRequestBodyPublisher 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(AbstractRequestBodyPublisher, Subscriber)} by
         * changing state to {@link #NO_DEMAND}.
         */
        UNSUBSCRIBED {
            @Override
            void subscribe(AbstractRequestBodyPublisher publisher, Subscriber<? super DataBuffer> subscriber) {
                Objects.requireNonNull(subscriber);
                if (publisher.changeState(this, NO_DEMAND)) {
                    Subscription subscription = new RequestBodySubscription(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(AbstractRequestBodyPublisher, long)} by increasing the demand,
         * changing state to {@link #DEMAND} and will check whether there
         * is data available for reading.
         */
        NO_DEMAND {
            @Override
            void request(AbstractRequestBodyPublisher publisher, long n) {
                if (Operators.checkRequest(n, publisher.subscriber)) {
                    Operators.addAndGet(publisher.demand, n);
                    if (publisher.changeState(this, DEMAND)) {
                        publisher.checkOnDataAvailable();
                    }
                }
            }
        },

        /**
         * State that gets entered when there is demand. Responds to
         * {@link #onDataAvailable(AbstractRequestBodyPublisher)} by
         * reading the available data. The state will be changed to
         * {@link #NO_DEMAND} if there is no demand.
         */
        DEMAND {
            @Override
            void request(AbstractRequestBodyPublisher publisher, long n) {
                if (Operators.checkRequest(n, publisher.subscriber)) {
                    Operators.addAndGet(publisher.demand, n);
                }
            }

            @Override
            void onDataAvailable(AbstractRequestBodyPublisher 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
            void request(AbstractRequestBodyPublisher publisher, long n) {
                if (Operators.checkRequest(n, publisher.subscriber)) {
                    Operators.addAndGet(publisher.demand, n);
                }
            }
        },

        /**
         * The terminal completed state. Does not respond to any events.
         */
        COMPLETED {
            @Override
            void request(AbstractRequestBodyPublisher publisher, long n) {
                // ignore
            }

            @Override
            void cancel(AbstractRequestBodyPublisher publisher) {
                // ignore
            }

            @Override
            void onAllDataRead(AbstractRequestBodyPublisher publisher) {
                // ignore
            }

            @Override
            void onError(AbstractRequestBodyPublisher publisher, Throwable t) {
                // ignore
            }
        };

        void subscribe(AbstractRequestBodyPublisher publisher, Subscriber<? super DataBuffer> subscriber) {
            throw new IllegalStateException(toString());
        }

        void request(AbstractRequestBodyPublisher publisher, long n) {
            throw new IllegalStateException(toString());
        }

        void cancel(AbstractRequestBodyPublisher publisher) {
            publisher.changeState(this, COMPLETED);
        }

        void onDataAvailable(AbstractRequestBodyPublisher publisher) {
            // ignore
        }

        void onAllDataRead(AbstractRequestBodyPublisher publisher) {
            if (publisher.changeState(this, COMPLETED)) {
                if (publisher.subscriber != null) {
                    publisher.subscriber.onComplete();
                }
            }
        }

        void onError(AbstractRequestBodyPublisher publisher, Throwable t) {
            if (publisher.changeState(this, COMPLETED)) {
                if (publisher.subscriber != null) {
                    publisher.subscriber.onError(t);
                }
            }
        }
    }

}