/*
* 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.engine.cfg.UserStat;
import net.openhft.chronicle.engine.tree.HostIdentifier;
import net.openhft.chronicle.network.ClientClosedProvider;
import net.openhft.chronicle.network.SessionMode;
import net.openhft.chronicle.network.api.session.SessionDetailsProvider;
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.jetbrains.annotations.Nullable;
import java.time.LocalTime;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import static net.openhft.chronicle.engine.server.internal.SystemHandler.EventId.heartbeat;
import static net.openhft.chronicle.engine.server.internal.SystemHandler.EventId.onClientClosing;
/**
* @author Rob Austin.
*/
public class SystemHandler extends AbstractHandler implements ClientClosedProvider {
private final StringBuilder eventName = new StringBuilder();
private SessionDetailsProvider sessionDetails;
private final WireParser<Void> wireParser = wireParser();
@Nullable
private Map<String, UserStat> monitoringMap;
private volatile boolean hasClientClosed;
private boolean wasHeartBeat;
@NotNull
private final BiConsumer<WireIn, Long> dataConsumer = (inWire, tid) -> {
eventName.setLength(0);
@NotNull final ValueIn valueIn = inWire.readEventName(eventName);
try {
assert startEnforceInValueReadCheck(inWire);
if (EventId.userId.contentEquals(eventName)) {
this.sessionDetails.userId(valueIn.text());
if (this.monitoringMap != null) {
@NotNull UserStat userStat = new UserStat();
userStat.setLoggedIn(LocalTime.now());
monitoringMap.put(sessionDetails.userId(), userStat);
}
while (inWire.bytes().readRemaining() > 0)
wireParser.parseOne(inWire, null);
return;
}
if (!heartbeat.contentEquals(eventName) && !onClientClosing.contentEquals(eventName))
return;
wasHeartBeat = true;
//noinspection ConstantConditions
outWire.writeDocument(true, wire -> outWire.writeEventName(CoreFields.tid).int64(tid));
writeData(inWire, out -> {
if (heartbeat.contentEquals(eventName)) {
outWire.write(EventId.heartbeatReply).int64(valueIn.int64());
return;
}
while (inWire.hasMore())
skipValue(valueIn);
if (onClientClosing.contentEquals(eventName)) {
hasClientClosed = true;
outWire.write(EventId.onClosingReply).text("");
}
});
} finally {
assert endEnforceInValueReadCheck(inWire);
}
};
public boolean wasHeartBeat() {
return wasHeartBeat;
}
void process(@NotNull final WireIn inWire,
@NotNull final WireOut outWire, final long tid,
@NotNull final SessionDetailsProvider sessionDetails,
@Nullable Map<String, UserStat> monitoringMap,
boolean isServerSocket,
@Nullable Supplier<WireOutPublisher> publisher,
@Nullable final HostIdentifier hostId,
@NotNull Consumer<WireType> onWireType, @Nullable WireType wireType0) {
this.wasHeartBeat = false;
this.sessionDetails = sessionDetails;
this.monitoringMap = monitoringMap;
setOutWire(outWire);
dataConsumer.accept(inWire, tid);
if (wireType0 == null && sessionDetails.wireType() != null)
onWireType.accept(sessionDetails.wireType());
/*
if (isServerSocket && sessionDetails.hostId() != 0) {
final VanillaSessionDetails sd = new VanillaSessionDetails();
sd.hostId(hostId.hostId());
sd.wireType(sessionDetails.wireType());
publisher.get().put(null, w -> w.writeDocument(false, sd));
}*/
}
@NotNull
private WireParser<Void> wireParser() {
@NotNull final WireParser<Void> parser = new VanillaWireParser<>((s, v, $) -> {
});
parser.register(EventId.domain::toString, (s, v, $) -> v.text(this, (o, x) -> o.sessionDetails.domain(x)));
parser.register(EventId.sessionMode::toString, (s, v, $) -> v.text(this, (o, x) -> o
.sessionDetails.sessionMode(SessionMode.valueOf(x))));
parser.register(EventId.securityToken::toString, (s, v, $) -> v.text(this, (o, x) -> o
.sessionDetails.securityToken(x)));
parser.register(EventId.clientId::toString, (s, v, $) -> v.text(this, (o, x) -> o
.sessionDetails.clientId(UUID.fromString(x))));
parser.register(EventId.wireType::toString, (s, v, $) -> v.text(this, (o, x) -> o
.sessionDetails.wireType(WireType.valueOf(x))));
parser.register(EventId.hostId::toString, (s, v, $) -> v.int8(this, (o, x) -> o
.sessionDetails.hostId(x)));
return parser;
}
/**
* @return {@code true} if the client has intentionally closed
*/
@Override
public boolean hasClientClosed() {
return hasClientClosed;
}
public enum EventId implements WireKey {
heartbeat,
heartbeatReply,
onClientClosing,
onClosingReply,
userId,
sessionMode,
domain,
securityToken,
wireType,
clientId,
hostId
}
}