31 changed files with 1541 additions and 790 deletions
@ -0,0 +1,230 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.service.subscription; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.annotation.Lazy; |
|||
import org.springframework.context.event.EventListener; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.EntityView; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.EntityViewId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.dao.entityview.EntityViewService; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.TbQueueProducer; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
import org.thingsboard.server.queue.discovery.PartitionService; |
|||
import org.thingsboard.server.queue.discovery.ServiceType; |
|||
import org.thingsboard.server.queue.discovery.TopicPartitionInfo; |
|||
import org.thingsboard.server.queue.provider.TbCoreQueueProvider; |
|||
import org.thingsboard.server.service.queue.TbMsgCallback; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; |
|||
import org.thingsboard.server.service.telemetry.sub.SubscriptionUpdate; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.Collections; |
|||
import java.util.HashSet; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
public class DefaultLocalSubscriptionService implements LocalSubscriptionService { |
|||
|
|||
private final Set<TopicPartitionInfo> currentPartitions = ConcurrentHashMap.newKeySet(); |
|||
private final Map<String, Map<Integer, TbSubscription>> subscriptionsBySessionId = new ConcurrentHashMap<>(); |
|||
|
|||
@Autowired |
|||
private TelemetryWebSocketService wsService; |
|||
|
|||
@Autowired |
|||
private EntityViewService entityViewService; |
|||
|
|||
@Autowired |
|||
private PartitionService partitionService; |
|||
|
|||
@Autowired |
|||
private TbCoreQueueProvider coreQueueProvider; |
|||
|
|||
@Autowired |
|||
@Lazy |
|||
private SubscriptionManagerService subscriptionManagerService; |
|||
|
|||
private ExecutorService wsCallBackExecutor; |
|||
private TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToCoreMsg>> toCoreProducer; |
|||
|
|||
@PostConstruct |
|||
public void initExecutor() { |
|||
wsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ws-sub-callback")); |
|||
toCoreProducer = coreQueueProvider.getTbCoreMsgProducer(); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void shutdownExecutor() { |
|||
if (wsCallBackExecutor != null) { |
|||
wsCallBackExecutor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
@EventListener(PartitionChangeEvent.class) |
|||
public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { |
|||
if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceKey().getServiceType())) { |
|||
currentPartitions.clear(); |
|||
currentPartitions.addAll(partitionChangeEvent.getPartitions()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
@EventListener(ClusterTopologyChangeEvent.class) |
|||
public void onApplicationEvent(ClusterTopologyChangeEvent event) { |
|||
if (event.getServiceKeys().stream().anyMatch(key -> ServiceType.TB_CORE.equals(key.getServiceType()))) { |
|||
/* |
|||
* If the cluster topology has changed, we need to push all current subscriptions to SubscriptionManagerService again. |
|||
* Otherwise, the SubscriptionManagerService may "forget" those subscriptions in case of restart. |
|||
* Although this is resource consuming operation, it is cheaper than sending ping/pong commands periodically |
|||
* It is also cheaper then caching the subscriptions by entity id and then lookup of those caches every time we have new telemetry in SubscriptionManagerService. |
|||
* Even if we cache locally the list of active subscriptions by entity id, it is still time consuming operation to get them from cache |
|||
* Since number of subscriptions is usually much less then number of devices that are pushing data. |
|||
*/ |
|||
subscriptionsBySessionId.values().forEach(map -> map.values() |
|||
.forEach(sub -> pushSubscriptionToManagerService(sub, false))); |
|||
} |
|||
} |
|||
|
|||
//TODO 3.1: replace null callbacks with callbacks from websocket service.
|
|||
@Override |
|||
public void addSubscription(TbSubscription subscription) { |
|||
EntityId entityId = subscription.getEntityId(); |
|||
// Telemetry subscription on Entity Views are handled differently, because we need to allow only certain keys and time ranges;
|
|||
if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW) && TbSubscriptionType.TIMESERIES.equals(subscription.getType())) { |
|||
subscription = resolveEntityViewSubscription((TbTimeseriesSubscription) subscription); |
|||
} |
|||
pushSubscriptionToManagerService(subscription, true); |
|||
registerSubscription(subscription); |
|||
} |
|||
|
|||
private void pushSubscriptionToManagerService(TbSubscription subscription, boolean pushToLocalService) { |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, subscription.getTenantId(), subscription.getEntityId()); |
|||
if (currentPartitions.contains(tpi)) { |
|||
// Subscription is managed on the same server;
|
|||
if (pushToLocalService) { |
|||
subscriptionManagerService.addSubscription(subscription, TbMsgCallback.EMPTY); |
|||
} |
|||
} else { |
|||
// Push to the queue;
|
|||
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toNewSubscriptionProto(subscription); |
|||
toCoreProducer.send(tpi, new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg), null); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onSubscriptionUpdate(String sessionId, SubscriptionUpdate update, TbMsgCallback callback) { |
|||
TbSubscription subscription = subscriptionsBySessionId |
|||
.getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId()); |
|||
if (subscription != null) { |
|||
switch (subscription.getType()) { |
|||
case TIMESERIES: |
|||
TbTimeseriesSubscription tsSub = (TbTimeseriesSubscription) subscription; |
|||
update.getLatestValues().forEach((key, value) -> tsSub.getKeyStates().put(key, value)); |
|||
break; |
|||
case ATTRIBUTES: |
|||
TbAttributeSubscription attrSub = (TbAttributeSubscription) subscription; |
|||
update.getLatestValues().forEach((key, value) -> attrSub.getKeyStates().put(key, value)); |
|||
break; |
|||
} |
|||
wsService.sendWsMsg(sessionId, update); |
|||
} |
|||
callback.onSuccess(); |
|||
} |
|||
|
|||
@Override |
|||
public void cancelSubscription(String sessionId, int subscriptionId) { |
|||
log.debug("[{}][{}] Going to remove subscription.", sessionId, subscriptionId); |
|||
Map<Integer, TbSubscription> sessionSubscriptions = subscriptionsBySessionId.get(sessionId); |
|||
if (sessionSubscriptions != null) { |
|||
TbSubscription subscription = sessionSubscriptions.remove(subscriptionId); |
|||
if (subscription != null) { |
|||
if (sessionSubscriptions.isEmpty()) { |
|||
subscriptionsBySessionId.remove(sessionId); |
|||
} |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, subscription.getTenantId(), subscription.getEntityId()); |
|||
if (currentPartitions.contains(tpi)) { |
|||
// Subscription is managed on the same server;
|
|||
subscriptionManagerService.cancelSubscription(sessionId, subscriptionId, TbMsgCallback.EMPTY); |
|||
} else { |
|||
// Push to the queue;
|
|||
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toCloseSubscriptionProto(subscription); |
|||
toCoreProducer.send(tpi, new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg), null); |
|||
} |
|||
} else { |
|||
log.debug("[{}][{}] Subscription not found!", sessionId, subscriptionId); |
|||
} |
|||
} else { |
|||
log.debug("[{}] No session subscriptions found!", sessionId); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void cancelAllSessionSubscriptions(String sessionId) { |
|||
Map<Integer, TbSubscription> subscriptions = subscriptionsBySessionId.get(sessionId); |
|||
if (subscriptions != null) { |
|||
Set<Integer> toRemove = new HashSet<>(subscriptions.keySet()); |
|||
toRemove.forEach(id -> cancelSubscription(sessionId, id)); |
|||
} |
|||
} |
|||
|
|||
private TbSubscription resolveEntityViewSubscription(TbTimeseriesSubscription subscription) { |
|||
EntityView entityView = entityViewService.findEntityViewById(TenantId.SYS_TENANT_ID, new EntityViewId(subscription.getEntityId().getId())); |
|||
|
|||
Map<String, Long> keyStates; |
|||
if (subscription.isAllKeys()) { |
|||
keyStates = entityView.getKeys().getTimeseries().stream().collect(Collectors.toMap(k -> k, k -> 0L)); |
|||
} else { |
|||
keyStates = subscription.getKeyStates().entrySet() |
|||
.stream().filter(entry -> entityView.getKeys().getTimeseries().contains(entry.getKey())) |
|||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |
|||
} |
|||
|
|||
return TbTimeseriesSubscription.builder() |
|||
.serviceId(subscription.getServiceId()) |
|||
.sessionId(subscription.getSessionId()) |
|||
.subscriptionId(subscription.getSubscriptionId()) |
|||
.tenantId(subscription.getTenantId()) |
|||
.entityId(entityView.getEntityId()) |
|||
.startTime(entityView.getStartTimeMs()) |
|||
.endTime(entityView.getEndTimeMs()) |
|||
.allKeys(false) |
|||
.keyStates(keyStates).build(); |
|||
} |
|||
|
|||
private void registerSubscription(TbSubscription subscription) { |
|||
Map<Integer, TbSubscription> sessionSubscriptions = subscriptionsBySessionId.computeIfAbsent(subscription.getSessionId(), k -> new ConcurrentHashMap<>()); |
|||
sessionSubscriptions.put(subscription.getSubscriptionId(), subscription); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,377 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.service.subscription; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.util.StringUtils; |
|||
import org.thingsboard.common.util.DonAsynchron; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; |
|||
import org.thingsboard.server.actors.service.ActorService; |
|||
import org.thingsboard.server.common.data.DataConstants; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.kv.Aggregation; |
|||
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|||
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; |
|||
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
|||
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; |
|||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|||
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateValueListProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; |
|||
import org.thingsboard.server.queue.TbQueueProducer; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
import org.thingsboard.server.queue.discovery.PartitionService; |
|||
import org.thingsboard.server.queue.discovery.ServiceType; |
|||
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
|||
import org.thingsboard.server.queue.discovery.TopicPartitionInfo; |
|||
import org.thingsboard.server.queue.provider.TbCoreQueueProvider; |
|||
import org.thingsboard.server.service.queue.TbMsgCallback; |
|||
import org.thingsboard.server.service.state.DefaultDeviceStateService; |
|||
import org.thingsboard.server.service.state.DeviceStateService; |
|||
import org.thingsboard.server.service.telemetry.sub.SubscriptionUpdate; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.ArrayList; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Objects; |
|||
import java.util.Set; |
|||
import java.util.TreeMap; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.function.Function; |
|||
import java.util.function.Predicate; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
public class DefaultSubscriptionManagerService implements SubscriptionManagerService { |
|||
|
|||
@Autowired |
|||
private AttributesService attrService; |
|||
|
|||
@Autowired |
|||
private TimeseriesService tsService; |
|||
|
|||
@Autowired |
|||
private PartitionService partitionService; |
|||
|
|||
@Autowired |
|||
private TbServiceInfoProvider serviceInfoProvider; |
|||
|
|||
@Autowired |
|||
private TbCoreQueueProvider coreQueueProvider; |
|||
|
|||
@Autowired |
|||
private LocalSubscriptionService localSubscriptionService; |
|||
|
|||
@Autowired |
|||
private DeviceStateService deviceStateService; |
|||
|
|||
@Autowired |
|||
private ActorService actorService; |
|||
|
|||
private final Map<EntityId, Set<TbSubscription>> subscriptionsByEntityId = new ConcurrentHashMap<>(); |
|||
private final Map<String, Map<Integer, TbSubscription>> subscriptionsByWsSessionId = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<TopicPartitionInfo, Set<TbSubscription>> partitionedSubscriptions = new ConcurrentHashMap<>(); |
|||
private final Set<TopicPartitionInfo> currentPartitions = ConcurrentHashMap.newKeySet(); |
|||
|
|||
private ExecutorService tsCallBackExecutor; |
|||
private String serviceId; |
|||
private TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> toCoreProducer; |
|||
|
|||
@PostConstruct |
|||
public void initExecutor() { |
|||
tsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ts-sub-callback")); |
|||
serviceId = serviceInfoProvider.getServiceId(); |
|||
toCoreProducer = coreQueueProvider.getTbCoreMsgProducer(); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void shutdownExecutor() { |
|||
if (tsCallBackExecutor != null) { |
|||
tsCallBackExecutor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void addSubscription(TbSubscription subscription, TbMsgCallback callback) { |
|||
log.trace("[{}][{}][{}] Registering remote subscription for entity [{}]", |
|||
subscription.getServiceId(), subscription.getSessionId(), subscription.getSubscriptionId(), subscription.getEntityId()); |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, subscription.getTenantId(), subscription.getEntityId()); |
|||
if (currentPartitions.contains(tpi)) { |
|||
partitionedSubscriptions.computeIfAbsent(tpi, k -> ConcurrentHashMap.newKeySet()).add(subscription); |
|||
callback.onSuccess(); |
|||
} else { |
|||
log.warn("[{}][{}] Entity belongs to external partition. Probably rebalancing is in progress. Topic: {}" |
|||
, subscription.getTenantId(), subscription.getEntityId(), tpi.getFullTopicName()); |
|||
callback.onFailure(new RuntimeException("Entity belongs to external partition " + tpi.getFullTopicName() + "!")); |
|||
} |
|||
boolean newSubscription = subscriptionsByEntityId |
|||
.computeIfAbsent(subscription.getEntityId(), k -> ConcurrentHashMap.newKeySet()).add(subscription); |
|||
subscriptionsByWsSessionId.computeIfAbsent(subscription.getSessionId(), k -> new ConcurrentHashMap<>()).put(subscription.getSubscriptionId(), subscription); |
|||
if (newSubscription) { |
|||
switch (subscription.getType()) { |
|||
case TIMESERIES: |
|||
handleNewTelemetrySubscription((TbTimeseriesSubscription) subscription); |
|||
break; |
|||
case ATTRIBUTES: |
|||
handleNewAttributeSubscription((TbAttributeSubscription) subscription); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void cancelSubscription(String sessionId, int subscriptionId, TbMsgCallback callback) { |
|||
log.debug("[{}][{}] Going to remove subscription.", sessionId, subscriptionId); |
|||
Map<Integer, TbSubscription> sessionSubscriptions = subscriptionsByWsSessionId.get(sessionId); |
|||
if (sessionSubscriptions != null) { |
|||
TbSubscription subscription = sessionSubscriptions.remove(subscriptionId); |
|||
if (subscription != null) { |
|||
removeSubscriptionFromEntityMap(subscription); |
|||
removeSubscriptionFromPartitionMap(subscription); |
|||
if (sessionSubscriptions.isEmpty()) { |
|||
subscriptionsByWsSessionId.remove(sessionId); |
|||
} |
|||
} else { |
|||
log.debug("[{}][{}] Subscription not found!", sessionId, subscriptionId); |
|||
} |
|||
} else { |
|||
log.debug("[{}] No session subscriptions found!", sessionId); |
|||
} |
|||
callback.onSuccess(); |
|||
} |
|||
|
|||
@Override |
|||
public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { |
|||
Set<TopicPartitionInfo> removedPartitions = new HashSet<>(currentPartitions); |
|||
removedPartitions.removeAll(partitionChangeEvent.getPartitions()); |
|||
|
|||
currentPartitions.clear(); |
|||
currentPartitions.addAll(partitionChangeEvent.getPartitions()); |
|||
|
|||
// We no longer manage current partition of devices;
|
|||
removedPartitions.forEach(partition -> { |
|||
Set<TbSubscription> subs = partitionedSubscriptions.remove(partition); |
|||
if (subs != null) { |
|||
subs.forEach(this::removeSubscriptionFromEntityMap); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public void onTimeseriesDataUpdate(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, TbMsgCallback callback) { |
|||
onLocalSubUpdate(entityId, |
|||
s -> { |
|||
if (TbSubscriptionType.TIMESERIES.equals(s.getType())) { |
|||
return (TbTimeseriesSubscription) s; |
|||
} else { |
|||
return null; |
|||
} |
|||
}, s -> true, s -> { |
|||
List<TsKvEntry> subscriptionUpdate = null; |
|||
for (TsKvEntry kv : ts) { |
|||
if (isInTimeRange(s, kv.getTs()) && (s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey())))) { |
|||
if (subscriptionUpdate == null) { |
|||
subscriptionUpdate = new ArrayList<>(); |
|||
} |
|||
subscriptionUpdate.add(kv); |
|||
} |
|||
} |
|||
return subscriptionUpdate; |
|||
}); |
|||
callback.onSuccess(); |
|||
} |
|||
|
|||
@Override |
|||
public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbMsgCallback callback) { |
|||
onLocalSubUpdate(entityId, |
|||
s -> { |
|||
if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) { |
|||
return (TbAttributeSubscription) s; |
|||
} else { |
|||
return null; |
|||
} |
|||
}, |
|||
s -> (StringUtils.isEmpty(s.getScope()) || scope.equals(s.getScope().name())), |
|||
s -> { |
|||
List<TsKvEntry> subscriptionUpdate = null; |
|||
for (AttributeKvEntry kv : attributes) { |
|||
if (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey())) { |
|||
if (subscriptionUpdate == null) { |
|||
subscriptionUpdate = new ArrayList<>(); |
|||
} |
|||
subscriptionUpdate.add(new BasicTsKvEntry(kv.getLastUpdateTs(), kv)); |
|||
} |
|||
} |
|||
return subscriptionUpdate; |
|||
}); |
|||
if (entityId.getEntityType() == EntityType.DEVICE) { |
|||
if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)) { |
|||
for (AttributeKvEntry attribute : attributes) { |
|||
if (attribute.getKey().equals(DefaultDeviceStateService.INACTIVITY_TIMEOUT)) { |
|||
deviceStateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), attribute.getLongValue().orElse(0L)); |
|||
} |
|||
} |
|||
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope)) { |
|||
DeviceAttributesEventNotificationMsg notificationMsg = DeviceAttributesEventNotificationMsg.onUpdate(tenantId, |
|||
new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes)); |
|||
actorService.onMsg(notificationMsg); |
|||
} |
|||
} |
|||
callback.onSuccess(); |
|||
} |
|||
|
|||
private <T extends TbSubscription> void onLocalSubUpdate(EntityId entityId, |
|||
Function<TbSubscription, T> castFunction, |
|||
Predicate<T> filterFunction, |
|||
Function<T, List<TsKvEntry>> processFunction) { |
|||
Set<TbSubscription> entitySubscriptions = subscriptionsByEntityId.get(entityId); |
|||
if (entitySubscriptions != null) { |
|||
entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> { |
|||
List<TsKvEntry> subscriptionUpdate = processFunction.apply(s); |
|||
if (subscriptionUpdate != null && !subscriptionUpdate.isEmpty()) { |
|||
if (serviceId.equals(s.getServiceId())) { |
|||
SubscriptionUpdate update = new SubscriptionUpdate(s.getSubscriptionId(), subscriptionUpdate); |
|||
localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbMsgCallback.EMPTY); |
|||
} else { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId()); |
|||
toCoreProducer.send(tpi, toProto(s, subscriptionUpdate), null); |
|||
} |
|||
} |
|||
}); |
|||
} else { |
|||
log.debug("[{}] No device subscriptions to process!", entityId); |
|||
} |
|||
} |
|||
|
|||
private boolean isInTimeRange(TbTimeseriesSubscription subscription, long kvTime) { |
|||
return (subscription.getStartTime() == 0 || subscription.getStartTime() <= kvTime) |
|||
&& (subscription.getEndTime() == 0 || subscription.getEndTime() >= kvTime); |
|||
} |
|||
|
|||
private void removeSubscriptionFromEntityMap(TbSubscription sub) { |
|||
Set<TbSubscription> entitySubSet = subscriptionsByEntityId.get(sub.getEntityId()); |
|||
if (entitySubSet != null) { |
|||
entitySubSet.remove(sub); |
|||
if (entitySubSet.isEmpty()) { |
|||
subscriptionsByEntityId.remove(sub.getEntityId()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void removeSubscriptionFromPartitionMap(TbSubscription sub) { |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, sub.getTenantId(), sub.getEntityId()); |
|||
Set<TbSubscription> subs = partitionedSubscriptions.get(tpi); |
|||
if (subs != null) { |
|||
subs.remove(sub); |
|||
} |
|||
} |
|||
|
|||
private void handleNewAttributeSubscription(TbAttributeSubscription subscription) { |
|||
log.trace("[{}][{}][{}] Processing remote attribute subscription for entity [{}]", |
|||
serviceId, subscription.getSessionId(), subscription.getSubscriptionId(), subscription.getEntityId()); |
|||
|
|||
final Map<String, Long> keyStates = subscription.getKeyStates(); |
|||
DonAsynchron.withCallback(attrService.find(subscription.getTenantId(), subscription.getEntityId(), DataConstants.CLIENT_SCOPE, keyStates.keySet()), values -> { |
|||
List<TsKvEntry> missedUpdates = new ArrayList<>(); |
|||
values.forEach(latestEntry -> { |
|||
if (latestEntry.getLastUpdateTs() > keyStates.get(latestEntry.getKey())) { |
|||
missedUpdates.add(new BasicTsKvEntry(latestEntry.getLastUpdateTs(), latestEntry)); |
|||
} |
|||
}); |
|||
if (!missedUpdates.isEmpty()) { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, subscription.getServiceId()); |
|||
toCoreProducer.send(tpi, toProto(subscription, missedUpdates), null); |
|||
} |
|||
}, |
|||
e -> log.error("Failed to fetch missed updates.", e), tsCallBackExecutor); |
|||
} |
|||
|
|||
private void handleNewTelemetrySubscription(TbTimeseriesSubscription subscription) { |
|||
log.trace("[{}][{}][{}] Processing remote telemetry subscription for entity [{}]", |
|||
serviceId, subscription.getSessionId(), subscription.getSubscriptionId(), subscription.getEntityId()); |
|||
|
|||
long curTs = System.currentTimeMillis(); |
|||
List<ReadTsKvQuery> queries = new ArrayList<>(); |
|||
subscription.getKeyStates().forEach((key, value) -> { |
|||
if (curTs > value) { |
|||
long startTs = subscription.getStartTime() > 0 ? Math.max(subscription.getStartTime(), value + 1L) : (value + 1L); |
|||
long endTs = subscription.getEndTime() > 0 ? Math.min(subscription.getEndTime(), curTs) : curTs; |
|||
queries.add(new BaseReadTsKvQuery(key, startTs, endTs, 0, 1000, Aggregation.NONE)); |
|||
} |
|||
}); |
|||
if (!queries.isEmpty()) { |
|||
DonAsynchron.withCallback(tsService.findAll(subscription.getTenantId(), subscription.getEntityId(), queries), |
|||
missedUpdates -> { |
|||
if (missedUpdates != null && !missedUpdates.isEmpty()) { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, subscription.getServiceId()); |
|||
toCoreProducer.send(tpi, toProto(subscription, missedUpdates), null); |
|||
} |
|||
}, |
|||
e -> log.error("Failed to fetch missed updates.", e), |
|||
tsCallBackExecutor); |
|||
} |
|||
} |
|||
|
|||
private TbProtoQueueMsg<ToCoreMsg> toProto(TbSubscription subscription, List<TsKvEntry> updates) { |
|||
TbSubscriptionUpdateProto.Builder builder = TbSubscriptionUpdateProto.newBuilder(); |
|||
|
|||
builder.setSessionId(subscription.getSessionId()); |
|||
builder.setSubscriptionId(subscription.getSubscriptionId()); |
|||
|
|||
Map<String, List<Object>> data = new TreeMap<>(); |
|||
for (TsKvEntry tsEntry : updates) { |
|||
List<Object> values = data.computeIfAbsent(tsEntry.getKey(), k -> new ArrayList<>()); |
|||
Object[] value = new Object[2]; |
|||
value[0] = tsEntry.getTs(); |
|||
value[1] = tsEntry.getValueAsString(); |
|||
values.add(value); |
|||
} |
|||
|
|||
data.forEach((key, value) -> { |
|||
TbSubscriptionUpdateValueListProto.Builder dataBuilder = TbSubscriptionUpdateValueListProto.newBuilder(); |
|||
dataBuilder.setKey(key); |
|||
value.forEach(v -> { |
|||
Object[] array = (Object[]) v; |
|||
dataBuilder.addTs((long) array[0]); |
|||
dataBuilder.addValue((String) array[1]); |
|||
}); |
|||
builder.addData(dataBuilder.build()); |
|||
}); |
|||
|
|||
ToCoreMsg toCoreMsg = ToCoreMsg.newBuilder().setToLocalSubscriptionServiceMsg( |
|||
LocalSubscriptionServiceMsgProto.newBuilder().setSubUpdate(builder.build()).build()) |
|||
.build(); |
|||
return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.service.subscription; |
|||
|
|||
import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
import org.thingsboard.server.service.queue.TbMsgCallback; |
|||
import org.thingsboard.server.service.telemetry.sub.SubscriptionUpdate; |
|||
|
|||
public interface LocalSubscriptionService { |
|||
|
|||
void addSubscription(TbSubscription subscription); |
|||
|
|||
void cancelSubscription(String sessionId, int subscriptionId); |
|||
|
|||
void cancelAllSessionSubscriptions(String sessionId); |
|||
|
|||
void onSubscriptionUpdate(String sessionId, SubscriptionUpdate update, TbMsgCallback callback); |
|||
|
|||
void onApplicationEvent(PartitionChangeEvent event); |
|||
|
|||
void onApplicationEvent(ClusterTopologyChangeEvent event); |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.service.subscription; |
|||
|
|||
import org.springframework.context.ApplicationListener; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
import org.thingsboard.server.service.queue.TbMsgCallback; |
|||
|
|||
import java.util.List; |
|||
|
|||
public interface SubscriptionManagerService extends ApplicationListener<PartitionChangeEvent> { |
|||
|
|||
void addSubscription(TbSubscription subscription, TbMsgCallback callback); |
|||
|
|||
void cancelSubscription(String sessionId, int subscriptionId, TbMsgCallback callback); |
|||
|
|||
void onTimeseriesDataUpdate(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, TbMsgCallback callback); |
|||
|
|||
void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbMsgCallback callback); |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.service.subscription; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
import java.util.Map; |
|||
|
|||
public class TbAttributeSubscription extends TbSubscription { |
|||
|
|||
@Getter private final boolean allKeys; |
|||
@Getter private final Map<String, Long> keyStates; |
|||
@Getter private final TbAttributeSubscriptionScope scope; |
|||
|
|||
@Builder |
|||
public TbAttributeSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, |
|||
boolean allKeys, Map<String, Long> keyStates, TbAttributeSubscriptionScope scope) { |
|||
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ATTRIBUTES); |
|||
this.allKeys = allKeys; |
|||
this.keyStates = keyStates; |
|||
this.scope = scope; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(Object o) { |
|||
return super.equals(o); |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
return super.hashCode(); |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.service.subscription; |
|||
|
|||
public enum TbAttributeSubscriptionScope { |
|||
|
|||
CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE |
|||
|
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.service.subscription; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
public abstract class TbSubscription { |
|||
|
|||
private final String serviceId; |
|||
private final String sessionId; |
|||
private final int subscriptionId; |
|||
private final TenantId tenantId; |
|||
private final EntityId entityId; |
|||
private final TbSubscriptionType type; |
|||
|
|||
@Override |
|||
public boolean equals(Object o) { |
|||
if (this == o) return true; |
|||
if (o == null || getClass() != o.getClass()) return false; |
|||
TbSubscription that = (TbSubscription) o; |
|||
return subscriptionId == that.subscriptionId && |
|||
sessionId.equals(that.sessionId) && |
|||
tenantId.equals(that.tenantId) && |
|||
entityId.equals(that.entityId) && |
|||
type == that.type; |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
return Objects.hash(sessionId, subscriptionId, tenantId, entityId, type); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.service.subscription; |
|||
|
|||
public enum TbSubscriptionType { |
|||
TIMESERIES, ATTRIBUTES |
|||
} |
|||
@ -0,0 +1,247 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.service.subscription; |
|||
|
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|||
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
|||
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
|||
import org.thingsboard.server.common.data.kv.BooleanDataEntry; |
|||
import org.thingsboard.server.common.data.kv.DataType; |
|||
import org.thingsboard.server.common.data.kv.DoubleDataEntry; |
|||
import org.thingsboard.server.common.data.kv.JsonDataEntry; |
|||
import org.thingsboard.server.common.data.kv.KvEntry; |
|||
import org.thingsboard.server.common.data.kv.LongDataEntry; |
|||
import org.thingsboard.server.common.data.kv.StringDataEntry; |
|||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeSubscriptionProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionKetStateProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesSubscriptionProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; |
|||
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; |
|||
import org.thingsboard.server.service.telemetry.sub.SubscriptionUpdate; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.TreeMap; |
|||
import java.util.UUID; |
|||
|
|||
public class TbSubscriptionUtils { |
|||
|
|||
public static ToCoreMsg toNewSubscriptionProto(TbSubscription subscription) { |
|||
SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); |
|||
TbSubscriptionProto subscriptionProto = TbSubscriptionProto.newBuilder() |
|||
.setServiceId(subscription.getServiceId()) |
|||
.setSessionId(subscription.getSessionId()) |
|||
.setSubscriptionId(subscription.getSubscriptionId()) |
|||
.setTenantIdMSB(subscription.getTenantId().getId().getMostSignificantBits()) |
|||
.setTenantIdLSB(subscription.getTenantId().getId().getLeastSignificantBits()) |
|||
.setEntityType(subscription.getEntityId().getEntityType().name()) |
|||
.setEntityIdMSB(subscription.getEntityId().getId().getMostSignificantBits()) |
|||
.setEntityIdLSB(subscription.getEntityId().getId().getLeastSignificantBits()).build(); |
|||
|
|||
switch (subscription.getType()) { |
|||
case TIMESERIES: |
|||
TbTimeseriesSubscription tSub = (TbTimeseriesSubscription) subscription; |
|||
TbTimeSeriesSubscriptionProto.Builder tSubProto = TbTimeSeriesSubscriptionProto.newBuilder() |
|||
.setSub(subscriptionProto) |
|||
.setAllKeys(tSub.isAllKeys()); |
|||
tSub.getKeyStates().forEach((key, value) -> tSubProto.addKeyStates( |
|||
TbSubscriptionKetStateProto.newBuilder().setKey(key).setTs(value).build())); |
|||
tSubProto.setStartTime(tSub.getStartTime()); |
|||
tSubProto.setEndTime(tSub.getEndTime()); |
|||
msgBuilder.setTelemetrySub(tSubProto.build()); |
|||
break; |
|||
case ATTRIBUTES: |
|||
TbAttributeSubscription aSub = (TbAttributeSubscription) subscription; |
|||
TbAttributeSubscriptionProto.Builder aSubProto = TbAttributeSubscriptionProto.newBuilder() |
|||
.setSub(subscriptionProto) |
|||
.setAllKeys(aSub.isAllKeys()) |
|||
.setScope(aSub.getScope().name()); |
|||
aSub.getKeyStates().forEach((key, value) -> aSubProto.addKeyStates( |
|||
TbSubscriptionKetStateProto.newBuilder().setKey(key).setTs(value).build())); |
|||
msgBuilder.setAttributeSub(aSubProto.build()); |
|||
break; |
|||
} |
|||
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); |
|||
} |
|||
|
|||
public static ToCoreMsg toCloseSubscriptionProto(TbSubscription subscription) { |
|||
SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); |
|||
TbSubscriptionCloseProto closeProto = TbSubscriptionCloseProto.newBuilder() |
|||
.setSessionId(subscription.getSessionId()) |
|||
.setSubscriptionId(subscription.getSubscriptionId()).build(); |
|||
msgBuilder.setSubClose(closeProto); |
|||
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); |
|||
} |
|||
|
|||
public static TbSubscription fromProto(TbAttributeSubscriptionProto attributeSub) { |
|||
TbSubscriptionProto subProto = attributeSub.getSub(); |
|||
TbAttributeSubscription.TbAttributeSubscriptionBuilder builder = TbAttributeSubscription.builder() |
|||
.serviceId(subProto.getServiceId()) |
|||
.sessionId(subProto.getSessionId()) |
|||
.subscriptionId(subProto.getSubscriptionId()) |
|||
.entityId(EntityIdFactory.getByTypeAndUuid(subProto.getEntityType(), new UUID(subProto.getEntityIdMSB(), subProto.getEntityIdLSB()))) |
|||
.tenantId(new TenantId(new UUID(subProto.getTenantIdMSB(), subProto.getTenantIdLSB()))); |
|||
|
|||
builder.scope(TbAttributeSubscriptionScope.valueOf(attributeSub.getScope())); |
|||
builder.allKeys(attributeSub.getAllKeys()); |
|||
Map<String, Long> keyStates = new HashMap<>(); |
|||
attributeSub.getKeyStatesList().forEach(ksProto -> keyStates.put(ksProto.getKey(), ksProto.getTs())); |
|||
builder.keyStates(keyStates); |
|||
return builder.build(); |
|||
} |
|||
|
|||
public static TbSubscription fromProto(TbTimeSeriesSubscriptionProto telemetrySub) { |
|||
TbSubscriptionProto subProto = telemetrySub.getSub(); |
|||
TbTimeseriesSubscription.TbTimeseriesSubscriptionBuilder builder = TbTimeseriesSubscription.builder() |
|||
.serviceId(subProto.getServiceId()) |
|||
.sessionId(subProto.getSessionId()) |
|||
.subscriptionId(subProto.getSubscriptionId()) |
|||
.entityId(EntityIdFactory.getByTypeAndUuid(subProto.getEntityType(), new UUID(subProto.getEntityIdMSB(), subProto.getEntityIdLSB()))) |
|||
.tenantId(new TenantId(new UUID(subProto.getTenantIdMSB(), subProto.getTenantIdLSB()))); |
|||
|
|||
builder.allKeys(telemetrySub.getAllKeys()); |
|||
Map<String, Long> keyStates = new HashMap<>(); |
|||
telemetrySub.getKeyStatesList().forEach(ksProto -> keyStates.put(ksProto.getKey(), ksProto.getTs())); |
|||
builder.startTime(telemetrySub.getStartTime()); |
|||
builder.endTime(telemetrySub.getEndTime()); |
|||
builder.keyStates(keyStates); |
|||
return builder.build(); |
|||
} |
|||
|
|||
public static SubscriptionUpdate fromProto(TbSubscriptionUpdateProto 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 static ToCoreMsg toTimeseriesUpdateProto(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts) { |
|||
TbTimeSeriesUpdateProto.Builder builder = TbTimeSeriesUpdateProto.newBuilder(); |
|||
builder.setEntityType(entityId.getEntityType().name()); |
|||
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits()); |
|||
builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits()); |
|||
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); |
|||
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); |
|||
ts.forEach(v -> builder.addData(toKeyValueProto(v.getTs(), v).build())); |
|||
SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); |
|||
msgBuilder.setTsUpdate(builder); |
|||
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); |
|||
} |
|||
|
|||
public static ToCoreMsg toAttributesUpdateProto(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) { |
|||
TbAttributeUpdateProto.Builder builder = TbAttributeUpdateProto.newBuilder(); |
|||
builder.setEntityType(entityId.getEntityType().name()); |
|||
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits()); |
|||
builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits()); |
|||
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); |
|||
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); |
|||
builder.setScope(scope); |
|||
attributes.forEach(v -> builder.addData(toKeyValueProto(v.getLastUpdateTs(), v).build())); |
|||
|
|||
SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); |
|||
msgBuilder.setAttrUpdate(builder); |
|||
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); |
|||
} |
|||
|
|||
private static TsKvProto.Builder toKeyValueProto(long ts, KvEntry attr) { |
|||
KeyValueProto.Builder dataBuilder = KeyValueProto.newBuilder(); |
|||
dataBuilder.setKey(attr.getKey()); |
|||
dataBuilder.setType(KeyValueType.forNumber(attr.getDataType().ordinal())); |
|||
switch (attr.getDataType()) { |
|||
case BOOLEAN: |
|||
attr.getBooleanValue().ifPresent(dataBuilder::setBoolV); |
|||
break; |
|||
case LONG: |
|||
attr.getLongValue().ifPresent(dataBuilder::setLongV); |
|||
break; |
|||
case DOUBLE: |
|||
attr.getDoubleValue().ifPresent(dataBuilder::setDoubleV); |
|||
break; |
|||
case JSON: |
|||
attr.getJsonValue().ifPresent(dataBuilder::setJsonV); |
|||
break; |
|||
case STRING: |
|||
attr.getStrValue().ifPresent(dataBuilder::setStringV); |
|||
break; |
|||
} |
|||
return TsKvProto.newBuilder().setTs(ts).setKv(dataBuilder); |
|||
} |
|||
|
|||
public static EntityId toEntityId(String entityType, long entityIdMSB, long entityIdLSB) { |
|||
return EntityIdFactory.getByTypeAndUuid(entityType, new UUID(entityIdMSB, entityIdLSB)); |
|||
} |
|||
|
|||
public static List<TsKvEntry> toTsKvEntityList(List<TsKvProto> dataList) { |
|||
List<TsKvEntry> result = new ArrayList<>(dataList.size()); |
|||
dataList.forEach(proto -> result.add(new BasicTsKvEntry(proto.getTs(), getKvEntry(proto.getKv())))); |
|||
return result; |
|||
} |
|||
|
|||
public static List<AttributeKvEntry> toAttributeKvList(List<TsKvProto> dataList) { |
|||
List<AttributeKvEntry> result = new ArrayList<>(dataList.size()); |
|||
dataList.forEach(proto -> result.add(new BaseAttributeKvEntry(getKvEntry(proto.getKv()), proto.getTs()))); |
|||
return result; |
|||
} |
|||
|
|||
private static KvEntry getKvEntry(KeyValueProto proto) { |
|||
KvEntry entry = null; |
|||
DataType type = DataType.values()[proto.getType().getNumber()]; |
|||
switch (type) { |
|||
case BOOLEAN: |
|||
entry = new BooleanDataEntry(proto.getKey(), proto.getBoolV()); |
|||
break; |
|||
case LONG: |
|||
entry = new LongDataEntry(proto.getKey(), proto.getLongV()); |
|||
break; |
|||
case DOUBLE: |
|||
entry = new DoubleDataEntry(proto.getKey(), proto.getDoubleV()); |
|||
break; |
|||
case STRING: |
|||
entry = new StringDataEntry(proto.getKey(), proto.getStringV()); |
|||
break; |
|||
case JSON: |
|||
entry = new JsonDataEntry(proto.getKey(), proto.getJsonV()); |
|||
break; |
|||
} |
|||
return entry; |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.service.subscription; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
import java.util.Map; |
|||
|
|||
public class TbTimeseriesSubscription extends TbSubscription { |
|||
|
|||
@Getter private final boolean allKeys; |
|||
@Getter private final Map<String, Long> keyStates; |
|||
@Getter private final long startTime; |
|||
@Getter private final long endTime; |
|||
|
|||
@Builder |
|||
public TbTimeseriesSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, |
|||
boolean allKeys, Map<String, Long> keyStates, long startTime, long endTime) { |
|||
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.TIMESERIES); |
|||
this.allKeys = allKeys; |
|||
this.keyStates = keyStates; |
|||
this.startTime = startTime; |
|||
this.endTime = endTime; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(Object o) { |
|||
return super.equals(o); |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
return super.hashCode(); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.queue.discovery; |
|||
|
|||
import lombok.Getter; |
|||
import org.springframework.context.ApplicationEvent; |
|||
|
|||
import java.util.Set; |
|||
|
|||
|
|||
public class ClusterTopologyChangeEvent extends ApplicationEvent { |
|||
|
|||
@Getter |
|||
private final Set<ServiceKey> serviceKeys; |
|||
|
|||
public ClusterTopologyChangeEvent(Object source, Set<ServiceKey> serviceKeys) { |
|||
super(source); |
|||
this.serviceKeys = serviceKeys; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue