diff --git a/application/pom.xml b/application/pom.xml
index c6058408f8..6ce77646f8 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -124,6 +124,10 @@
org.thingsboard.common
edge-api
+
+ org.thingsboard.common
+ edqs
+
org.thingsboard
dao
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
index 0387e8c24a..7fd0b12077 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
@@ -20,6 +20,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -27,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
+import org.thingsboard.server.common.data.edqs.ToCoreEdqsRequest;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
@@ -38,6 +41,8 @@ import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
+import org.thingsboard.server.common.msg.edqs.EdqsApiService;
+import org.thingsboard.server.common.msg.edqs.EdqsService;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.query.EntityQueryService;
@@ -55,6 +60,10 @@ public class EntityQueryController extends BaseController {
@Autowired
private EntityQueryService entityQueryService;
+ @Autowired
+ private EdqsService edqsService;
+ @Autowired
+ private EdqsApiService edqsApiService;
private static final int MAX_PAGE_SIZE = 100;
@@ -133,4 +142,16 @@ public class EntityQueryController extends BaseController {
return entityQueryService.getKeysByQuery(getCurrentUser(), tenantId, query, isTimeseries, isAttributes, scope);
}
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
+ @PostMapping("/edqs/system/request")
+ public void processSystemEdqsRequest(@RequestBody ToCoreEdqsRequest request) {
+ edqsService.processSystemRequest(request);
+ }
+
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
+ @GetMapping("/edqs/enabled")
+ public boolean isEdqsApiEnabled() {
+ return edqsApiService.isEnabled();
+ }
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/CfRocksDb.java b/application/src/main/java/org/thingsboard/server/service/cf/CfRocksDb.java
index cb6d3f0d6b..f95227bc24 100644
--- a/application/src/main/java/org/thingsboard/server/service/cf/CfRocksDb.java
+++ b/application/src/main/java/org/thingsboard/server/service/cf/CfRocksDb.java
@@ -15,22 +15,29 @@
*/
package org.thingsboard.server.service.cf;
+import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.rocksdb.Options;
import org.rocksdb.WriteOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
-import org.thingsboard.server.utils.TbRocksDb;
+import org.thingsboard.server.edqs.util.TbRocksDb;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='in-memory'")
public class CfRocksDb extends TbRocksDb {
- public CfRocksDb(@Value("${queue.calculated_fields.rocks_db_path:${user.home}/.rocksdb/cf_states}") String path) throws Exception {
+ public CfRocksDb(@Value("${queue.calculated_fields.rocks_db_path:${user.home}/.rocksdb/cf_states}") String path) {
super(path, new Options().setCreateIfMissing(true), new WriteOptions().setSync(true));
}
+ @PostConstruct
+ @Override
+ public void init() {
+ super.init();
+ }
+
@PreDestroy
@Override
public void close() {
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
index 6f9845b9a5..c62a551310 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
@@ -82,6 +82,11 @@ public class EdgeEventSourcingListener {
@TransactionalEventListener(fallbackExecution = true)
public void handleEvent(SaveEntityEvent> event) {
+ if (Boolean.FALSE.equals(event.getBroadcastEvent())) {
+ log.trace("Ignoring event {}", event);
+ return;
+ }
+
try {
if (!isValidSaveEntityEventForEdgeProcessing(event)) {
return;
diff --git a/application/src/main/java/org/thingsboard/server/service/edqs/DefaultEdqsApiService.java b/application/src/main/java/org/thingsboard/server/service/edqs/DefaultEdqsApiService.java
new file mode 100644
index 0000000000..51c963ed2f
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edqs/DefaultEdqsApiService.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright © 2016-2025 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.edqs;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.stereotype.Service;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.server.common.data.edqs.query.EdqsRequest;
+import org.thingsboard.server.common.data.edqs.query.EdqsResponse;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.edqs.EdqsApiService;
+import org.thingsboard.server.edqs.state.EdqsPartitionService;
+import org.thingsboard.server.gen.transport.TransportProtos;
+import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
+import org.thingsboard.server.queue.TbQueueRequestTemplate;
+import org.thingsboard.server.queue.common.TbProtoQueueMsg;
+import org.thingsboard.server.queue.provider.EdqsClientQueueFactory;
+
+import java.util.UUID;
+
+@Service
+@Slf4j
+@RequiredArgsConstructor
+@ConditionalOnExpression("'${queue.edqs.api.supported:true}' == 'true' && ('${service.type:null}' == 'monolith' || '${service.type:null}' == 'tb-core')")
+public class DefaultEdqsApiService implements EdqsApiService {
+
+ private final EdqsPartitionService edqsPartitionService;
+ private final EdqsClientQueueFactory queueFactory;
+ private TbQueueRequestTemplate, TbProtoQueueMsg> requestTemplate;
+
+ @Value("${queue.edqs.api.auto_enable:true}")
+ private boolean autoEnable;
+
+ private Boolean apiEnabled = null;
+
+ @PostConstruct
+ private void init() {
+ requestTemplate = queueFactory.createEdqsRequestTemplate();
+ requestTemplate.init();
+ }
+
+ @Override
+ public ListenableFuture processRequest(TenantId tenantId, CustomerId customerId, EdqsRequest request) {
+ var requestMsg = ToEdqsMsg.newBuilder()
+ .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
+ .setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
+ .setTs(System.currentTimeMillis())
+ .setRequestMsg(TransportProtos.EdqsRequestMsg.newBuilder()
+ .setValue(JacksonUtil.toString(request))
+ .build());
+ if (customerId != null && !customerId.isNullUid()) {
+ requestMsg.setCustomerIdMSB(customerId.getId().getMostSignificantBits());
+ requestMsg.setCustomerIdLSB(customerId.getId().getLeastSignificantBits());
+ }
+
+ Integer partition = edqsPartitionService.resolvePartition(tenantId);
+ ListenableFuture> resultFuture = requestTemplate.send(new TbProtoQueueMsg<>(UUID.randomUUID(), requestMsg.build()), partition);
+ return Futures.transform(resultFuture, msg -> {
+ TransportProtos.EdqsResponseMsg responseMsg = msg.getValue().getResponseMsg();
+ return JacksonUtil.fromString(responseMsg.getValue(), EdqsResponse.class);
+ }, MoreExecutors.directExecutor());
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return Boolean.TRUE.equals(apiEnabled);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (enabled) {
+ log.info("Enabling EDQS API");
+ } else {
+ log.info("Disabling EDQS API");
+ }
+ apiEnabled = enabled;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return true;
+ }
+
+ @Override
+ public boolean isAutoEnable() {
+ return autoEnable;
+ }
+
+ @PreDestroy
+ private void stop() {
+ requestTemplate.stop();
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edqs/DefaultEdqsService.java b/application/src/main/java/org/thingsboard/server/service/edqs/DefaultEdqsService.java
new file mode 100644
index 0000000000..e823dee4e7
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edqs/DefaultEdqsService.java
@@ -0,0 +1,298 @@
+/**
+ * Copyright © 2016-2025 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.edqs;
+
+import com.google.protobuf.ByteString;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.common.util.ThingsBoardExecutors;
+import org.thingsboard.server.cluster.TbClusterService;
+import org.thingsboard.server.common.data.AttributeScope;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.ObjectType;
+import org.thingsboard.server.common.data.edqs.EdqsEventType;
+import org.thingsboard.server.common.data.edqs.EdqsObject;
+import org.thingsboard.server.common.data.edqs.EdqsSyncRequest;
+import org.thingsboard.server.common.data.edqs.Entity;
+import org.thingsboard.server.common.data.edqs.ToCoreEdqsMsg;
+import org.thingsboard.server.common.data.edqs.ToCoreEdqsRequest;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
+import org.thingsboard.server.common.data.kv.JsonDataEntry;
+import org.thingsboard.server.common.data.kv.KvEntry;
+import org.thingsboard.server.common.msg.edqs.EdqsApiService;
+import org.thingsboard.server.common.msg.edqs.EdqsService;
+import org.thingsboard.server.common.msg.queue.ServiceType;
+import org.thingsboard.server.dao.attributes.AttributesService;
+import org.thingsboard.server.edqs.processor.EdqsProducer;
+import org.thingsboard.server.edqs.state.EdqsPartitionService;
+import org.thingsboard.server.edqs.util.EdqsConverter;
+import org.thingsboard.server.gen.transport.TransportProtos.EdqsEventMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsCoreServiceMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
+import org.thingsboard.server.queue.discovery.HashPartitionService;
+import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
+import org.thingsboard.server.queue.discovery.TopicService;
+import org.thingsboard.server.queue.edqs.EdqsQueue;
+import org.thingsboard.server.queue.environment.DistributedLock;
+import org.thingsboard.server.queue.environment.DistributedLockService;
+import org.thingsboard.server.queue.provider.EdqsClientQueueFactory;
+import org.thingsboard.server.queue.util.AfterStartUp;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+@ConditionalOnProperty(value = "queue.edqs.sync.enabled", havingValue = "true")
+public class DefaultEdqsService implements EdqsService {
+
+ private final EdqsClientQueueFactory queueFactory;
+ private final EdqsConverter edqsConverter;
+ private final EdqsSyncService edqsSyncService;
+ private final EdqsApiService edqsApiService;
+ private final DistributedLockService distributedLockService;
+ private final AttributesService attributesService;
+ private final EdqsPartitionService edqsPartitionService;
+ private final TopicService topicService;
+ private final TbServiceInfoProvider serviceInfoProvider;
+ @Autowired @Lazy
+ private TbClusterService clusterService;
+ @Autowired @Lazy
+ private HashPartitionService hashPartitionService;
+
+ private EdqsProducer eventsProducer;
+ private ExecutorService executor;
+ private DistributedLock syncLock;
+
+ @PostConstruct
+ private void init() {
+ executor = ThingsBoardExecutors.newWorkStealingPool(12, getClass());
+ eventsProducer = EdqsProducer.builder()
+ .queue(EdqsQueue.EVENTS)
+ .partitionService(edqsPartitionService)
+ .topicService(topicService)
+ .producer(queueFactory.createEdqsMsgProducer(EdqsQueue.EVENTS))
+ .build();
+ syncLock = distributedLockService.getLock("edqs_sync");
+ }
+
+ @AfterStartUp(order = AfterStartUp.REGULAR_SERVICE)
+ public void onStartUp() {
+ if (!serviceInfoProvider.isService(ServiceType.TB_CORE)) {
+ return;
+ }
+ executor.submit(() -> {
+ try {
+ EdqsSyncState syncState = getSyncState();
+ if (edqsSyncService.isSyncNeeded() || syncState == null || syncState.getStatus() != EdqsSyncStatus.FINISHED) {
+ if (hashPartitionService.isSystemPartitionMine(ServiceType.TB_CORE)) {
+ processSystemRequest(ToCoreEdqsRequest.builder()
+ .syncRequest(new EdqsSyncRequest())
+ .build());
+ }
+ } else if (edqsApiService.isSupported() && edqsApiService.isAutoEnable()) {
+ // only if topic/RocksDB is not empty and sync is finished
+ edqsApiService.setEnabled(true);
+ }
+ } catch (Throwable e) {
+ log.error("Failed to start EDQS service", e);
+ }
+ });
+ }
+
+ @Override
+ public void processSystemRequest(ToCoreEdqsRequest request) {
+ log.info("Processing system request {}", request);
+ if (request.getSyncRequest() != null) {
+ saveSyncState(EdqsSyncStatus.REQUESTED);
+ }
+ broadcast(request.toInternalMsg());
+ }
+
+ @Override
+ public void processSystemMsg(ToCoreEdqsMsg msg) {
+ executor.submit(() -> {
+ log.info("Processing system msg {}", msg);
+ try {
+ if (msg.getApiEnabled() != null) {
+ edqsApiService.setEnabled(msg.getApiEnabled());
+ }
+
+ if (msg.getSyncRequest() != null) {
+ syncLock.lock();
+ try {
+ EdqsSyncState syncState = getSyncState();
+ if (syncState != null && syncState.getStatus() == EdqsSyncStatus.FINISHED) {
+ log.info("EDQS sync is already finished");
+ return;
+ }
+
+ saveSyncState(EdqsSyncStatus.STARTED);
+ edqsSyncService.sync();
+ saveSyncState(EdqsSyncStatus.FINISHED);
+
+ if (edqsApiService.isSupported())
+ if (edqsApiService.isAutoEnable()) {
+ log.info("EDQS sync is finished, auto-enabling API");
+ broadcast(ToCoreEdqsMsg.builder()
+ .apiEnabled(Boolean.TRUE)
+ .build());
+ } else {
+ log.info("EDQS sync is finished, but leaving API disabled");
+ }
+ } catch (Exception e) {
+ log.error("Failed to complete sync", e);
+ saveSyncState(EdqsSyncStatus.FAILED);
+ } finally {
+ syncLock.unlock();
+ }
+ }
+ } catch (Throwable e) {
+ log.error("Failed to process msg {}", msg, e);
+ }
+ });
+ }
+
+ @Override
+ public void onUpdate(TenantId tenantId, EntityId entityId, Object entity) {
+ EntityType entityType = entityId.getEntityType();
+ ObjectType objectType = ObjectType.fromEntityType(entityType);
+ if (!isEdqsType(tenantId, objectType)) {
+ log.trace("[{}][{}] Ignoring update event, type {} not supported", tenantId, entityId, entityType);
+ return;
+ }
+ onUpdate(tenantId, objectType, edqsConverter.toEntity(entityType, entity));
+ }
+
+ @Override
+ public void onUpdate(TenantId tenantId, ObjectType objectType, EdqsObject object) {
+ processEvent(tenantId, objectType, EdqsEventType.UPDATED, object);
+ }
+
+ @Override
+ public void onDelete(TenantId tenantId, EntityId entityId) {
+ EntityType entityType = entityId.getEntityType();
+ ObjectType objectType = ObjectType.fromEntityType(entityType);
+ if (!isEdqsType(tenantId, objectType)) {
+ log.trace("[{}][{}] Ignoring deletion event, type {} not supported", tenantId, entityId, entityType);
+ return;
+ }
+ onDelete(tenantId, objectType, new Entity(entityType, entityId.getId(), Long.MAX_VALUE));
+ }
+
+ @Override
+ public void onDelete(TenantId tenantId, ObjectType objectType, EdqsObject object) {
+ processEvent(tenantId, objectType, EdqsEventType.DELETED, object);
+ }
+
+ protected void processEvent(TenantId tenantId, ObjectType objectType, EdqsEventType eventType, EdqsObject object) {
+ executor.submit(() -> {
+ try {
+ String key = object.key();
+ Long version = object.version();
+ EdqsEventMsg.Builder eventMsg = EdqsEventMsg.newBuilder()
+ .setKey(key)
+ .setObjectType(objectType.name())
+ .setData(ByteString.copyFrom(edqsConverter.serialize(objectType, object)))
+ .setEventType(eventType.name());
+ if (version != null) {
+ eventMsg.setVersion(version);
+ }
+ eventsProducer.send(tenantId, objectType, key, ToEdqsMsg.newBuilder()
+ .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
+ .setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
+ .setTs(System.currentTimeMillis())
+ .setEventMsg(eventMsg)
+ .build());
+ } catch (Throwable e) {
+ log.error("[{}] Failed to push {} event for {} {}", tenantId, eventType, objectType, object, e);
+ }
+ });
+ }
+
+ private boolean isEdqsType(TenantId tenantId, ObjectType objectType) {
+ if (objectType == null) {
+ return false;
+ }
+ if (!tenantId.isSysTenantId()) {
+ return ObjectType.edqsTypes.contains(objectType);
+ } else {
+ return ObjectType.edqsSystemTypes.contains(objectType);
+ }
+ }
+
+ private void broadcast(ToCoreEdqsMsg msg) {
+ clusterService.broadcastToCore(ToCoreNotificationMsg.newBuilder()
+ .setToEdqsCoreServiceMsg(ToEdqsCoreServiceMsg.newBuilder()
+ .setValue(ByteString.copyFrom(JacksonUtil.writeValueAsBytes(msg))))
+ .build());
+ }
+
+ @SneakyThrows
+ private EdqsSyncState getSyncState() {
+ EdqsSyncState state = attributesService.find(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID, AttributeScope.SERVER_SCOPE, "edqsSyncState").get(30, TimeUnit.SECONDS)
+ .flatMap(KvEntry::getJsonValue)
+ .map(value -> JacksonUtil.fromString(value, EdqsSyncState.class))
+ .orElse(null);
+ log.info("EDQS sync state: {}", state);
+ return state;
+ }
+
+ @SneakyThrows
+ private void saveSyncState(EdqsSyncStatus status) {
+ EdqsSyncState state = new EdqsSyncState(status);
+ log.info("New EDQS sync state: {}", state);
+ attributesService.save(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID, AttributeScope.SERVER_SCOPE, new BaseAttributeKvEntry(
+ new JsonDataEntry("edqsSyncState", JacksonUtil.toString(state)),
+ System.currentTimeMillis())).get(30, TimeUnit.SECONDS);
+ }
+
+ @PreDestroy
+ private void stop() {
+ executor.shutdown();
+ eventsProducer.stop();
+ }
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ private static class EdqsSyncState {
+ private EdqsSyncStatus status;
+ }
+
+ private enum EdqsSyncStatus {
+ REQUESTED,
+ STARTED,
+ FINISHED,
+ FAILED
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edqs/EdqsListener.java b/application/src/main/java/org/thingsboard/server/service/edqs/EdqsListener.java
new file mode 100644
index 0000000000..d77df5ced8
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edqs/EdqsListener.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright © 2016-2025 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.edqs;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.event.TransactionalEventListener;
+import org.thingsboard.server.common.data.ObjectType;
+import org.thingsboard.server.common.data.audit.ActionType;
+import org.thingsboard.server.common.msg.edqs.EdqsService;
+import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
+import org.thingsboard.server.dao.eventsourcing.RelationActionEvent;
+import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
+
+@Service
+@RequiredArgsConstructor
+@ConditionalOnProperty(value = "queue.edqs.sync.enabled", havingValue = "true")
+public class EdqsListener {
+
+ private final EdqsService edqsService;
+
+ @TransactionalEventListener(fallbackExecution = true)
+ public void onUpdate(SaveEntityEvent> event) {
+ if (event.getEntityId() == null || event.getEntity() == null) {
+ return;
+ }
+ edqsService.onUpdate(event.getTenantId(), event.getEntityId(), event.getEntity());
+ }
+
+ @TransactionalEventListener(fallbackExecution = true)
+ public void onDelete(DeleteEntityEvent> event) {
+ if (event.getEntityId() == null) {
+ return;
+ }
+ edqsService.onDelete(event.getTenantId(), event.getEntityId());
+ }
+
+ @TransactionalEventListener(fallbackExecution = true)
+ public void handleEvent(RelationActionEvent relationEvent) {
+ if (relationEvent.getActionType() == ActionType.RELATION_ADD_OR_UPDATE) {
+ edqsService.onUpdate(relationEvent.getTenantId(), ObjectType.RELATION, relationEvent.getRelation());
+ } else if (relationEvent.getActionType() == ActionType.RELATION_DELETED) {
+ edqsService.onDelete(relationEvent.getTenantId(), ObjectType.RELATION, relationEvent.getRelation());
+ }
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edqs/EdqsSyncService.java b/application/src/main/java/org/thingsboard/server/service/edqs/EdqsSyncService.java
new file mode 100644
index 0000000000..79e0e60983
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edqs/EdqsSyncService.java
@@ -0,0 +1,284 @@
+/**
+ * Copyright © 2016-2025 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.edqs;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.thingsboard.server.common.data.AttributeScope;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.ObjectType;
+import org.thingsboard.server.common.data.edqs.AttributeKv;
+import org.thingsboard.server.common.data.edqs.EdqsEventType;
+import org.thingsboard.server.common.data.edqs.EdqsObject;
+import org.thingsboard.server.common.data.edqs.Entity;
+import org.thingsboard.server.common.data.edqs.LatestTsKv;
+import org.thingsboard.server.common.data.edqs.fields.EntityFields;
+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.page.PageDataIterable;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.attributes.AttributesDao;
+import org.thingsboard.server.dao.dictionary.KeyDictionaryDao;
+import org.thingsboard.server.dao.entity.EntityDaoRegistry;
+import org.thingsboard.server.dao.model.sql.AttributeKvEntity;
+import org.thingsboard.server.dao.model.sql.RelationEntity;
+import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryEntry;
+import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity;
+import org.thingsboard.server.dao.sql.relation.RelationRepository;
+import org.thingsboard.server.dao.sqlts.latest.TsKvLatestRepository;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.thingsboard.server.common.data.ObjectType.ATTRIBUTE_KV;
+import static org.thingsboard.server.common.data.ObjectType.LATEST_TS_KV;
+import static org.thingsboard.server.common.data.ObjectType.RELATION;
+import static org.thingsboard.server.common.data.ObjectType.edqsTenantTypes;
+
+@Slf4j
+public abstract class EdqsSyncService {
+
+ @Value("${queue.edqs.sync.entity_batch_size:10000}")
+ private int entityBatchSize;
+ @Value("${queue.edqs.sync.ts_batch_size:10000}")
+ private int tsBatchSize;
+ @Autowired
+ private EntityDaoRegistry entityDaoRegistry;
+ @Autowired
+ private AttributesDao attributesDao;
+ @Autowired
+ private KeyDictionaryDao keyDictionaryDao;
+ @Autowired
+ private RelationRepository relationRepository;
+ @Autowired
+ private TsKvLatestRepository tsKvLatestRepository;
+ @Autowired
+ @Lazy
+ private DefaultEdqsService edqsService;
+
+ private final ConcurrentHashMap entityInfoMap = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap keys = new ConcurrentHashMap<>();
+
+ private final Map counters = new ConcurrentHashMap<>();
+
+ public abstract boolean isSyncNeeded();
+
+ public void sync() {
+ log.info("Synchronizing data to EDQS");
+ long startTs = System.currentTimeMillis();
+ counters.clear();
+
+ syncTenantEntities();
+ syncRelations();
+ loadKeyDictionary();
+ syncAttributes();
+ syncLatestTimeseries();
+
+ counters.clear();
+ log.info("Finishing synchronizing data to EDQS in {} ms", (System.currentTimeMillis() - startTs));
+ }
+
+ private void process(TenantId tenantId, ObjectType type, EdqsObject object) {
+ AtomicInteger counter = counters.computeIfAbsent(type, t -> new AtomicInteger());
+ if (counter.incrementAndGet() % 10000 == 0) {
+ log.info("Processed {} {} objects", counter.get(), type);
+ }
+ edqsService.processEvent(tenantId, type, EdqsEventType.UPDATED, object);
+ }
+
+ private void syncTenantEntities() {
+ for (ObjectType type : edqsTenantTypes) {
+ log.info("Synchronizing {} entities to EDQS", type);
+ long ts = System.currentTimeMillis();
+ EntityType entityType = type.toEntityType();
+ Dao> dao = entityDaoRegistry.getDao(entityType);
+ UUID lastId = UUID.fromString("00000000-0000-0000-0000-000000000000");
+ while (true) {
+ var batch = dao.findNextBatch(lastId, entityBatchSize);
+ if (batch.isEmpty()) {
+ break;
+ }
+ for (EntityFields entityFields : batch) {
+ TenantId tenantId = TenantId.fromUUID(entityFields.getTenantId());
+ entityInfoMap.put(entityFields.getId(), new EntityIdInfo(entityType, tenantId));
+ process(tenantId, type, new Entity(entityType, entityFields));
+ }
+ EntityFields lastRecord = batch.get(batch.size() - 1);
+ lastId = lastRecord.getId();
+ }
+ log.info("Finished synchronizing {} entities to EDQS in {} ms", type, (System.currentTimeMillis() - ts));
+ }
+ }
+
+ private void syncRelations() {
+ log.info("Synchronizing relations to EDQS");
+ long ts = System.currentTimeMillis();
+ UUID lastFromEntityId = UUID.fromString("00000000-0000-0000-0000-000000000000");
+ String lastFromEntityType = "";
+ String lastRelationTypeGroup = "";
+ String lastRelationType = "";
+ UUID lastToEntityId = UUID.fromString("00000000-0000-0000-0000-000000000000");
+ String lastToEntityType = "";
+
+ while (true) {
+ List batch = relationRepository.findNextBatch(lastFromEntityId, lastFromEntityType, lastRelationTypeGroup,
+ lastRelationType, lastToEntityId, lastToEntityType, entityBatchSize);
+ if (batch.isEmpty()) {
+ break;
+ }
+ processRelationBatch(batch);
+
+ RelationEntity lastRecord = batch.get(batch.size() - 1);
+ lastFromEntityId = lastRecord.getFromId();
+ lastFromEntityType = lastRecord.getFromType();
+ lastRelationTypeGroup = lastRecord.getRelationTypeGroup();
+ lastRelationType = lastRecord.getRelationType();
+ lastToEntityId = lastRecord.getToId();
+ lastToEntityType = lastRecord.getToType();
+ }
+ log.info("Finished synchronizing relations to EDQS in {} ms", (System.currentTimeMillis() - ts));
+ }
+
+ private void processRelationBatch(List relations) {
+ for (RelationEntity relation : relations) {
+ if (RelationTypeGroup.COMMON.name().equals(relation.getRelationTypeGroup())) {
+ EntityIdInfo entityIdInfo = entityInfoMap.get(relation.getFromId());
+ if (entityIdInfo != null) {
+ process(entityIdInfo.tenantId(), RELATION, relation.toData());
+ } else {
+ log.info("Relation from id not found: {} ", relation);
+ }
+ }
+ }
+ }
+
+ private void loadKeyDictionary() {
+ log.info("Loading key dictionary");
+ long ts = System.currentTimeMillis();
+ var keyDictionaryEntries = new PageDataIterable<>(keyDictionaryDao::findAll, 10000);
+ for (KeyDictionaryEntry keyDictionaryEntry : keyDictionaryEntries) {
+ keys.put(keyDictionaryEntry.getKeyId(), keyDictionaryEntry.getKey());
+ }
+ log.info("Finished loading key dictionary in {} ms", (System.currentTimeMillis() - ts));
+ }
+
+ private void syncAttributes() {
+ log.info("Synchronizing attributes to EDQS");
+ long ts = System.currentTimeMillis();
+
+ UUID lastEntityId = UUID.fromString("00000000-0000-0000-0000-000000000000");
+ int lastAttributeType = Integer.MIN_VALUE;
+ int lastAttributeKey = Integer.MIN_VALUE;
+
+ while (true) {
+ List batch = attributesDao.findNextBatch(lastEntityId, lastAttributeType, lastAttributeKey, tsBatchSize);
+ if (batch.isEmpty()) {
+ break;
+ }
+ processAttributeBatch(batch);
+
+ AttributeKvEntity lastRecord = batch.get(batch.size() - 1);
+ lastEntityId = lastRecord.getId().getEntityId();
+ lastAttributeType = lastRecord.getId().getAttributeType();
+ lastAttributeKey = lastRecord.getId().getAttributeKey();
+ }
+ log.info("Finished synchronizing attributes to EDQS in {} ms", (System.currentTimeMillis() - ts));
+ }
+
+ private void processAttributeBatch(List batch) {
+ for (AttributeKvEntity attribute : batch) {
+ attribute.setStrKey(getStrKeyOrFetchFromDb(attribute.getId().getAttributeKey()));
+ UUID entityId = attribute.getId().getEntityId();
+ EntityIdInfo entityIdInfo = entityInfoMap.get(entityId);
+ if (entityIdInfo == null) {
+ log.debug("Skipping attribute with entity UUID {} as it is not found in entityInfoMap", entityId);
+ continue;
+ }
+ AttributeKv attributeKv = new AttributeKv(
+ EntityIdFactory.getByTypeAndUuid(entityIdInfo.entityType(), entityId),
+ AttributeScope.valueOf(attribute.getId().getAttributeType()),
+ attribute.toData(),
+ attribute.getVersion());
+ process(entityIdInfo.tenantId(), ATTRIBUTE_KV, attributeKv);
+ }
+ }
+
+ private void syncLatestTimeseries() {
+ log.info("Synchronizing latest timeseries to EDQS");
+ long ts = System.currentTimeMillis();
+ UUID lastEntityId = UUID.fromString("00000000-0000-0000-0000-000000000000");
+ int lastKey = Integer.MIN_VALUE;
+
+ while (true) {
+ List batch = tsKvLatestRepository.findNextBatch(lastEntityId, lastKey, tsBatchSize);
+ if (batch.isEmpty()) {
+ break;
+ }
+ processTsKvLatestBatch(batch);
+
+ TsKvLatestEntity lastRecord = batch.get(batch.size() - 1);
+ lastEntityId = lastRecord.getEntityId();
+ lastKey = lastRecord.getKey();
+ }
+ log.info("Finished synchronizing latest timeseries to EDQS in {} ms", (System.currentTimeMillis() - ts));
+ }
+
+ private void processTsKvLatestBatch(List tsKvLatestEntities) {
+ for (TsKvLatestEntity tsKvLatestEntity : tsKvLatestEntities) {
+ try {
+ String strKey = getStrKeyOrFetchFromDb(tsKvLatestEntity.getKey());
+ if (strKey == null) {
+ log.debug("Skipping latest timeseries with key {} as it is not found in key dictionary", tsKvLatestEntity.getKey());
+ continue;
+ }
+ tsKvLatestEntity.setStrKey(strKey);
+ UUID entityUuid = tsKvLatestEntity.getEntityId();
+ EntityIdInfo entityIdInfo = entityInfoMap.get(entityUuid);
+ if (entityIdInfo != null) {
+ EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityIdInfo.entityType(), entityUuid);
+ LatestTsKv latestTsKv = new LatestTsKv(entityId, tsKvLatestEntity.toData(), tsKvLatestEntity.getVersion());
+ process(entityIdInfo.tenantId(), LATEST_TS_KV, latestTsKv);
+ }
+ } catch (Exception e) {
+ log.error("Failed to sync latest timeseries: {}", tsKvLatestEntity, e);
+ }
+ }
+ }
+
+ private String getStrKeyOrFetchFromDb(int key) {
+ String strKey = keys.get(key);
+ if (strKey != null) {
+ return strKey;
+ } else {
+ strKey = keyDictionaryDao.getKey(key);
+ if (strKey != null) {
+ keys.put(key, strKey);
+ }
+ }
+ return strKey;
+ }
+
+ public record EntityIdInfo(EntityType entityType, TenantId tenantId) {
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edqs/KafkaEdqsSyncService.java b/application/src/main/java/org/thingsboard/server/service/edqs/KafkaEdqsSyncService.java
new file mode 100644
index 0000000000..4ef552521b
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edqs/KafkaEdqsSyncService.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright © 2016-2025 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.edqs;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.queue.edqs.EdqsQueue;
+import org.thingsboard.server.queue.kafka.TbKafkaAdmin;
+import org.thingsboard.server.queue.kafka.TbKafkaSettings;
+
+import java.util.Collections;
+
+@Service
+@ConditionalOnExpression("'${queue.edqs.sync.enabled:true}' == 'true' && '${queue.type:null}' == 'kafka'")
+public class KafkaEdqsSyncService extends EdqsSyncService {
+
+ private final boolean syncNeeded;
+
+ public KafkaEdqsSyncService(TbKafkaSettings kafkaSettings) {
+ TbKafkaAdmin kafkaAdmin = new TbKafkaAdmin(kafkaSettings, Collections.emptyMap());
+ this.syncNeeded = kafkaAdmin.isTopicEmpty(EdqsQueue.EVENTS.getTopic());
+ }
+
+ @Override
+ public boolean isSyncNeeded() {
+ return syncNeeded;
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edqs/LocalEdqsSyncService.java b/application/src/main/java/org/thingsboard/server/service/edqs/LocalEdqsSyncService.java
new file mode 100644
index 0000000000..904391f172
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edqs/LocalEdqsSyncService.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright © 2016-2025 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.edqs;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.edqs.util.EdqsRocksDb;
+
+@Service
+@RequiredArgsConstructor
+@ConditionalOnExpression("'${queue.edqs.sync.enabled:true}' == 'true' && '${queue.type:null}' == 'in-memory'")
+public class LocalEdqsSyncService extends EdqsSyncService {
+
+ private final EdqsRocksDb db;
+
+ @Override
+ public boolean isSyncNeeded() {
+ return db.isNew();
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
index 03bd7b4e66..648e89adc9 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
@@ -77,6 +77,11 @@ public class EntityStateSourcingListener {
@TransactionalEventListener(fallbackExecution = true)
public void handleEvent(SaveEntityEvent> event) {
+ if (Boolean.FALSE.equals(event.getBroadcastEvent())) {
+ log.trace("Ignoring event {}", event);
+ return;
+ }
+
TenantId tenantId = event.getTenantId();
EntityId entityId = event.getEntityId();
if (entityId == null) {
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
index 7219179b2c..de1bd7d367 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
@@ -20,7 +20,6 @@ import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
-import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
@@ -33,6 +32,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.JavaSerDesUtil;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
+import org.thingsboard.server.common.data.edqs.ToCoreEdqsMsg;
import org.thingsboard.server.common.data.event.ErrorEvent;
import org.thingsboard.server.common.data.event.Event;
import org.thingsboard.server.common.data.event.LifecycleEvent;
@@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.queue.QueueConfig;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
+import org.thingsboard.server.common.msg.edqs.EdqsService;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
@@ -76,6 +77,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
+import org.thingsboard.server.queue.common.consumer.MainQueueConsumerManager;
import org.thingsboard.server.queue.common.consumer.QueueConsumerManager;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.QueueKey;
@@ -88,7 +90,6 @@ import org.thingsboard.server.service.notification.NotificationSchedulerService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
-import org.thingsboard.server.queue.common.consumer.MainQueueConsumerManager;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.IdMsgPair;
import org.thingsboard.server.service.resource.TbImageService;
@@ -146,9 +147,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService, CoreQueueConfig> mainConsumer;
+ private MainQueueConsumerManager, QueueConfig> mainConsumer;
private QueueConsumerManager> usageStatsConsumer;
private QueueConsumerManager> firmwareStatesConsumer;
@@ -175,7 +177,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService, CoreQueueConfig>builder()
+ this.mainConsumer = MainQueueConsumerManager., QueueConfig>builder()
.queueKey(new QueueKey(ServiceType.TB_CORE))
- .config(CoreQueueConfig.of(consumerPerPartition, (int) pollInterval))
+ .config(QueueConfig.of(consumerPerPartition, pollInterval))
.msgPackProcessor(this::processMsgs)
.consumerCreator((config, partitionId) -> queueFactory.createToCoreMsgConsumer())
.consumerExecutor(consumersExecutor)
@@ -251,7 +255,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> msgs, TbQueueConsumer> consumer, CoreQueueConfig config) throws Exception {
+ private void processMsgs(List> msgs, TbQueueConsumer> consumer, QueueConfig config) throws Exception {
List> orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).toList();
ConcurrentMap> pendingMap = orderedMsgList.stream().collect(
Collectors.toConcurrentMap(IdMsgPair::getUuid, IdMsgPair::getMsg));
@@ -389,6 +393,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService, EdgeQueueConfig> mainConsumer;
+ private MainQueueConsumerManager, QueueConfig> mainConsumer;
public DefaultTbEdgeConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext,
StatsFactory statsFactory, EdgeContextComponent edgeCtx) {
@@ -100,9 +100,9 @@ public class DefaultTbEdgeConsumerService extends AbstractConsumerService, EdgeQueueConfig>builder()
+ this.mainConsumer = MainQueueConsumerManager., QueueConfig>builder()
.queueKey(new QueueKey(ServiceType.TB_CORE).withQueueName(DataConstants.EDGE_QUEUE_NAME))
- .config(EdgeQueueConfig.of(consumerPerPartition, pollInterval))
+ .config(QueueConfig.of(consumerPerPartition, pollInterval))
.msgPackProcessor(this::processMsgs)
.consumerCreator((config, partitionId) -> queueFactory.createEdgeMsgConsumer())
.consumerExecutor(consumersExecutor)
@@ -128,7 +128,7 @@ public class DefaultTbEdgeConsumerService extends AbstractConsumerService> msgs, TbQueueConsumer> consumer, EdgeQueueConfig edgeQueueConfig) throws InterruptedException {
+ private void processMsgs(List> msgs, TbQueueConsumer> consumer, QueueConfig edgeQueueConfig) throws InterruptedException {
List> orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).toList();
ConcurrentMap> pendingMap = orderedMsgList.stream().collect(
Collectors.toConcurrentMap(IdMsgPair::getUuid, IdMsgPair::getMsg));
@@ -285,10 +285,4 @@ public class DefaultTbEdgeConsumerService extends AbstractConsumerService {
+ event.getNewPartitions().forEach((queueKey, partitions) -> {
if (CollectionsUtil.isOneOf(queueKey, QueueKey.CF, QueueKey.CF_STATES)) {
return;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManager.java b/application/src/main/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManager.java
index 31813803c3..c7f7f600a7 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManager.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManager.java
@@ -72,7 +72,7 @@ public class TbRuleEngineQueueConsumerManager extends MainQueueConsumerManager= deviceState.getLastActivityTime()) {
deviceState.setLastInactivityAlarmTime(0L);
- save(deviceId, INACTIVITY_ALARM_TIME, 0L);
+ save(state.getTenantId(), deviceId, INACTIVITY_ALARM_TIME, 0L);
}
}
}
@@ -583,7 +583,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService {
EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder();
EntityDataSortOrder entitiesSortOrder;
if (sortOrder == null || sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) {
- entitiesSortOrder = new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, ModelConstants.CREATED_TIME_PROPERTY));
+ entitiesSortOrder = new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, EntityKeyMapping.CREATED_TIME));
} else {
entitiesSortOrder = sortOrder;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java
index 62913a187b..4b60ec923a 100644
--- a/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java
+++ b/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java
@@ -93,7 +93,8 @@ public class TbCoreTransportApiService {
@AfterStartUp(order = AfterStartUp.REGULAR_SERVICE)
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
log.info("Received application ready event. Starting polling for events.");
- transportApiTemplate.init(transportApiService);
+ transportApiTemplate.subscribe();
+ transportApiTemplate.launch(transportApiService);
}
@PreDestroy
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java
index 90f6b36e0f..7288a8bac9 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java
@@ -229,6 +229,7 @@ public class DefaultWebSocketService implements WebSocketService {
} catch (TbRateLimitsException e) {
log.debug("{} Failed to handle WS cmd: {}", sessionRef, cmd, e);
} catch (Exception e) {
+ sendError(sessionRef, cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, e.getMessage());
log.error("{} Failed to handle WS cmd: {}", sessionRef, cmd, e);
}
}
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 53258a4296..b01a0cf55e 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -1599,6 +1599,16 @@ queue:
- key: max.poll.records
# Amount of records to be returned in a single poll. For Housekeeper reprocessing topic, we should consume messages (tasks) one by one
value: "${TB_QUEUE_KAFKA_HOUSEKEEPER_REPROCESSING_MAX_POLL_RECORDS:1}"
+ edqs.events:
+ # Key-value properties for Kafka consumer for edqs.events topic
+ - key: max.poll.records
+ # Max poll records for edqs.events topic
+ value: "${TB_QUEUE_KAFKA_EDQS_EVENTS_MAX_POLL_RECORDS:512}"
+ edqs.state:
+ # Key-value properties for Kafka consumer for edqs.state topic
+ - key: max.poll.records
+ # Max poll records for edqs.state topic
+ value: "${TB_QUEUE_KAFKA_EDQS_STATE_MAX_POLL_RECORDS:512}"
other-inline: "${TB_QUEUE_KAFKA_OTHER_PROPERTIES:}" # In this section you can specify custom parameters (semicolon separated) for Kafka consumer/producer/admin # Example "metrics.recording.level:INFO;metrics.sample.window.ms:30000"
other: # DEPRECATED. In this section, you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside
# - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms
@@ -1632,6 +1642,12 @@ queue:
calculated-field: "${TB_QUEUE_KAFKA_CF_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
# Kafka properties for Calculated Field State topics
calculated-field-state: "${TB_QUEUE_KAFKA_CF_STATE_TOPIC_PROPERTIES:retention.ms:-1;segment.bytes:52428800;retention.bytes:104857600000;partitions:1;min.insync.replicas:1;cleanup.policy:compact}"
+ # Kafka properties for EDQS events topics. Partitions number must be the same as queue.edqs.partitions
+ edqs-events: "${TB_QUEUE_KAFKA_EDQS_EVENTS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:52428800;retention.bytes:-1;partitions:12;min.insync.replicas:1}"
+ # Kafka properties for EDQS requests topic (default: 3 minutes retention). Partitions number must be the same as queue.edqs.partitions
+ edqs-requests: "${TB_QUEUE_KAFKA_EDQS_REQUESTS_TOPIC_PROPERTIES:retention.ms:180000;segment.bytes:52428800;retention.bytes:1048576000;partitions:12;min.insync.replicas:1}"
+ # Kafka properties for EDQS state topic (infinite retention, compaction). Partitions number must be the same as queue.edqs.partitions
+ edqs-state: "${TB_QUEUE_KAFKA_EDQS_STATE_TOPIC_PROPERTIES:retention.ms:-1;segment.bytes:52428800;retention.bytes:-1;partitions:12;min.insync.replicas:1;cleanup.policy:compact}"
consumer-stats:
# Prints lag between consumer group offset and last messages offset in Kafka topics
enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}"
@@ -1707,6 +1723,44 @@ queue:
enabled: "${TB_HOUSEKEEPER_STATS_ENABLED:true}"
# Statistics printing interval for Housekeeper
print-interval-ms: "${TB_HOUSEKEEPER_STATS_PRINT_INTERVAL_MS:60000}"
+ edqs:
+ sync:
+ # Enable/disable EDQS synchronization
+ enabled: "${TB_EDQS_SYNC_ENABLED:false}"
+ # Batch size of entities being synced with EDQS
+ entity_batch_size: "${TB_EDQS_SYNC_ENTITY_BATCH_SIZE:10000}"
+ # Batch size of timeseries data being synced with EDQS
+ ts_batch_size: "${TB_EDQS_SYNC_TS_BATCH_SIZE:10000}"
+ api:
+ # Whether to forward entity data query requests to EDQS (otherwise use PostgreSQL implementation)
+ supported: "${TB_EDQS_API_SUPPORTED:false}"
+ # Whether to auto-enable EDQS API (if queue.edqs.api.supported is true) when sync of data to Kafka is finished
+ auto_enable: "${TB_EDQS_API_AUTO_ENABLE:true}"
+ # Mode of EDQS: local (for monolith) or remote (with separate EDQS microservices)
+ mode: "${TB_EDQS_MODE:local}"
+ local:
+ # Path to RocksDB for EDQS backup when running in local mode
+ rocksdb_path: "${TB_EDQS_ROCKSDB_PATH:${user.home}/.rocksdb/edqs}"
+ # Number of partitions for EDQS topics
+ partitions: "${TB_EDQS_PARTITIONS:12}"
+ # EDQS partitioning strategy: tenant (partition is resolved by tenant id) or none (no specific strategy, resolving by message key)
+ partitioning_strategy: "${TB_EDQS_PARTITIONING_STRATEGY:tenant}"
+ # EDQS requests topic
+ requests_topic: "${TB_EDQS_REQUESTS_TOPIC:edqs.requests}"
+ # EDQS responses topic
+ responses_topic: "${TB_EDQS_RESPONSES_TOPIC:edqs.responses}"
+ # Poll interval for EDQS topics
+ poll_interval: "${TB_EDQS_POLL_INTERVAL_MS:125}"
+ # Maximum amount of pending requests to EDQS
+ max_pending_requests: "${TB_EDQS_MAX_PENDING_REQUESTS:10000}"
+ # Maximum timeout for requests to EDQS
+ max_request_timeout: "${TB_EDQS_MAX_REQUEST_TIMEOUT:20000}"
+ stats:
+ # Enable/disable statistics for EDQS
+ enabled: "${TB_EDQS_STATS_ENABLED:true}"
+ # Statistics printing interval for EDQS
+ print-interval-ms: "${TB_EDQS_STATS_PRINT_INTERVAL_MS:300000}"
+
vc:
# Default topic name
topic: "${TB_QUEUE_VC_TOPIC:tb_version_control}"
diff --git a/application/src/test/java/org/thingsboard/server/controller/EdqsEntityQueryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EdqsEntityQueryControllerTest.java
new file mode 100644
index 0000000000..ce5221ab89
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/controller/EdqsEntityQueryControllerTest.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright © 2016-2025 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.controller;
+
+import org.junit.Before;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.TestPropertySource;
+import org.thingsboard.server.common.data.page.PageData;
+import org.thingsboard.server.common.data.query.EntityCountQuery;
+import org.thingsboard.server.common.data.query.EntityData;
+import org.thingsboard.server.common.data.query.EntityDataQuery;
+import org.thingsboard.server.common.msg.edqs.EdqsApiService;
+import org.thingsboard.server.dao.service.DaoSqlTest;
+import org.thingsboard.server.edqs.state.EdqsStateService;
+import org.thingsboard.server.edqs.util.EdqsRocksDb;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.awaitility.Awaitility.await;
+
+@DaoSqlTest
+@TestPropertySource(properties = {
+// "queue.type=kafka", // uncomment to use Kafka
+// "queue.kafka.bootstrap.servers=10.7.1.254:9092",
+ "queue.edqs.sync.enabled=true",
+ "queue.edqs.api.supported=true",
+ "queue.edqs.api.auto_enable=true",
+ "queue.edqs.mode=local"
+})
+public class EdqsEntityQueryControllerTest extends EntityQueryControllerTest {
+
+ @Autowired
+ private EdqsApiService edqsApiService;
+
+ @Autowired
+ private EdqsStateService edqsStateService;
+
+ @MockBean // so that we don't do backup for tests
+ private EdqsRocksDb edqsRocksDb;
+
+ @Before
+ public void before() {
+ await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> edqsApiService.isEnabled() && edqsStateService.isReady());
+ }
+
+ @Override
+ protected PageData findByQueryAndCheck(EntityDataQuery query, int expectedResultSize) {
+ return await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> findByQuery(query),
+ result -> result.getTotalElements() == expectedResultSize);
+ }
+
+ @Override
+ protected Long countByQueryAndCheck(EntityCountQuery query, long expectedResult) {
+ return await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> countByQuery(query),
+ result -> result == expectedResult);
+ }
+
+}
diff --git a/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
index ac618d7f96..011399e883 100644
--- a/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
@@ -18,12 +18,14 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import org.awaitility.Awaitility;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.ResultActions;
+import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
@@ -49,6 +51,7 @@ import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
+import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.EntityListFilter;
import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
@@ -70,9 +73,11 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
@@ -130,36 +135,25 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
filter.setDeviceNameFilter("");
EntityCountQuery countQuery = new EntityCountQuery(filter);
-
- Long count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
- Assert.assertEquals(97, count.longValue());
+ countByQueryAndCheck(countQuery, 97);
filter.setDeviceTypes(List.of("unknown"));
- count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
- Assert.assertEquals(0, count.longValue());
+ countByQueryAndCheck(countQuery, 0);
filter.setDeviceTypes(List.of("default"));
filter.setDeviceNameFilter("Device1");
-
- count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
- Assert.assertEquals(11, count.longValue());
+ countByQueryAndCheck(countQuery, 11);
EntityListFilter entityListFilter = new EntityListFilter();
entityListFilter.setEntityType(EntityType.DEVICE);
entityListFilter.setEntityList(devices.stream().map(Device::getId).map(DeviceId::toString).collect(Collectors.toList()));
-
countQuery = new EntityCountQuery(entityListFilter);
-
- count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
- Assert.assertEquals(97, count.longValue());
+ countByQueryAndCheck(countQuery, 97);
EntityTypeFilter filter2 = new EntityTypeFilter();
filter2.setEntityType(EntityType.DEVICE);
-
- EntityCountQuery countQuery2 = new EntityCountQuery(filter2);
-
- Long count2 = doPostWithResponse("/api/entitiesQuery/count", countQuery2, Long.class);
- Assert.assertEquals(97, count2.longValue());
+ countQuery = new EntityCountQuery(filter2);
+ countByQueryAndCheck(countQuery, 97);
}
@Test
@@ -169,51 +163,44 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
EntityTypeFilter allDeviceFilter = new EntityTypeFilter();
allDeviceFilter.setEntityType(EntityType.DEVICE);
EntityCountQuery query = new EntityCountQuery(allDeviceFilter);
- Long initialCount = doPostWithResponse("/api/entitiesQuery/count", query, Long.class);
+ countByQueryAndCheck(query, 0);
loginTenantAdmin();
List devices = new ArrayList<>();
+ String devicePrefix = "Device" + RandomStringUtils.randomAlphabetic(5);
for (int i = 0; i < 97; i++) {
Device device = new Device();
- device.setName("Device" + i);
+ device.setName(devicePrefix + i);
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
devices.add(doPost("/api/device", device, Device.class));
Thread.sleep(1);
}
DeviceTypeFilter filter = new DeviceTypeFilter();
- filter.setDeviceType("default");
+ filter.setDeviceTypes(List.of("default"));
filter.setDeviceNameFilter("");
loginSysAdmin();
EntityCountQuery countQuery = new EntityCountQuery(filter);
+ countByQueryAndCheck(countQuery, 97);
- Long count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
- Assert.assertEquals(97, count.longValue());
-
- filter.setDeviceType("unknown");
- count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
- Assert.assertEquals(0, count.longValue());
-
- filter.setDeviceType("default");
- filter.setDeviceNameFilter("Device1");
+ filter.setDeviceTypes(List.of("unknown"));
+ countByQueryAndCheck(countQuery, 0);
- count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
- Assert.assertEquals(11, count.longValue());
+ filter.setDeviceTypes(List.of("default"));
+ filter.setDeviceNameFilter(devicePrefix + "1");
+ countByQueryAndCheck(countQuery, 11);
EntityListFilter entityListFilter = new EntityListFilter();
entityListFilter.setEntityType(EntityType.DEVICE);
entityListFilter.setEntityList(devices.stream().map(Device::getId).map(DeviceId::toString).collect(Collectors.toList()));
countQuery = new EntityCountQuery(entityListFilter);
+ countByQueryAndCheck(countQuery, 97);
- count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
- Assert.assertEquals(97, count.longValue());
-
- Long count2 = doPostWithResponse("/api/entitiesQuery/count", query, Long.class);
- Assert.assertEquals(initialCount + 97, count2.longValue());
+ countByQueryAndCheck(countQuery, 97);
}
@Test
@@ -371,11 +358,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, null, null);
- PageData data =
- doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {
- });
-
- Assert.assertEquals(97, data.getTotalElements());
+ PageData data = findByQueryAndCheck(query, 97);
Assert.assertEquals(10, data.getTotalPages());
Assert.assertTrue(data.hasNext());
Assert.assertEquals(10, data.getData().size());
@@ -383,8 +366,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
List loadedEntities = new ArrayList<>(data.getData());
while (data.hasNext()) {
query = query.next();
- data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {
- });
+ data = findByQuery(query);
loadedEntities.addAll(data.getData());
}
Assert.assertEquals(97, loadedEntities.size());
@@ -406,8 +388,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
pageLink = new EntityDataPageLink(10, 0, "device1", sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, null, null);
- data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {
- });
+ data = findByQuery(query);
Assert.assertEquals(11, data.getTotalElements());
Assert.assertEquals("Device19", data.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue());
@@ -423,9 +404,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
EntityDataQuery query2 = new EntityDataQuery(filter2, pageLink2, entityFields2, null, null);
- PageData data2 =
- doPostWithTypedResponse("/api/entitiesQuery/find", query2, new TypeReference>() {
- });
+ PageData data2 = findByQuery(query2);
Assert.assertEquals(97, data2.getTotalElements());
Assert.assertEquals(10, data2.getTotalPages());
@@ -473,20 +452,15 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
List entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, null, null);
-
- PageData data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<>() {});
-
- Assert.assertEquals(87, data.getTotalElements());
+ findByQueryAndCheck(query, 87);
filter.setFilters(List.of(new RelationEntityTypeFilter("NOT_CONTAINS", List.of(EntityType.DEVICE), false)));
query = new EntityDataQuery(filter, pageLink, entityFields, null, null);
- data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<>() {});
- Assert.assertEquals(10, data.getTotalElements());
+ findByQueryAndCheck(query, 10);
filter.setFilters(List.of(new RelationEntityTypeFilter("NOT_CONTAINS", List.of(EntityType.DEVICE), true)));
query = new EntityDataQuery(filter, pageLink, entityFields, null, null);
- data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<>() {});
- Assert.assertEquals(87, data.getTotalElements());
+ findByQueryAndCheck(query, 87);
}
private EntityRelation createFromRelation(Device mainDevice, Device device, String relationType) {
@@ -531,14 +505,12 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
List latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
- PageData data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {
- });
+ PageData data = findByQueryAndCheck(query, 67);
List loadedEntities = new ArrayList<>(data.getData());
while (data.hasNext()) {
query = query.next();
- data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {
- });
+ data = findByQuery(query);
loadedEntities.addAll(data.getData());
}
Assert.assertEquals(67, loadedEntities.size());
@@ -551,6 +523,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
+ highTemperatureFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(FilterPredicateValue.fromDouble(45));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
@@ -559,13 +532,11 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
- data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {
- });
+ data = findByQuery(query);
loadedEntities = new ArrayList<>(data.getData());
while (data.hasNext()) {
query = query.next();
- data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {
- });
+ data = findByQuery(query);
loadedEntities.addAll(data.getData());
}
Assert.assertEquals(highTemperatures.size(), loadedEntities.size());
@@ -604,6 +575,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "alarmActiveTime"));
+ highTemperatureFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate predicate = new NumericFilterPredicate();
DynamicValue dynamicValue =
@@ -627,16 +599,16 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
- Awaitility.await()
+ await()
.alias("data by query")
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> {
- var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {});
+ var data = findByQuery(query);
var loadedEntities = new ArrayList<>(data.getData());
return loadedEntities.size() == numOfDevices;
});
- var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {});
+ var data = findByQuery(query);
var loadedEntities = new ArrayList<>(data.getData());
Assert.assertEquals(numOfDevices, loadedEntities.size());
@@ -694,11 +666,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
EntityDataQuery query = new EntityDataQuery(entityTypeFilter, pageLink, entityFields, null, null);
- PageData data =
- doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {
- });
-
- Assert.assertEquals(97, data.getTotalElements());
+ PageData data = findByQueryAndCheck(query, 97);
Assert.assertEquals(10, data.getTotalPages());
Assert.assertTrue(data.hasNext());
Assert.assertEquals(10, data.getData().size());
@@ -712,9 +680,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
});
EntityCountQuery countQuery = new EntityCountQuery(entityTypeFilter);
-
- Long count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
- Assert.assertEquals(97, count.longValue());
+ countByQueryAndCheck(countQuery, 97);
}
@Test
@@ -742,28 +708,29 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
KeyFilter activeAlarmTimeToLongFilter = getServerAttributeNumericGreaterThanKeyFilter("alarmActiveTime", 30);
KeyFilter tenantOwnerNameFilter = getEntityFieldStringEqualToKeyFilter("ownerName", TEST_TENANT_NAME);
KeyFilter wrongOwnerNameFilter = getEntityFieldStringEqualToKeyFilter("ownerName", "wrongName");
- KeyFilter tenantOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "TENANT");
+ KeyFilter tenantOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "TENANT");
KeyFilter customerOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "CUSTOMER");
// all devices with ownerName = TEST TENANT
- EntityCountQuery query = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, tenantOwnerNameFilter));
- checkEntitiesCount(query, numOfDevices);
+ EntityCountQuery query = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, tenantOwnerNameFilter));
+ await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> countByQuery(query),
+ result -> result == numOfDevices);
// all devices with ownerName = TEST TENANT
- EntityCountQuery activeAlarmTimeToLongQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeToLongFilter, tenantOwnerNameFilter));
- checkEntitiesCount(activeAlarmTimeToLongQuery, 0);
+ EntityCountQuery activeAlarmTimeToLongQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeToLongFilter, tenantOwnerNameFilter));
+ countByQueryAndCheck(activeAlarmTimeToLongQuery, 0);
// all devices with wrong ownerName
EntityCountQuery wrongTenantNameQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, wrongOwnerNameFilter));
- checkEntitiesCount(wrongTenantNameQuery, 0);
+ countByQueryAndCheck(wrongTenantNameQuery, 0);
// all devices with owner type = TENANT
EntityCountQuery tenantEntitiesQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, tenantOwnerTypeFilter));
- checkEntitiesCount(tenantEntitiesQuery, numOfDevices);
+ countByQueryAndCheck(tenantEntitiesQuery, numOfDevices);
// all devices with owner type = CUSTOMER
EntityCountQuery customerEntitiesQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, customerOwnerTypeFilter));
- checkEntitiesCount(customerEntitiesQuery, 0);
+ countByQueryAndCheck(customerEntitiesQuery, 0);
}
@Test
@@ -790,7 +757,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
KeyFilter activeAlarmTimeFilter = getServerAttributeNumericGreaterThanKeyFilter("alarmActiveTime", 5);
KeyFilter tenantOwnerNameFilter = getEntityFieldStringEqualToKeyFilter("ownerName", TEST_TENANT_NAME);
KeyFilter wrongOwnerNameFilter = getEntityFieldStringEqualToKeyFilter("ownerName", "wrongName");
- KeyFilter tenantOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "TENANT");
+ KeyFilter tenantOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "TENANT");
KeyFilter customerOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "CUSTOMER");
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
@@ -851,41 +818,29 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, null, null);
- PageData data =
- doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {
- });
-
- Assert.assertEquals(1, data.getTotalElements());
- Assert.assertEquals(1, data.getTotalPages());
- Assert.assertEquals(1, data.getData().size());
+ findByQueryAndCheck(query, 1);
// unnassign dashboard
login(TENANT_EMAIL, TENANT_PASSWORD);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString() + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD);
- PageData dataAfterUnassign =
- doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {
- });
-
- Assert.assertEquals(0, dataAfterUnassign.getTotalElements());
- Assert.assertEquals(0, dataAfterUnassign.getTotalPages());
- Assert.assertEquals(0, dataAfterUnassign.getData().size());
+ findByQueryAndCheck(query, 0);
}
private void checkEntitiesByQuery(EntityDataQuery query, int expectedNumOfDevices, String expectedOwnerName, String expectedOwnerType) throws Exception {
- Awaitility.await()
+ await()
.alias("data by query")
.atMost(30, TimeUnit.SECONDS)
.until(() -> {
- var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {});
+ var data = findByQuery(query);
var loadedEntities = new ArrayList<>(data.getData());
return loadedEntities.size() == expectedNumOfDevices;
});
- if (expectedNumOfDevices == 0) {
- return;
- }
- var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {});
+ if (expectedNumOfDevices == 0) {
+ return;
+ }
+ var data = findByQuery(query);
var loadedEntities = new ArrayList<>(data.getData());
Assert.assertEquals(expectedNumOfDevices, loadedEntities.size());
@@ -898,25 +853,37 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
String alarmActiveTime = entity.getLatest().get(EntityKeyType.ATTRIBUTE).getOrDefault("alarmActiveTime", new TsValue(0, "-1")).getValue();
Assert.assertEquals("Device" + i, name);
- Assert.assertEquals( expectedOwnerName, ownerName);
- Assert.assertEquals( expectedOwnerType, ownerType);
+ Assert.assertEquals(expectedOwnerName, ownerName);
+ Assert.assertEquals(expectedOwnerType, ownerType);
Assert.assertEquals("1" + i, alarmActiveTime);
}
}
- private void checkEntitiesCount(EntityCountQuery query, int expectedNumOfDevices) {
- Awaitility.await()
- .alias("count by query")
- .atMost(30, TimeUnit.SECONDS)
- .until(() -> {
- var count = doPost("/api/entitiesQuery/count", query, Integer.class);
- return count == expectedNumOfDevices;
- });
- }
+ protected PageData findByQuery(EntityDataQuery query) throws Exception {
+ return doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<>() {
+ });
+ }
+
+ protected PageData findByQueryAndCheck(EntityDataQuery query, int expectedResultSize) throws Exception {
+ PageData result = findByQuery(query);
+ assertThat(result.getTotalElements()).isEqualTo(expectedResultSize);
+ return result;
+ }
+
+ protected Long countByQuery(EntityCountQuery countQuery) throws Exception {
+ return doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
+ }
+
+ protected Long countByQueryAndCheck(EntityCountQuery query, long expectedResult) throws Exception {
+ Long result = countByQuery(query);
+ assertThat(result).isEqualTo(expectedResult);
+ return result;
+ }
private KeyFilter getEntityFieldStringEqualToKeyFilter(String keyName, String value) {
KeyFilter tenantOwnerNameFilter = new KeyFilter();
tenantOwnerNameFilter.setKey(new EntityKey(EntityKeyType.ENTITY_FIELD, keyName));
+ tenantOwnerNameFilter.setValueType(EntityKeyValueType.STRING);
StringFilterPredicate ownerNamePredicate = new StringFilterPredicate();
ownerNamePredicate.setValue(FilterPredicateValue.fromString(value));
ownerNamePredicate.setOperation(StringFilterPredicate.StringOperation.EQUAL);
@@ -927,6 +894,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
private KeyFilter getServerAttributeNumericGreaterThanKeyFilter(String attribute, int value) {
KeyFilter numericFilter = new KeyFilter();
numericFilter.setKey(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, attribute));
+ numericFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(FilterPredicateValue.fromDouble(value));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
diff --git a/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java b/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java
index 17d0f3fe84..4a303e5253 100644
--- a/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java
@@ -153,7 +153,7 @@ public class HashPartitionServiceTest {
for (int queueIndex = 0; queueIndex < queueCount; queueIndex++) {
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, "queue" + queueIndex, tenantId);
for (int partition = 0; partition < partitionCount; partition++) {
- ServiceInfo serviceInfo = partitionService.resolveByPartitionIdx(services, queueKey, partition, Collections.emptyMap());
+ ServiceInfo serviceInfo = partitionService.resolveByPartitionIdx(services, queueKey, partition, Collections.emptyMap()).get(0);
String serviceId = serviceInfo.getServiceId();
map.put(serviceId, map.get(serviceId) + 1);
}
@@ -308,7 +308,7 @@ public class HashPartitionServiceTest {
partitionService_common.recalculatePartitions(commonRuleEngine, List.of(dedicatedRuleEngine));
verifyPartitionChangeEvent(event -> {
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.MAIN_QUEUE_NAME, TenantId.SYS_TENANT_ID);
- return event.getPartitionsMap().get(queueKey).size() == systemQueue.getPartitions();
+ return event.getNewPartitions().get(queueKey).size() == systemQueue.getPartitions();
});
Mockito.reset(applicationEventPublisher);
@@ -336,14 +336,14 @@ public class HashPartitionServiceTest {
// expecting event about no partitions for isolated queue key
verifyPartitionChangeEvent(event -> {
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.MAIN_QUEUE_NAME, tenantId);
- return event.getPartitionsMap().get(queueKey).isEmpty();
+ return event.getNewPartitions().get(queueKey).isEmpty();
});
partitionService_dedicated.updateQueues(List.of(queueUpdateMsg));
partitionService_dedicated.recalculatePartitions(dedicatedRuleEngine, List.of(commonRuleEngine));
verifyPartitionChangeEvent(event -> {
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.MAIN_QUEUE_NAME, tenantId);
- return event.getPartitionsMap().get(queueKey).size() == isolatedQueue.getPartitions();
+ return event.getNewPartitions().get(queueKey).size() == isolatedQueue.getPartitions();
});
@@ -361,7 +361,7 @@ public class HashPartitionServiceTest {
partitionService_dedicated.removeQueues(List.of(queueDeleteMsg));
verifyPartitionChangeEvent(event -> {
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.MAIN_QUEUE_NAME, tenantId);
- return event.getPartitionsMap().get(queueKey).isEmpty();
+ return event.getNewPartitions().get(queueKey).isEmpty();
});
}
@@ -381,12 +381,12 @@ public class HashPartitionServiceTest {
Stream.concat(Stream.of(TenantId.SYS_TENANT_ID), Stream.generate(UUID::randomUUID).map(TenantId::new).limit(10)).forEach(tenantId -> {
List queues = Stream.generate(() -> RandomStringUtils.randomAlphabetic(10))
.map(queueName -> new QueueKey(ServiceType.TB_RULE_ENGINE, queueName, tenantId))
- .limit(100).collect(Collectors.toList());
+ .limit(100).toList();
for (int partition = 0; partition < 10; partition++) {
- ServiceInfo expectedAssignedRuleEngine = partitionService.resolveByPartitionIdx(ruleEngines, new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId), partition, Collections.emptyMap());
+ ServiceInfo expectedAssignedRuleEngine = partitionService.resolveByPartitionIdx(ruleEngines, new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId), partition, Collections.emptyMap()).get(0);
for (QueueKey queueKey : queues) {
- ServiceInfo assignedRuleEngine = partitionService.resolveByPartitionIdx(ruleEngines, queueKey, partition, Collections.emptyMap());
+ ServiceInfo assignedRuleEngine = partitionService.resolveByPartitionIdx(ruleEngines, queueKey, partition, Collections.emptyMap()).get(0);
assertThat(assignedRuleEngine).as(queueKey + "[" + partition + "] should be assigned to " + expectedAssignedRuleEngine.getServiceId())
.isEqualTo(expectedAssignedRuleEngine);
}
@@ -434,6 +434,7 @@ public class HashPartitionServiceTest {
ReflectionTestUtils.setField(partitionService, "hashFunctionName", hashFunctionName);
ReflectionTestUtils.setField(partitionService, "edgeTopic", "tb.edge");
ReflectionTestUtils.setField(partitionService, "edgePartitions", 10);
+ ReflectionTestUtils.setField(partitionService, "edqsPartitions", 12);
partitionService.init();
partitionService.partitionsInit();
return partitionService;
diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/EdqsEntityServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/EdqsEntityServiceTest.java
new file mode 100644
index 0000000000..50c80d08c7
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/service/entitiy/EdqsEntityServiceTest.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright © 2016-2025 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.entitiy;
+
+import com.google.common.collect.Lists;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.TestPropertySource;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.IdBased;
+import org.thingsboard.server.common.data.page.PageData;
+import org.thingsboard.server.common.data.query.EntityCountQuery;
+import org.thingsboard.server.common.data.query.EntityData;
+import org.thingsboard.server.common.data.query.EntityDataQuery;
+import org.thingsboard.server.common.data.query.EntityKeyType;
+import org.thingsboard.server.common.data.query.RelationsQueryFilter;
+import org.thingsboard.server.common.data.relation.EntitySearchDirection;
+import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
+import org.thingsboard.server.common.msg.edqs.EdqsApiService;
+import org.thingsboard.server.dao.service.DaoSqlTest;
+import org.thingsboard.server.edqs.util.EdqsRocksDb;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static org.awaitility.Awaitility.await;
+
+@DaoSqlTest
+@TestPropertySource(properties = {
+ "queue.edqs.sync.enabled=true",
+ "queue.edqs.api.supported=true",
+ "queue.edqs.api.auto_enable=true",
+ "queue.edqs.mode=local"
+})
+public class EdqsEntityServiceTest extends EntityServiceTest {
+
+ @Autowired
+ private EdqsApiService edqsApiService;
+
+ @MockBean
+ private EdqsRocksDb edqsRocksDb;
+
+ @Before
+ public void beforeEach() {
+ await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> edqsApiService.isEnabled());
+ }
+
+ // sql implementation has a bug with data duplication, edqs implementation returns correct value
+ @Override
+ @Test
+ public void testCountHierarchicalEntitiesByMultiRootQuery() throws InterruptedException {
+ List buildings = new ArrayList<>();
+ List apartments = new ArrayList<>();
+ Map> entityNameByTypeMap = new HashMap<>();
+ Map childParentRelationMap = new HashMap<>();
+ createMultiRootHierarchy(buildings, apartments, entityNameByTypeMap, childParentRelationMap);
+
+ RelationsQueryFilter filter = new RelationsQueryFilter();
+ filter.setMultiRoot(true);
+ filter.setMultiRootEntitiesType(EntityType.ASSET);
+ filter.setMultiRootEntityIds(buildings.stream().map(IdBased::getId).map(d -> d.getId().toString()).collect(Collectors.toSet()));
+ filter.setDirection(EntitySearchDirection.FROM);
+
+ EntityCountQuery countQuery = new EntityCountQuery(filter);
+ countByQueryAndCheck(countQuery, 63);
+
+ filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("AptToHeat", Collections.singletonList(EntityType.DEVICE))));
+ countByQueryAndCheck(countQuery, 27);
+
+ filter.setMultiRootEntitiesType(EntityType.ASSET);
+ filter.setMultiRootEntityIds(apartments.stream().map(IdBased::getId).map(d -> d.getId().toString()).collect(Collectors.toSet()));
+ filter.setDirection(EntitySearchDirection.TO);
+ filter.setFilters(Lists.newArrayList(
+ new RelationEntityTypeFilter("buildingToApt", Collections.singletonList(EntityType.ASSET)),
+ new RelationEntityTypeFilter("AptToEnergy", Collections.singletonList(EntityType.DEVICE))));
+ countByQueryAndCheck(countQuery, 3);
+
+ deviceService.deleteDevicesByTenantId(tenantId);
+ assetService.deleteAssetsByTenantId(tenantId);
+ }
+
+ @Override
+ protected PageData findByQueryAndCheck(CustomerId customerId, EntityDataQuery query, long expectedResultSize) {
+ return await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> findByQuery(customerId, query),
+ result -> result.getTotalElements() == expectedResultSize);
+ }
+
+ @Override
+ protected List findByQueryAndCheckTelemetry(EntityDataQuery query, EntityKeyType entityKeyType, String key, List expectedTelemetries) {
+ return await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> findEntitiesTelemetry(query, entityKeyType, key, expectedTelemetries),
+ loadedEntities -> loadedEntities.stream().map(entityData -> entityData.getLatest().get(entityKeyType).get(key).getValue()).toList().containsAll(expectedTelemetries));
+ }
+
+ @Override
+ protected long countByQueryAndCheck(EntityCountQuery countQuery, int expectedResult) {
+ return countByQueryAndCheck(new CustomerId(CustomerId.NULL_UUID), countQuery, expectedResult);
+ }
+
+ @Override
+ protected long countByQueryAndCheck(CustomerId customerId, EntityCountQuery query, int expectedResult) {
+ return await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> countByQuery(customerId, query),
+ result -> result == expectedResult);
+ }
+
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/EntityServiceTest.java
similarity index 80%
rename from dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java
rename to application/src/test/java/org/thingsboard/server/service/entitiy/EntityServiceTest.java
index 77f9a6bc23..18687cedba 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/entitiy/EntityServiceTest.java
@@ -13,22 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.thingsboard.server.dao.service;
+package org.thingsboard.server.service.entitiy;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.ResultSetExtractor;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
@@ -37,6 +40,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@@ -47,6 +51,7 @@ 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.TimeseriesSaveResult;
+import org.thingsboard.server.common.data.objects.TelemetryEntityView;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.ApiUsageStateFilter;
import org.thingsboard.server.common.data.query.AssetSearchQueryFilter;
@@ -64,6 +69,7 @@ import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityListFilter;
import org.thingsboard.server.common.data.query.EntityNameFilter;
+import org.thingsboard.server.common.data.query.EntityViewTypeFilter;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
@@ -76,17 +82,22 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.alarm.AlarmService;
+import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardDao;
+import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewDao;
+import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.relation.RelationService;
+import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.sql.relation.RelationRepository;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
@@ -106,13 +117,12 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertEquals;
import static org.thingsboard.server.common.data.query.EntityKeyType.ATTRIBUTE;
import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD;
@Slf4j
@DaoSqlTest
-public class EntityServiceTest extends AbstractServiceTest {
+public class EntityServiceTest extends AbstractControllerTest {
static final int ENTITY_COUNT = 5;
public static final String TEST_CUSTOMER_NAME = "Test";
@@ -120,6 +130,12 @@ public class EntityServiceTest extends AbstractServiceTest {
@Autowired
AssetService assetService;
@Autowired
+ AssetProfileService assetProfileService;
+ @Autowired
+ DashboardService dashboardService;
+ @Autowired
+ EntityViewService entityViewService;
+ @Autowired
UserService userService;
@Autowired
AttributesService attributesService;
@@ -158,7 +174,7 @@ public class EntityServiceTest extends AbstractServiceTest {
}
@Test
- public void testCountEntitiesByQuery() throws InterruptedException {
+ public void testCountEntitiesByQuery() {
List devices = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Device device = new Device();
@@ -174,33 +190,26 @@ public class EntityServiceTest extends AbstractServiceTest {
filter.setDeviceNameFilter("");
EntityCountQuery countQuery = new EntityCountQuery(filter);
-
- long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(97, count);
+ countByQueryAndCheck(countQuery, 97);
filter.setDeviceTypes(List.of("unknown"));
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(0, count);
+ countByQueryAndCheck(countQuery, 0);
filter.setDeviceTypes(List.of("default"));
filter.setDeviceNameFilter("Device1");
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(11, count);
+ countByQueryAndCheck(countQuery, 11);
EntityListFilter entityListFilter = new EntityListFilter();
entityListFilter.setEntityType(EntityType.DEVICE);
entityListFilter.setEntityList(devices.stream().map(Device::getId).map(DeviceId::toString).collect(Collectors.toList()));
countQuery = new EntityCountQuery(entityListFilter);
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(97, count);
+ countByQueryAndCheck(countQuery, 97);
deviceService.deleteDevicesByTenantId(tenantId);
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(0, count);
+ countByQueryAndCheck(countQuery, 0);
}
-
@Test
public void testCountHierarchicalEntitiesByQuery() throws InterruptedException {
List assets = new ArrayList<>();
@@ -212,19 +221,15 @@ public class EntityServiceTest extends AbstractServiceTest {
filter.setDirection(EntitySearchDirection.FROM);
EntityCountQuery countQuery = new EntityCountQuery(filter);
-
- long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(31, count); //due to the loop relations in hierarchy, the TenantId included in total count (1*Tenant + 5*Asset + 5*5*Devices = 31)
+ countByQueryAndCheck(countQuery, 31); //due to the loop relations in hierarchy, the TenantId included in total count (1*Tenant + 5*Asset + 5*5*Devices = 31)
filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(25, count);
+ countByQueryAndCheck(countQuery, 25);
filter.setRootEntity(devices.get(0).getId());
filter.setDirection(EntitySearchDirection.TO);
filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Manages", Collections.singletonList(EntityType.TENANT))));
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(1, count);
+ countByQueryAndCheck(countQuery, 1);
DeviceSearchQueryFilter filter2 = new DeviceSearchQueryFilter();
filter2.setRootEntity(tenantId);
@@ -232,18 +237,14 @@ public class EntityServiceTest extends AbstractServiceTest {
filter2.setRelationType("Contains");
countQuery = new EntityCountQuery(filter2);
-
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(25, count);
+ countByQueryAndCheck(countQuery, 25);
filter2.setDeviceTypes(Arrays.asList("default0", "default1"));
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(10, count);
+ countByQueryAndCheck(countQuery, 10);
filter2.setRootEntity(devices.get(0).getId());
filter2.setDirection(EntitySearchDirection.TO);
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(0, count);
+ countByQueryAndCheck(countQuery, 0);
AssetSearchQueryFilter filter3 = new AssetSearchQueryFilter();
filter3.setRootEntity(tenantId);
@@ -251,18 +252,14 @@ public class EntityServiceTest extends AbstractServiceTest {
filter3.setRelationType("Manages");
countQuery = new EntityCountQuery(filter3);
-
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(5, count);
+ countByQueryAndCheck(countQuery, 5);
filter3.setAssetTypes(Arrays.asList("type0", "type1"));
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(2, count);
+ countByQueryAndCheck(countQuery, 2);
filter3.setRootEntity(devices.get(0).getId());
filter3.setDirection(EntitySearchDirection.TO);
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(0, count);
+ countByQueryAndCheck(countQuery, 0);
}
@Test
@@ -279,11 +276,12 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, null, null);
- PageData entityDataByQuery = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ PageData entityDataByQuery = findByQueryAndCheck(query, 5);
List data = entityDataByQuery.getData();
Assert.assertEquals(data.size(), 5);
data.forEach(entityData -> Assert.assertNotNull(entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("phone")));
+ countByQueryAndCheck(query, 5);
}
private void createTestUserRelations(TenantId tenantId, List users) {
@@ -313,30 +311,24 @@ public class EntityServiceTest extends AbstractServiceTest {
filter.setEdgeNameFilter("");
EntityCountQuery countQuery = new EntityCountQuery(filter);
-
- long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(97, count);
+ countByQueryAndCheck(countQuery, 97);
filter.setEdgeTypes(List.of("unknown"));
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(0, count);
+ countByQueryAndCheck(countQuery, 0);
filter.setEdgeTypes(List.of("default"));
filter.setEdgeNameFilter("Edge1");
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(11, count);
+ countByQueryAndCheck(countQuery, 11);
EntityListFilter entityListFilter = new EntityListFilter();
entityListFilter.setEntityType(EntityType.EDGE);
entityListFilter.setEntityList(edges.stream().map(Edge::getId).map(EdgeId::toString).collect(Collectors.toList()));
countQuery = new EntityCountQuery(entityListFilter);
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(97, count);
+ countByQueryAndCheck(countQuery, 97);
edgeService.deleteEdgesByTenantId(tenantId);
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(0, count);
+ countByQueryAndCheck(countQuery, 0);
}
@Test
@@ -361,13 +353,10 @@ public class EntityServiceTest extends AbstractServiceTest {
filter.setRelationType("Manages");
EntityCountQuery countQuery = new EntityCountQuery(filter);
-
- long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(5, count);
+ countByQueryAndCheck(countQuery, 5);
filter.setEdgeTypes(Arrays.asList("type0", "type1"));
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(2, count);
+ countByQueryAndCheck(countQuery, 2);
}
private Edge createEdge(int i, String type) {
@@ -424,19 +413,10 @@ public class EntityServiceTest extends AbstractServiceTest {
List entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
List latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
- EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
- PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- List loadedEntities = new ArrayList<>(data.getData());
- while (data.hasNext()) {
- query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities.addAll(data.getData());
- }
- Assert.assertEquals(25, loadedEntities.size());
- List loadedTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
List deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
- Assert.assertEquals(deviceTemperatures, loadedTemperatures);
+
+ EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
+ findByQueryAndCheckTelemetry(query, EntityKeyType.ATTRIBUTE, "temperature", deviceTemperatures);
pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
KeyFilter highTemperatureFilter = new KeyFilter();
@@ -447,23 +427,10 @@ public class EntityServiceTest extends AbstractServiceTest {
highTemperatureFilter.setPredicate(predicate);
List keyFilters = Collections.singletonList(highTemperatureFilter);
- query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
-
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
-
- loadedEntities = new ArrayList<>(data.getData());
- while (data.hasNext()) {
- query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities.addAll(data.getData());
- }
- Assert.assertEquals(highTemperatures.size(), loadedEntities.size());
-
- List loadedHighTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
List deviceHighTemperatures = highTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
- Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures);
+ query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
+ findByQueryAndCheckTelemetry(query, EntityKeyType.ATTRIBUTE, "temperature", deviceHighTemperatures);
deviceService.deleteDevicesByTenantId(tenantId);
}
@@ -483,13 +450,10 @@ public class EntityServiceTest extends AbstractServiceTest {
filter.setDirection(EntitySearchDirection.FROM);
EntityCountQuery countQuery = new EntityCountQuery(filter);
-
- long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(63, count);
+ countByQueryAndCheck(countQuery, 63);
filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("AptToHeat", Collections.singletonList(EntityType.DEVICE))));
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(27, count);
+ countByQueryAndCheck(countQuery, 27);
filter.setMultiRootEntitiesType(EntityType.ASSET);
filter.setMultiRootEntityIds(apartments.stream().map(IdBased::getId).map(d -> d.getId().toString()).collect(Collectors.toSet()));
@@ -497,13 +461,10 @@ public class EntityServiceTest extends AbstractServiceTest {
filter.setFilters(Lists.newArrayList(
new RelationEntityTypeFilter("buildingToApt", Collections.singletonList(EntityType.ASSET)),
new RelationEntityTypeFilter("AptToEnergy", Collections.singletonList(EntityType.DEVICE))));
-
- count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
- Assert.assertEquals(9, count);
+ countByQueryAndCheck(countQuery, 9);
deviceService.deleteDevicesByTenantId(tenantId);
assetService.deleteAssetsByTenantId(tenantId);
-
}
@Test
@@ -539,15 +500,6 @@ public class EntityServiceTest extends AbstractServiceTest {
onlineStatusFilter.setPredicate(predicate);
List keyFilters = Collections.singletonList(onlineStatusFilter);
- EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
- PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- List loadedEntities = new ArrayList<>(data.getData());
- while (data.hasNext()) {
- query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities.addAll(data.getData());
- }
-
long expectedEntitiesCnt = entityNameByTypeMap.entrySet()
.stream()
.filter(e -> !e.getKey().equals("building"))
@@ -555,6 +507,14 @@ public class EntityServiceTest extends AbstractServiceTest {
.map(Map.Entry::getValue)
.filter(e -> StringUtils.endsWith(e, "_1"))
.count();
+ EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
+ PageData data = findByQueryAndCheck(query, expectedEntitiesCnt);
+ List loadedEntities = new ArrayList<>(data.getData());
+ while (data.hasNext()) {
+ query = query.next();
+ data = findByQuery(query);
+ loadedEntities.addAll(data.getData());
+ }
Assert.assertEquals(expectedEntitiesCnt, loadedEntities.size());
Map actualRelations = new HashMap<>();
@@ -603,20 +563,12 @@ public class EntityServiceTest extends AbstractServiceTest {
List entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
List latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
+ List deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
+
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
- PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- List loadedEntities = new ArrayList<>(data.getData());
- while (data.hasNext()) {
- query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities.addAll(data.getData());
- }
- Assert.assertEquals(25, loadedEntities.size());
+ List loadedEntities = findByQueryAndCheckTelemetry(query, EntityKeyType.ATTRIBUTE, "temperature", deviceTemperatures);
+
loadedEntities.forEach(entity -> Assert.assertTrue(devices.stream().map(Device::getId).collect(Collectors.toSet()).contains(entity.getEntityId())));
- List loadedTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
- List deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
- Assert.assertEquals(deviceTemperatures, loadedTemperatures);
pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
KeyFilter highTemperatureFilter = new KeyFilter();
@@ -629,21 +581,8 @@ public class EntityServiceTest extends AbstractServiceTest {
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
-
- loadedEntities = new ArrayList<>(data.getData());
- while (data.hasNext()) {
- query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities.addAll(data.getData());
- }
- Assert.assertEquals(highTemperatures.size(), loadedEntities.size());
-
- List loadedHighTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
List deviceHighTemperatures = highTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
-
- Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures);
+ findByQueryAndCheckTelemetry(query, EntityKeyType.ATTRIBUTE, "temperature", deviceHighTemperatures);
deviceService.deleteDevicesByTenantId(tenantId);
}
@@ -677,18 +616,9 @@ public class EntityServiceTest extends AbstractServiceTest {
List latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "consumption"));
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
- PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- List loadedEntities = new ArrayList<>(data.getData());
- while (data.hasNext()) {
- query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities.addAll(data.getData());
- }
- Assert.assertEquals(5, loadedEntities.size());
- List loadedTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("consumption").getValue()).collect(Collectors.toList());
+
List deviceTemperatures = consumptions.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
- Assert.assertEquals(deviceTemperatures, loadedTemperatures);
+ findByQueryAndCheckTelemetry(query, EntityKeyType.ATTRIBUTE, "consumption", deviceTemperatures);
pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
KeyFilter highTemperatureFilter = new KeyFilter();
@@ -701,21 +631,8 @@ public class EntityServiceTest extends AbstractServiceTest {
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
-
- loadedEntities = new ArrayList<>(data.getData());
- while (data.hasNext()) {
- query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities.addAll(data.getData());
- }
- Assert.assertEquals(highConsumptions.size(), loadedEntities.size());
-
- List loadedHighTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("consumption").getValue()).collect(Collectors.toList());
List deviceHighTemperatures = highConsumptions.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
-
- Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures);
+ findByQueryAndCheckTelemetry(query, EntityKeyType.ATTRIBUTE, "consumption", deviceHighTemperatures);
deviceService.deleteDevicesByTenantId(tenantId);
}
@@ -897,9 +814,7 @@ public class EntityServiceTest extends AbstractServiceTest {
List entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, null, null);
- PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
-
- Assert.assertEquals(97, data.getTotalElements());
+ PageData data = findByQueryAndCheck(query, 97);
Assert.assertEquals(10, data.getTotalPages());
Assert.assertTrue(data.hasNext());
Assert.assertEquals(10, data.getData().size());
@@ -907,7 +822,7 @@ public class EntityServiceTest extends AbstractServiceTest {
List loadedEntities = new ArrayList<>(data.getData());
while (data.hasNext()) {
query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQuery(query);
loadedEntities.addAll(data.getData());
}
Assert.assertEquals(97, loadedEntities.size());
@@ -932,7 +847,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(10, 0, "device1", sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, null, null);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQuery(query);
Assert.assertEquals(11, data.getTotalElements());
Assert.assertEquals("Device19", data.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue());
@@ -946,11 +861,12 @@ public class EntityServiceTest extends AbstractServiceTest {
devices.get(1).setLabel(null);
devices.forEach(deviceService::saveDevice);
+ // FIXME (for Dasha, plz investigate):
+ // this and other tests below submit an empty value to a KEY FILTER, this is not "search text".
+ // why are we supposed to ignore it and return all devices? maybe it's a bug?
String searchQuery = "";
EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.EQUAL, searchQuery);
-
- PageData result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
}
@Test
@@ -962,9 +878,7 @@ public class EntityServiceTest extends AbstractServiceTest {
String searchQuery = devices.get(2).getLabel();
EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.NOT_EQUAL, searchQuery);
-
- PageData result = searchEntities(query);
- assertEquals(devices.size() - 1, result.getTotalElements());
+ findByQueryAndCheck(query, devices.size() - 1);
}
@Test
@@ -977,8 +891,7 @@ public class EntityServiceTest extends AbstractServiceTest {
String searchQuery = "";
EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.NOT_EQUAL, searchQuery);
- PageData result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
}
@Test
@@ -990,9 +903,7 @@ public class EntityServiceTest extends AbstractServiceTest {
String searchQuery = "";
EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.STARTS_WITH, searchQuery);
-
- PageData result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
}
@Test
@@ -1004,9 +915,7 @@ public class EntityServiceTest extends AbstractServiceTest {
String searchQuery = "";
EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.ENDS_WITH, searchQuery);
-
- PageData result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
}
@Test
@@ -1018,9 +927,7 @@ public class EntityServiceTest extends AbstractServiceTest {
String searchQuery = "";
EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.CONTAINS, searchQuery);
-
- PageData result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
}
@Test
@@ -1032,9 +939,7 @@ public class EntityServiceTest extends AbstractServiceTest {
String searchQuery = "label-";
EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.NOT_CONTAINS, searchQuery);
-
- PageData result = searchEntities(query);
- assertEquals(2, result.getTotalElements());
+ findByQueryAndCheck(query, 2);
}
@Test
@@ -1046,9 +951,7 @@ public class EntityServiceTest extends AbstractServiceTest {
String searchQuery = "";
EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.NOT_CONTAINS, searchQuery);
-
- PageData result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
}
@Test
@@ -1072,34 +975,27 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
EntityDataQuery query = new EntityDataQuery(deviceTypeFilter, pageLink, null, null, null);
-
- PageData result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
deviceTypeFilter.setEntityNameFilter("Device%");
-
- result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
deviceTypeFilter.setEntityNameFilter("%Device%");
-
- result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
deviceTypeFilter.setEntityNameFilter("%Device");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ findByQueryAndCheck(query, 0);
}
@Test
public void testFindEntityDataByQuery_filter_entity_name_ends_with() {
List devices = new ArrayList<>();
+ String suffixes = RandomStringUtils.randomAlphanumeric(5);
for (int i = 0; i < 10; i++) {
Device device = new Device();
device.setTenantId(tenantId);
- device.setName("Device " + i + " test");
+ device.setName("Device " + i + suffixes);
device.setType("default");
devices.add(device);
}
@@ -1108,29 +1004,21 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityNameFilter deviceTypeFilter = new EntityNameFilter();
deviceTypeFilter.setEntityType(EntityType.DEVICE);
- deviceTypeFilter.setEntityNameFilter("%test");
+ deviceTypeFilter.setEntityNameFilter("%" + suffixes);
EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
EntityDataQuery query = new EntityDataQuery(deviceTypeFilter, pageLink, null, null, null);
+ findByQueryAndCheck(query, devices.size());
- PageData result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ deviceTypeFilter.setEntityNameFilter("%" + suffixes + "%");
+ findByQueryAndCheck(query, devices.size());
- deviceTypeFilter.setEntityNameFilter("%test%");
-
- result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
-
- deviceTypeFilter.setEntityNameFilter("test%");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ deviceTypeFilter.setEntityNameFilter(suffixes + "%");
+ findByQueryAndCheck(query, 0);
- deviceTypeFilter.setEntityNameFilter("test");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ deviceTypeFilter.setEntityNameFilter(suffixes);
+ findByQueryAndCheck(query, 0);
}
@Test
@@ -1154,19 +1042,13 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
EntityDataQuery query = new EntityDataQuery(deviceTypeFilter, pageLink, null, null, null);
-
- PageData result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
deviceTypeFilter.setEntityNameFilter("test%");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ findByQueryAndCheck(query, 0);
deviceTypeFilter.setEntityNameFilter("%test");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ findByQueryAndCheck(query, 0);
}
@Test
@@ -1190,24 +1072,16 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
EntityDataQuery query = new EntityDataQuery(deviceTypeFilter, pageLink, null, null, null);
-
- PageData result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
deviceTypeFilter.setDeviceNameFilter("Device%");
-
- result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
deviceTypeFilter.setDeviceNameFilter("%Device%");
-
- result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
deviceTypeFilter.setDeviceNameFilter("%Device");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ findByQueryAndCheck(query, 0);
}
@Test
@@ -1233,31 +1107,12 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataQuery query = new EntityDataQuery(singleEntityFilter, pageLink, entityFields, null, null);
- PageData result = searchEntities(query);
- assertEquals(1, result.getTotalElements());
+ PageData result = findByQueryAndCheck(query, 1);
String deviceName = result.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue();
assertThat(deviceName).isEqualTo(devices.get(0).getName());
}
- @Test
- public void testFindEntitiesByApiUsageStateFilter() {
- apiUsageStateService.createDefaultApiUsageState(tenantId, customerId);
- ApiUsageStateFilter apiUsageStateFilter = new ApiUsageStateFilter();
- apiUsageStateFilter.setCustomerId(customerId);
-
- List entityFields = List.of(
- new EntityKey(EntityKeyType.ENTITY_FIELD, "name")
- );
-
- EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
- EntityDataQuery query = new EntityDataQuery(apiUsageStateFilter, pageLink, entityFields, null, null);
- PageData result = searchEntities(query);
- assertEquals(1, result.getTotalElements());
- String name = result.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue();
- assertThat(name).isEqualTo(TEST_CUSTOMER_NAME);
- }
-
@Test
public void testFindEntitiesByRelationEntityTypeFilter() {
Customer customer = new Customer();
@@ -1313,11 +1168,8 @@ public class EntityServiceTest extends AbstractServiceTest {
filter.setRootEntity(asset.getId());
EntityDataQuery query = new EntityDataQuery(filter, pageLink, Collections.emptyList(), Collections.emptyList(), keyFiltersEqualString);
- PageData relationsResult = entityService.findEntityDataByQuery(tenantId, customer.getId(), query);
- long relationsResultCnt = entityService.countEntitiesByQuery(tenantId, customer.getId(), query);
-
- Assert.assertEquals(relationsCnt, relationsResult.getData().size());
- Assert.assertEquals(relationsCnt, relationsResultCnt);
+ findByQueryAndCheck(customer.getId(), query, relationsCnt);
+ countByQueryAndCheck(customer.getId(), query, relationsCnt);
}
}
@@ -1342,24 +1194,16 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
EntityDataQuery query = new EntityDataQuery(deviceTypeFilter, pageLink, null, null, null);
-
- PageData result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
deviceTypeFilter.setDeviceNameFilter("%test%");
-
- result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
deviceTypeFilter.setDeviceNameFilter("test%");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ findByQueryAndCheck(query, 0);
deviceTypeFilter.setDeviceNameFilter("test");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ findByQueryAndCheck(query, 0);
}
@Test
@@ -1383,19 +1227,13 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
EntityDataQuery query = new EntityDataQuery(deviceTypeFilter, pageLink, null, null, null);
-
- PageData result = searchEntities(query);
- assertEquals(devices.size(), result.getTotalElements());
+ findByQueryAndCheck(query, devices.size());
deviceTypeFilter.setDeviceNameFilter("test%");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ findByQueryAndCheck(query, 0);
deviceTypeFilter.setDeviceNameFilter("%test");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ findByQueryAndCheck(query, 0);
}
@Test
@@ -1419,24 +1257,16 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
EntityDataQuery query = new EntityDataQuery(assetTypeFilter, pageLink, null, null, null);
-
- PageData result = searchEntities(query);
- assertEquals(assets.size(), result.getTotalElements());
+ findByQueryAndCheck(query, assets.size());
assetTypeFilter.setAssetNameFilter("Asset%");
-
- result = searchEntities(query);
- assertEquals(assets.size(), result.getTotalElements());
+ findByQueryAndCheck(query, assets.size());
assetTypeFilter.setAssetNameFilter("%Asset%");
-
- result = searchEntities(query);
- assertEquals(assets.size(), result.getTotalElements());
+ findByQueryAndCheck(query, assets.size());
assetTypeFilter.setAssetNameFilter("%Asset");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ findByQueryAndCheck(query, 0);
}
@Test
@@ -1460,24 +1290,16 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
EntityDataQuery query = new EntityDataQuery(assetTypeFilter, pageLink, null, null, null);
-
- PageData result = searchEntities(query);
- assertEquals(assets.size(), result.getTotalElements());
+ findByQueryAndCheck(query, assets.size());
assetTypeFilter.setAssetNameFilter("%test%");
-
- result = searchEntities(query);
- assertEquals(assets.size(), result.getTotalElements());
+ findByQueryAndCheck(query, assets.size());
assetTypeFilter.setAssetNameFilter("test%");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ findByQueryAndCheck(query, 0);
assetTypeFilter.setAssetNameFilter("test");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ findByQueryAndCheck(query, 0);
}
@Test
@@ -1489,6 +1311,7 @@ public class EntityServiceTest extends AbstractServiceTest {
asset.setTenantId(tenantId);
asset.setName("Asset test" + i);
asset.setType("default");
+ asset.setAssetProfileId(assetProfileService.findDefaultAssetProfile(tenantId).getId());
assets.add(asset);
}
@@ -1501,25 +1324,105 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
EntityDataQuery query = new EntityDataQuery(assetTypeFilter, pageLink, null, null, null);
-
- PageData result = searchEntities(query);
- assertEquals(assets.size(), result.getTotalElements());
+ findByQueryAndCheck(query, assets.size());
assetTypeFilter.setAssetNameFilter("test%");
-
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ findByQueryAndCheck(query, 0);
assetTypeFilter.setAssetNameFilter("%test");
+ findByQueryAndCheck(query, 0);
+ }
+
+ @Test
+ public void testFindEntitiesBySingleEntityFilter_customer() {
+ List customerDevices = new ArrayList<>();
+ List tenantDevices = new ArrayList<>();
+
+ for (int i = 0; i < 3; i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ device.setCustomerId(customerId);
+ device.setName("Device test" + i);
+ device.setType("default");
+ Device saved = deviceService.saveDevice(device);
+ customerDevices.add(saved);
+ }
+
+ for (int i = 0; i < 3; i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ device.setName("Tenant test device" + i);
+ device.setType("default");
+ tenantDevices.add(deviceService.saveDevice(device));
+ }
+
+ SingleEntityFilter singleEntityFilter = new SingleEntityFilter();
+ singleEntityFilter.setSingleEntity(customerDevices.get(0).getId());
+ List entityFields = List.of(
+ new EntityKey(EntityKeyType.ENTITY_FIELD, "name")
+ );
+ EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
+ EntityDataQuery query = new EntityDataQuery(singleEntityFilter, pageLink, entityFields, null, null);
- result = searchEntities(query);
- assertEquals(0, result.getTotalElements());
+ PageData result = findByQueryAndCheck(query, 1);
+ String deviceName = result.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue();
+ assertThat(deviceName).isEqualTo(customerDevices.get(0).getName());
+
+ // find by customer user with generic permission
+ PageData customerResults = findByQueryAndCheck(customerId, query, 1);
+
+ String cutomerDeviceName = customerResults.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue();
+ assertThat(cutomerDeviceName).isEqualTo(customerDevices.get(0).getName());
+
+ // try to find tenant device by customer user
+ SingleEntityFilter tenantDeviceFilter = new SingleEntityFilter();
+ tenantDeviceFilter.setSingleEntity(tenantDevices.get(0).getId());
+ EntityDataQuery customerQuery2 = new EntityDataQuery(tenantDeviceFilter, pageLink, entityFields, null, null);
+ findByQueryAndCheck(customerId, customerQuery2, 0);
}
- private PageData searchEntities(EntityDataQuery query) {
- return entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ private List getResultDeviceIds(PageData result) {
+ return result.getData().stream().map(entityData -> (DeviceId) entityData.getEntityId()).collect(Collectors.toList());
}
+ private Device createDevice(CustomerId customerId) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ device.setCustomerId(customerId);
+ device.setName("Device test " + RandomStringUtils.randomAlphabetic(5));
+ device.setType("default");
+ return device;
+ }
+
+ @Test
+ public void testFindEntitiesByApiUsageStateFilter() {
+ ApiUsageStateFilter apiUsageStateFilter = new ApiUsageStateFilter();
+
+ List entityFields = List.of(
+ new EntityKey(EntityKeyType.ENTITY_FIELD, "name")
+ );
+
+ EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
+ EntityDataQuery query = new EntityDataQuery(apiUsageStateFilter, pageLink, entityFields, null, null);
+ PageData result = findByQueryAndCheck(query, 1);
+ String name = result.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue();
+ assertThat(name).isEqualTo(TEST_TENANT_NAME);
+
+ // find by customer user with generic permissions
+ apiUsageStateService.createDefaultApiUsageState(tenantId, customerId);
+ PageData customerResult = findByQueryAndCheck(customerId, query, 1);
+
+ String customerResultName = customerResult.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue();
+ assertThat(customerResultName).isEqualTo(TEST_CUSTOMER_NAME);
+
+ // find by tenant user with customerId filter
+ apiUsageStateFilter.setCustomerId(customerId);
+ PageData tenantResult = findByQueryAndCheck(query, 1);
+ String tenantResultName = tenantResult.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue();
+ assertThat(tenantResultName).isEqualTo(TEST_CUSTOMER_NAME);
+ }
+
+
private EntityDataQuery createDeviceSearchQuery(String deviceField, StringOperation operation, String searchQuery) {
DeviceTypeFilter deviceTypeFilter = new DeviceTypeFilter();
deviceTypeFilter.setDeviceTypes(List.of("default"));
@@ -1598,45 +1501,15 @@ public class EntityServiceTest extends AbstractServiceTest {
for (EntityKeyType currentAttributeKeyType : attributesEntityTypes) {
List latestValues = Collections.singletonList(new EntityKey(currentAttributeKeyType, "temperature"));
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
- PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- List loadedEntities = new ArrayList<>(data.getData());
- while (data.hasNext()) {
- query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities.addAll(data.getData());
- }
- Assert.assertEquals(67, loadedEntities.size());
- List loadedTemperatures = new ArrayList<>();
- for (Device device : devices) {
- loadedTemperatures.add(loadedEntities.stream().filter(entityData -> entityData.getEntityId().equals(device.getId())).findFirst().orElse(null)
- .getLatest().get(currentAttributeKeyType).get("temperature").getValue());
- }
- List deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
- Assert.assertEquals(deviceTemperatures, loadedTemperatures);
+ List deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).toList();
+ findByQueryAndCheckTelemetry(query, currentAttributeKeyType, "temperature", deviceTemperatures);
pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
KeyFilter highTemperatureFilter = createNumericKeyFilter("temperature", currentAttributeKeyType, NumericFilterPredicate.NumericOperation.GREATER, 45);
List keyFiltersHighTemperature = Collections.singletonList(highTemperatureFilter);
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersHighTemperature);
-
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
-
- loadedEntities = new ArrayList<>(data.getData());
-
- while (data.hasNext()) {
- query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities.addAll(data.getData());
- }
- Assert.assertEquals(highTemperatures.size(), loadedEntities.size());
-
- List loadedHighTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(currentAttributeKeyType).get("temperature").getValue()).collect(Collectors.toList());
- List deviceHighTemperatures = highTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
-
- Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures);
-
+ findByQueryAndCheckTelemetry(query, currentAttributeKeyType, "temperature", highTemperatures.stream().map(Object::toString).toList());
}
deviceService.deleteDevicesByTenantId(tenantId);
}
@@ -1716,89 +1589,48 @@ public class EntityServiceTest extends AbstractServiceTest {
List keyFiltersNotEqualTemperature = Collections.singletonList(notEqualTemperatureFilter);
//Greater Operation
+ List deviceTemperatures = greaterTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
EntityDataPageLink pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersGreaterTemperature);
- PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- List loadedEntities = getLoadedEntities(data, query);
- Assert.assertEquals(greaterTemperatures.size(), loadedEntities.size());
- List loadedTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
- List deviceTemperatures = greaterTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
-
- Assert.assertEquals(deviceTemperatures, loadedTemperatures);
+ findByQueryAndCheckTelemetry(query, EntityKeyType.CLIENT_ATTRIBUTE, "temperature", deviceTemperatures);
//Greater or equal Operation
+ deviceTemperatures = greaterOrEqualTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersGreaterOrEqualTemperature);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities = getLoadedEntities(data, query);
- Assert.assertEquals(greaterOrEqualTemperatures.size(), loadedEntities.size());
-
- loadedTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
- deviceTemperatures = greaterOrEqualTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
- Assert.assertEquals(deviceTemperatures, loadedTemperatures);
+ findByQueryAndCheckTelemetry(query, EntityKeyType.CLIENT_ATTRIBUTE, "temperature", deviceTemperatures);
//Less Operation
+ deviceTemperatures = lessTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersLessTemperature);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities = getLoadedEntities(data, query);
- Assert.assertEquals(lessTemperatures.size(), loadedEntities.size());
-
- loadedTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
- deviceTemperatures = lessTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
-
- Assert.assertEquals(deviceTemperatures, loadedTemperatures);
+ findByQueryAndCheckTelemetry(query, EntityKeyType.CLIENT_ATTRIBUTE, "temperature", deviceTemperatures);
//Less or equal Operation
+ deviceTemperatures = lessOrEqualTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersLessOrEqualTemperature);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities = getLoadedEntities(data, query);
- Assert.assertEquals(lessOrEqualTemperatures.size(), loadedEntities.size());
-
- loadedTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
- deviceTemperatures = lessOrEqualTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
-
- Assert.assertEquals(deviceTemperatures, loadedTemperatures);
+ findByQueryAndCheckTelemetry(query, EntityKeyType.CLIENT_ATTRIBUTE, "temperature", deviceTemperatures);
//Equal Operation
+ deviceTemperatures = equalTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersEqualTemperature);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities = getLoadedEntities(data, query);
- Assert.assertEquals(equalTemperatures.size(), loadedEntities.size());
-
- loadedTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
- deviceTemperatures = equalTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
-
- Assert.assertEquals(deviceTemperatures, loadedTemperatures);
+ findByQueryAndCheckTelemetry(query, EntityKeyType.CLIENT_ATTRIBUTE, "temperature", deviceTemperatures);
//Not equal Operation
+ deviceTemperatures = notEqualTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersNotEqualTemperature);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities = getLoadedEntities(data, query);
- Assert.assertEquals(notEqualTemperatures.size(), loadedEntities.size());
-
- loadedTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
- deviceTemperatures = notEqualTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
-
- Assert.assertEquals(deviceTemperatures, loadedTemperatures);
-
+ findByQueryAndCheckTelemetry(query, EntityKeyType.CLIENT_ATTRIBUTE, "temperature", deviceTemperatures);
deviceService.deleteDevicesByTenantId(tenantId);
}
@@ -1843,24 +1675,10 @@ public class EntityServiceTest extends AbstractServiceTest {
List entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
List latestValues = Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
- EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
- PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
-
- List loadedEntities = new ArrayList<>(data.getData());
- while (data.hasNext()) {
- query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities.addAll(data.getData());
- }
- Assert.assertEquals(67, loadedEntities.size());
- List loadedTemperatures = new ArrayList<>();
- for (Device device : devices) {
- loadedTemperatures.add(loadedEntities.stream().filter(entityData -> entityData.getEntityId().equals(device.getId())).findFirst().orElse(null)
- .getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue());
- }
List deviceTemperatures = temperatures.stream().map(aDouble -> Double.toString(aDouble)).collect(Collectors.toList());
- Assert.assertEquals(deviceTemperatures, loadedTemperatures);
+ EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
+ findByQueryAndCheckTelemetry(query, EntityKeyType.TIME_SERIES, "temperature", deviceTemperatures);
pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
KeyFilter highTemperatureFilter = new KeyFilter();
@@ -1871,23 +1689,10 @@ public class EntityServiceTest extends AbstractServiceTest {
highTemperatureFilter.setPredicate(predicate);
List keyFilters = Collections.singletonList(highTemperatureFilter);
- query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
-
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
-
- loadedEntities = new ArrayList<>(data.getData());
- while (data.hasNext()) {
- query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
- loadedEntities.addAll(data.getData());
- }
- Assert.assertEquals(highTemperatures.size(), loadedEntities.size());
-
- List loadedHighTemperatures = loadedEntities.stream().map(entityData ->
- entityData.getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()).collect(Collectors.toList());
List deviceHighTemperatures = highTemperatures.stream().map(aDouble -> Double.toString(aDouble)).collect(Collectors.toList());
- Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures);
+ query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
+ findByQueryAndCheckTelemetry(query, EntityKeyType.TIME_SERIES, "temperature", deviceHighTemperatures);
deviceService.deleteDevicesByTenantId(tenantId);
}
@@ -1995,7 +1800,7 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataPageLink pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersEqualString);
- PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ PageData data = findByQueryAndCheck(query, equalStrings.size());
List loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(equalStrings.size(), loadedEntities.size());
@@ -2008,7 +1813,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersNotEqualString);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, notEqualStrings.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(notEqualStrings.size(), loadedEntities.size());
@@ -2021,7 +1826,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersStartsWithString);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, startsWithStrings.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(startsWithStrings.size(), loadedEntities.size());
@@ -2034,7 +1839,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersEndsWithString);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, endsWithStrings.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(endsWithStrings.size(), loadedEntities.size());
@@ -2047,7 +1852,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersContainsString);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, containsStrings.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(containsStrings.size(), loadedEntities.size());
@@ -2060,7 +1865,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFiltersNotContainsString);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, notContainsStrings.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(notContainsStrings.size(), loadedEntities.size());
@@ -2073,7 +1878,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, deviceTypeFilters);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, devices.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(devices.size(), loadedEntities.size());
@@ -2118,7 +1923,7 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataPageLink pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, null, keyFiltersEqualString);
- PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ PageData data = findByQueryAndCheck(query, devices.size());
List loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(devices.size(), loadedEntities.size());
@@ -2133,7 +1938,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, null, keyFiltersNotEqualString);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, devices.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(devices.size(), loadedEntities.size());
@@ -2146,7 +1951,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, null, keyFiltersStartsWithString);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, devices.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(devices.size(), loadedEntities.size());
@@ -2159,7 +1964,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, null, keyFiltersEndsWithString);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, devices.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(devices.size(), loadedEntities.size());
@@ -2172,7 +1977,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, null, keyFiltersContainsString);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, devices.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(devices.size(), loadedEntities.size());
@@ -2185,7 +1990,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, null, keyFiltersNotContainsString);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, devices.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(devices.size(), loadedEntities.size());
@@ -2233,7 +2038,7 @@ public class EntityServiceTest extends AbstractServiceTest {
EntityDataPageLink pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, null, deviceTypeFilters);
- PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ PageData data = findByQueryAndCheck(query, devices.size());
List loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(devices.size(), loadedEntities.size());
@@ -2241,7 +2046,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, null, createdTimeFilters);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, devices.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(devices.size(), loadedEntities.size());
@@ -2249,7 +2054,7 @@ public class EntityServiceTest extends AbstractServiceTest {
pageLink = new EntityDataPageLink(100, 0, null, null);
query = new EntityDataQuery(filter, pageLink, entityFields, null, nameFilters);
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQueryAndCheck(query, devices.size());
loadedEntities = getLoadedEntities(data, query);
Assert.assertEquals(devices.size(), loadedEntities.size());
@@ -2297,12 +2102,12 @@ public class EntityServiceTest extends AbstractServiceTest {
// query with textSearch - optimization is not performing
EntityDataPageLink originalPageLink = new EntityDataPageLink(pageSize, 0, "Device", sortOrder);
EntityDataQuery originalQuery = new EntityDataQuery(filter, originalPageLink, entityFields, null, deviceTypeFilters);
- PageData originalData = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), originalQuery);
+ PageData originalData = findByQueryAndCheck(originalQuery, expectedDevicesSize);
// query without textSearch - optimization is performing
EntityDataPageLink optimizedPageLink = new EntityDataPageLink(pageSize, 0, null, sortOrder);
EntityDataQuery optimizedQuery = new EntityDataQuery(filter, optimizedPageLink, entityFields, null, deviceTypeFilters);
- PageData optimizedData = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), optimizedQuery);
+ PageData optimizedData = findByQueryAndCheck(optimizedQuery, expectedDevicesSize);
List loadedEntities = getLoadedEntities(optimizedData, optimizedQuery);
Assert.assertEquals(expectedDevicesSize, loadedEntities.size());
loadedEntities = getLoadedEntities(originalData, originalQuery);
@@ -2326,12 +2131,12 @@ public class EntityServiceTest extends AbstractServiceTest {
// query with textSearch - optimization is not performing
originalPageLink = new EntityDataPageLink(pageSize, 0, "Device", sortOrder);
originalQuery = new EntityDataQuery(filter, originalPageLink, entityFields, null, attributeFilters);
- originalData = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), originalQuery);
+ originalData = findByQuery(originalQuery);
// query without textSearch - optimization is performing
optimizedPageLink = new EntityDataPageLink(pageSize, 0, null, sortOrder);
optimizedQuery = new EntityDataQuery(filter, optimizedPageLink, entityFields, null, attributeFilters);
- optimizedData = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), optimizedQuery);
+ optimizedData = findByQuery(optimizedQuery);
loadedEntities = getLoadedEntities(optimizedData, optimizedQuery);
Assert.assertEquals(expectedDevicesSize, loadedEntities.size());
loadedEntities = getLoadedEntities(originalData, originalQuery);
@@ -2355,12 +2160,12 @@ public class EntityServiceTest extends AbstractServiceTest {
// query with textSearch - optimization is not performing
originalPageLink = new EntityDataPageLink(pageSize, 0, "Device", sortOrder);
originalQuery = new EntityDataQuery(filter, originalPageLink, entityFields, null, nameFilters);
- originalData = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), originalQuery);
+ originalData = findByQuery(originalQuery);
// query without textSearch - optimization is performing
optimizedPageLink = new EntityDataPageLink(pageSize, 0, null, sortOrder);
optimizedQuery = new EntityDataQuery(filter, optimizedPageLink, entityFields, null, nameFilters);
- optimizedData = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), optimizedQuery);
+ optimizedData = findByQuery(optimizedQuery);
loadedEntities = getLoadedEntities(optimizedData, optimizedQuery);
Assert.assertEquals(expectedDevicesSize, loadedEntities.size());
loadedEntities = getLoadedEntities(originalData, originalQuery);
@@ -2388,10 +2193,9 @@ public class EntityServiceTest extends AbstractServiceTest {
private List getLoadedEntities(PageData data, EntityDataQuery query) {
List loadedEntities = new ArrayList<>(data.getData());
-
while (data.hasNext()) {
query = query.next();
- data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
+ data = findByQuery(query);
loadedEntities.addAll(data.getData());
}
return loadedEntities;
@@ -2422,13 +2226,13 @@ public class EntityServiceTest extends AbstractServiceTest {
private ListenableFuture> saveLongAttribute(EntityId entityId, String key, long value, AttributeScope scope) {
KvEntry attrValue = new LongDataEntry(key, value);
AttributeKvEntry attr = new BaseAttributeKvEntry(attrValue, 42L);
- return attributesService.save(SYSTEM_TENANT_ID, entityId, scope, Collections.singletonList(attr));
+ return attributesService.save(tenantId, entityId, scope, Collections.singletonList(attr));
}
private ListenableFuture> saveStringAttribute(EntityId entityId, String key, String value, AttributeScope scope) {
KvEntry attrValue = new StringDataEntry(key, value);
AttributeKvEntry attr = new BaseAttributeKvEntry(attrValue, 42L);
- return attributesService.save(SYSTEM_TENANT_ID, entityId, scope, Collections.singletonList(attr));
+ return attributesService.save(tenantId, entityId, scope, Collections.singletonList(attr));
}
private ListenableFuture saveLongTimeseries(EntityId entityId, String key, Double value) {
@@ -2437,10 +2241,10 @@ public class EntityServiceTest extends AbstractServiceTest {
tsKv.setDoubleValue(value);
KvEntry telemetryValue = new DoubleDataEntry(key, value);
BasicTsKvEntry timeseries = new BasicTsKvEntry(42L, telemetryValue);
- return timeseriesService.save(SYSTEM_TENANT_ID, entityId, timeseries);
+ return timeseriesService.save(tenantId, entityId, timeseries);
}
- private void createMultiRootHierarchy(List buildings, List apartments,
+ protected void createMultiRootHierarchy(List buildings, List apartments,
Map> entityNameByTypeMap,
Map childParentRelationMap) throws InterruptedException {
for (int k = 0; k < 3; k++) {
@@ -2511,4 +2315,93 @@ public class EntityServiceTest extends AbstractServiceTest {
}
}
}
+
+ @Test
+ public void testFindEntitiesWithEntityViewFilter() {
+ EntityView entityView = new EntityView();
+ entityView.setTenantId(tenantId);
+ entityView.setCustomerId(customerId);
+ entityView.setName("test");
+ entityView.setType("default");
+ entityView.setEntityId(new DeviceId(UUID.randomUUID()));
+ entityView.setKeys(new TelemetryEntityView(List.of("test"), null));
+ entityView.setStartTimeMs(124);
+ entityView.setEndTimeMs(256);
+ entityView.setExternalId(new EntityViewId(UUID.randomUUID()));
+ entityView.setAdditionalInfo(JacksonUtil.newObjectNode().put("test", "test"));
+ entityView = entityViewService.saveEntityView(entityView);
+
+ EntityViewTypeFilter entityViewTypeFilter = new EntityViewTypeFilter();
+ entityViewTypeFilter.setEntityViewNameFilter("test");
+ entityViewTypeFilter.setEntityViewTypes(List.of("non-existing", "default"));
+ EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, null);
+ List entityFields = List.of(
+ new EntityKey(EntityKeyType.ENTITY_FIELD, "name")
+ );
+ EntityDataQuery query = new EntityDataQuery(entityViewTypeFilter, pageLink, entityFields, Collections.emptyList(), null);
+
+ PageData relationsResult = findByQueryAndCheck(new CustomerId(EntityId.NULL_UUID), query, 1);
+ assertThat(relationsResult.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue()).isEqualTo(entityView.getName());
+
+ // find with non existing name
+ entityViewTypeFilter.setEntityViewNameFilter("non-existing");
+ findByQueryAndCheck(new CustomerId(EntityId.NULL_UUID), query, 0);
+
+ // find with non existing type
+ entityViewTypeFilter.setEntityViewNameFilter(null);
+ entityViewTypeFilter.setEntityViewTypes(Collections.singletonList("non-existing"));
+
+ findByQueryAndCheck(new CustomerId(EntityId.NULL_UUID), query, 0);
+ }
+
+ protected PageData findByQuery(EntityDataQuery query) {
+ return findByQuery(new CustomerId(CustomerId.NULL_UUID), query);
+ }
+
+ protected PageData findByQuery(CustomerId customerId, EntityDataQuery query) {
+ return entityService.findEntityDataByQuery(tenantId, customerId, query);
+ }
+
+ protected PageData findByQueryAndCheck(EntityDataQuery query, long expectedResultSize) {
+ return findByQueryAndCheck(new CustomerId(CustomerId.NULL_UUID), query, expectedResultSize);
+ }
+
+ protected PageData findByQueryAndCheck(CustomerId customerId, EntityDataQuery query, long expectedResultSize) {
+ PageData result = entityService.findEntityDataByQuery(tenantId, customerId, query);
+ assertThat(result.getTotalElements()).isEqualTo(expectedResultSize);
+ return result;
+ }
+
+ protected List findByQueryAndCheckTelemetry(EntityDataQuery query, EntityKeyType entityKeyType, String key, List expectedTelemetry) {
+ List loadedEntities = findEntitiesTelemetry(query, entityKeyType, key, expectedTelemetry);
+ List entitiesTelemetry = loadedEntities.stream().map(entityData -> entityData.getLatest().get(entityKeyType).get(key).getValue()).toList();
+ assertThat(entitiesTelemetry).containsExactlyInAnyOrderElementsOf(expectedTelemetry);
+ return loadedEntities;
+ }
+
+ protected List findEntitiesTelemetry(EntityDataQuery query, EntityKeyType entityKeyType, String key, List expectedTelemetries) {
+ PageData data = findByQueryAndCheck(query, expectedTelemetries.size());
+ List loadedEntities = new ArrayList<>(data.getData());
+ while (data.hasNext()) {
+ query = query.next();
+ data = findByQuery(query);
+ loadedEntities.addAll(data.getData());
+ }
+ return loadedEntities;
+ }
+
+ protected long countByQuery(CustomerId customerId, EntityCountQuery query) {
+ return entityService.countEntitiesByQuery(tenantId, customerId, query);
+ }
+
+ protected long countByQueryAndCheck(EntityCountQuery countQuery, int expectedResult) {
+ return countByQueryAndCheck(new CustomerId(CustomerId.NULL_UUID), countQuery, expectedResult);
+ }
+
+ protected long countByQueryAndCheck(CustomerId customerId, EntityCountQuery query, int expectedResult) {
+ long result = countByQuery(customerId, query);
+ assertThat(result).isEqualTo(expectedResult);
+ return result;
+ }
+
}
diff --git a/application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManagerTest.java b/application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManagerTest.java
index 8141b924f6..314c76b058 100644
--- a/application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManagerTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManagerTest.java
@@ -626,8 +626,7 @@ public class TbRuleEngineQueueConsumerManagerTest {
.until(() -> consumer.subscribed && consumer.getPartitions().equals(expectedPartitions) && consumer.pollingStarted);
verify(consumer, times(1)).subscribe(any());
verify(consumer).subscribe(eq(expectedPartitions));
- verify(consumer).doSubscribe(argThat(topics -> topics.containsAll(expectedPartitions.stream()
- .map(TopicPartitionInfo::getFullTopicName).collect(Collectors.toList()))));
+ verify(consumer).doSubscribe(argThat(topics -> topics.containsAll(expectedPartitions)));
verify(consumer, atLeastOnce()).poll(eq((long) queue.getPollInterval()));
verify(consumer, atLeastOnce()).doPoll(eq((long) queue.getPollInterval()));
verify(consumer, never()).unsubscribe();
@@ -743,9 +742,11 @@ public class TbRuleEngineQueueConsumerManagerTest {
}
@Override
- protected void doSubscribe(List topicNames) {
- log.debug("doSubscribe({})", topicNames);
- this.topics = topicNames;
+ protected void doSubscribe(Set partitions) {
+ this.topics = partitions.stream()
+ .map(TopicPartitionInfo::getFullTopicName)
+ .collect(Collectors.toList());
+ log.debug("doSubscribe({})", topics);
subscribed = true;
}
diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java
index 26c978bbd0..de84237016 100644
--- a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java
@@ -211,7 +211,7 @@ public class DefaultDeviceStateServiceTest {
// THEN
then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
- request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) &&
request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(LAST_CONNECT_TIME) &&
request.getEntries().get(0).getValue().equals(lastConnectTime)
@@ -298,7 +298,7 @@ public class DefaultDeviceStateServiceTest {
// THEN
then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
- request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) &&
request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(LAST_DISCONNECT_TIME) &&
request.getEntries().get(0).getValue().equals(lastDisconnectTime)
@@ -421,13 +421,13 @@ public class DefaultDeviceStateServiceTest {
// THEN
then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
- request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) &&
request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) &&
request.getEntries().get(0).getValue().equals(lastInactivityTime)
));
then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
- request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) &&
request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(false)
@@ -465,12 +465,12 @@ public class DefaultDeviceStateServiceTest {
// THEN
then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
- request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) &&
request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME)
));
then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
- request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) &&
request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(false)
@@ -556,7 +556,7 @@ public class DefaultDeviceStateServiceTest {
.thenReturn(new PageData<>(List.of(deviceIdInfo), 0, 1, false));
PartitionChangeEvent event = new PartitionChangeEvent(this, ServiceType.TB_CORE, Map.of(
new QueueKey(ServiceType.TB_CORE), Collections.singleton(tpi)
- ));
+ ), Collections.emptyMap());
service.onApplicationEvent(event);
Thread.sleep(100);
}
@@ -1002,7 +1002,7 @@ public class DefaultDeviceStateServiceTest {
assertThat(actualNotification.isActive()).isFalse();
then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
- request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) &&
request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) &&
request.getEntries().get(0).getValue().equals(expectedLastInactivityAlarmTime)
@@ -1170,7 +1170,7 @@ public class DefaultDeviceStateServiceTest {
assertThat(attributeRequestCaptor.getAllValues()).hasSize(2)
.anySatisfy(request -> {
- assertThat(request.getTenantId()).isEqualTo(TenantId.SYS_TENANT_ID);
+ assertThat(request.getTenantId()).isEqualTo(tenantId);
assertThat(request.getEntityId()).isEqualTo(deviceId);
assertThat(request.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE);
assertThat(request.getEntries()).singleElement().satisfies(attributeKvEntry -> {
@@ -1179,7 +1179,7 @@ public class DefaultDeviceStateServiceTest {
});
})
.anySatisfy(request -> {
- assertThat(request.getTenantId()).isEqualTo(TenantId.SYS_TENANT_ID);
+ assertThat(request.getTenantId()).isEqualTo(tenantId);
assertThat(request.getEntityId()).isEqualTo(deviceId);
assertThat(request.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE);
assertThat(request.getEntries()).singleElement().satisfies(attributeKvEntry -> {
diff --git a/application/src/test/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionServiceTest.java b/application/src/test/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionServiceTest.java
index b8bfa13e50..907e171985 100644
--- a/application/src/test/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionServiceTest.java
@@ -159,7 +159,7 @@ class DefaultTelemetrySubscriptionServiceTest {
}).when(calculatedFieldQueueService).pushRequestToQueue(any(TimeseriesSaveRequest.class), any(), any());
// send partition change event so currentPartitions set is populated
- telemetryService.onTbApplicationEvent(new PartitionChangeEvent(this, ServiceType.TB_CORE, Map.of(new QueueKey(ServiceType.TB_CORE), Set.of(tpi))));
+ telemetryService.onTbApplicationEvent(new PartitionChangeEvent(this, ServiceType.TB_CORE, Map.of(new QueueKey(ServiceType.TB_CORE), Set.of(tpi)), Collections.emptyMap()));
}
@AfterEach
diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties
index 3022650d90..b8bdcf67e6 100644
--- a/application/src/test/resources/application-test.properties
+++ b/application/src/test/resources/application-test.properties
@@ -58,3 +58,6 @@ sql.ttl.edge_events.edge_event_ttl=2592000
server.log_controller_error_stack_trace=false
transport.gateway.dashboard.sync.enabled=false
+
+queue.edqs.sync.enabled=false
+queue.edqs.api.supported=false
diff --git a/application/src/test/resources/update/330/device_profile_001_out.json b/application/src/test/resources/update/330/device_profile_001_out.json
index 9a349c6638..29e2241ee9 100644
--- a/application/src/test/resources/update/330/device_profile_001_out.json
+++ b/application/src/test/resources/update/330/device_profile_001_out.json
@@ -64,7 +64,8 @@
"dynamicValue": {
"sourceType": null,
"sourceAttribute": null,
- "inherit": false
+ "inherit": false,
+ "resolvedValue" : null
}
}
}
@@ -103,7 +104,8 @@
"dynamicValue": {
"sourceType": null,
"sourceAttribute": null,
- "inherit": false
+ "inherit": false,
+ "resolvedValue" : null
}
}
}
diff --git a/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java
index abde4cce97..609a61d575 100644
--- a/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java
+++ b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java
@@ -26,6 +26,8 @@ public interface TbQueueRequestTemplate send(Request request, long timeoutNs);
+ ListenableFuture send(Request request, Integer partition);
+
void stop();
void setMessagesStats(MessagesStats messagesStats);
diff --git a/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueResponseTemplate.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueResponseTemplate.java
index e7e5361381..918d656af0 100644
--- a/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueResponseTemplate.java
+++ b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueResponseTemplate.java
@@ -15,9 +15,17 @@
*/
package org.thingsboard.server.queue;
+import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
+
+import java.util.Set;
+
public interface TbQueueResponseTemplate {
- void init(TbQueueHandler handler);
+ void subscribe();
+
+ void subscribe(Set partitions);
+
+ void launch(TbQueueHandler handler);
void stop();
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ObjectType.java b/common/data/src/main/java/org/thingsboard/server/common/data/ObjectType.java
new file mode 100644
index 0000000000..51c631fe4f
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/ObjectType.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright © 2016-2025 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.common.data;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+public enum ObjectType {
+ TENANT,
+ TENANT_PROFILE,
+ CUSTOMER,
+ QUEUE,
+ RPC,
+ RULE_CHAIN,
+ OTA_PACKAGE,
+ RESOURCE,
+ EVENT,
+ RULE_NODE,
+ USER,
+ EDGE,
+ WIDGETS_BUNDLE,
+ WIDGET_TYPE,
+ DASHBOARD,
+ DEVICE_PROFILE,
+ DEVICE,
+ DEVICE_CREDENTIALS,
+ ASSET_PROFILE,
+ ASSET,
+ ENTITY_VIEW,
+ ALARM,
+ ENTITY_ALARM,
+ OAUTH2_CLIENT,
+ OAUTH2_DOMAIN,
+ OAUTH2_MOBILE,
+ USER_SETTINGS,
+ NOTIFICATION_TARGET,
+ NOTIFICATION_TEMPLATE,
+ NOTIFICATION_RULE,
+ ALARM_COMMENT,
+ API_USAGE_STATE,
+ QUEUE_STATS,
+
+ AUDIT_LOG,
+ RELATION,
+ ATTRIBUTE_KV,
+ LATEST_TS_KV;
+
+ public static final Set edqsTenantTypes = EnumSet.of(
+ TENANT, CUSTOMER, DEVICE_PROFILE, DEVICE, ASSET_PROFILE, ASSET, EDGE, ENTITY_VIEW, USER, DASHBOARD,
+ RULE_CHAIN, WIDGET_TYPE, WIDGETS_BUNDLE, API_USAGE_STATE, QUEUE_STATS
+ );
+ public static final Set edqsTypes = EnumSet.copyOf(edqsTenantTypes);
+ public static final Set edqsSystemTypes = EnumSet.of(TENANT, USER, DASHBOARD,
+ API_USAGE_STATE, ATTRIBUTE_KV, LATEST_TS_KV);
+ public static final Set unversionedTypes = EnumSet.of(
+ QUEUE_STATS // created once, never updated
+ );
+
+ static {
+ edqsTypes.addAll(List.of(RELATION, ATTRIBUTE_KV, LATEST_TS_KV));
+ }
+
+ public EntityType toEntityType() {
+ return EntityType.valueOf(name());
+ }
+
+ public static ObjectType fromEntityType(EntityType entityType) {
+ try {
+ return ObjectType.valueOf(entityType.name());
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/AttributeKv.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/AttributeKv.java
new file mode 100644
index 0000000000..c162365257
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/AttributeKv.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.thingsboard.server.common.data.AttributeScope;
+import org.thingsboard.server.common.data.ObjectType;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.kv.AttributeKvEntry;
+import org.thingsboard.server.common.data.kv.KvEntry;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class AttributeKv implements EdqsObject {
+
+ private EntityId entityId;
+ private AttributeScope scope;
+ private String key;
+ private Long version;
+
+ private DataPoint dataPoint; // optional (on deletion)
+
+ private Long lastUpdateTs; // only for serialization
+ private KvEntry value; // only for serialization
+
+ public AttributeKv(EntityId entityId, AttributeScope scope, AttributeKvEntry attributeKvEntry, long version) {
+ this.entityId = entityId;
+ this.scope = scope;
+ this.key = attributeKvEntry.getKey();
+ this.version = version;
+ this.lastUpdateTs = attributeKvEntry.getLastUpdateTs();
+ this.value = attributeKvEntry;
+ }
+
+ public AttributeKv(EntityId entityId, AttributeScope scope, String key, long version) {
+ this.entityId = entityId;
+ this.scope = scope;
+ this.key = key;
+ this.version = version;
+ }
+
+ @Override
+ public String key() {
+ return "a_" + entityId + "_" + scope + "_" + key;
+ }
+
+ @Override
+ public Long version() {
+ return version;
+ }
+
+ @Override
+ public ObjectType type() {
+ return ObjectType.ATTRIBUTE_KV;
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/DataPoint.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/DataPoint.java
new file mode 100644
index 0000000000..a6f30c8004
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/DataPoint.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs;
+
+import org.thingsboard.server.common.data.kv.DataType;
+
+public interface DataPoint {
+
+ String NOT_SUPPORTED = "Not supported!";
+
+ long getTs();
+
+ DataType getType();
+
+ String getStr();
+
+ long getLong();
+
+ double getDouble();
+
+ boolean getBool();
+
+ String getJson();
+
+ String valueToString();
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/EdqsEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/EdqsEvent.java
new file mode 100644
index 0000000000..50c8d268de
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/EdqsEvent.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import org.thingsboard.server.common.data.ObjectType;
+import org.thingsboard.server.common.data.id.TenantId;
+
+@Data
+@AllArgsConstructor
+@Builder
+public class EdqsEvent {
+
+ private final TenantId tenantId;
+ private final ObjectType objectType;
+ private final EdqsEventType eventType;
+ private final EdqsObject object;
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/EdqsEventType.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/EdqsEventType.java
new file mode 100644
index 0000000000..df31048365
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/EdqsEventType.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs;
+
+public enum EdqsEventType {
+ UPDATED,
+ DELETED
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/EdqsObject.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/EdqsObject.java
new file mode 100644
index 0000000000..a74c90208a
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/EdqsObject.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.thingsboard.server.common.data.ObjectType;
+
+public interface EdqsObject {
+
+ @JsonIgnore
+ String key();
+
+ @JsonIgnore
+ Long version();
+
+ @JsonIgnore
+ ObjectType type();
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/EdqsSyncRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/EdqsSyncRequest.java
new file mode 100644
index 0000000000..12f7068c71
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/EdqsSyncRequest.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties
+public class EdqsSyncRequest {
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/Entity.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/Entity.java
new file mode 100644
index 0000000000..c22ef147e3
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/Entity.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.ObjectType;
+import org.thingsboard.server.common.data.edqs.fields.EntityFields;
+import org.thingsboard.server.common.data.edqs.fields.EntityIdFields;
+
+import java.util.UUID;
+
+@Data
+@NoArgsConstructor
+public class Entity implements EdqsObject {
+
+ private EntityType type;
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+ private EntityFields fields;
+
+ public Entity(EntityType type) {
+ this.type = type;
+ }
+
+ public Entity(EntityType type, EntityFields fields) {
+ this.type = type;
+ this.fields = fields;
+ }
+
+ public Entity(EntityType entityType, UUID id, long version) {
+ this.type = entityType;
+ this.fields = new EntityIdFields(id, version);
+ }
+
+ @Override
+ public String key() {
+ return "e_" + fields.getId().toString();
+ }
+
+ @Override
+ public Long version() {
+ return fields.getVersion();
+ }
+
+ @Override
+ public ObjectType type() {
+ return ObjectType.fromEntityType(type);
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/LatestTsKv.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/LatestTsKv.java
new file mode 100644
index 0000000000..8bd69c41a4
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/LatestTsKv.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.thingsboard.server.common.data.ObjectType;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.kv.KvEntry;
+import org.thingsboard.server.common.data.kv.TsKvEntry;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class LatestTsKv implements EdqsObject {
+
+ private EntityId entityId;
+ private String key;
+ private Long version;
+
+ private DataPoint dataPoint; // optional (on deletion)
+
+ private Long ts; // only for serialization
+ private KvEntry value; // only for serialization
+
+ public LatestTsKv(EntityId entityId, TsKvEntry tsKvEntry, Long version) {
+ this.entityId = entityId;
+ this.key = tsKvEntry.getKey();
+ this.ts = tsKvEntry.getTs();
+ this.version = version != null ? version : 0L;
+ this.value = tsKvEntry;
+ }
+
+ public LatestTsKv(EntityId entityId, String key, Long version) {
+ this.entityId = entityId;
+ this.key = key;
+ this.version = version != null ? version : 0L;
+ }
+
+ public String key() {
+ return "l_" + entityId + "_" + key;
+ }
+
+ @Override
+ public Long version() {
+ return version;
+ }
+
+ @Override
+ public ObjectType type() {
+ return ObjectType.LATEST_TS_KV;
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/ToCoreEdqsMsg.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/ToCoreEdqsMsg.java
new file mode 100644
index 0000000000..78bebba20a
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/ToCoreEdqsMsg.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class ToCoreEdqsMsg {
+
+ private EdqsSyncRequest syncRequest;
+ private Boolean apiEnabled;
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/ToCoreEdqsRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/ToCoreEdqsRequest.java
new file mode 100644
index 0000000000..c4f262fbf0
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/ToCoreEdqsRequest.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class ToCoreEdqsRequest {
+
+ private EdqsSyncRequest syncRequest;
+ private Boolean apiEnabled;
+
+ @JsonIgnore
+ public ToCoreEdqsMsg toInternalMsg() {
+ return new ToCoreEdqsMsg(syncRequest, apiEnabled);
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/AbstractEntityFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/AbstractEntityFields.java
new file mode 100644
index 0000000000..4aad5eb4dd
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/AbstractEntityFields.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import lombok.Data;
+import lombok.experimental.SuperBuilder;
+import org.thingsboard.server.common.data.id.CustomerId;
+
+import java.util.UUID;
+
+@Data
+@SuperBuilder
+public class AbstractEntityFields implements EntityFields {
+
+ private UUID id;
+ private long createdTime;
+ private UUID tenantId;
+ private UUID customerId;
+ private String name;
+ private Long version;
+
+ public AbstractEntityFields(UUID id, long createdTime, UUID tenantId, UUID customerId, String name, Long version) {
+ this.id = id;
+ this.createdTime = createdTime;
+ this.tenantId = tenantId;
+ this.customerId = (customerId != null && customerId != CustomerId.NULL_UUID) ? customerId : null;
+ this.name = name;
+ this.version = version;
+ }
+
+ public AbstractEntityFields() {
+ }
+
+ public AbstractEntityFields(UUID id, long createdTime, UUID tenantId, String name, Long version) {
+ this(id, createdTime, tenantId, null, name, version);
+ }
+
+ public AbstractEntityFields(UUID id, long createdTime, UUID tenantId, UUID customerId, Long version) {
+ this(id, createdTime, tenantId, customerId, null, version);
+
+ }
+
+ public AbstractEntityFields(UUID id, long createdTime, String name, Long version) {
+ this(id, createdTime, null, name, version);
+ }
+
+
+ public AbstractEntityFields(UUID id, long createdTime, UUID tenantId) {
+ this(id, createdTime, tenantId, null, null, null);
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/ApiUsageStateFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/ApiUsageStateFields.java
new file mode 100644
index 0000000000..d10f375bc1
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/ApiUsageStateFields.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.thingsboard.server.common.data.ApiUsageStateValue;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.EntityIdFactory;
+
+import java.util.UUID;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@SuperBuilder
+public class ApiUsageStateFields extends AbstractEntityFields {
+
+ private EntityId entityId;
+ private ApiUsageStateValue transportState;
+ private ApiUsageStateValue dbStorageState;
+ private ApiUsageStateValue reExecState;
+ private ApiUsageStateValue jsExecState;
+ private ApiUsageStateValue tbelExecState;
+ private ApiUsageStateValue emailExecState;
+ private ApiUsageStateValue smsExecState;
+ private ApiUsageStateValue alarmExecState;
+
+ public ApiUsageStateFields(UUID id, long createdTime, UUID tenantId, UUID entityId, String entityType, ApiUsageStateValue transportState, ApiUsageStateValue dbStorageState,
+ ApiUsageStateValue reExecState, ApiUsageStateValue jsExecState, ApiUsageStateValue tbelExecState,
+ ApiUsageStateValue emailExecState, ApiUsageStateValue smsExecState, ApiUsageStateValue alarmExecState,
+ Long version) {
+ super(id, createdTime, tenantId, null, null, version);
+ this.entityId = (entityType != null && entityId != null) ? EntityIdFactory.getByTypeAndUuid(entityType, entityId) : null;
+ this.transportState = transportState;
+ this.dbStorageState = dbStorageState;
+ this.reExecState = reExecState;
+ this.jsExecState = jsExecState;
+ this.tbelExecState = tbelExecState;
+ this.emailExecState = emailExecState;
+ this.smsExecState = smsExecState;
+ this.alarmExecState = alarmExecState;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/AssetFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/AssetFields.java
new file mode 100644
index 0000000000..b4020e5788
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/AssetFields.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.common.data.edqs.fields.FieldsUtil.getText;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class AssetFields extends AbstractEntityFields implements ProfileAwareFields {
+
+ private String type;
+ private UUID assetProfileId;
+ private String label;
+ private String additionalInfo;
+
+ @JsonIgnore
+ @Override
+ public String getProfileName() {
+ return type;
+ }
+
+ @JsonIgnore
+ @Override
+ public UUID getProfileId() {
+ return assetProfileId;
+ }
+
+ public AssetFields(UUID id, long createdTime, UUID tenantId, UUID customerId, String name,
+ Long version, String type, String label, UUID assetProfileId, JsonNode additionalInfo) {
+ super(id, createdTime, tenantId, customerId, name, version);
+ this.type = type;
+ this.assetProfileId = assetProfileId;
+ this.label = label;
+ this.additionalInfo = getText(additionalInfo);
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/AssetProfileFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/AssetProfileFields.java
new file mode 100644
index 0000000000..56d8691cbe
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/AssetProfileFields.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class AssetProfileFields extends AbstractEntityFields {
+
+ private boolean isDefault;
+
+ public AssetProfileFields(UUID id, long createdTime, UUID tenantId, String name, Long version, boolean isDefault) {
+ super(id, createdTime, tenantId, null, name, version);
+ this.isDefault = isDefault;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/CustomerFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/CustomerFields.java
new file mode 100644
index 0000000000..4132e9e094
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/CustomerFields.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.common.data.edqs.fields.FieldsUtil.getText;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class CustomerFields extends AbstractEntityFields {
+
+ private String additionalInfo;
+ private String country;
+ private String state;
+ private String city;
+ private String address;
+ private String address2;
+ private String zip;
+ private String phone;
+ private String email;
+
+ public CustomerFields(UUID id, long createdTime, UUID tenantId, String name, Long version, JsonNode additionalInfo,
+ String country, String state, String city, String address, String address2, String zip, String phone, String email) {
+ super(id, createdTime, tenantId, name, version);
+ this.additionalInfo = getText(additionalInfo);
+ this.country = country;
+ this.state = state;
+ this.city = city;
+ this.address = address;
+ this.address2 = address2;
+ this.zip = zip;
+ this.phone = phone;
+ this.email = email;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/DashboardFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/DashboardFields.java
new file mode 100644
index 0000000000..0061b7e10b
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/DashboardFields.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class DashboardFields extends AbstractEntityFields {
+
+ private static ObjectMapper objectMapper = new ObjectMapper();
+ private List assignedCustomerIds;
+
+ public DashboardFields(UUID id, long createdTime, UUID tenantId, String assignedCustomers, String name, Long version) {
+ super(id, createdTime, tenantId, name, version);
+ this.assignedCustomerIds = getCustomerIds(assignedCustomers);
+ }
+
+ private static List getCustomerIds(String assignedCustomers) {
+ List ids = new ArrayList<>();
+ if (assignedCustomers == null || assignedCustomers.isEmpty()) {
+ return ids;
+ }
+ try {
+ JsonNode rootNode = objectMapper.readTree(assignedCustomers);
+ for (JsonNode node : rootNode) {
+ String idStr = node.path("customerId").path("id").asText();
+ if (!idStr.isEmpty()) {
+ ids.add(UUID.fromString(idStr));
+ }
+ }
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ return ids;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/DeviceFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/DeviceFields.java
new file mode 100644
index 0000000000..ea1ef383de
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/DeviceFields.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.common.data.edqs.fields.FieldsUtil.getText;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class DeviceFields extends AbstractEntityFields implements ProfileAwareFields {
+
+ private String label;
+ private String type;
+ private UUID deviceProfileId;
+ private String additionalInfo;
+
+ @JsonIgnore
+ @Override
+ public String getProfileName() {
+ return type;
+ }
+
+ @JsonIgnore
+ @Override
+ public UUID getProfileId() {
+ return deviceProfileId;
+ }
+
+ public DeviceFields(UUID id, long createdTime, UUID tenantId, UUID customerId, String name, Long version, String type,
+ String label, UUID deviceProfileId, JsonNode additionalInfo) {
+ super(id, createdTime, tenantId, customerId, name, version);
+ this.label = label;
+ this.type = type;
+ this.deviceProfileId = deviceProfileId;
+ this.additionalInfo = getText(additionalInfo);
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/DeviceProfileFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/DeviceProfileFields.java
new file mode 100644
index 0000000000..1e8d56ab14
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/DeviceProfileFields.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.thingsboard.server.common.data.DeviceProfileType;
+
+import java.util.UUID;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class DeviceProfileFields extends AbstractEntityFields {
+
+ private String type;
+ private boolean isDefault;
+
+ public DeviceProfileFields(UUID id, long createdTime, UUID tenantId, String name, Long version, DeviceProfileType type, boolean isDefault) {
+ super(id, createdTime, tenantId, null, name, version);
+ this.type = type.name();
+ this.isDefault = isDefault;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EdgeFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EdgeFields.java
new file mode 100644
index 0000000000..483a7f2680
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EdgeFields.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.common.data.edqs.fields.FieldsUtil.getText;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class EdgeFields extends AbstractEntityFields {
+
+ private String type;
+ private String label;
+ private String additionalInfo;
+
+ public EdgeFields(UUID id, long createdTime, UUID tenantId, UUID customerId, String name, Long version,
+ String type, String label, JsonNode additionalInfo) {
+ super(id, createdTime, tenantId, customerId, name, version);
+ this.type = type;
+ this.label = label;
+ this.additionalInfo = getText(additionalInfo);
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EntityFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EntityFields.java
new file mode 100644
index 0000000000..532c4a92ac
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EntityFields.java
@@ -0,0 +1,179 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.thingsboard.server.common.data.id.EntityId;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public interface EntityFields {
+
+ Logger log = LoggerFactory.getLogger(EntityFields.class);
+
+ default UUID getId() {
+ return null;
+ }
+
+ default UUID getTenantId() {
+ return null;
+ }
+
+ default UUID getCustomerId() {
+ return null;
+ }
+
+ default List getAssignedCustomerIds() {
+ return Collections.emptyList();
+ }
+
+ default long getCreatedTime() {
+ return 0;
+ }
+
+ default String getName() {
+ return "";
+ }
+
+ default String getType() {
+ return "";
+ }
+
+ default String getLabel() {
+ return "";
+ }
+
+ default String getAdditionalInfo() {
+ return "";
+ }
+
+ default String getEmail() {
+ return "";
+ }
+
+ default String getCountry() {
+ return "";
+ }
+
+ default String getState() {
+ return "";
+ }
+
+ default String getCity() {
+ return "";
+ }
+
+ default String getAddress() {
+ return "";
+ }
+
+ default String getAddress2() {
+ return "";
+ }
+
+ default String getZip() {
+ return "";
+ }
+
+ default String getPhone() {
+ return "";
+ }
+
+ default String getRegion() {
+ return "";
+ }
+
+ default String getFirstName() {
+ return "";
+ }
+
+ default String getLastName() {
+ return "";
+ }
+
+ default boolean isEdgeTemplate() {
+ return false;
+ }
+
+ default String getConfiguration() {
+ return "";
+ }
+
+ default String getSchedule() {
+ return "";
+ }
+
+ default EntityId getOriginatorId() {
+ return null;
+ }
+
+ default String getQueueName() {
+ return "";
+ }
+
+ default String getServiceId() {
+ return "";
+ }
+
+ default boolean isDefault() {
+ return false;
+ }
+
+ default UUID getOwnerId() {
+ return null;
+ }
+
+ default Long getVersion() {
+ return null;
+ }
+
+ default String getAsString(String key) {
+ return switch (key) {
+ case "createdTime" -> Long.toString(getCreatedTime());
+ case "type" -> getType();
+ case "label" -> getLabel();
+ case "additionalInfo" -> getAdditionalInfo();
+ case "email" -> getEmail();
+ case "country" -> getCountry();
+ case "state" -> getState();
+ case "city" -> getCity();
+ case "address" -> getAddress();
+ case "address2" -> getAddress2();
+ case "zip" -> getZip();
+ case "phone" -> getPhone();
+ case "region" -> getRegion();
+ case "firstName" -> getFirstName();
+ case "lastName" -> getLastName();
+ case "edgeTemplate" -> Boolean.toString(isEdgeTemplate());
+ case "configuration" -> getConfiguration();
+ case "schedule" -> getSchedule();
+ case "originatorId" -> getOriginatorId().getId().toString();
+ case "originatorType" -> getOriginatorId().getEntityType().toString();
+ case "queueName" -> getQueueName();
+ case "serviceId" -> getServiceId();
+ default -> {
+ log.warn("Unknown field '{}'", key);
+ yield null;
+ }
+ };
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EntityIdFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EntityIdFields.java
new file mode 100644
index 0000000000..835577c4f1
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EntityIdFields.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class EntityIdFields implements EntityFields {
+
+ private UUID id;
+ private Long version;
+
+ public EntityIdFields(UUID id, Long version) {
+ this.id = id;
+ this.version = version;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EntityViewFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EntityViewFields.java
new file mode 100644
index 0000000000..7674fba200
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EntityViewFields.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class EntityViewFields extends AbstractEntityFields {
+
+ private String type;
+ private String additionalInfo;
+
+ public EntityViewFields(UUID id, long createdTime, UUID tenantId, UUID customerId, String name, String type, String additionalInfo, Long version) {
+ super(id, createdTime, tenantId, customerId, name, version);
+ this.type = type;
+ this.additionalInfo = additionalInfo;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/FieldsUtil.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/FieldsUtil.java
new file mode 100644
index 0000000000..a36514248c
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/FieldsUtil.java
@@ -0,0 +1,299 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.thingsboard.server.common.data.ApiUsageState;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.DeviceProfile;
+import org.thingsboard.server.common.data.DeviceProfileType;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.EntityView;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.TenantProfile;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.AssetProfile;
+import org.thingsboard.server.common.data.edge.Edge;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.queue.QueueStats;
+import org.thingsboard.server.common.data.rule.RuleChain;
+import org.thingsboard.server.common.data.rule.RuleNode;
+import org.thingsboard.server.common.data.widget.WidgetType;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+
+import java.util.UUID;
+
+public class FieldsUtil {
+
+ public static EntityFields toFields(Object entity) {
+ if (entity instanceof Customer customer) {
+ return toFields(customer);
+ } else if (entity instanceof Tenant tenant) {
+ return toFields(tenant);
+ } else if (entity instanceof TenantProfile tenantProfile) {
+ return toFields(tenantProfile);
+ } else if (entity instanceof Device device) {
+ return toFields(device);
+ } else if (entity instanceof Asset asset) {
+ return toFields(asset);
+ } else if (entity instanceof Edge edge) {
+ return toFields(edge);
+ } else if (entity instanceof EntityView entityView) {
+ return toFields(entityView);
+ } else if (entity instanceof User user) {
+ return toFields(user);
+ } else if (entity instanceof Dashboard dashboard) {
+ return toFields(dashboard);
+ } else if (entity instanceof RuleChain ruleChain) {
+ return toFields(ruleChain);
+ } else if (entity instanceof RuleNode ruleNode) {
+ return toFields(ruleNode);
+ } else if (entity instanceof WidgetType widgetType) {
+ return toFields(widgetType);
+ } else if (entity instanceof WidgetsBundle widgetsBundle) {
+ return toFields(widgetsBundle);
+ } else if (entity instanceof DeviceProfile deviceProfile) {
+ return toFields(deviceProfile);
+ } else if (entity instanceof AssetProfile assetProfile) {
+ return toFields(assetProfile);
+ } else if (entity instanceof QueueStats queueStats) {
+ return toFields(queueStats);
+ } else if (entity instanceof ApiUsageState apiUsageState) {
+ return toFields(apiUsageState);
+ } else {
+ throw new IllegalArgumentException("Unsupported entity type: " + entity.getClass().getName());
+ }
+ }
+
+ private static CustomerFields toFields(Customer entity) {
+ return CustomerFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .name(entity.getTitle())
+ .additionalInfo(getText(entity.getAdditionalInfo()))
+ .email(entity.getEmail())
+ .country(entity.getCountry())
+ .state(entity.getState())
+ .city(entity.getCity())
+ .address(entity.getAddress())
+ .address2(entity.getAddress2())
+ .zip(entity.getZip())
+ .phone(entity.getPhone())
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static TenantFields toFields(Tenant entity) {
+ return TenantFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .name(entity.getTitle())
+ .additionalInfo(getText(entity.getAdditionalInfo()))
+ .email(entity.getEmail())
+ .country(entity.getCountry())
+ .state(entity.getState())
+ .city(entity.getCity())
+ .address(entity.getAddress())
+ .address2(entity.getAddress2())
+ .zip(entity.getZip())
+ .phone(entity.getPhone())
+ .region(entity.getRegion())
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static TenantProfileFields toFields(TenantProfile tenantProfile) {
+ return TenantProfileFields.builder()
+ .id(tenantProfile.getUuidId())
+ .createdTime(tenantProfile.getCreatedTime())
+ .name(tenantProfile.getName())
+ .isDefault(tenantProfile.isDefault())
+ .build();
+ }
+
+ private static DeviceFields toFields(Device entity) {
+ return DeviceFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .customerId(getCustomerId(entity.getCustomerId()))
+ .name(entity.getName())
+ .type(entity.getType())
+ .deviceProfileId(entity.getDeviceProfileId().getId())
+ .label(entity.getLabel())
+ .additionalInfo(getText(entity.getAdditionalInfo()))
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static AssetFields toFields(Asset entity) {
+ return AssetFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .customerId(getCustomerId(entity.getCustomerId()))
+ .name(entity.getName())
+ .type(entity.getType())
+ .assetProfileId(entity.getAssetProfileId().getId())
+ .label(entity.getLabel())
+ .additionalInfo(getText(entity.getAdditionalInfo()))
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static EdgeFields toFields(Edge entity) {
+ return EdgeFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .customerId(getCustomerId(entity.getCustomerId()))
+ .name(entity.getName())
+ .type(entity.getType())
+ .label(entity.getLabel())
+ .additionalInfo(getText(entity.getAdditionalInfo()))
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static EntityViewFields toFields(EntityView entity) {
+ return EntityViewFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .customerId(getCustomerId(entity.getCustomerId()))
+ .name(entity.getName())
+ .type(entity.getType())
+ .additionalInfo(getText(entity.getAdditionalInfo()))
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static UserFields toFields(User entity) {
+ return UserFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .customerId(getCustomerId(entity.getCustomerId()))
+ .firstName(entity.getFirstName())
+ .lastName(entity.getLastName())
+ .email(entity.getEmail())
+ .phone(entity.getPhone())
+ .additionalInfo(getText(entity.getAdditionalInfo()))
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static DashboardFields toFields(Dashboard entity) {
+ return DashboardFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .name(entity.getTitle())
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static RuleChainFields toFields(RuleChain entity) {
+ return RuleChainFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .name(entity.getName())
+ .additionalInfo(getText(entity.getAdditionalInfo()))
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static RuleNodeFields toFields(RuleNode entity) {
+ return RuleNodeFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .name(entity.getName())
+ .additionalInfo(getText(entity.getAdditionalInfo()))
+ .build();
+ }
+
+ private static WidgetTypeFields toFields(WidgetType entity) {
+ return WidgetTypeFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .name(entity.getName())
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static WidgetsBundleFields toFields(WidgetsBundle entity) {
+ return WidgetsBundleFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .name(entity.getName())
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static AssetProfileFields toFields(AssetProfile entity) {
+ return AssetProfileFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .name(entity.getName())
+ .isDefault(entity.isDefault())
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static DeviceProfileFields toFields(DeviceProfile entity) {
+ return DeviceProfileFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .name(entity.getName())
+ .type(DeviceProfileType.DEFAULT.name())
+ .isDefault(entity.isDefault())
+ .version(entity.getVersion())
+ .build();
+ }
+
+ private static QueueStatsFields toFields(QueueStats entity) {
+ return QueueStatsFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .queueName(entity.getQueueName())
+ .serviceId(entity.getServiceId())
+ .build();
+ }
+
+ private static ApiUsageStateFields toFields(ApiUsageState entity) {
+ return ApiUsageStateFields.builder()
+ .id(entity.getUuidId())
+ .createdTime(entity.getCreatedTime())
+ .customerId(entity.getEntityId().getEntityType() == EntityType.CUSTOMER ? entity.getEntityId().getId() : null)
+ .entityId(entity.getEntityId())
+ .transportState(entity.getTransportState())
+ .dbStorageState(entity.getDbStorageState())
+ .reExecState(entity.getReExecState())
+ .jsExecState(entity.getJsExecState())
+ .tbelExecState(entity.getTbelExecState())
+ .emailExecState(entity.getEmailExecState())
+ .smsExecState(entity.getSmsExecState())
+ .alarmExecState(entity.getAlarmExecState())
+ .version(entity.getVersion())
+ .build();
+ }
+
+ public static String getText(JsonNode node) {
+ return node != null ? node.asText() : "";
+ }
+
+ private static UUID getCustomerId(CustomerId customerId) {
+ return (customerId != null && !customerId.getId().equals(CustomerId.NULL_UUID)) ? customerId.getId() : null;
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/GenericFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/GenericFields.java
new file mode 100644
index 0000000000..68366a6b4d
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/GenericFields.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import lombok.NoArgsConstructor;
+
+import java.util.UUID;
+
+@NoArgsConstructor
+public class GenericFields extends AbstractEntityFields {
+
+ public GenericFields(UUID id, long createdTime, UUID tenantId, String name, Long version) {
+ super(id, createdTime, tenantId, name, version);
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/ProfileAwareFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/ProfileAwareFields.java
new file mode 100644
index 0000000000..4228755808
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/ProfileAwareFields.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import java.util.UUID;
+
+public interface ProfileAwareFields extends EntityFields {
+
+ String getProfileName();
+
+ UUID getProfileId();
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/QueueStatsFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/QueueStatsFields.java
new file mode 100644
index 0000000000..cbc1ad4b8e
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/QueueStatsFields.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class QueueStatsFields extends AbstractEntityFields {
+
+ private String queueName;
+ private String serviceId;
+
+ @Override
+ public String getName() {
+ return queueName + '_' + serviceId;
+ }
+
+ public QueueStatsFields(UUID id, long createdTime, UUID tenantId, String queueName, String serviceId) {
+ super(id, createdTime, tenantId);
+ this.queueName = queueName;
+ this.serviceId = serviceId;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/RuleChainFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/RuleChainFields.java
new file mode 100644
index 0000000000..a047eebd85
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/RuleChainFields.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.common.data.edqs.fields.FieldsUtil.getText;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class RuleChainFields extends AbstractEntityFields {
+
+ private String additionalInfo;
+
+ public RuleChainFields(UUID id, long createdTime, UUID tenantId, String name, Long version, JsonNode additionalInfo) {
+ super(id, createdTime, tenantId, name, version);
+ this.additionalInfo = getText(additionalInfo);
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/RuleNodeFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/RuleNodeFields.java
new file mode 100644
index 0000000000..8aa5b7f42f
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/RuleNodeFields.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.common.data.edqs.fields.FieldsUtil.getText;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class RuleNodeFields implements EntityFields {
+
+ private UUID id;
+ private long createdTime;
+ private String name;
+ private String additionalInfo;
+
+ public RuleNodeFields(UUID id, long createdTime, String name, JsonNode additionalInfo) {
+ this.id = id;
+ this.createdTime = createdTime;
+ this.name = name;
+ this.additionalInfo = getText(additionalInfo);
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/TenantFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/TenantFields.java
new file mode 100644
index 0000000000..342e2974a4
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/TenantFields.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.common.data.edqs.fields.FieldsUtil.getText;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class TenantFields extends AbstractEntityFields {
+
+ private String additionalInfo;
+ private String country;
+ private String state;
+ private String city;
+ private String address;
+ private String address2;
+ private String zip;
+ private String phone;
+ private String email;
+ private String region;
+
+ public TenantFields(UUID id, long createdTime, String name, Long version,
+ JsonNode additionalInfo, String country, String state, String city, String address,
+ String address2, String zip, String phone, String email, String region) {
+ super(id, createdTime, name, version);
+ this.additionalInfo = getText(additionalInfo);
+ this.country = country;
+ this.state = state;
+ this.city = city;
+ this.address = address;
+ this.address2 = address2;
+ this.zip = zip;
+ this.phone = phone;
+ this.email = email;
+ this.region = region;
+ }
+
+ @Override
+ public UUID getTenantId() {
+ return getId();
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/TenantProfileFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/TenantProfileFields.java
new file mode 100644
index 0000000000..b897bd1334
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/TenantProfileFields.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.thingsboard.server.common.data.id.TenantId;
+
+import java.util.UUID;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class TenantProfileFields extends AbstractEntityFields {
+
+ private boolean isDefault;
+
+ public TenantProfileFields(UUID id, long createdTime, String name, boolean isDefault) {
+ super(id, createdTime, TenantId.SYS_TENANT_ID.getId(), null, name, 0L);
+ this.isDefault = isDefault;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/UserFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/UserFields.java
new file mode 100644
index 0000000000..9863506ed4
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/UserFields.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.common.data.edqs.fields.FieldsUtil.getText;
+
+@Data
+@NoArgsConstructor
+@SuperBuilder
+public class UserFields extends AbstractEntityFields {
+
+ private String firstName;
+ private String lastName;
+ private String email;
+ private String phone;
+ private String additionalInfo;
+
+ public UserFields(UUID id, long createdTime, UUID tenantId, UUID customerId,
+ Long version, String firstName, String lastName, String email,
+ String phone, JsonNode additionalInfo) {
+ super(id, createdTime, tenantId, customerId, version);
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.email = email;
+ this.phone = phone;
+ this.additionalInfo = getText(additionalInfo);
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/WidgetTypeFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/WidgetTypeFields.java
new file mode 100644
index 0000000000..4fd0079012
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/WidgetTypeFields.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+@NoArgsConstructor
+@SuperBuilder
+public class WidgetTypeFields extends AbstractEntityFields {
+
+ public WidgetTypeFields(UUID id, long createdTime, UUID tenantId, String name, Long version) {
+ super(id, createdTime, tenantId, name, version);
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/WidgetsBundleFields.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/WidgetsBundleFields.java
new file mode 100644
index 0000000000..f2fb1df508
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/WidgetsBundleFields.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.fields;
+
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.UUID;
+
+@NoArgsConstructor
+@SuperBuilder
+public class WidgetsBundleFields extends AbstractEntityFields {
+
+ public WidgetsBundleFields(UUID id, long createdTime, UUID tenantId, String name, Long version) {
+ super(id, createdTime, tenantId, name, version);
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/query/EdqsRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/query/EdqsRequest.java
new file mode 100644
index 0000000000..e7d8b1df49
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/query/EdqsRequest.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.query;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.thingsboard.server.common.data.query.EntityCountQuery;
+import org.thingsboard.server.common.data.query.EntityDataQuery;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class EdqsRequest {
+
+ private EntityDataQuery entityDataQuery;
+ private EntityCountQuery entityCountQuery;
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/query/EdqsResponse.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/query/EdqsResponse.java
new file mode 100644
index 0000000000..d4e53dea9f
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/query/EdqsResponse.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.query;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.thingsboard.server.common.data.page.PageData;
+import org.thingsboard.server.common.data.query.EntityData;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class EdqsResponse {
+
+ private PageData entityDataQueryResult;
+ private Long entityCountQueryResult;
+ private String error;
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edqs/query/QueryResult.java b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/query/QueryResult.java
new file mode 100644
index 0000000000..67a05323a1
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/edqs/query/QueryResult.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016-2025 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.common.data.edqs.query;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.query.EntityData;
+import org.thingsboard.server.common.data.query.EntityKeyType;
+import org.thingsboard.server.common.data.query.TsValue;
+
+import java.util.Collections;
+import java.util.Map;
+
+@Data
+@RequiredArgsConstructor
+public class QueryResult {
+
+ private final EntityId entityId;
+ private final Map> latest;
+
+ public EntityData toOldEntityData() {
+ return new EntityData(entityId, latest, Collections.emptyMap(), Collections.emptyMap());
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UserAuthSettingsId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/UserAuthSettingsId.java
index f17151dea9..a57ee7ee48 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/UserAuthSettingsId.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/UserAuthSettingsId.java
@@ -15,11 +15,15 @@
*/
package org.thingsboard.server.common.data.id;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
import java.util.UUID;
public class UserAuthSettingsId extends UUIDBased {
- public UserAuthSettingsId(UUID id) {
+ @JsonCreator
+ public UserAuthSettingsId(@JsonProperty("id") UUID id) {
super(id);
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/QuerySecurityContext.java b/common/data/src/main/java/org/thingsboard/server/common/data/permission/QueryContext.java
similarity index 72%
rename from dao/src/main/java/org/thingsboard/server/dao/sql/query/QuerySecurityContext.java
rename to common/data/src/main/java/org/thingsboard/server/common/data/permission/QueryContext.java
index bb5fdddb9d..ace36a1f37 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/QuerySecurityContext.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/permission/QueryContext.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.thingsboard.server.dao.sql.query;
+package org.thingsboard.server.common.data.permission;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -21,8 +21,12 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
@AllArgsConstructor
-public class QuerySecurityContext {
+public class QueryContext {
@Getter
private final TenantId tenantId;
@@ -33,7 +37,14 @@ public class QuerySecurityContext {
@Getter
private final boolean ignorePermissionCheck;
- public QuerySecurityContext(TenantId tenantId, CustomerId customerId, EntityType entityType) {
+ @Getter
+ private final Map relatedParentIdMap = new HashMap<>();
+
+ public QueryContext(TenantId tenantId, CustomerId customerId, EntityType entityType) {
this(tenantId, customerId, entityType, false);
}
+
+ public boolean isTenantUser() {
+ return customerId == null || customerId.isNullUid();
+ }
}
\ No newline at end of file
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValue.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValue.java
index 9ae56a1d60..8c4ae1e8ac 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValue.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValue.java
@@ -15,7 +15,6 @@
*/
package org.thingsboard.server.common.data.query;
-import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.thingsboard.server.common.data.validation.NoXss;
@@ -26,7 +25,6 @@ import java.io.Serializable;
@RequiredArgsConstructor
public class DynamicValue implements Serializable {
- @JsonIgnore
private T resolvedValue;
private final DynamicValueSourceType sourceType;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityCountQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityCountQuery.java
index e2e7e18d9f..12b5331651 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityCountQuery.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityCountQuery.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.query;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.ToString;
@@ -24,6 +25,7 @@ import java.util.List;
@Schema
@ToString
+@JsonIgnoreProperties(ignoreUnknown = true)
public class EntityCountQuery {
@Getter
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityData.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityData.java
index 1519e3910d..328882bbd0 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityData.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityData.java
@@ -16,20 +16,22 @@
package org.thingsboard.server.common.data.query;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.AllArgsConstructor;
import lombok.Data;
-import lombok.RequiredArgsConstructor;
+import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map;
@Data
-@RequiredArgsConstructor
+@AllArgsConstructor
+@NoArgsConstructor
public class EntityData {
- private final EntityId entityId;
- private final Map> latest;
- private final Map timeseries;
- private final Map aggLatest;
+ private EntityId entityId;
+ private Map> latest;
+ private Map timeseries;
+ private Map aggLatest;
public EntityData(EntityId entityId, Map> latest, Map timeseries) {
this(entityId, latest, timeseries, null);
@@ -44,4 +46,5 @@ public class EntityData {
aggLatest.clear();
}
}
+
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java
index 4be9633d96..5507f53f08 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java
@@ -39,7 +39,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonSubTypes.Type(value = AssetSearchQueryFilter.class, name = "assetSearchQuery"),
@JsonSubTypes.Type(value = DeviceSearchQueryFilter.class, name = "deviceSearchQuery"),
@JsonSubTypes.Type(value = EntityViewSearchQueryFilter.class, name = "entityViewSearchQuery"),
- @JsonSubTypes.Type(value = EdgeSearchQueryFilter.class, name = "edgeSearchQuery")})
+ @JsonSubTypes.Type(value = EdgeSearchQueryFilter.class, name = "edgeSearchQuery")
+})
public interface EntityFilter {
@JsonIgnore
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/queue/ProcessingStrategyType.java b/common/data/src/main/java/org/thingsboard/server/common/data/queue/ProcessingStrategyType.java
index 5b8c86d7d1..ca63d34a84 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/queue/ProcessingStrategyType.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/queue/ProcessingStrategyType.java
@@ -13,21 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/**
- * 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.common.data.queue;
public enum ProcessingStrategyType {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java
index 7cbedcbacb..8980d0e634 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java
@@ -25,6 +25,8 @@ import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.BaseDataWithAdditionalInfo;
import org.thingsboard.server.common.data.HasVersion;
+import org.thingsboard.server.common.data.ObjectType;
+import org.thingsboard.server.common.data.edqs.EdqsObject;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.validation.Length;
@@ -34,7 +36,7 @@ import java.io.Serializable;
@Schema
@EqualsAndHashCode(exclude = "additionalInfoBytes")
@ToString(exclude = {"additionalInfoBytes"})
-public class EntityRelation implements HasVersion, Serializable {
+public class EntityRelation implements HasVersion, Serializable, EdqsObject {
private static final long serialVersionUID = 2807343040519543363L;
@@ -107,7 +109,7 @@ public class EntityRelation implements HasVersion, Serializable {
return typeGroup;
}
- @Schema(description = "Additional parameters of the relation",implementation = com.fasterxml.jackson.databind.JsonNode.class)
+ @Schema(description = "Additional parameters of the relation", implementation = com.fasterxml.jackson.databind.JsonNode.class)
public JsonNode getAdditionalInfo() {
return BaseDataWithAdditionalInfo.getJson(() -> additionalInfo, () -> additionalInfoBytes);
}
@@ -116,4 +118,19 @@ public class EntityRelation implements HasVersion, Serializable {
BaseDataWithAdditionalInfo.setJson(addInfo, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes);
}
+ @JsonIgnore
+ public String key() {
+ return "r_" + from + "_" + to + "_" + typeGroup + "_" + type;
+ }
+
+ @Override
+ public Long version() {
+ return version;
+ }
+
+ @Override
+ public ObjectType type() {
+ return ObjectType.RELATION;
+ }
+
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java
index 82ca82e9cc..71c5256203 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.util;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -75,6 +76,13 @@ public class CollectionsUtil {
return isEmpty(collection) || collection.contains(element);
}
+ public static HashSet