/* * 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.server.internal; 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.Asset; import net.openhft.chronicle.engine.api.tree.AssetNotFoundException; import net.openhft.chronicle.engine.api.tree.RequestContext; import net.openhft.chronicle.engine.query.Filter; import net.openhft.chronicle.network.connection.CoreFields; import net.openhft.chronicle.network.connection.WireOutPublisher; import net.openhft.chronicle.wire.*; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static net.openhft.chronicle.engine.server.internal.SubscriptionHandler.SubscriptionEventID.*; import static net.openhft.chronicle.network.connection.CoreFields.reply; import static net.openhft.chronicle.network.connection.WireOutPublisher.newThrottledWireOutPublisher; /** * Created by rob on 28/06/2015. */ public class SubscriptionHandler<T extends SubscriptionCollection> extends AbstractHandler { private static final Logger LOG = LoggerFactory.getLogger(SubscriptionHandler.class); final StringBuilder eventName = new StringBuilder(); final Map<Long, Object> tidToListener = new ConcurrentHashMap<>(); Wire outWire; T subscription; WireOutPublisher publisher; Asset asset; /** * after writing the tid to the wire * * @param eventName the name of the event * @return true if processed */ boolean after(@NotNull StringBuilder eventName) { if (topicSubscriberCount.contentEquals(eventName)) { outWire.writeEventName(reply).int8(subscription.topicSubscriberCount()); return true; } if (keySubscriberCount.contentEquals(eventName)) { outWire.writeEventName(reply).int8(subscription.keySubscriberCount()); return true; } if (entrySubscriberCount.contentEquals(eventName)) { outWire.writeEventName(reply).int8(subscription.entrySubscriberCount()); return true; } return false; } /** * before writing the tid to the wire * * @param tid the tid * @param valueIn the value in from the wire * @return true if processed */ boolean before(Long tid, @NotNull ValueIn valueIn) throws AssetNotFoundException { if (registerSubscriber.contentEquals(eventName)) { final Class subscriptionType = valueIn.typeLiteral(); final StringBuilder sb = Wires.acquireStringBuilder(); @NotNull final ValueIn valueIn1 = valueIn.wireIn().readEventName(sb); @NotNull final Filter filter = "filter".contentEquals(sb) ? valueIn1.object(Filter.class) : Filter.empty(); if (tidToListener.containsKey(tid)) { LOG.info("Duplicate registration for tid " + tid); return true; } final WireOutPublisher pub = (requestContext.throttlePeriodMs() == 0) ? publisher : newThrottledWireOutPublisher(requestContext.throttlePeriodMs(), publisher); @NotNull Subscriber<Object> listener = new LocalSubscriber(tid, pub); tidToListener.put(tid, listener); @NotNull RequestContext rc = requestContext.clone().elementType(subscriptionType); @NotNull final SubscriptionCollection subscription = asset.acquireSubscription(rc); subscription.registerSubscriber(rc, listener, filter); return true; } if (unregisterSubscriber.contentEquals(eventName)) { skipValue(valueIn); @NotNull Subscriber<Object> listener = (Subscriber) tidToListener.remove(tid); if (listener == null) { Jvm.debug().on(getClass(), "No subscriber to present to unregisterSubscriber (" + tid + ")"); return true; } asset.unregisterSubscriber(requestContext, listener); return true; } return false; } @Override protected void unregisterAll() { tidToListener.forEach((k, listener) -> asset.unregisterSubscriber(requestContext, (Subscriber<Object>) listener)); tidToListener.clear(); } public enum SubscriptionEventID implements ParameterizeWireKey { registerSubscriber, unregisterSubscriber, keySubscriberCount, entrySubscriberCount, topicSubscriberCount; private final WireKey[] params; <P extends WireKey> SubscriptionEventID(P... params) { this.params = params; } @NotNull public <P extends WireKey> P[] params() { return (P[]) this.params; } } class LocalSubscriber implements Subscriber<Object> { private final Long tid; private final WireOutPublisher publisher; volatile boolean subscriptionEnded; LocalSubscriber(Long tid, WireOutPublisher publisher) { this.tid = tid; this.publisher = publisher; } @Override public void onMessage(Object e) throws InvalidSubscriberException { if (subscriptionEnded) return; @NotNull final WriteMarshallable event = p -> { p.writeDocument(true, wire -> wire.writeEventName(CoreFields.tid).int64(tid)); p.writeNotCompleteDocument(false, wire -> wire.write(reply).object(e)); }; final Object key = (e instanceof MapEvent) ? ((MapEvent) e).getKey() : e; synchronized (publisher) { publisher.put(key, event); } } @Override public void onEndOfSubscription() { subscriptionEnded = true; synchronized (publisher) { if (!publisher.isClosed()) { // no more data. @NotNull WriteMarshallable toPublish = publish -> { publish.writeDocument(true, wire -> wire.writeEventName(CoreFields.tid).int64(tid)); publish.writeDocument(false, wire -> wire.writeEventName(ObjectKVSubscriptionHandler.EventId.onEndOfSubscription).text("")); }; publisher.put(null, toPublish); } } } @NotNull @Override public String toString() { return "LocalSubscriber{" + "tid=" + tid + '}'; } } }