/* * Copyright (C) 2015 SoftIndex LLC. * * 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 io.datakernel.stream; import io.datakernel.annotation.Nullable; import io.datakernel.eventloop.Eventloop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; import static io.datakernel.stream.StreamStatus.*; /** * It is basic implementation of {@link StreamProducer} * * @param <T> type of received item */ public abstract class AbstractStreamProducer<T> implements StreamProducer<T> { private final Logger logger = LoggerFactory.getLogger(this.getClass()); protected final Eventloop eventloop; protected final List<T> bufferedList = new ArrayList<>(); protected StreamConsumer<T> downstreamConsumer; protected StreamDataReceiver<T> downstreamDataReceiver = new DataReceiverBeforeStart<>(this, bufferedList); private StreamStatus status = READY; private boolean ready = true; protected Exception error; protected Object tag; protected AbstractStreamProducer(Eventloop eventloop) { this.eventloop = checkNotNull(eventloop); } private boolean rewiring; /** * Sets consumer for this producer. At the moment of calling this method producer shouldn't have consumer, * as well as consumer shouldn't have producer, otherwise there will be error * * @param downstreamConsumer consumer for streaming */ @Override public final void streamTo(final StreamConsumer<T> downstreamConsumer) { checkNotNull(downstreamConsumer); if (rewiring || this.downstreamConsumer == downstreamConsumer) return; rewiring = true; boolean firstTime = this.downstreamConsumer == null; if (this.downstreamConsumer != null) { this.downstreamConsumer.streamFrom(StreamProducers.<T>closingWithError(eventloop, new Exception("Downstream disconnected"))); } this.downstreamConsumer = downstreamConsumer; downstreamConsumer.streamFrom(this); bindDataReceiver(); if (firstTime && bufferedList.size() != 0) { logger.trace("{} Send buffered items", this); for (T item : bufferedList) { downstreamConsumer.getDataReceiver().onData(item); } bufferedList.clear(); } if (status == END_OF_STREAM) { downstreamConsumer.onProducerEndOfStream(); return; } if (status == CLOSED_WITH_ERROR) { downstreamConsumer.onProducerError(error); return; } if (firstTime) { eventloop.post(new Runnable() { @Override public void run() { if (status.isOpen()) { onStarted(); } } }); } rewiring = false; } /** * Sends {@code item} to consumer * * @param item item to be sent */ public void send(T item) { assert status.isOpen(); downstreamDataReceiver.onData(item); } protected void doProduce() { } public void produce() { if (!isStatusReady()) return; doProduce(); } public void resumeProduce() { eventloop.post(new Runnable() { @Override public void run() { produce(); } }); } protected void onStarted() { } @Nullable public final StreamConsumer<T> getDownstream() { return downstreamConsumer; } /** * Connects consumer's {@link StreamDataReceiver} to producer */ @Override public final void bindDataReceiver() { if (status.isClosed()) { downstreamDataReceiver = new DataReceiverAfterClose<>(); return; } StreamDataReceiver<T> newDataReceiver = downstreamConsumer.getDataReceiver(); assert newDataReceiver != null; if (downstreamDataReceiver != newDataReceiver) { downstreamDataReceiver = newDataReceiver; onDataReceiverChanged(); } } protected void onDataReceiverChanged() { } public StreamDataReceiver<T> getDownstreamDataReceiver() { return downstreamDataReceiver; } protected void onSuspended() { } @Override public final void onConsumerSuspended() { if (status != READY) return; setStatus(SUSPENDED); onSuspended(); } protected void onResumed() { } @Override public final void onConsumerResumed() { if (status != SUSPENDED) return; setStatus(READY); onResumed(); } public void sendEndOfStream() { if (status.isClosed()) return; setStatus(END_OF_STREAM); downstreamDataReceiver = new DataReceiverAfterClose<>(); if (downstreamConsumer != null) { downstreamConsumer.onProducerEndOfStream(); } onEndOfStream(); doCleanup(); } private void closeWithError(Exception e, boolean sendToConsumer) { if (status.isClosed()) return; setStatus(CLOSED_WITH_ERROR); error = e; downstreamDataReceiver = new DataReceiverAfterClose<>(); logger.trace("StreamProducer {} closed with error {}", this, error.toString()); if (sendToConsumer && downstreamConsumer != null) { downstreamConsumer.onProducerError(e); } onError(e); doCleanup(); } public void closeWithError(Exception e) { closeWithError(e, true); } @Override public final void onConsumerError(Exception e) { closeWithError(e, false); } protected void onEndOfStream() { } protected void onError(Exception e) { } protected void doCleanup() { } @Override public final StreamStatus getProducerStatus() { return status; } @Override public final Exception getProducerException() { return error; } private void setStatus(StreamStatus status) { this.status = status; this.ready = status == READY; } public final boolean isStatusReady() { return ready; } public final Object getTag() { return tag; } public final void setTag(Object tag) { this.tag = tag; } @Override public String toString() { return tag != null ? tag.toString() : super.toString(); } private final class DataReceiverBeforeStart<T> implements StreamDataReceiver<T> { private final AbstractStreamProducer self; private final List<T> list; private DataReceiverBeforeStart(AbstractStreamProducer self, List<T> list) { this.self = self; this.list = list; } @Override public void onData(T item) { logger.trace("{} add item to buffer", self); self.onConsumerSuspended(); list.add(item); } } private final class DataReceiverAfterClose<T> implements StreamDataReceiver<T> { @Override public void onData(T item) { logger.error("Unexpected item {} after end-of-stream of {}", item, this); // TODO(vmykhalko): throw new AssertionError instead of logging // throw new AssertionError(format("Unexpected item \"%s\" after end-of-stream of \"%s\"", item, this)); } } }