/*
* 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.core.pool.ClassAliasPool;
import net.openhft.chronicle.core.util.SerializableBiFunction;
import net.openhft.chronicle.core.util.SerializableFunction;
import net.openhft.chronicle.engine.api.pubsub.InvalidSubscriberException;
import net.openhft.chronicle.engine.api.pubsub.Reference;
import net.openhft.chronicle.engine.api.pubsub.Subscriber;
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.ReferenceHandler.EventId;
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.engine.server.internal.ReferenceHandler.EventId.*;
/**
* Created by Rob Austin
*/
public class RemoteReference<E> extends AbstractStatelessClient<ReferenceHandler.EventId> implements Reference<E> {
private static final Logger LOG = LoggerFactory.getLogger(ReferenceHandler.class);
private final Class<E> messageClass;
private final Map<Object, Long> subscribersToTid = new ConcurrentHashMap<>();
public RemoteReference(@NotNull RequestContext requestContext, @NotNull Asset asset) {
this(asset.findView(TcpChannelHub.class), requestContext.messageType(), asset.fullName());
}
public RemoteReference(@NotNull TcpChannelHub hub, Class<E> messageClass, String fullName)
throws AssetNotFoundException {
super(hub, (long) 0, toUri(fullName, messageClass));
this.messageClass = messageClass;
}
private static String toUri(String fullName, Class messageClass) {
@NotNull StringBuilder uri = new StringBuilder();
uri.append(fullName).append("?view=reference");
if (messageClass != String.class)
uri.append("&messageType=").append(ClassAliasPool.CLASS_ALIASES.nameFor(messageClass));
return uri.toString();
}
@Override
public long set(final E event) {
checkEvent(event);
sendEventAsync(set, valueOut -> valueOut.object(event), true);
return 0;
}
@Nullable
@Override
public E get() {
return proxyReturnTypedObject(get, null, messageClass);
}
@Nullable
@Override
public E getAndSet(E e) {
return proxyReturnTypedObject(getAndSet, null, messageClass, e);
}
@Override
public void remove() {
sendEventAsync(remove, null, true);
}
@Nullable
@Override
public E getAndRemove() {
return proxyReturnTypedObject(getAndRemove, null, messageClass);
}
@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() {
return proxyReturnInt(countSubscribers);
}
@Override
public void registerSubscriber(boolean bootstrap,
int throttlePeriodMs,
@NotNull final Subscriber subscriber)
throws AssetNotFoundException {
if (hub.outBytesLock().isHeldByCurrentThread())
throw new IllegalStateException("Cannot view map while debugging");
@NotNull final AbstractAsyncSubscription asyncSubscription = new AbstractAsyncSubscription(hub,
csp + "&bootstrap=" + bootstrap + "&throttlePeriodMs=" + throttlePeriodMs,
"Remote Ref registerSubscriber") {
@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 (EventId.onEndOfSubscription.contentEquals(eventname)) {
subscriber.onEndOfSubscription();
subscribersToTid.remove(this);
hub.unsubscribe(tid());
} else if (CoreFields.reply.contentEquals(eventname)) {
valueIn.marshallable(m -> {
@Nullable final E message = m.read(() -> "message").object(messageClass);
RemoteReference.this.onEvent(message, subscriber);
});
}
});
}
};
hub.subscribe(asyncSubscription);
}
@Override
public Class getType() {
return messageClass;
}
void onEvent(@Nullable E message, @NotNull Subscriber<E> subscriber) {
if (message == null)
return;
try {
subscriber.onMessage(message);
} catch (InvalidSubscriberException noLongerValid) {
unregisterSubscriber(subscriber);
}
}
private void checkEvent(@Nullable Object key) {
if (key == null)
throw new NullPointerException("event can not be null");
}
@Nullable
@Override
public <R> R applyTo(@NotNull SerializableFunction<E, R> function) {
return applyTo((x, $) -> function.apply(x), null);
}
@Override
public void asyncUpdate(@NotNull SerializableFunction<E, E> updateFunction) {
asyncUpdate((x, $) -> updateFunction.apply(x), null);
}
@Nullable
@Override
public <R> R syncUpdate(@NotNull SerializableFunction<E, E> updateFunction, @NotNull SerializableFunction<E, R> returnFunction) {
return syncUpdate((x, $) -> updateFunction.apply(x), null, (x, $) -> returnFunction.apply(x), null);
}
@Nullable
@Override
public <T, R> R applyTo(@NotNull SerializableBiFunction<E, T, R> function, T argument) {
return (R) super.proxyReturnTypedObject(applyTo2, null, Object.class, function, argument);
}
@Override
public <T> void asyncUpdate(@NotNull SerializableBiFunction<E, T, E> updateFunction, T argument) {
sendEventAsync(update2, toParameters(update2, updateFunction, argument), true);
}
@Nullable
@Override
public <UT, RT, R> R syncUpdate(@NotNull SerializableBiFunction<E, UT, E> updateFunction, @Nullable UT updateArgument,
@NotNull SerializableBiFunction<E, RT, R> returnFunction, @Nullable RT returnArgument) {
return (R) proxyReturnTypedObject(update4, null, Object.class, updateFunction,
updateArgument, returnFunction, returnArgument);
}
}