diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index acade3afba..808fec6b53 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -33,7 +33,7 @@ import org.thingsboard.rule.engine.metadata.TbFetchDeviceCredentialsNode; import org.thingsboard.rule.engine.metadata.TbGetAttributesNode; import org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode; import org.thingsboard.rule.engine.metadata.TbGetCustomerDetailsNode; -import org.thingsboard.rule.engine.metadata.TbGetDeviceAttrNode; +import org.thingsboard.rule.engine.metadata.TbGetRelatedDeviceAttrNode; import org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode; import org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode; import org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode; @@ -232,7 +232,7 @@ public class DefaultDataUpdateService implements DataUpdateService { TbGetOriginatorFieldsNode.class.getName(), TbFetchDeviceCredentialsNode.class.getName(), TbGetAttributesNode.class.getName(), - TbGetDeviceAttrNode.class.getName(), + TbGetRelatedDeviceAttrNode.class.getName(), TbGetRelatedAttributeNode.class.getName(), TbGetTenantAttributeNode.class.getName(), TbGetCustomerAttributeNode.class.getName(), diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDeviceAttrNode.java similarity index 96% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDeviceAttrNode.java index fdbb412e6b..53220837a9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDeviceAttrNode.java @@ -39,7 +39,7 @@ import org.thingsboard.server.common.msg.TbMsg; "metadata.cs_temperature or metadata.shared_limit ", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeDeviceAttributesConfig") -public class TbGetDeviceAttrNode extends TbAbstractGetAttributesNode { +public class TbGetRelatedDeviceAttrNode extends TbAbstractGetAttributesNode { private static final String RELATED_DEVICE_NOT_FOUND_MESSAGE = "Failed to find related device to message originator using relation query specified in the configuration!"; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java new file mode 100644 index 0000000000..dfea960c07 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java @@ -0,0 +1,117 @@ +package org.thingsboard.rule.engine.util; + +import com.google.common.util.concurrent.Futures; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.data.DeviceRelationsQuery; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.device.DeviceSearchQuery; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.relation.RelationsSearchParameters; +import org.thingsboard.server.dao.device.DeviceService; + +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class EntitiesRelatedDeviceIdAsyncLoaderTest { + + private static final EntityId DUMMY_ORIGINATOR = new DeviceId(UUID.randomUUID()); + private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + @Mock + private TbContext ctxMock; + @Mock + private DeviceService deviceServiceMock; + + @Test + public void givenDeviceRelationsQuery_whenFindDeviceAsync_ShouldBuildCorrectDeviceSearchQuery() { + // GIVEN + var deviceRelationsQuery = new DeviceRelationsQuery(); + deviceRelationsQuery.setDeviceTypes(List.of("Device type 1", "Device type 2", "default")); + deviceRelationsQuery.setDirection(EntitySearchDirection.FROM); + deviceRelationsQuery.setMaxLevel(2); + deviceRelationsQuery.setRelationType(EntityRelation.CONTAINS_TYPE); + + var expectedDeviceSearchQuery = new DeviceSearchQuery(); + var parameters = new RelationsSearchParameters( + DUMMY_ORIGINATOR, + deviceRelationsQuery.getDirection(), + deviceRelationsQuery.getMaxLevel(), + deviceRelationsQuery.isFetchLastLevelOnly() + ); + expectedDeviceSearchQuery.setParameters(parameters); + expectedDeviceSearchQuery.setRelationType(deviceRelationsQuery.getRelationType()); + expectedDeviceSearchQuery.setDeviceTypes(deviceRelationsQuery.getDeviceTypes()); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(deviceServiceMock.findDevicesByQuery(eq(TENANT_ID), eq(expectedDeviceSearchQuery))) + .thenReturn(Futures.immediateFuture(null)); + + // WHEN + EntitiesRelatedDeviceIdAsyncLoader.findDeviceAsync(ctxMock, DUMMY_ORIGINATOR, deviceRelationsQuery); + + // THEN + verify(deviceServiceMock, times(1)).findDevicesByQuery(eq(TENANT_ID), eq(expectedDeviceSearchQuery)); + } + + @Test + public void givenSeveralDevicesFound_whenFindDeviceAsync_ShouldKeepOneAndDiscardOthers() throws Exception { + // GIVEN + var deviceRelationsQuery = new DeviceRelationsQuery(); + deviceRelationsQuery.setDeviceTypes(List.of("Device type 1", "Device type 2", "default")); + deviceRelationsQuery.setDirection(EntitySearchDirection.FROM); + deviceRelationsQuery.setMaxLevel(2); + deviceRelationsQuery.setRelationType(EntityRelation.CONTAINS_TYPE); + + var expectedDeviceSearchQuery = new DeviceSearchQuery(); + var parameters = new RelationsSearchParameters( + DUMMY_ORIGINATOR, + deviceRelationsQuery.getDirection(), + deviceRelationsQuery.getMaxLevel(), + deviceRelationsQuery.isFetchLastLevelOnly() + ); + expectedDeviceSearchQuery.setParameters(parameters); + expectedDeviceSearchQuery.setRelationType(deviceRelationsQuery.getRelationType()); + expectedDeviceSearchQuery.setDeviceTypes(deviceRelationsQuery.getDeviceTypes()); + + var device1 = new Device(new DeviceId(UUID.randomUUID())); + device1.setName("Device 1"); + var device2 = new Device(new DeviceId(UUID.randomUUID())); + device1.setName("Device 2"); + var device3 = new Device(new DeviceId(UUID.randomUUID())); + device1.setName("Device 3"); + + var devicesList = List.of(device1, device2, device3); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(deviceServiceMock.findDevicesByQuery(eq(TENANT_ID), eq(expectedDeviceSearchQuery))) + .thenReturn(Futures.immediateFuture(devicesList)); + + // WHEN + var entityIdFuture = EntitiesRelatedDeviceIdAsyncLoader.findDeviceAsync(ctxMock, DUMMY_ORIGINATOR, deviceRelationsQuery); + + // THEN + assertNotNull(entityIdFuture); + + var actualEntityId = entityIdFuture.get(); + assertNotNull(actualEntityId); + assertEquals(device1.getId(), actualEntityId); + } + +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java new file mode 100644 index 0000000000..2b27ce12bf --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java @@ -0,0 +1,138 @@ +package org.thingsboard.rule.engine.util; + +import com.google.common.util.concurrent.Futures; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.data.RelationsQuery; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntityRelationsQuery; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; +import org.thingsboard.server.common.data.relation.RelationsSearchParameters; +import org.thingsboard.server.dao.relation.RelationService; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class EntitiesRelatedEntitiesIdAsyncLoaderTest { + + private static final EntityId DUMMY_ORIGINATOR = new DeviceId(UUID.randomUUID()); + private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + @Mock + private TbContext ctxMock; + @Mock + private RelationService relationServiceMock; + + @Test + public void givenRelationsQuery_whenFindEntityAsync_ShouldBuildCorrectEntityRelationsQuery() { + // GIVEN + var relationsQuery = new RelationsQuery(); + var relationEntityTypeFilter = new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList()); + relationsQuery.setDirection(EntitySearchDirection.FROM); + relationsQuery.setMaxLevel(1); + relationsQuery.setFilters(Collections.singletonList(relationEntityTypeFilter)); + + var expectedEntityRelationsQuery = new EntityRelationsQuery(); + var parameters = new RelationsSearchParameters( + DUMMY_ORIGINATOR, + relationsQuery.getDirection(), + relationsQuery.getMaxLevel(), + relationsQuery.isFetchLastLevelOnly() + ); + expectedEntityRelationsQuery.setParameters(parameters); + expectedEntityRelationsQuery.setFilters(relationsQuery.getFilters()); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getRelationService()).thenReturn(relationServiceMock); + when(relationServiceMock.findByQuery(eq(TENANT_ID), eq(expectedEntityRelationsQuery))) + .thenReturn(Futures.immediateFuture(null)); + + // WHEN + EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctxMock, DUMMY_ORIGINATOR, relationsQuery); + + // THEN + verify(relationServiceMock, times(1)).findByQuery(eq(TENANT_ID), eq(expectedEntityRelationsQuery)); + } + + @Test + public void givenSeveralEntitiesFound_whenFindEntityAsync_ShouldKeepOneAndDiscardOthers() throws Exception { + // GIVEN + var relationsQuery = new RelationsQuery(); + var relationEntityTypeFilter = new RelationEntityTypeFilter( + EntityRelation.CONTAINS_TYPE, + List.of(EntityType.DEVICE, EntityType.ASSET) + ); + relationsQuery.setDirection(EntitySearchDirection.FROM); + relationsQuery.setMaxLevel(2); + relationsQuery.setFilters(Collections.singletonList(relationEntityTypeFilter)); + + var expectedEntityRelationsQuery = new EntityRelationsQuery(); + var parameters = new RelationsSearchParameters( + DUMMY_ORIGINATOR, + relationsQuery.getDirection(), + relationsQuery.getMaxLevel(), + relationsQuery.isFetchLastLevelOnly() + ); + expectedEntityRelationsQuery.setParameters(parameters); + expectedEntityRelationsQuery.setFilters(relationsQuery.getFilters()); + + var device1 = new Device(new DeviceId(UUID.randomUUID())); + device1.setName("Device 1"); + var device2 = new Device(new DeviceId(UUID.randomUUID())); + device1.setName("Device 2"); + var asset = new Asset(new AssetId(UUID.randomUUID())); + asset.setName("Asset"); + + var entityRelationDevice1 = new EntityRelation(); + entityRelationDevice1.setFrom(DUMMY_ORIGINATOR); + entityRelationDevice1.setTo(device1.getId()); + entityRelationDevice1.setType(EntityRelation.CONTAINS_TYPE); + + var entityRelationDevice2 = new EntityRelation(); + entityRelationDevice2.setFrom(DUMMY_ORIGINATOR); + entityRelationDevice2.setTo(device2.getId()); + entityRelationDevice2.setType(EntityRelation.CONTAINS_TYPE); + + var entityRelationAsset = new EntityRelation(); + entityRelationAsset.setFrom(DUMMY_ORIGINATOR); + entityRelationAsset.setTo(asset.getId()); + entityRelationAsset.setType(EntityRelation.CONTAINS_TYPE); + + var expectedEntityRelationsList = List.of(entityRelationDevice1, entityRelationDevice2, entityRelationAsset); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getRelationService()).thenReturn(relationServiceMock); + when(relationServiceMock.findByQuery(eq(TENANT_ID), eq(expectedEntityRelationsQuery))) + .thenReturn(Futures.immediateFuture(expectedEntityRelationsList)); + + // WHEN + var deviceIdFuture = EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctxMock, DUMMY_ORIGINATOR, relationsQuery); + + // THEN + assertNotNull(deviceIdFuture); + + var actualDeviceId = deviceIdFuture.get(); + assertNotNull(actualDeviceId); + assertEquals(device1.getId(), actualDeviceId); + } + +}