/*
* Copyright 2016 higherfrequencytrading.com
*
* 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 net.openhft.chronicle.engine.map.remote;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.engine.api.pubsub.InvalidSubscriberException;
import net.openhft.chronicle.engine.api.pubsub.Subscriber;
import net.openhft.chronicle.engine.api.query.IndexQuery;
import net.openhft.chronicle.engine.api.query.IndexQueueView;
import net.openhft.chronicle.engine.api.query.IndexedValue;
import net.openhft.chronicle.engine.api.query.VanillaIndexQuery;
import net.openhft.chronicle.engine.api.tree.Asset;
import net.openhft.chronicle.engine.api.tree.RequestContext;
import net.openhft.chronicle.engine.server.internal.MapWireHandler;
import net.openhft.chronicle.network.connection.AbstractAsyncSubscription;
import net.openhft.chronicle.network.connection.AbstractStatelessClient;
import net.openhft.chronicle.network.connection.TcpChannelHub;
import net.openhft.chronicle.wire.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import static net.openhft.chronicle.engine.server.internal.IndexQueueViewHandler.EventId.*;
import static net.openhft.chronicle.network.connection.CoreFields.reply;
/**
* @author Rob Austin.
*/
public class RemoteIndexQueueView<K extends Marshallable, V extends Marshallable> extends
AbstractStatelessClient<MapWireHandler.EventId>
implements IndexQueueView<Subscriber<IndexedValue<V>>, V> {
private static final Logger LOG = LoggerFactory.getLogger(RemoteIndexQueueView.class);
private final Map<Object, Long> subscribersToTid = new ConcurrentHashMap<>();
int i;
public RemoteIndexQueueView(@NotNull final RequestContext context,
@NotNull Asset asset) {
super(asset.findView(TcpChannelHub.class), (long) 0, toUri(context));
}
@NotNull
private static String toUri(@NotNull final RequestContext context) {
return context.viewType(IndexQueueView.class).toUri();
}
@Override
public void registerSubscriber(@NotNull Subscriber<IndexedValue<V>> subscriber,
@NotNull IndexQuery<V> vanillaIndexQuery) {
@NotNull final AtomicBoolean hasAlreadySubscribed = new AtomicBoolean();
if (hub.outBytesLock().isHeldByCurrentThread())
throw new IllegalStateException("Cannot view map while debugging");
@Nullable final AbstractAsyncSubscription asyncSubscription = new AbstractAsyncSubscription(
hub,
csp,
"RemoteIndexQueueView registerTopicSubscriber") {
// this allows us to resubscribe from the last index we received
volatile long fromIndex = 0;
@Override
public void onSubscribe(@NotNull final WireOut wireOut) {
IndexQuery<V> q;
// this allows us to resubscribe from the last index we received
if (hasAlreadySubscribed.getAndSet(true)) {
VanillaIndexQuery query = vanillaIndexQuery.deepCopy();
query.fromIndex(fromIndex + 1);
query.bootstrap(false);
q = query;
} else
q = vanillaIndexQuery;
subscribersToTid.put(subscriber, tid());
wireOut.writeEventName(registerSubscriber)
.typedMarshallable(q);
}
private final IndexedValue<V> instance = new IndexedValue<V>();
private final Function<Class, ReadMarshallable> reuseFunction = c -> instance;
@Override
public void onConsumer(@NotNull final WireIn inWire) {
try (DocumentContext dc = inWire.readingDocument()) {
if (!dc.isPresent())
return;
final StringBuilder sb = Wires.acquireStringBuilder();
@NotNull final ValueIn valueIn = dc.wire().readEventName(sb);
if (reply.contentEquals(sb))
try {
@Nullable final IndexedValue<V> e = valueIn.typedMarshallable(reuseFunction);
fromIndex = Math.max(fromIndex, e.index());
subscriber.onMessage(e);
} catch (InvalidSubscriberException e) {
RemoteIndexQueueView.this.unregisterSubscriber(subscriber);
}
else if (onEndOfSubscription.contentEquals(sb)) {
subscriber.onEndOfSubscription();
hub.unsubscribe(tid());
}
} catch (Exception e) {
Jvm.warn().on(getClass(), e);
}
}
};
hub.subscribe(asyncSubscription);
}
@Override
public void unregisterSubscriber(@NotNull Subscriber<IndexedValue<V>> listener) {
Long tid = subscribersToTid.get(listener);
if (tid == null) {
Jvm.warn().on(getClass(), "There is no subscription to unsubscribe, was " + subscribersToTid.size() + " other subscriptions.");
return;
}
hub.preventSubscribeUponReconnect(tid);
if (!hub.isOpen()) {
hub.unsubscribe(tid);
return;
}
hub.lock(() -> {
writeMetaDataForKnownTID(tid);
hub.outWire().writeDocument(false, wireOut -> {
wireOut.writeEventName(unregisterSubscriber).text("");
});
});
}
}