@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License .
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
* You may obtain a copy of the License at
*
*
* http : //www.apache.org/licenses/LICENSE-2.0
* http : //www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing , software
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* distributed under the License is distributed on an "AS IS" BASIS ,
@ -20,11 +20,14 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Repository ;
import org.springframework.stereotype.Repository ;
import org.thingsboard.server.common.data.EntityType ;
import org.thingsboard.server.common.data.EntityType ;
import org.thingsboard.server.common.data.UUIDConverter ;
import org.thingsboard.server.common.data.UUIDConverter ;
import org.thingsboard.server.common.data.asset.Asset ;
import org.thingsboard.server.common.data.id.CustomerId ;
import org.thingsboard.server.common.data.id.CustomerId ;
import org.thingsboard.server.common.data.id.EntityId ;
import org.thingsboard.server.common.data.id.EntityId ;
import org.thingsboard.server.common.data.id.TenantId ;
import org.thingsboard.server.common.data.id.TenantId ;
import org.thingsboard.server.common.data.page.PageData ;
import org.thingsboard.server.common.data.page.PageData ;
import org.thingsboard.server.common.data.query.AssetSearchQueryFilter ;
import org.thingsboard.server.common.data.query.AssetTypeFilter ;
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.DeviceTypeFilter ;
import org.thingsboard.server.common.data.query.EntityCountQuery ;
import org.thingsboard.server.common.data.query.EntityCountQuery ;
import org.thingsboard.server.common.data.query.EntityData ;
import org.thingsboard.server.common.data.query.EntityData ;
@ -35,6 +38,7 @@ import org.thingsboard.server.common.data.query.EntityFilter;
import org.thingsboard.server.common.data.query.EntityFilterType ;
import org.thingsboard.server.common.data.query.EntityFilterType ;
import org.thingsboard.server.common.data.query.EntityListFilter ;
import org.thingsboard.server.common.data.query.EntityListFilter ;
import org.thingsboard.server.common.data.query.EntityNameFilter ;
import org.thingsboard.server.common.data.query.EntityNameFilter ;
import org.thingsboard.server.common.data.query.EntitySearchQueryFilter ;
import org.thingsboard.server.common.data.query.EntityViewTypeFilter ;
import org.thingsboard.server.common.data.query.EntityViewTypeFilter ;
import org.thingsboard.server.common.data.query.RelationsQueryFilter ;
import org.thingsboard.server.common.data.query.RelationsQueryFilter ;
import org.thingsboard.server.common.data.query.SingleEntityFilter ;
import org.thingsboard.server.common.data.query.SingleEntityFilter ;
@ -57,7 +61,7 @@ import java.util.stream.Collectors;
@Repository
@Repository
@Slf4j
@Slf4j
public class DefaultEntityQueryRepository implements EntityQueryRepository {
public class DefaultEntityQueryRepository implements EntityQueryRepository {
//TODO: rafactoring to protect from SQL injections;
private static final Map < EntityType , String > entityTableMap = new HashMap < > ( ) ;
private static final Map < EntityType , String > entityTableMap = new HashMap < > ( ) ;
static {
static {
@ -70,6 +74,22 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
entityTableMap . put ( EntityType . TENANT , "tenant" ) ;
entityTableMap . put ( EntityType . TENANT , "tenant" ) ;
}
}
public static final String HIERARCHICAL_QUERY_TEMPLATE = " FROM (WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, relation_type, lvl) AS (" +
" SELECT from_id, from_type, to_id, to_type, relation_type, 1 as lvl" +
" FROM relation" +
" WHERE $in_id = '%s' and $in_type = '%s' and relation_type_group = 'COMMON'" +
" UNION ALL" +
" SELECT r.from_id, r.from_type, r.to_id, r.to_type, r.relation_type, lvl + 1" +
" FROM relation r" +
" INNER JOIN related_entities re ON" +
" r.$in_id = re.$out_id and r.$in_type = re.$out_type and" +
" relation_type_group = 'COMMON' %s)" +
" SELECT re.$out_id entity_id, re.$out_type entity_type, re.lvl lvl" +
" from related_entities re" +
" %s ) entity" ;
public static final String HIERARCHICAL_TO_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE . replace ( "$in" , "to" ) . replace ( "$out" , "from" ) ;
public static final String HIERARCHICAL_FROM_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE . replace ( "$in" , "from" ) . replace ( "$out" , "to" ) ;
@PersistenceContext
@PersistenceContext
private EntityManager entityManager ;
private EntityManager entityManager ;
@ -109,13 +129,19 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
String entityWhereClause = this . buildEntityWhere ( tenantId , customerId , query . getEntityFilter ( ) , entityFieldsFiltersMapping , entityType ) ;
String entityWhereClause = this . buildEntityWhere ( tenantId , customerId , query . getEntityFilter ( ) , entityFieldsFiltersMapping , entityType ) ;
String latestJoins = EntityKeyMapping . buildLatestJoins ( entityType , allLatestMappings ) ;
String latestJoins = EntityKeyMapping . buildLatestJoins ( query . getEntityFilter ( ) , entityType , allLatestMappings ) ;
String whereClause = this . buildWhere ( selectionMapping , latestFiltersMapping , pageLink . getTextSearch ( ) ) ;
String whereClause = this . buildWhere ( selectionMapping , latestFiltersMapping , pageLink . getTextSearch ( ) ) ;
String entityFieldsSelection = EntityKeyMapping . buildSelections ( entityFieldsSelectionMapping ) ;
String entityFieldsSelection = EntityKeyMapping . buildSelections ( entityFieldsSelectionMapping ) ;
String entityTypeStr ;
if ( query . getEntityFilter ( ) . getType ( ) . equals ( EntityFilterType . RELATIONS_QUERY ) ) {
entityTypeStr = "e.entity_type" ;
} else {
entityTypeStr = "'" + entityType . name ( ) + "'" ;
}
if ( ! StringUtils . isEmpty ( entityFieldsSelection ) ) {
if ( ! StringUtils . isEmpty ( entityFieldsSelection ) ) {
entityFieldsSelection = String . format ( "e.id, '%s', %s" , entityType . name ( ) , entityFieldsSelection ) ;
entityFieldsSelection = String . format ( "e.id, %s, %s" , entityTypeStr , entityFieldsSelection ) ;
} else {
} else {
entityFieldsSelection = String . format ( "e.id, '%s'" , entityType . name ( ) ) ;
entityFieldsSelection = String . format ( "e.id, %s" , entityTypeStr ) ;
}
}
String latestSelection = EntityKeyMapping . buildSelections ( latestSelectionMapping ) ;
String latestSelection = EntityKeyMapping . buildSelections ( latestSelectionMapping ) ;
String topSelection = "entities.*" ;
String topSelection = "entities.*" ;
@ -175,16 +201,19 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
}
}
private String buildPermissionQuery ( EntityFilter entityFilter , TenantId tenantId , CustomerId customerId , EntityType entityType ) {
private String buildPermissionQuery ( EntityFilter entityFilter , TenantId tenantId , CustomerId customerId , EntityType entityType ) {
if ( entityFilter . getType ( ) . equals ( EntityFilterType . RELATIONS_QUERY ) ) {
switch ( entityFilter . getType ( ) ) {
return String . format ( "e.tenant_id='%s' and e.customer_id='%s'" , UUIDConverter . fromTimeUUID ( tenantId . getId ( ) ) , UUIDConverter . fromTimeUUID ( customerId . getId ( ) ) ) ;
case RELATIONS_QUERY :
} else {
case DEVICE_SEARCH_QUERY :
if ( entityType = = EntityType . TENANT ) {
case ASSET_SEARCH_QUERY :
return String . format ( "e.id='%s'" , UUIDConverter . fromTimeUUID ( tenantId . getId ( ) ) ) ;
} else if ( entityType = = EntityType . CUSTOMER ) {
return String . format ( "e.tenant_id='%s' and e.id='%s'" , UUIDConverter . fromTimeUUID ( tenantId . getId ( ) ) , UUIDConverter . fromTimeUUID ( customerId . getId ( ) ) ) ;
} else {
return String . format ( "e.tenant_id='%s' and e.customer_id='%s'" , UUIDConverter . fromTimeUUID ( tenantId . getId ( ) ) , UUIDConverter . fromTimeUUID ( customerId . getId ( ) ) ) ;
return String . format ( "e.tenant_id='%s' and e.customer_id='%s'" , UUIDConverter . fromTimeUUID ( tenantId . getId ( ) ) , UUIDConverter . fromTimeUUID ( customerId . getId ( ) ) ) ;
}
default :
if ( entityType = = EntityType . TENANT ) {
return String . format ( "e.id='%s'" , UUIDConverter . fromTimeUUID ( tenantId . getId ( ) ) ) ;
} else if ( entityType = = EntityType . CUSTOMER ) {
return String . format ( "e.tenant_id='%s' and e.id='%s'" , UUIDConverter . fromTimeUUID ( tenantId . getId ( ) ) , UUIDConverter . fromTimeUUID ( customerId . getId ( ) ) ) ;
} else {
return String . format ( "e.tenant_id='%s' and e.customer_id='%s'" , UUIDConverter . fromTimeUUID ( tenantId . getId ( ) ) , UUIDConverter . fromTimeUUID ( customerId . getId ( ) ) ) ;
}
}
}
}
}
@ -201,6 +230,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
case ENTITY_VIEW_TYPE :
case ENTITY_VIEW_TYPE :
return this . typeQuery ( entityFilter ) ;
return this . typeQuery ( entityFilter ) ;
case RELATIONS_QUERY :
case RELATIONS_QUERY :
case DEVICE_SEARCH_QUERY :
case ASSET_SEARCH_QUERY :
return "" ;
return "" ;
default :
default :
throw new RuntimeException ( "Not implemented!" ) ;
throw new RuntimeException ( "Not implemented!" ) ;
@ -211,84 +242,42 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
switch ( entityFilter . getType ( ) ) {
switch ( entityFilter . getType ( ) ) {
case RELATIONS_QUERY :
case RELATIONS_QUERY :
return relationQuery ( ( RelationsQueryFilter ) entityFilter ) ;
return relationQuery ( ( RelationsQueryFilter ) entityFilter ) ;
case DEVICE_SEARCH_QUERY :
DeviceSearchQueryFilter deviceQuery = ( DeviceSearchQueryFilter ) entityFilter ;
return entitySearchQuery ( deviceQuery , EntityType . DEVICE , deviceQuery . getDeviceTypes ( ) ) ;
case ASSET_SEARCH_QUERY :
AssetSearchQueryFilter assetQuery = ( AssetSearchQueryFilter ) entityFilter ;
return entitySearchQuery ( assetQuery , EntityType . ASSET , assetQuery . getAssetTypes ( ) ) ;
default :
default :
return entityTableMap . get ( entityType ) ;
return entityTableMap . get ( entityType ) ;
}
}
}
}
private String relationQuery ( RelationsQueryFilter entityFilter ) {
private String entitySearchQuery ( EntitySearchQueryFilter entityFilter , EntityType entityType , List < String > types ) {
EntityId rootId = entityFilter . getRootEntity ( ) ;
EntityId rootId = entityFilter . getRootEntity ( ) ;
String lvlFilter = entityFilter . getMaxLevel ( ) > 0 ? ( "and lvl <= " + ( entityFilter . getMaxLevel ( ) - 1 ) ) : "" ;
//TODO: fetch last level only.
//TODO: refactoring
String lvlFilter = getLvlFilter ( entityFilter . getMaxLevel ( ) ) ;
String selectFields = " select CASE\n" +
String selectFields = "SELECT tenant_id, customer_id, id, type, name, label FROM " + entityType . name ( ) + " WHERE id in ( SELECT entity_id" ;
" WHEN entity.entity_type = 'DEVICE'\n" +
String from = getQueryTemplate ( entityFilter . getDirection ( ) ) ;
" THEN (select tenant_id from device where id = entity_id)\n" +
" WHEN entity.entity_type = 'ASSET'\n" +
" THEN (select tenant_id from asset where id = entity_id)\n" +
" WHEN entity.entity_type = 'CUSTOMER'\n" +
" THEN (select tenant_id from customer where id = entity_id)\n" +
" WHEN entity.entity_type = 'TENANT'\n" +
" THEN entity_id\n" +
" END as tenant_id,\n" +
" CASE\n" +
" WHEN entity.entity_type = 'DEVICE'\n" +
" THEN (select customer_id from device where id = entity_id)\n" +
" WHEN entity.entity_type = 'ASSET'\n" +
" THEN (select customer_id from asset where id = entity_id)\n" +
" WHEN entity.entity_type = 'CUSTOMER'\n" +
" THEN entity_id\n" +
" WHEN entity.entity_type = 'TENANT'\n" +
" THEN '1b21dd2138140008080808080808080'\n" +
" END as customer_id,\n" +
" entity.entity_id as id,\n" +
" CASE\n" +
" WHEN entity.entity_type = 'DEVICE'\n" +
" THEN (select type from device where id = entity_id)\n" +
" WHEN entity.entity_type = 'ASSET' THEN (select type from asset where id = entity_id)\n" +
" ELSE entity.entity_type\n" +
" END as type,\n" +
" CASE\n" +
" WHEN entity.entity_type = 'DEVICE'\n" +
" THEN (select name from device where id = entity_id)\n" +
" WHEN entity.entity_type = 'ASSET' THEN (select name from asset where id = entity_id)\n" +
" WHEN entity.entity_type = 'CUSTOMER'\n" +
" THEN (select title from customer where id = entity_id)\n" +
" WHEN entity.entity_type = 'TENANT'\n" +
" THEN (select title from tenant where id = entity_id)\n" +
" ELSE entity.entity_type\n" +
" END as name,\n" +
" CASE\n" +
" WHEN entity.entity_type = 'DEVICE'\n" +
" THEN (select label from device where id = entity_id)\n" +
" WHEN entity.entity_type = 'ASSET' THEN (select label from asset where id = entity_id)\n" +
" WHEN entity.entity_type = 'CUSTOMER'\n" +
" THEN (select title from customer where id = entity_id)\n" +
" WHEN entity.entity_type = 'TENANT'\n" +
" THEN (select title from tenant where id = entity_id)\n" +
" ELSE entity.entity_type\n" +
" END as label,\n" +
" entity.entity_type as entity_type" ;
String fromTemplate = " FROM (WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, relation_type, lvl) AS (" +
" SELECT from_id, from_type, to_id, to_type, relation_type, 1 as lvl" +
" FROM relation" +
" WHERE $in_id = '%s' and $in_type = '%s' and relation_type_group = 'COMMON'" +
" UNION ALL" +
" SELECT r.from_id, r.from_type, r.to_id, r.to_type, r.relation_type, lvl + 1" +
" FROM relation r" +
" INNER JOIN related_entities re ON" +
" r.$in_id = re.$out_id and r.$in_type = re.$out_type and" +
" relation_type_group = 'COMMON' %s)" +
" SELECT re.$out_id entity_id, re.$out_type entity_type, re.lvl lvl" +
" from related_entities re" +
" %s ) entity" ;
String from ;
String whereFilter = " WHERE " + " re.relation_type = '" + entityFilter . getRelationType ( ) + "'" +
if ( entityFilter . getDirection ( ) . equals ( EntitySearchDirection . FROM ) ) {
" AND re.to_type = '" + entityType . name ( ) + "'" ;
from = fromTemplate . replace ( "$in" , "from" ) . replace ( "$out" , "to" ) ;
from = String . format ( from , UUIDConverter . fromTimeUUID ( rootId . getId ( ) ) , rootId . getEntityType ( ) . name ( ) , lvlFilter , whereFilter ) ;
} else {
String query = "( " + selectFields + from + ")" ;
from = fromTemplate . replace ( "$in" , "to" ) . replace ( "$out" , "from" ) ;
if ( types ! = null & & ! types . isEmpty ( ) ) {
query + = " and type in (" + types . stream ( ) . map ( type - > "'" + type + "'" ) . collect ( Collectors . joining ( ", " ) ) + ")" ;
}
}
query + = " )" ;
return query ;
}
private String relationQuery ( RelationsQueryFilter entityFilter ) {
EntityId rootId = entityFilter . getRootEntity ( ) ;
String lvlFilter = getLvlFilter ( entityFilter . getMaxLevel ( ) ) ;
String selectFields = getSelectTenantId ( ) + ", " + getSelectCustomerId ( ) + ", " +
" entity.entity_id as id," + getSelectType ( ) + ", " + getSelectName ( ) + ", " +
getSelectLabel ( ) + ", entity.entity_type as entity_type" ;
String from = getQueryTemplate ( entityFilter . getDirection ( ) ) ;
StringBuilder whereFilter ;
StringBuilder whereFilter ;
if ( entityFilter . getFilters ( ) ! = null & & ! entityFilter . getFilters ( ) . isEmpty ( ) ) {
if ( entityFilter . getFilters ( ) ! = null & & ! entityFilter . getFilters ( ) . isEmpty ( ) ) {
@ -302,13 +291,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
whereFilter . append ( " AND " ) ;
whereFilter . append ( " AND " ) ;
}
}
String relationType = etf . getRelationType ( ) ;
String relationType = etf . getRelationType ( ) ;
String types = etf . getEntityTypes ( ) . stream ( ) . map ( type - > "'" + type + "'" ) . collect ( Collectors . joining ( ", " ) ) ;
String en tityT ypes = etf . getEntityTypes ( ) . stream ( ) . map ( type - > "'" + type + "'" ) . collect ( Collectors . joining ( ", " ) ) ;
if ( ! single ) {
if ( ! single ) {
whereFilter . append ( " (" ) ;
whereFilter . append ( " (" ) ;
}
}
whereFilter . append ( " re.relation_type = '" ) . append ( relationType ) . append ( "' and re." )
whereFilter . append ( " re.relation_type = '" ) . append ( relationType ) . append ( "' and re." )
. append ( entityFilter . getDirection ( ) . equals ( EntitySearchDirection . FROM ) ? "to" : "from" )
. append ( entityFilter . getDirection ( ) . equals ( EntitySearchDirection . FROM ) ? "to" : "from" )
. append ( "_type in (" ) . append ( types ) . append ( ")" ) ;
. append ( "_type in (" ) . append ( en tityT ypes) . append ( ")" ) ;
if ( ! single ) {
if ( ! single ) {
whereFilter . append ( " )" ) ;
whereFilter . append ( " )" ) ;
}
}
@ -320,6 +309,107 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
return "( " + selectFields + from + ")" ;
return "( " + selectFields + from + ")" ;
}
}
private String getLvlFilter ( int maxLevel ) {
return maxLevel > 0 ? ( "and lvl <= " + ( maxLevel - 1 ) ) : "" ;
}
private String getQueryTemplate ( EntitySearchDirection direction ) {
String from ;
if ( direction . equals ( EntitySearchDirection . FROM ) ) {
from = HIERARCHICAL_FROM_QUERY_TEMPLATE ;
} else {
from = HIERARCHICAL_TO_QUERY_TEMPLATE ;
}
return from ;
}
private String getSelectTenantId ( ) {
return "SELECT CASE" +
" WHEN entity.entity_type = 'TENANT' THEN entity_id" +
" WHEN entity.entity_type = 'CUSTOMER'" +
" THEN (select tenant_id from customer where id = entity_id)" +
" WHEN entity.entity_type = 'USER'" +
" THEN (select tenant_id from tb_user where id = entity_id)" +
" WHEN entity.entity_type = 'DASHBOARD'" +
" THEN (select tenant_id from dashboard where id = entity_id)" +
" WHEN entity.entity_type = 'ASSET'" +
" THEN (select tenant_id from asset where id = entity_id)" +
" WHEN entity.entity_type = 'DEVICE'" +
" 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)" +
" END as tenant_id" ;
}
private String getSelectCustomerId ( ) {
return "CASE" +
" WHEN entity.entity_type = 'TENANT'" +
" THEN '" + UUIDConverter . fromTimeUUID ( TenantId . NULL_UUID ) + "'" +
" WHEN entity.entity_type = 'CUSTOMER' THEN entity_id" +
" WHEN entity.entity_type = 'USER'" +
" THEN (select customer_id from tb_user where id = entity_id)" +
" WHEN entity.entity_type = 'DASHBOARD'" +
//TODO: parse assigned customers or use contains?
" THEN NULL" +
" WHEN entity.entity_type = 'ASSET'" +
" THEN (select customer_id from asset where id = entity_id)" +
" WHEN entity.entity_type = 'DEVICE'" +
" 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)" +
" END as customer_id" ;
}
private String getSelectName ( ) {
return " CASE" +
" WHEN entity.entity_type = 'TENANT'" +
" THEN (select title from tenant where id = entity_id)" +
" WHEN entity.entity_type = 'CUSTOMER' " +
" THEN (select title from customer where id = entity_id)" +
" WHEN entity.entity_type = 'USER'" +
" THEN (select CONCAT (first_name, ' ', last_name) from tb_user where id = entity_id)" +
" WHEN entity.entity_type = 'DASHBOARD'" +
" THEN (select title from dashboard where id = entity_id)" +
" WHEN entity.entity_type = 'ASSET'" +
" THEN (select name from asset where id = entity_id)" +
" WHEN entity.entity_type = 'DEVICE'" +
" 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)" +
" END as name" ;
}
private String getSelectType ( ) {
return " CASE" +
" WHEN entity.entity_type = 'USER'" +
" THEN (select authority from tb_user where id = entity_id)" +
" WHEN entity.entity_type = 'ASSET'" +
" THEN (select type from asset where id = entity_id)" +
" WHEN entity.entity_type = 'DEVICE'" +
" 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)" +
" ELSE entity.entity_type END as type" ;
}
private String getSelectLabel ( ) {
return " CASE" +
" WHEN entity.entity_type = 'TENANT'" +
" THEN (select title from tenant where id = entity_id)" +
" WHEN entity.entity_type = 'CUSTOMER' " +
" THEN (select title from customer where id = entity_id)" +
" WHEN entity.entity_type = 'USER'" +
" THEN (select CONCAT (first_name, ' ', last_name) from tb_user where id = entity_id)" +
" WHEN entity.entity_type = 'DASHBOARD'" +
" THEN (select title from dashboard where id = entity_id)" +
" WHEN entity.entity_type = 'ASSET'" +
" THEN (select label from asset where id = entity_id)" +
" WHEN entity.entity_type = 'DEVICE'" +
" 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)" +
" END as label" ;
}
private String buildWhere
private String buildWhere
( List < EntityKeyMapping > selectionMapping , List < EntityKeyMapping > latestFiltersMapping , String searchText ) {
( List < EntityKeyMapping > selectionMapping , List < EntityKeyMapping > latestFiltersMapping , String searchText ) {