/* * 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.pubsub; import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.engine.api.pubsub.InvalidSubscriberException; import net.openhft.chronicle.engine.api.pubsub.Publisher; 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.AssetNotFoundException; import net.openhft.chronicle.engine.api.tree.RequestContext; import net.openhft.chronicle.engine.server.internal.ReferenceHandler; import net.openhft.chronicle.engine.server.internal.TopicPublisherHandler.EventId; import net.openhft.chronicle.engine.server.internal.TopicPublisherHandler.Params; 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.PublisherHandler.EventId.registerSubscriber; import static net.openhft.chronicle.engine.server.internal.ReferenceHandler.EventId.unregisterSubscriber; import static net.openhft.chronicle.engine.server.internal.TopicPublisherHandler.EventId.onEndOfSubscription; import static net.openhft.chronicle.engine.server.internal.TopicPublisherHandler.EventId.publish; /** * Created by Rob Austin */ public class RemotePublisher<T, M> extends AbstractStatelessClient<EventId> implements Publisher<M> { private static final Logger LOG = LoggerFactory.getLogger(ReferenceHandler.class); private final Class<M> messageClass; private final Map<Object, Long> subscribersToTid = new ConcurrentHashMap<>(); public RemotePublisher(@NotNull RequestContext context, @NotNull Asset asset) throws AssetNotFoundException { super(asset.findView(TcpChannelHub.class), (long) 0, toUri(context)); messageClass = context.messageType(); } private static String toUri(@NotNull final RequestContext context) { @NotNull final StringBuilder uri = new StringBuilder(context.fullName() + "?view=publisher"); if (context.messageType() != String.class) uri.append("&messageType=").append(CLASS_ALIASES.nameFor(context.messageType())); if (context.elementType() != String.class) uri.append("&elementType=").append(CLASS_ALIASES.nameFor(context.elementType())); if (context.dontPersist()) uri.append("&dontPersist=").append(context.dontPersist()); return uri.toString(); } private void checkTopic(@Nullable Object topic) { if (topic == null) throw new NullPointerException("topic can not be null"); } private void checkMessage(@Nullable Object message) { if (message == null) throw new NullPointerException("message can not be null"); } private void onEvent(T topic, @Nullable M message, @NotNull TopicSubscriber<T, M> topicSubscriber) throws InvalidSubscriberException { if (message != null) { topicSubscriber.onMessage(topic, message); } else { // todo } } private void onEvent(@Nullable M message, @NotNull Subscriber<M> topicSubscriber) throws InvalidSubscriberException { if (message != null) { topicSubscriber.onMessage(message); } else { // todo } } @Override public void publish(M event) { checkMessage(event); sendEventAsync(publish, valueOut -> valueOut.marshallable(m -> { m.write(Params.message).object(event); }), true); } @Override public void registerSubscriber(boolean bootstrap, int throttlePeriodMs, @NotNull Subscriber<M> subscriber) throws AssetNotFoundException { if (hub.outBytesLock().isHeldByCurrentThread()) throw new IllegalStateException("Cannot view map while debugging"); hub.subscribe(new AbstractAsyncSubscription(hub, csp, "Remote Topic publisher register subscribe") { @Override public void onSubscribe(@NotNull final WireOut wireOut) { subscribersToTid.put(subscriber, tid()); wireOut.writeEventName(registerSubscriber).text(""); } @Override public void onConsumer(@NotNull final WireIn w) { w.readDocument(null, d -> { final StringBuilder eventname = Wires.acquireStringBuilder(); @NotNull final ValueIn valueIn = d.readEventName(eventname); if (onEndOfSubscription.contentEquals(eventname)) { subscriber.onEndOfSubscription(); subscribersToTid.remove(this); hub.unsubscribe(tid()); } else if (CoreFields.reply.contentEquals(eventname)) { valueIn.marshallable(m -> { try { @Nullable final M message = m.read(() -> "message").object(messageClass); RemotePublisher.this.onEvent(message, subscriber); } catch (InvalidSubscriberException e) { throw Jvm.rethrow(e); } }); } }); } }); } @Override public void unregisterSubscriber(Subscriber subscriber) { final Long tid = subscribersToTid.get(subscriber); if (tid == null) { Jvm.debug().on(getClass(), "No subscriber to unsubscribe"); return; } hub.preventSubscribeUponReconnect(tid); if (!hub.isOpen()) { hub.unsubscribe(tid); return; } sendEventAsync(unregisterSubscriber, valueOut -> valueOut.int64(tid), false); } @Override public int subscriberCount() { throw new UnsupportedOperationException("todo"); } }