/*
* 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.map.MapEvent;
import net.openhft.chronicle.engine.api.pubsub.InvalidSubscriberException;
import net.openhft.chronicle.engine.api.pubsub.Subscriber;
import net.openhft.chronicle.engine.api.pubsub.SubscriptionCollection;
import net.openhft.chronicle.engine.api.tree.RequestContext;
import net.openhft.chronicle.engine.query.Filter;
import net.openhft.chronicle.engine.server.internal.MapWireHandler;
import net.openhft.chronicle.engine.server.internal.PublisherHandler;
import net.openhft.chronicle.engine.tree.TopologicalEvent;
import net.openhft.chronicle.network.connection.AbstractAsyncSubscription;
import net.openhft.chronicle.network.connection.AbstractStatelessClient;
import net.openhft.chronicle.network.connection.CoreFields;
import net.openhft.chronicle.network.connection.TcpChannelHub;
import net.openhft.chronicle.wire.ValueIn;
import net.openhft.chronicle.wire.WireIn;
import net.openhft.chronicle.wire.WireOut;
import net.openhft.chronicle.wire.Wires;
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 static net.openhft.chronicle.core.pool.ClassAliasPool.CLASS_ALIASES;
import static net.openhft.chronicle.engine.server.internal.SubscriptionHandler.SubscriptionEventID.*;
/**
* Created by rob on 27/06/2015.
*/
abstract class AbstractRemoteSubscription<E> extends AbstractStatelessClient implements SubscriptionCollection<E> {
private static final Logger LOG = LoggerFactory.getLogger(MapWireHandler.class);
final Map<Object, Long> subscribersToTid = new ConcurrentHashMap<>();
/**
* @param hub for this connection
* @param cid used by proxies such as the entry-set
* @param csp the uri of the request
*/
AbstractRemoteSubscription(@NotNull TcpChannelHub hub, long cid, @NotNull String csp) {
super(hub, cid, csp);
}
public void registerSubscriber(@NotNull RequestContext rc,
@NotNull Subscriber<E> subscriber,
@NotNull Filter<E> filter) {
registerSubscriber0(rc, subscriber, filter);
}
public void unregisterSubscriber(@NotNull Subscriber subscriber) {
unregisterSubscriber0(subscriber);
}
void registerSubscriber0(@NotNull RequestContext rc,
@NotNull Subscriber subscriber,
@NotNull Filter filter) {
if (hub.outBytesLock().isHeldByCurrentThread())
throw new IllegalStateException("Cannot view map while debugging");
@Nullable final Boolean bootstrap = rc.bootstrap();
@Nullable final Boolean endSubscriptionAfterBootstrap = rc.endSubscriptionAfterBootstrap();
@Nullable String csp = this.csp;
if (bootstrap != null)
csp = csp + "&bootstrap=" + bootstrap;
if (endSubscriptionAfterBootstrap != null)
csp = csp + "&endSubscriptionAfterBootstrap=" + endSubscriptionAfterBootstrap;
if (rc.throttlePeriodMs() > 0)
csp = csp + "&throttlePeriodMs=" + rc.throttlePeriodMs();
if (rc.dontPersist())
csp = csp + "&dontPersist=" + rc.dontPersist();
hub.subscribe(new AbstractAsyncSubscription(hub, csp, this.getClass().getSimpleName()) {
{
subscribersToTid.put(subscriber, tid());
}
@Override
public void onSubscribe(@NotNull final WireOut wireOut) {
wireOut.writeEventName(registerSubscriber).
typeLiteral(CLASS_ALIASES.nameFor(rc.elementType()));
if (!filter.isEmpty())
wireOut.writeEventName(() -> "filter").object(filter);
}
@Override
public void onConsumer(@NotNull final WireIn inWire) {
inWire.readDocument(null, d -> {
final StringBuilder eventName = Wires.acquireStringBuilder();
@NotNull final ValueIn valueIn = d.readEventName(eventName);
if (PublisherHandler.EventId.onEndOfSubscription.contentEquals(eventName)) {
subscriber.onEndOfSubscription();
subscribersToTid.remove(this);
hub.unsubscribe(tid());
} else if (CoreFields.reply.contentEquals(eventName)) {
@NotNull final Class aClass = rc.elementType();
@Nullable final Object object = (MapEvent.class.isAssignableFrom(aClass) ||
(TopologicalEvent.class.isAssignableFrom(aClass))) ?
valueIn.typedMarshallable()
: valueIn.object(rc.elementType());
AbstractRemoteSubscription.this.onEvent(object, subscriber);
}
});
}
});
}
void onEvent(@Nullable Object message, @NotNull Subscriber subscriber) {
try {
if (message == null) {
// todo remove subscriber.
} else {
subscriber.onMessage(message);
}
} catch (InvalidSubscriberException noLongerValid) {
unregisterSubscriber(subscriber);
}
}
void unregisterSubscriber0(@NotNull Subscriber subscriber) {
final Long tid = subscribersToTid.get(subscriber);
if (tid == null) {
Jvm.warn().on(getClass(), "There is no subscription to unsubscribe");
return;
}
hub.preventSubscribeUponReconnect(tid);
if (!hub.isOpen()) {
hub.unsubscribe(tid);
return;
}
boolean success = hub.lock(() -> {
writeMetaDataForKnownTID(tid);
hub.outWire().writeDocument(false, wireOut ->
wireOut.writeEventName(unregisterSubscriber).text(""));
});
if (!success)
hub.unsubscribe(tid);
}
@Override
public int topicSubscriberCount() {
return proxyReturnInt(topicSubscriberCount);
}
@Override
public int keySubscriberCount() {
return proxyReturnInt(keySubscriberCount);
}
@Override
public int entrySubscriberCount() {
return proxyReturnInt(entrySubscriberCount);
}
}