Multi-Root Query implementation improvements
This commit is contained in:
		
						commit
						20c9e77083
					
				@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Copyright © 2016-2021 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 com.fasterxml.jackson.databind.JsonNode;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.ObjectMapper;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
				
			||||||
 | 
					import io.swagger.annotations.Api;
 | 
				
			||||||
 | 
					import io.swagger.annotations.ApiOperation;
 | 
				
			||||||
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
 | 
					import org.springframework.boot.info.BuildProperties;
 | 
				
			||||||
 | 
					import org.springframework.security.access.prepost.PreAuthorize;
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestMapping;
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestMethod;
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.ResponseBody;
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RestController;
 | 
				
			||||||
 | 
					import org.thingsboard.server.queue.util.TbCoreComponent;
 | 
				
			||||||
 | 
					import springfox.documentation.annotations.ApiIgnore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.annotation.PostConstruct;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@ApiIgnore
 | 
				
			||||||
 | 
					@RestController
 | 
				
			||||||
 | 
					@TbCoreComponent
 | 
				
			||||||
 | 
					@RequestMapping("/api")
 | 
				
			||||||
 | 
					@Slf4j
 | 
				
			||||||
 | 
					public class SystemInfoController {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Autowired(required = false)
 | 
				
			||||||
 | 
					    private BuildProperties buildProperties;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @PostConstruct
 | 
				
			||||||
 | 
					    public void init() {
 | 
				
			||||||
 | 
					        JsonNode info = buildInfoObject();
 | 
				
			||||||
 | 
					        log.info("System build info: {}", info);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
 | 
				
			||||||
 | 
					    @RequestMapping(value = "/system/info", method = RequestMethod.GET)
 | 
				
			||||||
 | 
					    @ResponseBody
 | 
				
			||||||
 | 
					    public JsonNode getSystemVersionInfo() {
 | 
				
			||||||
 | 
					        return buildInfoObject();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private JsonNode buildInfoObject() {
 | 
				
			||||||
 | 
					        ObjectMapper objectMapper = new ObjectMapper();
 | 
				
			||||||
 | 
					        ObjectNode infoObject = objectMapper.createObjectNode();
 | 
				
			||||||
 | 
					        if (buildProperties != null) {
 | 
				
			||||||
 | 
					            infoObject.put("version", buildProperties.getVersion());
 | 
				
			||||||
 | 
					            infoObject.put("artifact", buildProperties.getArtifact());
 | 
				
			||||||
 | 
					            infoObject.put("name", buildProperties.getName());
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            infoObject.put("version", "unknown");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return infoObject;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -16,11 +16,13 @@
 | 
				
			|||||||
package org.thingsboard.server.common.data.query;
 | 
					package org.thingsboard.server.common.data.query;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import lombok.Data;
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.EntityType;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
					import org.thingsboard.server.common.data.id.EntityId;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
 | 
					import org.thingsboard.server.common.data.relation.EntitySearchDirection;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
 | 
					import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Data
 | 
					@Data
 | 
				
			||||||
public class RelationsQueryFilter implements EntityFilter {
 | 
					public class RelationsQueryFilter implements EntityFilter {
 | 
				
			||||||
@ -31,6 +33,9 @@ public class RelationsQueryFilter implements EntityFilter {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private EntityId rootEntity;
 | 
					    private EntityId rootEntity;
 | 
				
			||||||
 | 
					    private boolean isMultiRoot;
 | 
				
			||||||
 | 
					    private EntityType multiRootEntitiesType;
 | 
				
			||||||
 | 
					    private Set<String> multiRootEntityIds;
 | 
				
			||||||
    private EntitySearchDirection direction;
 | 
					    private EntitySearchDirection direction;
 | 
				
			||||||
    private List<RelationEntityTypeFilter> filters;
 | 
					    private List<RelationEntityTypeFilter> filters;
 | 
				
			||||||
    private int maxLevel;
 | 
					    private int maxLevel;
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,7 @@ import com.google.common.util.concurrent.MoreExecutors;
 | 
				
			|||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
 | 
					import org.springframework.util.CollectionUtils;
 | 
				
			||||||
import org.thingsboard.server.common.data.HasCustomerId;
 | 
					import org.thingsboard.server.common.data.HasCustomerId;
 | 
				
			||||||
import org.thingsboard.server.common.data.HasName;
 | 
					import org.thingsboard.server.common.data.HasName;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.AlarmId;
 | 
					import org.thingsboard.server.common.data.id.AlarmId;
 | 
				
			||||||
@ -42,6 +43,8 @@ import org.thingsboard.server.common.data.query.EntityCountQuery;
 | 
				
			|||||||
import org.thingsboard.server.common.data.query.EntityData;
 | 
					import org.thingsboard.server.common.data.query.EntityData;
 | 
				
			||||||
import org.thingsboard.server.common.data.query.EntityDataPageLink;
 | 
					import org.thingsboard.server.common.data.query.EntityDataPageLink;
 | 
				
			||||||
import org.thingsboard.server.common.data.query.EntityDataQuery;
 | 
					import org.thingsboard.server.common.data.query.EntityDataQuery;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.query.EntityFilterType;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.query.RelationsQueryFilter;
 | 
				
			||||||
import org.thingsboard.server.dao.alarm.AlarmService;
 | 
					import org.thingsboard.server.dao.alarm.AlarmService;
 | 
				
			||||||
import org.thingsboard.server.dao.asset.AssetService;
 | 
					import org.thingsboard.server.dao.asset.AssetService;
 | 
				
			||||||
import org.thingsboard.server.dao.customer.CustomerService;
 | 
					import org.thingsboard.server.dao.customer.CustomerService;
 | 
				
			||||||
@ -204,7 +207,8 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
 | 
				
			|||||||
            case ALARM:
 | 
					            case ALARM:
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    hasCustomerId = alarmService.findAlarmByIdAsync(tenantId, new AlarmId(entityId.getId())).get();
 | 
					                    hasCustomerId = alarmService.findAlarmByIdAsync(tenantId, new AlarmId(entityId.getId())).get();
 | 
				
			||||||
                } catch (Exception e) {}
 | 
					                } catch (Exception e) {
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case ENTITY_VIEW:
 | 
					            case ENTITY_VIEW:
 | 
				
			||||||
                hasCustomerId = entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId()));
 | 
					                hasCustomerId = entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId()));
 | 
				
			||||||
@ -223,6 +227,8 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
 | 
				
			|||||||
            throw new IncorrectParameterException("Query entity filter must be specified.");
 | 
					            throw new IncorrectParameterException("Query entity filter must be specified.");
 | 
				
			||||||
        } else if (query.getEntityFilter().getType() == null) {
 | 
					        } else if (query.getEntityFilter().getType() == null) {
 | 
				
			||||||
            throw new IncorrectParameterException("Query entity filter type must be specified.");
 | 
					            throw new IncorrectParameterException("Query entity filter type must be specified.");
 | 
				
			||||||
 | 
					        } else if (query.getEntityFilter().getType().equals(EntityFilterType.RELATIONS_QUERY)) {
 | 
				
			||||||
 | 
					            validateRelationQuery((RelationsQueryFilter) query.getEntityFilter());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -241,4 +247,15 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static void validateRelationQuery(RelationsQueryFilter queryFilter) {
 | 
				
			||||||
 | 
					        if (queryFilter.isMultiRoot() && queryFilter.getMultiRootEntitiesType() ==null){
 | 
				
			||||||
 | 
					            throw new IncorrectParameterException("Multi-root relation query filter should contain 'multiRootEntitiesType'");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (queryFilter.isMultiRoot() && CollectionUtils.isEmpty(queryFilter.getMultiRootEntityIds())) {
 | 
				
			||||||
 | 
					            throw new IncorrectParameterException("Multi-root relation query filter should contain 'multiRootEntityIds' array that contains string representation of UUIDs");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!queryFilter.isMultiRoot() && queryFilter.getRootEntity() == null) {
 | 
				
			||||||
 | 
					            throw new IncorrectParameterException("Relation query filter root entity should not be blank");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -222,6 +222,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
 | 
				
			|||||||
            " THEN (select additional_info from edge where id = entity_id)" +
 | 
					            " THEN (select additional_info from edge where id = entity_id)" +
 | 
				
			||||||
            " END as additional_info";
 | 
					            " END as additional_info";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final String SELECT_RELATED_PARENT_ID = "entity.parent_id AS parent_id";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final String SELECT_API_USAGE_STATE = "(select aus.id, aus.created_time, aus.tenant_id, aus.entity_id, " +
 | 
					    private static final String SELECT_API_USAGE_STATE = "(select aus.id, aus.created_time, aus.tenant_id, aus.entity_id, " +
 | 
				
			||||||
            "coalesce((select title from tenant where id = aus.entity_id), (select title from customer where id = aus.entity_id)) as name " +
 | 
					            "coalesce((select title from tenant where id = aus.entity_id), (select title from customer where id = aus.entity_id)) as name " +
 | 
				
			||||||
            "from api_usage_state as aus)";
 | 
					            "from api_usage_state as aus)";
 | 
				
			||||||
@ -246,7 +248,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
 | 
				
			|||||||
            "        1 as lvl," +
 | 
					            "        1 as lvl," +
 | 
				
			||||||
            "        ARRAY[$in_id] as path" + // initial path
 | 
					            "        ARRAY[$in_id] as path" + // initial path
 | 
				
			||||||
            " FROM relation " +
 | 
					            " FROM relation " +
 | 
				
			||||||
            " WHERE $in_id = :relation_root_id and $in_type = :relation_root_type and relation_type_group = 'COMMON'" +
 | 
					            " WHERE $in_id $rootIdCondition and $in_type = :relation_root_type and relation_type_group = 'COMMON'" +
 | 
				
			||||||
            " GROUP BY from_id, from_type, to_id, to_type, lvl, path" +
 | 
					            " GROUP BY from_id, from_type, to_id, to_type, lvl, path" +
 | 
				
			||||||
            " UNION ALL" +
 | 
					            " UNION ALL" +
 | 
				
			||||||
            " SELECT r.from_id, r.from_type, r.to_id, r.to_type," +
 | 
					            " SELECT r.from_id, r.from_type, r.to_id, r.to_type," +
 | 
				
			||||||
@ -260,14 +262,34 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
 | 
				
			|||||||
            " %s" +
 | 
					            " %s" +
 | 
				
			||||||
            " GROUP BY r.from_id, r.from_type, r.to_id, r.to_type, (re.lvl + 1), (re.path || ARRAY[r.$in_id])" +
 | 
					            " GROUP BY r.from_id, r.from_type, r.to_id, r.to_type, (re.lvl + 1), (re.path || ARRAY[r.$in_id])" +
 | 
				
			||||||
            " )" +
 | 
					            " )" +
 | 
				
			||||||
            " SELECT re.$out_id entity_id, re.$out_type entity_type, max(r_int.lvl) lvl" +
 | 
					            " SELECT re.$out_id entity_id, re.$out_type entity_type, $parenIdExp max(r_int.lvl) lvl" +
 | 
				
			||||||
            " from related_entities r_int" +
 | 
					            " from related_entities r_int" +
 | 
				
			||||||
            "  INNER JOIN relation re ON re.from_id = r_int.from_id AND re.from_type = r_int.from_type" +
 | 
					            "  INNER JOIN relation re ON re.from_id = r_int.from_id AND re.from_type = r_int.from_type" +
 | 
				
			||||||
            "                         AND re.to_id = r_int.to_id AND re.to_type = r_int.to_type" +
 | 
					            "                         AND re.to_id = r_int.to_id AND re.to_type = r_int.to_type" +
 | 
				
			||||||
            "                         AND re.relation_type_group = 'COMMON'" +
 | 
					            "                         AND re.relation_type_group = 'COMMON'" +
 | 
				
			||||||
            " %s GROUP BY entity_id, entity_type) entity";
 | 
					            " %s GROUP BY entity_id, entity_type $parenIdSelection) entity";
 | 
				
			||||||
    private static final String HIERARCHICAL_TO_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "to").replace("$out", "from");
 | 
					
 | 
				
			||||||
    private static final String HIERARCHICAL_FROM_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "from").replace("$out", "to");
 | 
					    private static final String HIERARCHICAL_TO_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE
 | 
				
			||||||
 | 
					            .replace("$parenIdExp", "")
 | 
				
			||||||
 | 
					            .replace("$parenIdSelection", "")
 | 
				
			||||||
 | 
					            .replace("$in", "to").replace("$out", "from")
 | 
				
			||||||
 | 
					            .replace("$rootIdCondition", "= :relation_root_id");
 | 
				
			||||||
 | 
					    private static final String HIERARCHICAL_TO_MR_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE
 | 
				
			||||||
 | 
					            .replace("$parenIdExp", "re.$in_id parent_id, ")
 | 
				
			||||||
 | 
					            .replace("$parenIdSelection", ", parent_id")
 | 
				
			||||||
 | 
					            .replace("$in", "to").replace("$out", "from")
 | 
				
			||||||
 | 
					            .replace("$rootIdCondition", "in (:relation_root_ids)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final String HIERARCHICAL_FROM_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE
 | 
				
			||||||
 | 
					            .replace("$parenIdExp", "")
 | 
				
			||||||
 | 
					            .replace("$parenIdSelection", "")
 | 
				
			||||||
 | 
					            .replace("$in", "from").replace("$out", "to")
 | 
				
			||||||
 | 
					            .replace("$rootIdCondition", "= :relation_root_id");
 | 
				
			||||||
 | 
					    private static final String HIERARCHICAL_FROM_MR_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE
 | 
				
			||||||
 | 
					            .replace("$parenIdExp", "re.$in_id parent_id, ")
 | 
				
			||||||
 | 
					            .replace("$parenIdSelection", ", parent_id")
 | 
				
			||||||
 | 
					            .replace("$in", "from").replace("$out", "to")
 | 
				
			||||||
 | 
					            .replace("$rootIdCondition", "in (:relation_root_ids)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Getter
 | 
					    @Getter
 | 
				
			||||||
    @Value("${sql.relations.max_level:50}")
 | 
					    @Value("${sql.relations.max_level:50}")
 | 
				
			||||||
@ -580,7 +602,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
 | 
				
			|||||||
        String selectFields = "SELECT tenant_id, customer_id, id, created_time, type, name, additional_info "
 | 
					        String selectFields = "SELECT tenant_id, customer_id, id, created_time, type, name, additional_info "
 | 
				
			||||||
                + (entityType.equals(EntityType.ENTITY_VIEW) ? "" : ", label ")
 | 
					                + (entityType.equals(EntityType.ENTITY_VIEW) ? "" : ", label ")
 | 
				
			||||||
                + "FROM " + entityType.name() + " WHERE id in ( SELECT entity_id";
 | 
					                + "FROM " + entityType.name() + " WHERE id in ( SELECT entity_id";
 | 
				
			||||||
        String from = getQueryTemplate(entityFilter.getDirection());
 | 
					        String from = getQueryTemplate(entityFilter.getDirection(), false);
 | 
				
			||||||
        String whereFilter = " WHERE";
 | 
					        String whereFilter = " WHERE";
 | 
				
			||||||
        if (!StringUtils.isEmpty(entityFilter.getRelationType())) {
 | 
					        if (!StringUtils.isEmpty(entityFilter.getRelationType())) {
 | 
				
			||||||
            ctx.addStringParameter("where_relation_type", entityFilter.getRelationType());
 | 
					            ctx.addStringParameter("where_relation_type", entityFilter.getRelationType());
 | 
				
			||||||
@ -623,11 +645,18 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
 | 
				
			|||||||
                + SELECT_TYPE + ", " + SELECT_NAME + ", " + SELECT_LABEL + ", " +
 | 
					                + SELECT_TYPE + ", " + SELECT_NAME + ", " + SELECT_LABEL + ", " +
 | 
				
			||||||
                SELECT_FIRST_NAME + ", " + SELECT_LAST_NAME + ", " + SELECT_EMAIL + ", " + SELECT_REGION + ", " +
 | 
					                SELECT_FIRST_NAME + ", " + SELECT_LAST_NAME + ", " + SELECT_EMAIL + ", " + SELECT_REGION + ", " +
 | 
				
			||||||
                SELECT_TITLE + ", " + SELECT_COUNTRY + ", " + SELECT_STATE + ", " + SELECT_CITY + ", " +
 | 
					                SELECT_TITLE + ", " + SELECT_COUNTRY + ", " + SELECT_STATE + ", " + SELECT_CITY + ", " +
 | 
				
			||||||
                SELECT_ADDRESS + ", " + SELECT_ADDRESS_2 + ", " + SELECT_ZIP + ", " + SELECT_PHONE + ", " + SELECT_ADDITIONAL_INFO +
 | 
					                SELECT_ADDRESS + ", " + SELECT_ADDRESS_2 + ", " + SELECT_ZIP + ", " + SELECT_PHONE + ", " +
 | 
				
			||||||
 | 
					                SELECT_ADDITIONAL_INFO + (entityFilter.isMultiRoot() ? (", " + SELECT_RELATED_PARENT_ID) : "") +
 | 
				
			||||||
                ", entity.entity_type as entity_type";
 | 
					                ", entity.entity_type as entity_type";
 | 
				
			||||||
        String from = getQueryTemplate(entityFilter.getDirection());
 | 
					        String from = getQueryTemplate(entityFilter.getDirection(), entityFilter.isMultiRoot());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (entityFilter.isMultiRoot()) {
 | 
				
			||||||
 | 
					            ctx.addUuidListParameter("relation_root_ids", entityFilter.getMultiRootEntityIds().stream().map(UUID::fromString).collect(Collectors.toList()));
 | 
				
			||||||
 | 
					            ctx.addStringParameter("relation_root_type", entityFilter.getMultiRootEntitiesType().name());
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
            ctx.addUuidParameter("relation_root_id", rootId.getId());
 | 
					            ctx.addUuidParameter("relation_root_id", rootId.getId());
 | 
				
			||||||
            ctx.addStringParameter("relation_root_type", rootId.getEntityType().name());
 | 
					            ctx.addStringParameter("relation_root_type", rootId.getEntityType().name());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        StringBuilder whereFilter = new StringBuilder();
 | 
					        StringBuilder whereFilter = new StringBuilder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -720,12 +749,12 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
 | 
				
			|||||||
        return (maxLevel <= 0 || maxLevel > this.maxLevelAllowed) ? this.maxLevelAllowed : maxLevel;
 | 
					        return (maxLevel <= 0 || maxLevel > this.maxLevelAllowed) ? this.maxLevelAllowed : maxLevel;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String getQueryTemplate(EntitySearchDirection direction) {
 | 
					    private String getQueryTemplate(EntitySearchDirection direction, boolean isMultiRoot) {
 | 
				
			||||||
        String from;
 | 
					        String from;
 | 
				
			||||||
        if (direction.equals(EntitySearchDirection.FROM)) {
 | 
					        if (direction.equals(EntitySearchDirection.FROM)) {
 | 
				
			||||||
            from = HIERARCHICAL_FROM_QUERY_TEMPLATE;
 | 
					            from = isMultiRoot ? HIERARCHICAL_FROM_MR_QUERY_TEMPLATE : HIERARCHICAL_FROM_QUERY_TEMPLATE;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            from = HIERARCHICAL_TO_QUERY_TEMPLATE;
 | 
					            from = isMultiRoot ? HIERARCHICAL_TO_MR_QUERY_TEMPLATE : HIERARCHICAL_TO_QUERY_TEMPLATE;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return from;
 | 
					        return from;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -813,7 +842,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
 | 
				
			|||||||
            case EDGE_SEARCH_QUERY:
 | 
					            case EDGE_SEARCH_QUERY:
 | 
				
			||||||
                return EntityType.EDGE;
 | 
					                return EntityType.EDGE;
 | 
				
			||||||
            case RELATIONS_QUERY:
 | 
					            case RELATIONS_QUERY:
 | 
				
			||||||
                return ((RelationsQueryFilter) entityFilter).getRootEntity().getEntityType();
 | 
					                RelationsQueryFilter rgf = (RelationsQueryFilter) entityFilter;
 | 
				
			||||||
 | 
					                return rgf.isMultiRoot() ? rgf.getMultiRootEntitiesType() : rgf.getRootEntity().getEntityType();
 | 
				
			||||||
            case API_USAGE_STATE:
 | 
					            case API_USAGE_STATE:
 | 
				
			||||||
                return EntityType.API_USAGE_STATE;
 | 
					                return EntityType.API_USAGE_STATE;
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
 | 
				
			|||||||
@ -72,6 +72,7 @@ public class EntityKeyMapping {
 | 
				
			|||||||
    public static final String ZIP = "zip";
 | 
					    public static final String ZIP = "zip";
 | 
				
			||||||
    public static final String PHONE = "phone";
 | 
					    public static final String PHONE = "phone";
 | 
				
			||||||
    public static final String ADDITIONAL_INFO = "additionalInfo";
 | 
					    public static final String ADDITIONAL_INFO = "additionalInfo";
 | 
				
			||||||
 | 
					    public static final String RELATED_PARENT_ID = "parentId";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final List<String> typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO);
 | 
					    public static final List<String> typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO);
 | 
				
			||||||
    public static final List<String> widgetEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME);
 | 
					    public static final List<String> widgetEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME);
 | 
				
			||||||
@ -82,7 +83,7 @@ public class EntityKeyMapping {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public static final Set<String> apiUsageStateEntityFields =  new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME));
 | 
					    public static final Set<String> apiUsageStateEntityFields =  new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME));
 | 
				
			||||||
    public static final Set<String> commonEntityFieldsSet = new HashSet<>(commonEntityFields);
 | 
					    public static final Set<String> commonEntityFieldsSet = new HashSet<>(commonEntityFields);
 | 
				
			||||||
    public static final Set<String> relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO));
 | 
					    public static final Set<String> relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO, RELATED_PARENT_ID));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static {
 | 
					    static {
 | 
				
			||||||
        allowedEntityFieldMap.put(EntityType.DEVICE, new HashSet<>(labeledEntityFields));
 | 
					        allowedEntityFieldMap.put(EntityType.DEVICE, new HashSet<>(labeledEntityFields));
 | 
				
			||||||
@ -120,6 +121,7 @@ public class EntityKeyMapping {
 | 
				
			|||||||
        entityFieldColumnMap.put(ZIP, ModelConstants.ZIP_PROPERTY);
 | 
					        entityFieldColumnMap.put(ZIP, ModelConstants.ZIP_PROPERTY);
 | 
				
			||||||
        entityFieldColumnMap.put(PHONE, ModelConstants.PHONE_PROPERTY);
 | 
					        entityFieldColumnMap.put(PHONE, ModelConstants.PHONE_PROPERTY);
 | 
				
			||||||
        entityFieldColumnMap.put(ADDITIONAL_INFO, ModelConstants.ADDITIONAL_INFO_PROPERTY);
 | 
					        entityFieldColumnMap.put(ADDITIONAL_INFO, ModelConstants.ADDITIONAL_INFO_PROPERTY);
 | 
				
			||||||
 | 
					        entityFieldColumnMap.put(RELATED_PARENT_ID, "parent_id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Map<String, String> contactBasedAliases = new HashMap<>();
 | 
					        Map<String, String> contactBasedAliases = new HashMap<>();
 | 
				
			||||||
        contactBasedAliases.put(NAME, TITLE);
 | 
					        contactBasedAliases.put(NAME, TITLE);
 | 
				
			||||||
 | 
				
			|||||||
@ -15,12 +15,14 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
package org.thingsboard.server.dao.service;
 | 
					package org.thingsboard.server.dao.service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.common.collect.Lists;
 | 
				
			||||||
import com.google.common.util.concurrent.Futures;
 | 
					import com.google.common.util.concurrent.Futures;
 | 
				
			||||||
import com.google.common.util.concurrent.ListenableFuture;
 | 
					import com.google.common.util.concurrent.ListenableFuture;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.apache.commons.lang3.RandomStringUtils;
 | 
					import org.apache.commons.lang3.RandomStringUtils;
 | 
				
			||||||
import org.apache.commons.lang3.RandomUtils;
 | 
					import org.apache.commons.lang3.RandomUtils;
 | 
				
			||||||
import org.hamcrest.Matchers;
 | 
					import org.hamcrest.Matchers;
 | 
				
			||||||
 | 
					import org.apache.commons.lang3.StringUtils;
 | 
				
			||||||
import org.junit.After;
 | 
					import org.junit.After;
 | 
				
			||||||
import org.junit.Assert;
 | 
					import org.junit.Assert;
 | 
				
			||||||
import org.junit.Before;
 | 
					import org.junit.Before;
 | 
				
			||||||
@ -38,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.DeviceId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.EdgeId;
 | 
					import org.thingsboard.server.common.data.id.EdgeId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
					import org.thingsboard.server.common.data.id.EntityId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.IdBased;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
					import org.thingsboard.server.common.data.id.TenantId;
 | 
				
			||||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 | 
					import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 | 
				
			||||||
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
 | 
					import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
 | 
				
			||||||
@ -79,8 +82,11 @@ import java.util.ArrayList;
 | 
				
			|||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.Comparator;
 | 
					import java.util.Comparator;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
import java.util.Random;
 | 
					import java.util.Random;
 | 
				
			||||||
 | 
					import java.util.UUID;
 | 
				
			||||||
import java.util.concurrent.ExecutionException;
 | 
					import java.util.concurrent.ExecutionException;
 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
import java.util.stream.Stream;
 | 
					import java.util.stream.Stream;
 | 
				
			||||||
@ -400,6 +406,112 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
 | 
				
			|||||||
        deviceService.deleteDevicesByTenantId(tenantId);
 | 
					        deviceService.deleteDevicesByTenantId(tenantId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testCountHierarchicalEntitiesByMultiRootQuery() throws InterruptedException {
 | 
				
			||||||
 | 
					        List<Asset> buildings = new ArrayList<>();
 | 
				
			||||||
 | 
					        List<Asset> apartments = new ArrayList<>();
 | 
				
			||||||
 | 
					        Map<String, Map<UUID, String>> entityNameByTypeMap = new HashMap<>();
 | 
				
			||||||
 | 
					        Map<UUID, UUID> 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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
 | 
				
			||||||
 | 
					        Assert.assertEquals(63, count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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))));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
 | 
				
			||||||
 | 
					        Assert.assertEquals(9, count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        deviceService.deleteDevicesByTenantId(tenantId);
 | 
				
			||||||
 | 
					        assetService.deleteAssetsByTenantId(tenantId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testMultiRootHierarchicalFindEntityDataWithAttributesByQuery() throws ExecutionException, InterruptedException {
 | 
				
			||||||
 | 
					        List<Asset> buildings = new ArrayList<>();
 | 
				
			||||||
 | 
					        List<Asset> apartments = new ArrayList<>();
 | 
				
			||||||
 | 
					        Map<String, Map<UUID, String>> entityNameByTypeMap = new HashMap<>();
 | 
				
			||||||
 | 
					        Map<UUID, UUID> 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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        EntityDataSortOrder sortOrder = new EntityDataSortOrder(
 | 
				
			||||||
 | 
					                new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
 | 
				
			||||||
 | 
					        List<EntityKey> entityFields = Lists.newArrayList(
 | 
				
			||||||
 | 
					                new EntityKey(EntityKeyType.ENTITY_FIELD, "name"),
 | 
				
			||||||
 | 
					                new EntityKey(EntityKeyType.ENTITY_FIELD, "parentId"),
 | 
				
			||||||
 | 
					                new EntityKey(EntityKeyType.ENTITY_FIELD, "type")
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        List<EntityKey> latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "status"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        KeyFilter onlineStatusFilter = new KeyFilter();
 | 
				
			||||||
 | 
					        onlineStatusFilter.setKey(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
 | 
				
			||||||
 | 
					        StringFilterPredicate predicate = new StringFilterPredicate();
 | 
				
			||||||
 | 
					        predicate.setOperation(StringOperation.ENDS_WITH);
 | 
				
			||||||
 | 
					        predicate.setValue(FilterPredicateValue.fromString("_1"));
 | 
				
			||||||
 | 
					        onlineStatusFilter.setPredicate(predicate);
 | 
				
			||||||
 | 
					        List<KeyFilter> keyFilters = Collections.singletonList(onlineStatusFilter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
 | 
				
			||||||
 | 
					        PageData<EntityData> data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
 | 
				
			||||||
 | 
					        List<EntityData> 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"))
 | 
				
			||||||
 | 
					                .flatMap(e -> e.getValue().entrySet().stream())
 | 
				
			||||||
 | 
					                .map(Map.Entry::getValue)
 | 
				
			||||||
 | 
					                .filter(e -> StringUtils.endsWith(e, "_1"))
 | 
				
			||||||
 | 
					                .count();
 | 
				
			||||||
 | 
					        Assert.assertEquals(expectedEntitiesCnt, loadedEntities.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Map<UUID, UUID> actualRelations = new HashMap<>();
 | 
				
			||||||
 | 
					        loadedEntities.forEach(ed -> {
 | 
				
			||||||
 | 
					            UUID parentId = UUID.fromString(ed.getLatest().get(EntityKeyType.ENTITY_FIELD).get("parentId").getValue());
 | 
				
			||||||
 | 
					            UUID entityId = ed.getEntityId().getId();
 | 
				
			||||||
 | 
					            Assert.assertEquals(childParentRelationMap.get(entityId), parentId);
 | 
				
			||||||
 | 
					            actualRelations.put(entityId, parentId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            String entityType = ed.getLatest().get(EntityKeyType.ENTITY_FIELD).get("type").getValue();
 | 
				
			||||||
 | 
					            String actualEntityName = ed.getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue();
 | 
				
			||||||
 | 
					            String expectedEntityName = entityNameByTypeMap.get(entityType).get(entityId);
 | 
				
			||||||
 | 
					            Assert.assertEquals(expectedEntityName, actualEntityName);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        deviceService.deleteDevicesByTenantId(tenantId);
 | 
				
			||||||
 | 
					        assetService.deleteAssetsByTenantId(tenantId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void testHierarchicalFindDevicesWithAttributesByQuery() throws ExecutionException, InterruptedException {
 | 
					    public void testHierarchicalFindDevicesWithAttributesByQuery() throws ExecutionException, InterruptedException {
 | 
				
			||||||
        List<Asset> assets = new ArrayList<>();
 | 
					        List<Asset> assets = new ArrayList<>();
 | 
				
			||||||
@ -1674,4 +1786,76 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
 | 
				
			|||||||
        BasicTsKvEntry timeseries = new BasicTsKvEntry(42L, telemetryValue);
 | 
					        BasicTsKvEntry timeseries = new BasicTsKvEntry(42L, telemetryValue);
 | 
				
			||||||
        return timeseriesService.save(SYSTEM_TENANT_ID, entityId, timeseries);
 | 
					        return timeseriesService.save(SYSTEM_TENANT_ID, entityId, timeseries);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void createMultiRootHierarchy(List<Asset> buildings, List<Asset> apartments,
 | 
				
			||||||
 | 
					                                          Map<String, Map<UUID, String>> entityNameByTypeMap,
 | 
				
			||||||
 | 
					                                          Map<UUID, UUID> childParentRelationMap) throws InterruptedException {
 | 
				
			||||||
 | 
					        for (int k = 0; k < 3; k++) {
 | 
				
			||||||
 | 
					            Asset building = new Asset();
 | 
				
			||||||
 | 
					            building.setTenantId(tenantId);
 | 
				
			||||||
 | 
					            building.setName("Building _" + k);
 | 
				
			||||||
 | 
					            building.setType("building");
 | 
				
			||||||
 | 
					            building.setLabel("building label" + k);
 | 
				
			||||||
 | 
					            building = assetService.saveAsset(building);
 | 
				
			||||||
 | 
					            buildings.add(building);
 | 
				
			||||||
 | 
					            entityNameByTypeMap.computeIfAbsent(building.getType(), n -> new HashMap<>()).put(building.getId().getId(), building.getName());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (int i = 0; i < 3; i++) {
 | 
				
			||||||
 | 
					                Asset asset = new Asset();
 | 
				
			||||||
 | 
					                asset.setTenantId(tenantId);
 | 
				
			||||||
 | 
					                asset.setName("Apt " + k + "_" + i);
 | 
				
			||||||
 | 
					                asset.setType("apartment");
 | 
				
			||||||
 | 
					                asset.setLabel("apartment " + i);
 | 
				
			||||||
 | 
					                asset = assetService.saveAsset(asset);
 | 
				
			||||||
 | 
					                //TO make sure devices have different created time
 | 
				
			||||||
 | 
					                Thread.sleep(1);
 | 
				
			||||||
 | 
					                entityNameByTypeMap.computeIfAbsent(asset.getType(), n -> new HashMap<>()).put(asset.getId().getId(), asset.getName());
 | 
				
			||||||
 | 
					                apartments.add(asset);
 | 
				
			||||||
 | 
					                EntityRelation er = new EntityRelation();
 | 
				
			||||||
 | 
					                er.setFrom(building.getId());
 | 
				
			||||||
 | 
					                er.setTo(asset.getId());
 | 
				
			||||||
 | 
					                er.setType("buildingToApt");
 | 
				
			||||||
 | 
					                er.setTypeGroup(RelationTypeGroup.COMMON);
 | 
				
			||||||
 | 
					                relationService.saveRelation(tenantId, er);
 | 
				
			||||||
 | 
					                childParentRelationMap.put(asset.getUuidId(), building.getUuidId());
 | 
				
			||||||
 | 
					                for (int j = 0; j < 3; j++) {
 | 
				
			||||||
 | 
					                    Device device = new Device();
 | 
				
			||||||
 | 
					                    device.setTenantId(tenantId);
 | 
				
			||||||
 | 
					                    device.setName("Heat" + k + "_" + i + "_" + j);
 | 
				
			||||||
 | 
					                    device.setType("heatmeter");
 | 
				
			||||||
 | 
					                    device.setLabel("heatmeter" + (int) (Math.random() * 1000));
 | 
				
			||||||
 | 
					                    device = deviceService.saveDevice(device);
 | 
				
			||||||
 | 
					                    //TO make sure devices have different created time
 | 
				
			||||||
 | 
					                    Thread.sleep(1);
 | 
				
			||||||
 | 
					                    entityNameByTypeMap.computeIfAbsent(device.getType(), n -> new HashMap<>()).put(device.getId().getId(), device.getName());
 | 
				
			||||||
 | 
					                    er = new EntityRelation();
 | 
				
			||||||
 | 
					                    er.setFrom(asset.getId());
 | 
				
			||||||
 | 
					                    er.setTo(device.getId());
 | 
				
			||||||
 | 
					                    er.setType("AptToHeat");
 | 
				
			||||||
 | 
					                    er.setTypeGroup(RelationTypeGroup.COMMON);
 | 
				
			||||||
 | 
					                    relationService.saveRelation(tenantId, er);
 | 
				
			||||||
 | 
					                    childParentRelationMap.put(device.getUuidId(), asset.getUuidId());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for (int j = 0; j < 3; j++) {
 | 
				
			||||||
 | 
					                    Device device = new Device();
 | 
				
			||||||
 | 
					                    device.setTenantId(tenantId);
 | 
				
			||||||
 | 
					                    device.setName("Energy" + k + "_" + i + "_" + j);
 | 
				
			||||||
 | 
					                    device.setType("energymeter");
 | 
				
			||||||
 | 
					                    device.setLabel("energymeter" + (int) (Math.random() * 1000));
 | 
				
			||||||
 | 
					                    device = deviceService.saveDevice(device);
 | 
				
			||||||
 | 
					                    //TO make sure devices have different created time
 | 
				
			||||||
 | 
					                    Thread.sleep(1);
 | 
				
			||||||
 | 
					                    entityNameByTypeMap.computeIfAbsent(device.getType(), n -> new HashMap<>()).put(device.getId().getId(), device.getName());
 | 
				
			||||||
 | 
					                    er = new EntityRelation();
 | 
				
			||||||
 | 
					                    er.setFrom(asset.getId());
 | 
				
			||||||
 | 
					                    er.setTo(device.getId());
 | 
				
			||||||
 | 
					                    er.setType("AptToEnergy");
 | 
				
			||||||
 | 
					                    er.setTypeGroup(RelationTypeGroup.COMMON);
 | 
				
			||||||
 | 
					                    relationService.saveRelation(tenantId, er);
 | 
				
			||||||
 | 
					                    childParentRelationMap.put(device.getUuidId(), asset.getUuidId());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user