Commit cc1887fc8dac658535b2c6c2858f210b7cc3e936

Authored by Andrii Shvaika
1 parent 9e27d453

Added WS notification on deleted attribute

@@ -355,10 +355,9 @@ public class TelemetryController extends BaseController { @@ -355,10 +355,9 @@ public class TelemetryController extends BaseController {
355 DataConstants.SHARED_SCOPE.equals(scope) || 355 DataConstants.SHARED_SCOPE.equals(scope) ||
356 DataConstants.CLIENT_SCOPE.equals(scope)) { 356 DataConstants.CLIENT_SCOPE.equals(scope)) {
357 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { 357 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> {
358 - ListenableFuture<List<Void>> future = attributesService.removeAll(user.getTenantId(), entityId, scope, keys);  
359 - Futures.addCallback(future, new FutureCallback<List<Void>>() { 358 + tsSubService.deleteAndNotify(tenantId, entityId, scope, keys, new FutureCallback<Void>() {
360 @Override 359 @Override
361 - public void onSuccess(@Nullable List<Void> tmp) { 360 + public void onSuccess(@Nullable Void tmp) {
362 logAttributesDeleted(user, entityId, scope, keys, null); 361 logAttributesDeleted(user, entityId, scope, keys, null);
363 if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) { 362 if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) {
364 DeviceId deviceId = new DeviceId(entityId.getId()); 363 DeviceId deviceId = new DeviceId(entityId.getId());
@@ -375,7 +374,7 @@ public class TelemetryController extends BaseController { @@ -375,7 +374,7 @@ public class TelemetryController extends BaseController {
375 logAttributesDeleted(user, entityId, scope, keys, t); 374 logAttributesDeleted(user, entityId, scope, keys, t);
376 result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); 375 result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
377 } 376 }
378 - }, executor); 377 + });
379 }); 378 });
380 } else { 379 } else {
381 return getImmediateDeferredResult("Invalid attribute scope: " + scope, HttpStatus.BAD_REQUEST); 380 return getImmediateDeferredResult("Invalid attribute scope: " + scope, HttpStatus.BAD_REQUEST);
@@ -31,6 +31,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCRespons @@ -31,6 +31,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCRespons
31 import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; 31 import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
32 import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; 32 import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto;
33 import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto; 33 import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto;
  34 +import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto;
34 import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto; 35 import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto;
35 import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; 36 import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
36 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; 37 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
@@ -54,6 +55,7 @@ import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWra @@ -54,6 +55,7 @@ import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWra
54 55
55 import javax.annotation.PostConstruct; 56 import javax.annotation.PostConstruct;
56 import javax.annotation.PreDestroy; 57 import javax.annotation.PreDestroy;
  58 +import java.util.ArrayList;
57 import java.util.List; 59 import java.util.List;
58 import java.util.Optional; 60 import java.util.Optional;
59 import java.util.UUID; 61 import java.util.UUID;
@@ -259,6 +261,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -259,6 +261,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
259 new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), 261 new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
260 TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), 262 TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
261 proto.getScope(), TbSubscriptionUtils.toAttributeKvList(proto.getDataList()), callback); 263 proto.getScope(), TbSubscriptionUtils.toAttributeKvList(proto.getDataList()), callback);
  264 + } else if (msg.hasAttrDelete()) {
  265 + TbAttributeDeleteProto proto = msg.getAttrDelete();
  266 + subscriptionManagerService.onAttributesDelete(
  267 + new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
  268 + TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
  269 + proto.getScope(), proto.getKeysList(), callback);
262 } else { 270 } else {
263 throwNotHandled(msg, callback); 271 throwNotHandled(msg, callback);
264 } 272 }
@@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
32 import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; 32 import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
33 import org.thingsboard.server.common.data.kv.BasicTsKvEntry; 33 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
34 import org.thingsboard.server.common.data.kv.ReadTsKvQuery; 34 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
  35 +import org.thingsboard.server.common.data.kv.StringDataEntry;
35 import org.thingsboard.server.common.data.kv.TsKvEntry; 36 import org.thingsboard.server.common.data.kv.TsKvEntry;
36 import org.thingsboard.server.common.msg.queue.ServiceType; 37 import org.thingsboard.server.common.msg.queue.ServiceType;
37 import org.thingsboard.server.common.msg.queue.TbCallback; 38 import org.thingsboard.server.common.msg.queue.TbCallback;
@@ -253,6 +254,32 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer @@ -253,6 +254,32 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
253 callback.onSuccess(); 254 callback.onSuccess();
254 } 255 }
255 256
  257 + @Override
  258 + public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback callback) {
  259 + onLocalSubUpdate(entityId,
  260 + s -> {
  261 + if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) {
  262 + return (TbAttributeSubscription) s;
  263 + } else {
  264 + return null;
  265 + }
  266 + },
  267 + s -> (TbAttributeSubscriptionScope.ANY_SCOPE.equals(s.getScope()) || scope.equals(s.getScope().name())),
  268 + s -> {
  269 + List<TsKvEntry> subscriptionUpdate = null;
  270 + for (String key : keys) {
  271 + if (s.isAllKeys() || s.getKeyStates().containsKey(key)) {
  272 + if (subscriptionUpdate == null) {
  273 + subscriptionUpdate = new ArrayList<>();
  274 + }
  275 + subscriptionUpdate.add(new BasicTsKvEntry(0, new StringDataEntry(key, null)));
  276 + }
  277 + }
  278 + return subscriptionUpdate;
  279 + });
  280 + callback.onSuccess();
  281 + }
  282 +
256 private <T extends TbSubscription> void onLocalSubUpdate(EntityId entityId, 283 private <T extends TbSubscription> void onLocalSubUpdate(EntityId entityId,
257 Function<TbSubscription, T> castFunction, 284 Function<TbSubscription, T> castFunction,
258 Predicate<T> filterFunction, 285 Predicate<T> filterFunction,
@@ -35,4 +35,5 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio @@ -35,4 +35,5 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
35 35
36 void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback); 36 void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback);
37 37
  38 + void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty);
38 } 39 }
@@ -218,12 +218,17 @@ public class TbEntityDataSubCtx { @@ -218,12 +218,17 @@ public class TbEntityDataSubCtx {
218 latestCtxValues.forEach((k, v) -> { 218 latestCtxValues.forEach((k, v) -> {
219 TsValue update = latestUpdate.get(k); 219 TsValue update = latestUpdate.get(k);
220 if (update != null) { 220 if (update != null) {
221 - if (update.getTs() < v.getTs()) {  
222 - log.trace("[{}][{}][{}] Removed stale update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs());  
223 - latestUpdate.remove(k);  
224 - } else if ((update.getTs() == v.getTs() && update.getValue().equals(v.getValue()))) {  
225 - log.trace("[{}][{}][{}] Removed duplicate update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs());  
226 - latestUpdate.remove(k); 221 + //Ignore notifications about deleted keys
  222 + if (!(update.getTs() == 0 && (update.getValue() == null || update.getValue().isEmpty()))) {
  223 + if (update.getTs() < v.getTs()) {
  224 + log.trace("[{}][{}][{}] Removed stale update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs());
  225 + latestUpdate.remove(k);
  226 + } else if ((update.getTs() == v.getTs() && update.getValue().equals(v.getValue()))) {
  227 + log.trace("[{}][{}][{}] Removed duplicate update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs());
  228 + latestUpdate.remove(k);
  229 + }
  230 + } else {
  231 + log.trace("[{}][{}][{}] Received deleted notification for: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k);
227 } 232 }
228 } 233 }
229 }); 234 });
@@ -34,6 +34,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; @@ -34,6 +34,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
34 import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; 34 import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto;
35 import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeSubscriptionProto; 35 import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeSubscriptionProto;
36 import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto; 36 import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto;
  37 +import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto;
37 import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto; 38 import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto;
38 import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionKetStateProto; 39 import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionKetStateProto;
39 import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionProto; 40 import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionProto;
@@ -182,6 +183,22 @@ public class TbSubscriptionUtils { @@ -182,6 +183,22 @@ public class TbSubscriptionUtils {
182 return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); 183 return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
183 } 184 }
184 185
  186 + public static ToCoreMsg toAttributesDeleteProto(TenantId tenantId, EntityId entityId, String scope, List<String> keys) {
  187 + TbAttributeDeleteProto.Builder builder = TbAttributeDeleteProto.newBuilder();
  188 + builder.setEntityType(entityId.getEntityType().name());
  189 + builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
  190 + builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits());
  191 + builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
  192 + builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
  193 + builder.setScope(scope);
  194 + builder.addAllKeys(keys);
  195 +
  196 + SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder();
  197 + msgBuilder.setAttrDelete(builder);
  198 + return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
  199 + }
  200 +
  201 +
185 private static TsKvProto.Builder toKeyValueProto(long ts, KvEntry attr) { 202 private static TsKvProto.Builder toKeyValueProto(long ts, KvEntry attr) {
186 KeyValueProto.Builder dataBuilder = KeyValueProto.newBuilder(); 203 KeyValueProto.Builder dataBuilder = KeyValueProto.newBuilder();
187 dataBuilder.setKey(attr.getKey()); 204 dataBuilder.setKey(attr.getKey());
@@ -134,6 +134,13 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio @@ -134,6 +134,13 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
134 } 134 }
135 135
136 @Override 136 @Override
  137 + public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback) {
  138 + ListenableFuture<List<Void>> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys);
  139 + addMainCallback(deleteFuture, callback);
  140 + addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys));
  141 + }
  142 +
  143 + @Override
137 public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback) { 144 public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback) {
138 saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value) 145 saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value)
139 , System.currentTimeMillis())), callback); 146 , System.currentTimeMillis())), callback);
@@ -171,6 +178,20 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio @@ -171,6 +178,20 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
171 } 178 }
172 } 179 }
173 180
  181 + private void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys) {
  182 + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
  183 + if (currentPartitions.contains(tpi)) {
  184 + if (subscriptionManagerService.isPresent()) {
  185 + subscriptionManagerService.get().onAttributesDelete(tenantId, entityId, scope, keys, TbCallback.EMPTY);
  186 + } else {
  187 + log.warn("Possible misconfiguration because subscriptionManagerService is null!");
  188 + }
  189 + } else {
  190 + TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys);
  191 + clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
  192 + }
  193 + }
  194 +
174 private void onTimeSeriesUpdate(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts) { 195 private void onTimeSeriesUpdate(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts) {
175 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); 196 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
176 if (currentPartitions.contains(tpi)) { 197 if (currentPartitions.contains(tpi)) {
@@ -30,6 +30,8 @@ @@ -30,6 +30,8 @@
30 <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />--> 30 <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
31 <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />--> 31 <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->
32 32
  33 +<!-- <logger name="org.thingsboard.server.service.subscription" level="TRACE"/>-->
  34 +<!-- <logger name="org.thingsboard.server.service.telemetry" level="TRACE"/>-->
33 <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" /> 35 <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
34 36
35 <root level="INFO"> 37 <root level="INFO">
@@ -295,6 +295,16 @@ message TbAttributeUpdateProto { @@ -295,6 +295,16 @@ message TbAttributeUpdateProto {
295 repeated TsKvProto data = 7; 295 repeated TsKvProto data = 7;
296 } 296 }
297 297
  298 +message TbAttributeDeleteProto {
  299 + string entityType = 1;
  300 + int64 entityIdMSB = 2;
  301 + int64 entityIdLSB = 3;
  302 + int64 tenantIdMSB = 4;
  303 + int64 tenantIdLSB = 5;
  304 + string scope = 6;
  305 + repeated string keys = 7;
  306 +}
  307 +
298 message TbTimeSeriesUpdateProto { 308 message TbTimeSeriesUpdateProto {
299 string entityType = 1; 309 string entityType = 1;
300 int64 entityIdMSB = 2; 310 int64 entityIdMSB = 2;
@@ -340,6 +350,7 @@ message SubscriptionMgrMsgProto { @@ -340,6 +350,7 @@ message SubscriptionMgrMsgProto {
340 TbSubscriptionCloseProto subClose = 3; 350 TbSubscriptionCloseProto subClose = 3;
341 TbTimeSeriesUpdateProto tsUpdate = 4; 351 TbTimeSeriesUpdateProto tsUpdate = 4;
342 TbAttributeUpdateProto attrUpdate = 5; 352 TbAttributeUpdateProto attrUpdate = 5;
  353 + TbAttributeDeleteProto attrDelete = 6;
343 } 354 }
344 355
345 message LocalSubscriptionServiceMsgProto { 356 message LocalSubscriptionServiceMsgProto {
@@ -398,7 +398,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -398,7 +398,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
398 //TODO: fetch last level only. 398 //TODO: fetch last level only.
399 //TODO: fetch distinct records. 399 //TODO: fetch distinct records.
400 String lvlFilter = getLvlFilter(entityFilter.getMaxLevel()); 400 String lvlFilter = getLvlFilter(entityFilter.getMaxLevel());
401 - String selectFields = "SELECT tenant_id, customer_id, id, type, name, label FROM " + entityType.name() + " WHERE id in ( SELECT entity_id"; 401 + String selectFields = "SELECT tenant_id, customer_id, id, created_time, type, name, label FROM " + entityType.name() + " WHERE id in ( SELECT entity_id";
402 String from = getQueryTemplate(entityFilter.getDirection()); 402 String from = getQueryTemplate(entityFilter.getDirection());
403 String whereFilter = " WHERE re.relation_type = :where_relation_type AND re.to_type = :where_entity_type"; 403 String whereFilter = " WHERE re.relation_type = :where_relation_type AND re.to_type = :where_entity_type";
404 404
@@ -24,7 +24,7 @@ import java.util.Arrays; @@ -24,7 +24,7 @@ import java.util.Arrays;
24 24
25 @RunWith(ClasspathSuite.class) 25 @RunWith(ClasspathSuite.class)
26 @ClassnameFilters({ 26 @ClassnameFilters({
27 - "org.thingsboard.server.dao.service.sql.*SqlTest" 27 + "org.thingsboard.server.dao.service.sql.EntityServiceSqlTest"
28 }) 28 })
29 public class SqlDaoServiceTestSuite { 29 public class SqlDaoServiceTestSuite {
30 30
@@ -61,6 +61,7 @@ import org.thingsboard.server.dao.attributes.AttributesService; @@ -61,6 +61,7 @@ import org.thingsboard.server.dao.attributes.AttributesService;
61 import java.util.ArrayList; 61 import java.util.ArrayList;
62 import java.util.Arrays; 62 import java.util.Arrays;
63 import java.util.Collections; 63 import java.util.Collections;
  64 +import java.util.Comparator;
64 import java.util.List; 65 import java.util.List;
65 import java.util.concurrent.ExecutionException; 66 import java.util.concurrent.ExecutionException;
66 import java.util.stream.Collectors; 67 import java.util.stream.Collectors;
@@ -490,13 +491,16 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { @@ -490,13 +491,16 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
490 491
491 List<EntityId> loadedIds = loadedEntities.stream().map(EntityData::getEntityId).collect(Collectors.toList()); 492 List<EntityId> loadedIds = loadedEntities.stream().map(EntityData::getEntityId).collect(Collectors.toList());
492 List<EntityId> deviceIds = devices.stream().map(Device::getId).collect(Collectors.toList()); 493 List<EntityId> deviceIds = devices.stream().map(Device::getId).collect(Collectors.toList());
493 - 494 + deviceIds.sort(Comparator.comparing(EntityId::getId));
  495 + loadedIds.sort(Comparator.comparing(EntityId::getId));
494 Assert.assertEquals(deviceIds, loadedIds); 496 Assert.assertEquals(deviceIds, loadedIds);
495 497
496 List<String> loadedNames = loadedEntities.stream().map(entityData -> 498 List<String> loadedNames = loadedEntities.stream().map(entityData ->
497 entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue()).collect(Collectors.toList()); 499 entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue()).collect(Collectors.toList());
498 List<String> deviceNames = devices.stream().map(Device::getName).collect(Collectors.toList()); 500 List<String> deviceNames = devices.stream().map(Device::getName).collect(Collectors.toList());
499 501
  502 + Collections.sort(loadedNames);
  503 + Collections.sort(deviceNames);
500 Assert.assertEquals(deviceNames, loadedNames); 504 Assert.assertEquals(deviceNames, loadedNames);
501 505
502 sortOrder = new EntityDataSortOrder( 506 sortOrder = new EntityDataSortOrder(
@@ -560,8 +564,11 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { @@ -560,8 +564,11 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
560 loadedEntities.addAll(data.getData()); 564 loadedEntities.addAll(data.getData());
561 } 565 }
562 Assert.assertEquals(67, loadedEntities.size()); 566 Assert.assertEquals(67, loadedEntities.size());
563 - List<String> loadedTemperatures = loadedEntities.stream().map(entityData ->  
564 - entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList()); 567 + List<String> loadedTemperatures = new ArrayList<>();
  568 + for (Device device : devices) {
  569 + loadedTemperatures.add(loadedEntities.stream().filter(entityData -> entityData.getEntityId().equals(device.getId())).findFirst().orElse(null)
  570 + .getLatest().get(EntityKeyType.ATTRIBUTE).get("temperature").getValue());
  571 + }
565 List<String> deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList()); 572 List<String> deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
566 Assert.assertEquals(deviceTemperatures, loadedTemperatures); 573 Assert.assertEquals(deviceTemperatures, loadedTemperatures);
567 574
@@ -44,4 +44,7 @@ public interface RuleEngineTelemetryService { @@ -44,4 +44,7 @@ public interface RuleEngineTelemetryService {
44 44
45 void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value, FutureCallback<Void> callback); 45 void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value, FutureCallback<Void> callback);
46 46
  47 + void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback);
  48 +
  49 +
47 } 50 }