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

com.kolibrifx.plovercrest.server.internal.StreamObservers 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;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.log4j.Logger;
import com.kolibrifx.common.Disposable;
import com.kolibrifx.common.dispatcher.Dispatcher;
import com.kolibrifx.common.dispatcher.ThreadedDispatcher;
import com.kolibrifx.plovercrest.client.internal.SubscriptionQuery;
import com.kolibrifx.plovercrest.server.streams.PollableReader;
import com.kolibrifx.plovercrest.server.streams.Stream;
import com.kolibrifx.plovercrest.server.streams.StreamCreateListener;
import com.kolibrifx.plovercrest.server.streams.StreamDataTriggerer;
import com.kolibrifx.plovercrest.server.streams.StreamEngine;
import com.kolibrifx.plovercrest.server.streams.StreamObserver;

/**
 * Adapts poll-based readers to subscribable events.
 */
public class StreamObservers implements Disposable {
    private static final Logger log = Logger.getLogger(StreamObservers.class);

    private final Dispatcher dispatcher = new ThreadedDispatcher("StreamObservers"); // could be injected
    private final StreamEngine streamEngine;
    private final Collection> activePollers =
            Collections.synchronizedSet(new HashSet>());
    private final Map, Disposable> createListeners =
            Collections.synchronizedMap(new HashMap, Disposable>());
    private final AtomicBoolean closed = new AtomicBoolean(false);

    private class ResumablePoller implements Runnable {
        private final String name;
        private final SubscriptionQuery query;
        private final StreamObserver observer;
        private final Class elementType;
        private PollableReader reader;
        private volatile boolean closed = false;
        private final StreamDataTriggerer triggerer = createDataTriggerer(this);

        ResumablePoller(final String name,
                        final SubscriptionQuery query,
                        final StreamObserver observer,
                        final Class elementType) {
            this.name = name;
            this.query = query;
            this.observer = observer;
            this.elementType = elementType;
        }

        private PollableReader createReader(final Stream stream) {
            switch (query.getKind()) {
                case INDEX:
                    return stream.createReaderFromIndex(query.getStart(), observer);
                case TIMESTAMP:
                    return stream.createReaderFromTimestamp(query.getStart(), observer);
                default:
                    throw new IllegalStateException();
            }
        }

        @Override
        public void run() {
            if (reader == null) {
                final Stream stream = streamEngine.open(name, elementType);
                if (stream == null) {
                    log.info("Could not open stream: " + name);
                    addCreateListener(name, this);
                    return;
                }
                reader = createReader(stream);
            }
            assert reader != null;
            // observe elements until end
            while (!closed && reader.poll()) {
                // do nothing: observer is triggered by poll()
            }
            if (!closed) {
                // reached the end of the stream, make sure we wake up when more data is available
                reader.registerDataTriggerer(triggerer);
            }
        }

        void close() {
            closed = true;
        }
    }

    public StreamObservers(final StreamEngine streamEngine) {
        this.streamEngine = streamEngine;
    }

    private boolean disposeCreateListener(final ResumablePoller poller) {
        final Disposable d = createListeners.remove(poller);
        if (d != null) {
            d.close();
        }
        return d != null;
    }

    private void addCreateListener(final String name, final ResumablePoller poller) {
        if (disposeCreateListener(poller)) {
            // Not a normal case, but can happen due to create/delete race conditions
            log.warn("Added create listener more than once for stream: " + name);
        }
        createListeners.put(poller, streamEngine.addCreateListener(name, new StreamCreateListener() {
            @Override
            public void onCreated(final String name) {
                disposeCreateListener(poller);
                dispatcher.runLater(poller);
            }
        }));
    }

    private StreamDataTriggerer createDataTriggerer(final Runnable runnable) {
        return new StreamDataTriggerer() {
            @Override
            public void wakeUpNow() {
                if (closed.get()) {
                    return;
                }
                dispatcher.runLater(runnable);
            }

            @Override
            public void wakeUpAtTime(final long clockMillis) {
                if (closed.get()) {
                    return;
                }
                final long delay = clockMillis - dispatcher.currentTimeMillis();
                dispatcher.runLater(runnable, Math.max(0, delay));
            }
        };
    }

    public  Disposable subscribe(final String name, final SubscriptionQuery query, final StreamObserver observer,
                                    final Class elementType) {
        final ResumablePoller poller = new ResumablePoller<>(name, query, observer, elementType);
        activePollers.add(poller);
        dispatcher.runLater(poller);
        return new Disposable() {
            @Override
            public void close() {
                if (activePollers.remove(poller)) {
                    poller.close();
                }
            }
        };
    }

    @Override
    public void close() {
        if (!closed.compareAndSet(false, true)) {
            return;
        }
        synchronized (activePollers) {
            for (final ResumablePoller d : activePollers) {
                d.close();
            }
            activePollers.clear();
        }
        synchronized (createListeners) {
            for (final Disposable d : createListeners.values()) {
                d.close();
            }
            createListeners.clear();
        }
        dispatcher.close();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy