/* * 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.core.io.Closeable; import net.openhft.chronicle.engine.api.map.KeyValueStore; 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.TopicSubscriber; import net.openhft.chronicle.engine.api.tree.Asset; import net.openhft.chronicle.engine.api.tree.RequestContext; import net.openhft.chronicle.engine.map.EventConsumer; import net.openhft.chronicle.engine.map.ObjectSubscription; import net.openhft.chronicle.engine.query.Filter; import net.openhft.chronicle.engine.server.internal.MapWireHandler; import net.openhft.chronicle.network.connection.AbstractAsyncSubscription; 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 static net.openhft.chronicle.core.pool.ClassAliasPool.CLASS_ALIASES; import static net.openhft.chronicle.engine.server.internal.ObjectKVSubscriptionHandler.EventId.*; import static net.openhft.chronicle.network.connection.CoreFields.reply; public class RemoteKVSSubscription<K, V> extends AbstractRemoteSubscription<MapEvent<K, V>> implements ObjectSubscription<K, V>, Closeable { private static final Logger LOG = LoggerFactory.getLogger(MapWireHandler.class); private final Class<K> kClass; private final Class<V> vClass; private RequestContext rc; public RemoteKVSSubscription(@NotNull RequestContext context, @NotNull Asset asset) { super(asset.findView(TcpChannelHub.class), (long) 0, toUri(context)); kClass = context.keyType(); vClass = context.valueType(); this.rc = context; } @NotNull private static String toUri(@NotNull final RequestContext context) { StringBuilder sb = Wires.acquireStringBuilder(); sb.append(context.fullName()).append("?view=subscription"); if (context.messageType() != String.class) sb.append("&messageType=").append(CLASS_ALIASES.nameFor(context.messageType())); if (context.elementType() != String.class) sb.append("&elementType=").append(CLASS_ALIASES.nameFor(context.elementType())); return sb.toString(); } @Override public void registerTopicSubscriber(@NotNull RequestContext rc, @NotNull TopicSubscriber<K, V> subscriber) { if (hub.outBytesLock().isHeldByCurrentThread()) throw new IllegalStateException("Cannot view map while debugging"); hub.subscribe(new AbstractAsyncSubscription(hub, csp, "Remove KV Subscription registerTopicSubscriber") { @Override public void onSubscribe(@NotNull final WireOut wireOut) { subscribersToTid.put(subscriber, tid()); wireOut.writeEventName(registerTopicSubscriber).marshallable(m -> { m.write(() -> "keyType").typeLiteral(kClass); m.write(() -> "valueType").typeLiteral(vClass); if (rc.bootstrap() != null) m.writeEventName(() -> "bootstrap").bool(rc.bootstrap()); }); } @Override public void onConsumer(@NotNull final WireIn inWire) { inWire.readDocument(null, d -> { StringBuilder sb = Wires.acquireStringBuilder(); @NotNull ValueIn valueIn = d.readEventName(sb); if (reply.contentEquals(sb)) { valueIn.marshallable(m -> { @Nullable final K topic = m.read(() -> "topic").object(kClass); @Nullable final V message = m.read(() -> "message").object(vClass); RemoteKVSSubscription.this.onEvent(topic, message, subscriber); }); } else if (onEndOfSubscription.contentEquals(sb)) { RemoteKVSSubscription.this.onEndOfSubscription(); hub.unsubscribe(tid()); } }); } }); } private void onEvent(K topic, @Nullable V message, @NotNull TopicSubscriber<K, V> subscriber) { try { subscriber.onMessage(topic, message); } catch (InvalidSubscriberException noLongerValid) { unregisterTopicSubscriber(subscriber); } } @Override public void unregisterTopicSubscriber(@NotNull final TopicSubscriber subscriber) { Long tid = subscribersToTid.get(subscriber); 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(unregisterTopicSubscriber).text(""); }); }); } @Override public void registerKeySubscriber(@NotNull RequestContext rc, @NotNull Subscriber<K> subscriber, @NotNull Filter<K> filter) { registerSubscriber0(rc, subscriber, filter); } @Override public boolean needsPrevious() { return true; } @Override public void setKvStore(KeyValueStore<K, V> store) { } @Override public void notifyEvent(MapEvent<K, V> mpe) { throw new UnsupportedOperationException(""); } @Override public boolean hasSubscribers() { throw new UnsupportedOperationException("has subscribers, is only implemented on the " + "server"); } @Override public void registerDownstream(@NotNull EventConsumer<K, V> subscription) { registerSubscriber(rc.clone().messageType(rc.messageType()).elementType(MapEvent.class), subscription::notifyEvent, Filter.empty()); } }