diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EdgeSearchQueryFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EdgeSearchQueryFilter.java new file mode 100644 index 0000000000..74d4438e5b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EdgeSearchQueryFilter.java @@ -0,0 +1,32 @@ +/** + * 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.query; + +import lombok.Data; + +import java.util.List; + +@Data +public class EdgeSearchQueryFilter extends EntitySearchQueryFilter { + + @Override + public EntityFilterType getType() { + return EntityFilterType.EDGE_SEARCH_QUERY; + } + + private List edgeTypes; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EdgeTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EdgeTypeFilter.java new file mode 100644 index 0000000000..4b198b9d59 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EdgeTypeFilter.java @@ -0,0 +1,32 @@ +/** + * 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.query; + +import lombok.Data; + +@Data +public class EdgeTypeFilter implements EntityFilter { + + @Override + public EntityFilterType getType() { + return EntityFilterType.EDGE_TYPE; + } + + private String edgeType; + + private String edgeNameFilter; + +} 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 f2a6dde626..40a7e7921b 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 @@ -31,12 +31,14 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonSubTypes.Type(value = EntityNameFilter.class, name = "entityName"), @JsonSubTypes.Type(value = AssetTypeFilter.class, name = "assetType"), @JsonSubTypes.Type(value = DeviceTypeFilter.class, name = "deviceType"), + @JsonSubTypes.Type(value = EdgeTypeFilter.class, name = "edgeType"), @JsonSubTypes.Type(value = EntityViewTypeFilter.class, name = "entityViewType"), @JsonSubTypes.Type(value = ApiUsageStateFilter.class, name = "apiUsageState"), @JsonSubTypes.Type(value = RelationsQueryFilter.class, name = "relationsQuery"), @JsonSubTypes.Type(value = AssetSearchQueryFilter.class, name = "assetSearchQuery"), @JsonSubTypes.Type(value = DeviceSearchQueryFilter.class, name = "deviceSearchQuery"), - @JsonSubTypes.Type(value = EntityViewSearchQueryFilter.class, name = "entityViewSearchQuery")}) + @JsonSubTypes.Type(value = EntityViewSearchQueryFilter.class, name = "entityViewSearchQuery"), + @JsonSubTypes.Type(value = EdgeSearchQueryFilter.class, name = "edgeSearchQuery")}) public interface EntityFilter { @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java index 12860b8453..ba26dd763c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java @@ -22,10 +22,12 @@ public enum EntityFilterType { ASSET_TYPE("assetType"), DEVICE_TYPE("deviceType"), ENTITY_VIEW_TYPE("entityViewType"), + EDGE_TYPE("edgeType"), RELATIONS_QUERY("relationsQuery"), ASSET_SEARCH_QUERY("assetSearchQuery"), DEVICE_SEARCH_QUERY("deviceSearchQuery"), ENTITY_VIEW_SEARCH_QUERY("entityViewSearchQuery"), + EDGE_SEARCH_QUERY("edgeSearchQuery"), API_USAGE_STATE("apiUsageState"); private final String label; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index 8edb1917ba..bae6af89ee 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -29,6 +29,8 @@ import org.thingsboard.server.common.data.query.AssetSearchQueryFilter; import org.thingsboard.server.common.data.query.AssetTypeFilter; import org.thingsboard.server.common.data.query.DeviceSearchQueryFilter; import org.thingsboard.server.common.data.query.DeviceTypeFilter; +import org.thingsboard.server.common.data.query.EdgeSearchQueryFilter; +import org.thingsboard.server.common.data.query.EdgeTypeFilter; import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; @@ -111,6 +113,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { " THEN (select customer_id from device where id = entity_id)" + " WHEN entity.entity_type = 'ENTITY_VIEW'" + " THEN (select customer_id from entity_view where id = entity_id)" + + " WHEN entity.entity_type = 'EDGE'" + + " THEN (select customer_id from edge where id = entity_id)" + " END as customer_id"; private static final String SELECT_TENANT_ID = "SELECT CASE" + " WHEN entity.entity_type = 'TENANT' THEN entity_id" + @@ -126,6 +130,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { " THEN (select tenant_id from device where id = entity_id)" + " WHEN entity.entity_type = 'ENTITY_VIEW'" + " THEN (select tenant_id from entity_view where id = entity_id)" + + " WHEN entity.entity_type = 'EDGE'" + + " THEN (select tenant_id from edge where id = entity_id)" + " END as tenant_id"; private static final String SELECT_CREATED_TIME = " CASE" + " WHEN entity.entity_type = 'TENANT'" + @@ -142,6 +148,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { " THEN (select created_time from device where id = entity_id)" + " WHEN entity.entity_type = 'ENTITY_VIEW'" + " THEN (select created_time from entity_view where id = entity_id)" + + " WHEN entity.entity_type = 'EDGE'" + + " THEN (select created_time from edge where id = entity_id)" + " END as created_time"; private static final String SELECT_NAME = " CASE" + " WHEN entity.entity_type = 'TENANT'" + @@ -158,6 +166,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { " THEN (select name from device where id = entity_id)" + " WHEN entity.entity_type = 'ENTITY_VIEW'" + " THEN (select name from entity_view where id = entity_id)" + + " WHEN entity.entity_type = 'EDGE'" + + " THEN (select name from edge where id = entity_id)" + " END as name"; private static final String SELECT_TYPE = " CASE" + " WHEN entity.entity_type = 'USER'" + @@ -168,6 +178,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { " THEN (select type from device where id = entity_id)" + " WHEN entity.entity_type = 'ENTITY_VIEW'" + " THEN (select type from entity_view where id = entity_id)" + + " WHEN entity.entity_type = 'EDGE'" + + " THEN (select type from edge where id = entity_id)" + " ELSE entity.entity_type END as type"; private static final String SELECT_LABEL = " CASE" + " WHEN entity.entity_type = 'TENANT'" + @@ -184,6 +196,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { " THEN (select label from device where id = entity_id)" + " WHEN entity.entity_type = 'ENTITY_VIEW'" + " THEN (select name from entity_view where id = entity_id)" + + " WHEN entity.entity_type = 'EDGE'" + + " THEN (select label from edge where id = entity_id)" + " END as label"; private static final String SELECT_ADDITIONAL_INFO = " CASE" + " WHEN entity.entity_type = 'TENANT'" + @@ -200,6 +214,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { " THEN (select additional_info from device where id = entity_id)" + " WHEN entity.entity_type = 'ENTITY_VIEW'" + " THEN (select additional_info from entity_view where id = entity_id)" + + " WHEN entity.entity_type = 'EDGE'" + + " THEN (select additional_info from edge where id = entity_id)" + " END as additional_info"; private static final String SELECT_API_USAGE_STATE = "(select aus.id, aus.created_time, aus.tenant_id, '13814000-1dd2-11b2-8080-808080808080'::uuid as customer_id, " + @@ -214,6 +230,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { entityTableMap.put(EntityType.USER, "tb_user"); entityTableMap.put(EntityType.TENANT, "tenant"); entityTableMap.put(EntityType.API_USAGE_STATE, SELECT_API_USAGE_STATE); + entityTableMap.put(EntityType.EDGE, "edge"); } public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ @@ -394,6 +411,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { case DEVICE_SEARCH_QUERY: case ASSET_SEARCH_QUERY: case ENTITY_VIEW_SEARCH_QUERY: + case EDGE_SEARCH_QUERY: return this.defaultPermissionQuery(ctx); default: if (ctx.getEntityType() == EntityType.TENANT) { @@ -430,11 +448,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { case ASSET_TYPE: case DEVICE_TYPE: case ENTITY_VIEW_TYPE: + case EDGE_TYPE: return this.typeQuery(ctx, entityFilter); case RELATIONS_QUERY: case DEVICE_SEARCH_QUERY: case ASSET_SEARCH_QUERY: case ENTITY_VIEW_SEARCH_QUERY: + case EDGE_SEARCH_QUERY: case API_USAGE_STATE: return ""; default: @@ -455,6 +475,9 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { case ENTITY_VIEW_SEARCH_QUERY: EntityViewSearchQueryFilter entityViewQuery = (EntityViewSearchQueryFilter) entityFilter; return entitySearchQuery(ctx, entityViewQuery, EntityType.ENTITY_VIEW, entityViewQuery.getEntityViewTypes()); + case EDGE_SEARCH_QUERY: + EdgeSearchQueryFilter edgeQuery = (EdgeSearchQueryFilter) entityFilter; + return entitySearchQuery(ctx, edgeQuery, EntityType.EDGE, edgeQuery.getEdgeTypes()); default: return entityTableMap.get(ctx.getEntityType()); } @@ -660,6 +683,10 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { type = ((EntityViewTypeFilter) filter).getEntityViewType(); name = ((EntityViewTypeFilter) filter).getEntityViewNameFilter(); break; + case EDGE_TYPE: + type = ((EdgeTypeFilter) filter).getEdgeType(); + name = ((EdgeTypeFilter) filter).getEdgeNameFilter(); + break; default: throw new RuntimeException("Not supported!"); } @@ -685,6 +712,9 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { case ENTITY_VIEW_TYPE: case ENTITY_VIEW_SEARCH_QUERY: return EntityType.ENTITY_VIEW; + case EDGE_TYPE: + case EDGE_SEARCH_QUERY: + return EntityType.EDGE; case RELATIONS_QUERY: return ((RelationsQueryFilter) entityFilter).getRootEntity().getEntityType(); case API_USAGE_STATE: diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java index ff87e17d56..c502f98ea8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java @@ -20,6 +20,8 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import org.apache.commons.lang3.RandomStringUtils; +import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -31,6 +33,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.data.page.PageData; @@ -189,7 +192,90 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { Assert.assertEquals(0, count); } - + @Test + public void testCountEdgeEntitiesByQuery() throws InterruptedException { + List edges = new ArrayList<>(); + for (int i = 0; i < 97; i++) { + Edge edge = createEdge(i, "default"); + edges.add(edgeService.saveEdge(edge)); + } + + EdgeTypeFilter filter = new EdgeTypeFilter(); + filter.setEdgeType("default"); + filter.setEdgeNameFilter(""); + + EntityCountQuery countQuery = new EntityCountQuery(filter); + + long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery); + Assert.assertEquals(97, count); + + filter.setEdgeType("unknown"); + count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery); + Assert.assertEquals(0, count); + + filter.setEdgeType("default"); + filter.setEdgeNameFilter("Edge1"); + count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery); + Assert.assertEquals(11, count); + + 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); + + edgeService.deleteEdgesByTenantId(tenantId); + count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery); + Assert.assertEquals(0, count); + } + + @Test + public void testCountHierarchicalEntitiesByEdgeSearchQuery() throws InterruptedException { + for (int i = 0; i < 5; i++) { + Edge edge = createEdge(i, "type" + i); + edge = edgeService.saveEdge(edge); + //TO make sure devices have different created time + Thread.sleep(1); + + EntityRelation er = new EntityRelation(); + er.setFrom(tenantId); + er.setTo(edge.getId()); + er.setType("Manages"); + er.setTypeGroup(RelationTypeGroup.COMMON); + relationService.saveRelation(tenantId, er); + } + + EdgeSearchQueryFilter filter = new EdgeSearchQueryFilter(); + filter.setRootEntity(tenantId); + filter.setDirection(EntitySearchDirection.FROM); + filter.setRelationType("Manages"); + + EntityCountQuery countQuery = new EntityCountQuery(filter); + + long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery); + Assert.assertEquals(5, count); + + filter.setEdgeTypes(Arrays.asList("type0", "type1")); + count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery); + Assert.assertEquals(2, count); + } + + @NotNull + private Edge createEdge(int i, String type) { + Edge edge = new Edge(); + edge.setTenantId(tenantId); + edge.setName("Edge" + i); + edge.setType(type); + edge.setLabel("EdgeLabel" + i); + edge.setSecret(RandomStringUtils.randomAlphanumeric(20)); + edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); + edge.setEdgeLicenseKey(RandomStringUtils.randomAlphanumeric(20)); + edge.setCloudEndpoint("http://localhost:8080"); + return edge; + } + @Test public void testHierarchicalFindEntityDataWithAttributesByQuery() throws ExecutionException, InterruptedException { List assets = new ArrayList<>();