All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.kolibrifx.plovercrest.server.internal.streams.TableStream Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010-2017, KolibriFX AS. Licensed under the Apache License, version 2.0.
 */

package com.kolibrifx.plovercrest.server.internal.streams;

import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import com.kolibrifx.common.Disposable;
import com.kolibrifx.plovercrest.client.TableMetadata;
import com.kolibrifx.plovercrest.client.TableSerializer;
import com.kolibrifx.plovercrest.server.PlovercrestEngine;
import com.kolibrifx.plovercrest.server.ReadObserver;
import com.kolibrifx.plovercrest.server.Table;
import com.kolibrifx.plovercrest.server.TableReader;
import com.kolibrifx.plovercrest.server.internal.EventType;
import com.kolibrifx.plovercrest.server.internal.TableEvent;
import com.kolibrifx.plovercrest.server.internal.TableListener;
import com.kolibrifx.plovercrest.server.streams.ReaderPosition;
import com.kolibrifx.plovercrest.server.streams.SeekableReader;
import com.kolibrifx.plovercrest.server.streams.SeekableStream;
import com.kolibrifx.plovercrest.server.streams.StreamDataTriggerer;
import com.kolibrifx.plovercrest.server.streams.StreamObserver;
import com.kolibrifx.plovercrest.server.streams.StreamWriter;

public class TableStream implements SeekableStream {
    private final Table table;
    private final TableSerializer serializer;
    private final PlovercrestEngine engine;
    private final TableStreamWriter writer;

    /**
     * Small wrapper for {@link StreamDataTriggerer} to make sure it is never triggered more than
     * once.
     */
    private static class DataTriggererWrapper {
        private final StreamDataTriggerer triggerer;
        private final AtomicBoolean done = new AtomicBoolean();

        DataTriggererWrapper(final StreamDataTriggerer triggerer) {
            this.triggerer = triggerer;
        }

        void wakeUpNow() {
            if (done.compareAndSet(false, true)) {
                triggerer.wakeUpNow();
            }
        }
    }

    private static class OneShotWakeUp implements Disposable {
        private final Disposable disposable;
        private volatile boolean closed = false;

        OneShotWakeUp(final DataTriggererWrapper triggerer, final String tableName, final PlovercrestEngine engine) {
            disposable = engine.addTableListener(tableName, new TableListener() {
                @Override
                public boolean shouldHandleTableEvent(final EventType type) {
                    switch (type) {
                        case ENTRY_WRITTEN:
                        case LAST_VALID_TIMESTAMP_UPDATED:
                        case TABLE_FROZEN:
                            return true;
                        default:
                            return false;
                    }
                }

                @Override
                public void handleTableEvent(final TableEvent event) {
                    if (closed) {
                        return;
                    }
                    triggerer.wakeUpNow();
                    close();
                }
            });
        }

        @Override
        public void close() {
            closed = true;
            if (disposable != null) {
                disposable.close();
            }
        }
    }

    private class TableStreamReadObserver extends ReadObserver {
        private boolean observedFrozen = false;
        private final StreamObserver observer;
        private final TableReader tableReader;

        TableStreamReadObserver(final StreamObserver observer, final TableReader tableReader) {
            this.observer = observer;
            this.tableReader = tableReader;
        }

        void triggerEndEvents() {
            observer.onObserveEnd(getLastValidTimestamp(), tableReader.getEntryIndex());
            if (observedFrozen) {
                observer.onObserveFrozen();
                observedFrozen = false; // in case of unfreeze
            }
        }

        @Override
        public T observe(final long timestamp, final ByteBuffer bytes) {
            final T element = serializer.unserialize(timestamp, bytes);
            observer.onObserve(timestamp, tableReader.getEntryIndex(), element);
            return element;
        }

        @Override
        public void observeEnd(final long lastValidTimestamp) {
            super.observeEnd(lastValidTimestamp);
            // We cannot trigger onObserveEnd() here due to race conditions: it is done through triggerEndEvents() instead
        }

        @Override
        public void observeFrozen() {
            observedFrozen = true;
            // observer.onObserveFrozen() is done through triggerEndEvents()
        }

        /**
         * Has anything occured in the table that has not yet been observed? (new entries, last
         * valid timestamp, frozen state)
         * 

* Note: here be race conditions; a true return value can always be trusted, * but a false value can change at any time. */ boolean isObserverDataAvailable() { if (table.getEntryCount() > tableReader.getEntryIndex()) { return true; } if (table.getLastValidTimestamp() > getLastTimestamp()) { return true; } if (table.getWriter().isFrozen() && !observedFrozen) { return true; } return false; } } public TableStream(final PlovercrestEngine engine, final Table table, final TableSerializer serializer) { this.engine = engine; this.table = table; this.serializer = serializer; this.writer = new TableStreamWriter(table.getWriter()); } private SeekableReader createReaderImpl(final long timestampOrIndex, final boolean useIndex, final StreamObserver observer) { final TableReader tableReader = table.getReader(); final TableStreamReadObserver readObserver = new TableStreamReadObserver(observer, tableReader); if (useIndex) { tableReader.seekToEntryIndex(timestampOrIndex); } else { tableReader.seekTakePrevious(timestampOrIndex); } return new SeekableReader() { private Disposable wakeUpListener; @Override public boolean poll() { final T element = tableReader.read(readObserver); if (element == null) { readObserver.triggerEndEvents(); } return element != null; } @Override public void registerDataTriggerer(final StreamDataTriggerer triggerer) { final DataTriggererWrapper triggererWrapper = new DataTriggererWrapper(triggerer); if (readObserver.isObserverDataAvailable()) { // more data is available now triggererWrapper.wakeUpNow(); } else { if (wakeUpListener != null) { wakeUpListener.close(); } wakeUpListener = new OneShotWakeUp(triggererWrapper, table.getName(), engine); if (readObserver.isObserverDataAvailable()) { // Corner case: new data became available while the listener was initialized. // In this case the listener could have registered too late to get the write event, // so we have to invoke the data triggerer now. wakeUpListener.close(); wakeUpListener = null; triggererWrapper.wakeUpNow(); } } } @Override public ReaderPosition getReaderPosition() { return ReaderPosition.createIndexPosition(tableReader.getEntryIndex()); } @Override public long seekTakePrevious(final long timestamp) { return tableReader.seekTakePrevious(timestamp); } }; } @Override public SeekableReader createReaderFromTimestamp(final long timestamp, final StreamObserver observer) { return createReaderImpl(timestamp, false, observer); } @Override public SeekableReader createReaderFromIndex(final long index, final StreamObserver observer) { return createReaderImpl(index, true, observer); } @Override public long getFirstTimestamp() { return table.getFirstTimestamp(); } @Override public long getLastTimestamp() { return table.getLastTimestamp(); } @Override public long getDataLengthInBytes() { return table.getDataLength(); } @Override public long getEntryCount() { return table.getEntryCount(); } @Override public TableMetadata getMetadata() { return table.getInfo().getMetadata(); } @Override public long getLastValidTimestamp() { return table.getLastValidTimestamp(); } @Override public boolean isFrozen() { return table.getWriter().isFrozen(); } @Override public StreamWriter getWriter() { return writer; } @Override public boolean delete() { return engine.delete(table.getName()); } @Override public boolean rename(final String newName) { return engine.rename(table.getName(), newName) != null; } @Override public String getName() { return table.getName(); } @Override public Class getElementClass() { return serializer.elementClass(); } @Override public long seekPreviousTimestamp(final long targetTimestamp) { final TableReader reader = table.getReader(); return reader.seekTakePrevious(targetTimestamp); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy