com.kolibrifx.plovercrest.server.internal.StreamObservers Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plovercrest-server Show documentation
Show all versions of plovercrest-server Show documentation
Plovercrest server library.
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();
}
}