/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* 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 org.thingsboard.server.extensions.core.plugin.telemetry.handlers;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.kv.*;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.handlers.RpcMsgHandler;
import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
import org.thingsboard.server.extensions.core.plugin.telemetry.SubscriptionManager;
import org.thingsboard.server.extensions.core.plugin.telemetry.gen.TelemetryPluginProtos.*;
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
/**
* @author Andrew Shvayka
*/
@Slf4j
@RequiredArgsConstructor
public class TelemetryRpcMsgHandler implements RpcMsgHandler {
private final SubscriptionManager subscriptionManager;
private static final int SUBSCRIPTION_CLAZZ = 1;
private static final int ATTRIBUTES_UPDATE_CLAZZ = 2;
private static final int SUBSCRIPTION_UPDATE_CLAZZ = 3;
private static final int SESSION_CLOSE_CLAZZ = 4;
private static final int SUBSCRIPTION_CLOSE_CLAZZ = 5;
@Override
public void process(PluginContext ctx, RpcMsg msg) {
switch (msg.getMsgClazz()) {
case SUBSCRIPTION_CLAZZ:
processSubscriptionCmd(ctx, msg);
break;
case SUBSCRIPTION_UPDATE_CLAZZ:
processRemoteSubscriptionUpdate(ctx, msg);
break;
case ATTRIBUTES_UPDATE_CLAZZ:
processAttributeUpdate(ctx, msg);
break;
case SESSION_CLOSE_CLAZZ:
processSessionClose(ctx, msg);
break;
case SUBSCRIPTION_CLOSE_CLAZZ:
processSubscriptionClose(ctx, msg);
break;
default:
throw new RuntimeException("Unknown command id: " + msg.getMsgClazz());
}
}
private void processRemoteSubscriptionUpdate(PluginContext ctx, RpcMsg msg) {
SubscriptionUpdateProto proto;
try {
proto = SubscriptionUpdateProto.parseFrom(msg.getMsgData());
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
subscriptionManager.onRemoteSubscriptionUpdate(ctx, proto.getSessionId(), convert(proto));
}
private void processAttributeUpdate(PluginContext ctx, RpcMsg msg) {
AttributeUpdateProto proto;
try {
proto = AttributeUpdateProto.parseFrom(msg.getMsgData());
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
subscriptionManager.onAttributesUpdateFromServer(ctx, DeviceId.fromString(proto.getDeviceId()), proto.getScope(),
proto.getDataList().stream().map(this::toAttribute).collect(Collectors.toList()));
}
private void processSubscriptionCmd(PluginContext ctx, RpcMsg msg) {
SubscriptionProto proto;
try {
proto = SubscriptionProto.parseFrom(msg.getMsgData());
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
Map<String, Long> statesMap = proto.getKeyStatesList().stream().collect(Collectors.toMap(SubscriptionKetStateProto::getKey, SubscriptionKetStateProto::getTs));
Subscription subscription = new Subscription(
new SubscriptionState(proto.getSessionId(), proto.getSubscriptionId(), DeviceId.fromString(proto.getDeviceId()), SubscriptionType.valueOf(proto.getType()), proto.getAllKeys(), statesMap),
false, msg.getServerAddress());
subscriptionManager.addRemoteWsSubscription(ctx, msg.getServerAddress(), proto.getSessionId(), subscription);
}
public void onNewSubscription(PluginContext ctx, ServerAddress address, String sessionId, Subscription cmd) {
SubscriptionProto.Builder builder = SubscriptionProto.newBuilder();
builder.setSessionId(sessionId);
builder.setSubscriptionId(cmd.getSubscriptionId());
builder.setDeviceId(cmd.getDeviceId().toString());
builder.setType(cmd.getType().name());
builder.setAllKeys(cmd.isAllKeys());
cmd.getKeyStates().entrySet().forEach(e -> builder.addKeyStates(SubscriptionKetStateProto.newBuilder().setKey(e.getKey()).setTs(e.getValue()).build()));
ctx.sendPluginRpcMsg(new RpcMsg(address, SUBSCRIPTION_CLAZZ, builder.build().toByteArray()));
}
public void onSubscriptionUpdate(PluginContext ctx, ServerAddress address, String sessionId, SubscriptionUpdate update) {
SubscriptionUpdateProto proto = getSubscriptionUpdateProto(sessionId, update);
ctx.sendPluginRpcMsg(new RpcMsg(address, SUBSCRIPTION_UPDATE_CLAZZ, proto.toByteArray()));
}
public void onSessionClose(PluginContext ctx, ServerAddress address, String vSessionId) {
SessionCloseProto proto = SessionCloseProto.newBuilder().setSessionId(vSessionId).build();
ctx.sendPluginRpcMsg(new RpcMsg(address, SESSION_CLOSE_CLAZZ, proto.toByteArray()));
}
public void onSubscriptionClose(PluginContext ctx, ServerAddress address, String vSessionId, int subscriptionId) {
SubscriptionCloseProto proto = SubscriptionCloseProto.newBuilder().setSessionId(vSessionId).setSubscriptionId(subscriptionId).build();
ctx.sendPluginRpcMsg(new RpcMsg(address, SUBSCRIPTION_CLOSE_CLAZZ, proto.toByteArray()));
}
private void processSessionClose(PluginContext ctx, RpcMsg msg) {
SessionCloseProto proto;
try {
proto = SessionCloseProto.parseFrom(msg.getMsgData());
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
subscriptionManager.cleanupRemoteWsSessionSubscriptions(ctx, proto.getSessionId());
}
private void processSubscriptionClose(PluginContext ctx, RpcMsg msg) {
SubscriptionCloseProto proto;
try {
proto = SubscriptionCloseProto.parseFrom(msg.getMsgData());
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
subscriptionManager.removeSubscription(ctx, proto.getSessionId(), proto.getSubscriptionId());
}
private static SubscriptionUpdateProto getSubscriptionUpdateProto(String sessionId, SubscriptionUpdate update) {
SubscriptionUpdateProto.Builder builder = SubscriptionUpdateProto.newBuilder();
builder.setSessionId(sessionId);
builder.setSubscriptionId(update.getSubscriptionId());
builder.setErrorCode(update.getErrorCode());
if (update.getErrorMsg() != null) {
builder.setErrorMsg(update.getErrorMsg());
}
update.getData().entrySet().forEach(
e -> {
SubscriptionUpdateValueListProto.Builder dataBuilder = SubscriptionUpdateValueListProto.newBuilder();
dataBuilder.setKey(e.getKey());
e.getValue().forEach(v -> {
Object[] array = (Object[]) v;
dataBuilder.addTs((long) array[0]);
dataBuilder.addValue((String) array[1]);
});
builder.addData(dataBuilder.build());
}
);
return builder.build();
}
private SubscriptionUpdate convert(SubscriptionUpdateProto proto) {
if (proto.getErrorCode() > 0) {
return new SubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg());
} else {
Map<String, List<Object>> data = new TreeMap<>();
proto.getDataList().forEach(v -> {
List<Object> values = data.computeIfAbsent(v.getKey(), k -> new ArrayList<>());
for (int i = 0; i < v.getTsCount(); i++) {
Object[] value = new Object[2];
value[0] = v.getTs(i);
value[1] = v.getValue(i);
values.add(value);
}
});
return new SubscriptionUpdate(proto.getSubscriptionId(), data);
}
}
public void onAttributesUpdate(PluginContext ctx, ServerAddress address, DeviceId deviceId, String scope, List<AttributeKvEntry> attributes) {
ctx.sendPluginRpcMsg(new RpcMsg(address, ATTRIBUTES_UPDATE_CLAZZ, getAttributesUpdateProto(deviceId, scope, attributes).toByteArray()));
}
private AttributeUpdateProto getAttributesUpdateProto(DeviceId deviceId, String scope, List<AttributeKvEntry> attributes) {
AttributeUpdateProto.Builder builder = AttributeUpdateProto.newBuilder();
builder.setDeviceId(deviceId.toString());
builder.setScope(scope);
attributes.forEach(
attr -> {
AttributeUpdateValueListProto.Builder dataBuilder = AttributeUpdateValueListProto.newBuilder();
dataBuilder.setKey(attr.getKey());
dataBuilder.setTs(attr.getLastUpdateTs());
dataBuilder.setValueType(attr.getDataType().ordinal());
switch (attr.getDataType()) {
case BOOLEAN:
dataBuilder.setBoolValue(attr.getBooleanValue().get());
break;
case LONG:
dataBuilder.setLongValue(attr.getLongValue().get());
break;
case DOUBLE:
dataBuilder.setDoubleValue(attr.getDoubleValue().get());
break;
case STRING:
dataBuilder.setStrValue(attr.getStrValue().get());
break;
}
builder.addData(dataBuilder.build());
}
);
return builder.build();
}
private AttributeKvEntry toAttribute(AttributeUpdateValueListProto proto) {
KvEntry entry = null;
DataType type = DataType.values()[proto.getValueType()];
switch (type) {
case BOOLEAN:
entry = new BooleanDataEntry(proto.getKey(), proto.getBoolValue());
break;
case LONG:
entry = new LongDataEntry(proto.getKey(), proto.getLongValue());
break;
case DOUBLE:
entry = new DoubleDataEntry(proto.getKey(), proto.getDoubleValue());
break;
case STRING:
entry = new StringDataEntry(proto.getKey(), proto.getStrValue());
break;
}
return new BaseAttributeKvEntry(entry, proto.getTs());
}
}