new relation path query
This commit is contained in:
		
							parent
							
								
									2c06aa475f
								
							
						
					
					
						commit
						909497703a
					
				@ -26,6 +26,7 @@ import lombok.extern.slf4j.Slf4j;
 | 
				
			|||||||
import org.thingsboard.common.util.ThingsBoardExecutors;
 | 
					import org.thingsboard.common.util.ThingsBoardExecutors;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.configuration.Argument;
 | 
					import org.thingsboard.server.common.data.cf.configuration.Argument;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
 | 
					import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.cf.configuration.RelationPathQueryDynamicSourceConfiguration;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.configuration.RelationQueryDynamicSourceConfiguration;
 | 
					import org.thingsboard.server.common.data.cf.configuration.RelationQueryDynamicSourceConfiguration;
 | 
				
			||||||
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;
 | 
				
			||||||
@ -178,6 +179,11 @@ public abstract class AbstractCalculatedFieldProcessingService {
 | 
				
			|||||||
                yield Futures.transform(relationService.findByQuery(tenantId, configuration.toEntityRelationsQuery(entityId)),
 | 
					                yield Futures.transform(relationService.findByQuery(tenantId, configuration.toEntityRelationsQuery(entityId)),
 | 
				
			||||||
                        configuration::resolveEntityIds, calculatedFieldCallbackExecutor);
 | 
					                        configuration::resolveEntityIds, calculatedFieldCallbackExecutor);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            case RELATION_PATH_QUERY -> {
 | 
				
			||||||
 | 
					                var configuration = (RelationPathQueryDynamicSourceConfiguration) refDynamicSourceConfiguration;
 | 
				
			||||||
 | 
					                yield Futures.transform(relationService.findByRelationPathQueryAsync(tenantId, configuration.toRelationPathQuery(entityId)),
 | 
				
			||||||
 | 
					                        configuration::resolveEntityIds, calculatedFieldCallbackExecutor);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -20,6 +20,7 @@ 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.relation.EntityRelation;
 | 
					import org.thingsboard.server.common.data.relation.EntityRelation;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
 | 
					import org.thingsboard.server.common.data.relation.EntityRelationInfo;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.relation.EntityRelationPathQuery;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
 | 
					import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
					import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
				
			||||||
import org.thingsboard.server.common.data.rule.RuleChainType;
 | 
					import org.thingsboard.server.common.data.rule.RuleChainType;
 | 
				
			||||||
@ -83,6 +84,8 @@ public interface RelationService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    List<EntityRelation> findRuleNodeToRuleChainRelations(TenantId tenantId, RuleChainType ruleChainType, int limit);
 | 
					    List<EntityRelation> findRuleNodeToRuleChainRelations(TenantId tenantId, RuleChainType ruleChainType, int limit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ListenableFuture<List<EntityRelation>> findByRelationPathQueryAsync(TenantId tenantId, EntityRelationPathQuery relationPathQuery);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//    TODO: This method may be useful for some validations in the future
 | 
					//    TODO: This method may be useful for some validations in the future
 | 
				
			||||||
//    ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
 | 
					//    ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,6 @@ package org.thingsboard.server.common.data.cf.configuration;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
public enum CFArgumentDynamicSourceType {
 | 
					public enum CFArgumentDynamicSourceType {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    RELATION_QUERY
 | 
					    RELATION_QUERY, RELATION_PATH_QUERY
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -26,7 +26,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
 | 
				
			|||||||
        property = "type"
 | 
					        property = "type"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@JsonSubTypes({
 | 
					@JsonSubTypes({
 | 
				
			||||||
        @JsonSubTypes.Type(value = RelationQueryDynamicSourceConfiguration.class, name = "RELATION_QUERY")
 | 
					        @JsonSubTypes.Type(value = RelationQueryDynamicSourceConfiguration.class, name = "RELATION_QUERY"),
 | 
				
			||||||
 | 
					        @JsonSubTypes.Type(value = RelationPathQueryDynamicSourceConfiguration.class, name = "RELATION_PATH_QUERY")
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
@JsonIgnoreProperties(ignoreUnknown = true)
 | 
					@JsonIgnoreProperties(ignoreUnknown = true)
 | 
				
			||||||
public interface CfArgumentDynamicSourceConfiguration {
 | 
					public interface CfArgumentDynamicSourceConfiguration {
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Copyright © 2016-2025 The Thingsboard Authors
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package org.thingsboard.server.common.data.cf.configuration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JsonIgnore;
 | 
				
			||||||
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.EntityId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.relation.EntityRelation;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.relation.EntityRelationPathQuery;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.relation.EntitySearchDirection;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.relation.RelationPathLevel;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.util.CollectionsUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.NoSuchElementException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Data
 | 
				
			||||||
 | 
					public class RelationPathQueryDynamicSourceConfiguration implements CfArgumentDynamicSourceConfiguration, RelationQueryBased {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private List<RelationPathLevel> levels;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public CFArgumentDynamicSourceType getType() {
 | 
				
			||||||
 | 
					        return CFArgumentDynamicSourceType.RELATION_PATH_QUERY;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void validate() {
 | 
				
			||||||
 | 
					        if (CollectionsUtil.isEmpty(levels)) {
 | 
				
			||||||
 | 
					            throw new IllegalArgumentException("At least one relation level must be specified!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        levels.forEach(RelationPathLevel::validate);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public List<EntityId> resolveEntityIds(List<EntityRelation> relations) {
 | 
				
			||||||
 | 
					        EntitySearchDirection lastLevelDirection = getLastLevel().direction();
 | 
				
			||||||
 | 
					        return switch (lastLevelDirection) {
 | 
				
			||||||
 | 
					            case FROM -> relations.stream().map(EntityRelation::getTo).toList();
 | 
				
			||||||
 | 
					            case TO -> relations.stream().map(EntityRelation::getFrom).toList();
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    @JsonIgnore
 | 
				
			||||||
 | 
					    public int getMaxLevel() {
 | 
				
			||||||
 | 
					        return levels != null ? levels.size() : 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public EntityRelationPathQuery toRelationPathQuery(EntityId entityId) {
 | 
				
			||||||
 | 
					        return new EntityRelationPathQuery(entityId, levels);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private RelationPathLevel getLastLevel() {
 | 
				
			||||||
 | 
					        if (CollectionsUtil.isEmpty(levels)) {
 | 
				
			||||||
 | 
					            throw new NoSuchElementException();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return levels.get(levels.size() - 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Copyright © 2016-2025 The Thingsboard Authors
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package org.thingsboard.server.common.data.cf.configuration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public interface RelationQueryBased {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int getMaxLevel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default void validateMaxRelationLevel(String argumentName, int maxAllowedRelationLevel) {
 | 
				
			||||||
 | 
					        if (getMaxLevel() > maxAllowedRelationLevel) {
 | 
				
			||||||
 | 
					            throw new IllegalArgumentException("Max relation level is greater than configured " +
 | 
				
			||||||
 | 
					                    "maximum allowed relation level in tenant profile: " + maxAllowedRelationLevel + " for argument: " + argumentName);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -29,7 +29,7 @@ import java.util.Collections;
 | 
				
			|||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Data
 | 
					@Data
 | 
				
			||||||
public class RelationQueryDynamicSourceConfiguration implements CfArgumentDynamicSourceConfiguration {
 | 
					public class RelationQueryDynamicSourceConfiguration implements CfArgumentDynamicSourceConfiguration, RelationQueryBased {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private int maxLevel;
 | 
					    private int maxLevel;
 | 
				
			||||||
    private boolean fetchLastLevelOnly;
 | 
					    private boolean fetchLastLevelOnly;
 | 
				
			||||||
@ -59,13 +59,6 @@ public class RelationQueryDynamicSourceConfiguration implements CfArgumentDynami
 | 
				
			|||||||
        return maxLevel == 1;
 | 
					        return maxLevel == 1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void validateMaxRelationLevel(String argumentName, int maxAllowedRelationLevel) {
 | 
					 | 
				
			||||||
        if (maxLevel > maxAllowedRelationLevel) {
 | 
					 | 
				
			||||||
            throw new IllegalArgumentException("Max relation level is greater than configured " +
 | 
					 | 
				
			||||||
                                               "maximum allowed relation level in tenant profile: " + maxAllowedRelationLevel + " for argument: " + argumentName);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public EntityRelationsQuery toEntityRelationsQuery(EntityId rootEntityId) {
 | 
					    public EntityRelationsQuery toEntityRelationsQuery(EntityId rootEntityId) {
 | 
				
			||||||
        if (isSimpleRelation()) {
 | 
					        if (isSimpleRelation()) {
 | 
				
			||||||
            throw new IllegalArgumentException("Entity relations query can't be created for a simple relation!");
 | 
					            throw new IllegalArgumentException("Entity relations query can't be created for a simple relation!");
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Copyright © 2016-2025 The Thingsboard Authors
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package org.thingsboard.server.common.data.relation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.EntityId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public record EntityRelationPathQuery(EntityId rootEntityId, List<RelationPathLevel> levels) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Copyright © 2016-2025 The Thingsboard Authors
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package org.thingsboard.server.common.data.relation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.StringUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public record RelationPathLevel(EntitySearchDirection direction, String relationType) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void validate() {
 | 
				
			||||||
 | 
					        if (direction == null) {
 | 
				
			||||||
 | 
					            throw new IllegalArgumentException("Direction must be specified!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (StringUtils.isBlank(relationType)) {
 | 
				
			||||||
 | 
					            throw new IllegalArgumentException("Relation type must be specified!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -32,17 +32,21 @@ import org.springframework.stereotype.Service;
 | 
				
			|||||||
import org.springframework.transaction.annotation.Transactional;
 | 
					import org.springframework.transaction.annotation.Transactional;
 | 
				
			||||||
import org.springframework.transaction.event.TransactionalEventListener;
 | 
					import org.springframework.transaction.event.TransactionalEventListener;
 | 
				
			||||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
 | 
					import org.springframework.transaction.support.TransactionSynchronizationManager;
 | 
				
			||||||
 | 
					import org.springframework.util.CollectionUtils;
 | 
				
			||||||
import org.thingsboard.common.util.ThingsBoardExecutors;
 | 
					import org.thingsboard.common.util.ThingsBoardExecutors;
 | 
				
			||||||
import org.thingsboard.server.cache.TbTransactionalCache;
 | 
					import org.thingsboard.server.cache.TbTransactionalCache;
 | 
				
			||||||
import org.thingsboard.server.common.data.StringUtils;
 | 
					import org.thingsboard.server.common.data.StringUtils;
 | 
				
			||||||
import org.thingsboard.server.common.data.audit.ActionType;
 | 
					import org.thingsboard.server.common.data.audit.ActionType;
 | 
				
			||||||
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.id.UUIDBased;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.EntityRelation;
 | 
					import org.thingsboard.server.common.data.relation.EntityRelation;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
 | 
					import org.thingsboard.server.common.data.relation.EntityRelationInfo;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.relation.EntityRelationPathQuery;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
 | 
					import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
 | 
				
			||||||
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 org.thingsboard.server.common.data.relation.RelationPathLevel;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
					import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
 | 
					import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
 | 
				
			||||||
import org.thingsboard.server.common.data.rule.RuleChainType;
 | 
					import org.thingsboard.server.common.data.rule.RuleChainType;
 | 
				
			||||||
@ -495,6 +499,23 @@ public class BaseRelationService implements RelationService {
 | 
				
			|||||||
        return relationDao.findRuleNodeToRuleChainRelations(ruleChainType, limit);
 | 
					        return relationDao.findRuleNodeToRuleChainRelations(ruleChainType, limit);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public ListenableFuture<List<EntityRelation>> findByRelationPathQueryAsync(TenantId tenantId, EntityRelationPathQuery relationPathQuery) {
 | 
				
			||||||
 | 
					        log.trace("Executing findByRelationPathQuery, tenantId [{}], relationPathQuery {}", tenantId, relationPathQuery);
 | 
				
			||||||
 | 
					        validateId(tenantId, id -> "Invalid tenant id: " + id);
 | 
				
			||||||
 | 
					        validate(relationPathQuery);
 | 
				
			||||||
 | 
					        return executor.submit(() -> relationDao.findByRelationPathQuery(tenantId, relationPathQuery));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void validate(EntityRelationPathQuery relationPathQuery) {
 | 
				
			||||||
 | 
					        validateId((UUIDBased) relationPathQuery.rootEntityId(), id -> "Invalid root entity id: " + id);
 | 
				
			||||||
 | 
					        List<RelationPathLevel> levels = relationPathQuery.levels();
 | 
				
			||||||
 | 
					        if (CollectionUtils.isEmpty(levels)) {
 | 
				
			||||||
 | 
					            throw new DataValidationException("Relation path levels should be specified!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        levels.forEach(RelationPathLevel::validate);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected void validate(EntityRelation relation) {
 | 
					    protected void validate(EntityRelation relation) {
 | 
				
			||||||
        if (relation == null) {
 | 
					        if (relation == null) {
 | 
				
			||||||
            throw new DataValidationException("Relation type should be specified!");
 | 
					            throw new DataValidationException("Relation type should be specified!");
 | 
				
			||||||
 | 
				
			|||||||
@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
 | 
				
			|||||||
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.relation.EntityRelation;
 | 
					import org.thingsboard.server.common.data.relation.EntityRelation;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.relation.EntityRelationPathQuery;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
					import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
				
			||||||
import org.thingsboard.server.common.data.rule.RuleChainType;
 | 
					import org.thingsboard.server.common.data.rule.RuleChainType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -71,4 +72,6 @@ public interface RelationDao {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    List<EntityRelation> findRuleNodeToRuleChainRelations(RuleChainType ruleChainType, int limit);
 | 
					    List<EntityRelation> findRuleNodeToRuleChainRelations(RuleChainType ruleChainType, int limit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    List<EntityRelation> findByRelationPathQuery(TenantId tenantId, EntityRelationPathQuery relationPathQuery);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			|||||||
import org.springframework.stereotype.Component;
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.CalculatedField;
 | 
					import org.thingsboard.server.common.data.cf.CalculatedField;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration;
 | 
					import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.configuration.RelationQueryDynamicSourceConfiguration;
 | 
					import org.thingsboard.server.common.data.cf.configuration.RelationQueryBased;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.configuration.ScheduledUpdateSupportedCalculatedFieldConfiguration;
 | 
					import org.thingsboard.server.common.data.cf.configuration.ScheduledUpdateSupportedCalculatedFieldConfiguration;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
					import org.thingsboard.server.common.data.id.TenantId;
 | 
				
			||||||
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
 | 
					import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
 | 
				
			||||||
@ -98,10 +98,10 @@ public class CalculatedFieldDataValidator extends DataValidator<CalculatedField>
 | 
				
			|||||||
        if (!(calculatedField.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration argumentsBasedCfg)) {
 | 
					        if (!(calculatedField.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration argumentsBasedCfg)) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Map<String, RelationQueryDynamicSourceConfiguration> relationQueryBasedArguments = argumentsBasedCfg.getArguments().entrySet()
 | 
					        Map<String, RelationQueryBased> relationQueryBasedArguments = argumentsBasedCfg.getArguments().entrySet()
 | 
				
			||||||
                .stream()
 | 
					                .stream()
 | 
				
			||||||
                .filter(entry -> entry.getValue().hasDynamicSource())
 | 
					                .filter(entry -> entry.getValue().hasDynamicSource())
 | 
				
			||||||
                .collect(Collectors.toMap(Map.Entry::getKey, entry -> (RelationQueryDynamicSourceConfiguration) entry.getValue().getRefDynamicSourceConfiguration()));
 | 
					                .collect(Collectors.toMap(Map.Entry::getKey, entry -> (RelationQueryBased) entry.getValue().getRefDynamicSourceConfiguration()));
 | 
				
			||||||
        if (relationQueryBasedArguments.isEmpty()) {
 | 
					        if (relationQueryBasedArguments.isEmpty()) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -25,6 +25,9 @@ import org.thingsboard.server.common.data.id.EntityId;
 | 
				
			|||||||
import org.thingsboard.server.common.data.id.EntityIdFactory;
 | 
					import org.thingsboard.server.common.data.id.EntityIdFactory;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
					import org.thingsboard.server.common.data.id.TenantId;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.EntityRelation;
 | 
					import org.thingsboard.server.common.data.relation.EntityRelation;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.relation.EntityRelationPathQuery;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.relation.EntitySearchDirection;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.relation.RelationPathLevel;
 | 
				
			||||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
					import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
				
			||||||
import org.thingsboard.server.common.data.rule.RuleChainType;
 | 
					import org.thingsboard.server.common.data.rule.RuleChainType;
 | 
				
			||||||
import org.thingsboard.server.dao.DaoUtil;
 | 
					import org.thingsboard.server.dao.DaoUtil;
 | 
				
			||||||
@ -43,6 +46,7 @@ import java.util.stream.Collectors;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import static org.thingsboard.server.dao.model.ModelConstants.RELATION_FROM_ID_PROPERTY;
 | 
					import static org.thingsboard.server.dao.model.ModelConstants.RELATION_FROM_ID_PROPERTY;
 | 
				
			||||||
import static org.thingsboard.server.dao.model.ModelConstants.RELATION_FROM_TYPE_PROPERTY;
 | 
					import static org.thingsboard.server.dao.model.ModelConstants.RELATION_FROM_TYPE_PROPERTY;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TABLE_NAME;
 | 
				
			||||||
import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TO_ID_PROPERTY;
 | 
					import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TO_ID_PROPERTY;
 | 
				
			||||||
import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TO_TYPE_PROPERTY;
 | 
					import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TO_TYPE_PROPERTY;
 | 
				
			||||||
import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TYPE_GROUP_PROPERTY;
 | 
					import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TYPE_GROUP_PROPERTY;
 | 
				
			||||||
@ -293,4 +297,99 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
 | 
				
			|||||||
    public List<EntityRelation> findRuleNodeToRuleChainRelations(RuleChainType ruleChainType, int limit) {
 | 
					    public List<EntityRelation> findRuleNodeToRuleChainRelations(RuleChainType ruleChainType, int limit) {
 | 
				
			||||||
        return DaoUtil.convertDataList(relationRepository.findRuleNodeToRuleChainRelations(ruleChainType, PageRequest.of(0, limit)));
 | 
					        return DaoUtil.convertDataList(relationRepository.findRuleNodeToRuleChainRelations(ruleChainType, PageRequest.of(0, limit)));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public List<EntityRelation> findByRelationPathQuery(TenantId tenantId, EntityRelationPathQuery query) {
 | 
				
			||||||
 | 
					        List<RelationPathLevel> levels = query.levels();
 | 
				
			||||||
 | 
					        if (levels == null || levels.isEmpty()) {
 | 
				
			||||||
 | 
					            return Collections.emptyList();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        String sql = buildRelationPathSql(query);
 | 
				
			||||||
 | 
					        Object[] params = buildRelationPathParams(query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        log.info("[{}] relation path query: {}", tenantId, sql);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return jdbcTemplate.queryForList(sql, params).stream()
 | 
				
			||||||
 | 
					                .map(row -> {
 | 
				
			||||||
 | 
					                    var entityRelation = new EntityRelation();
 | 
				
			||||||
 | 
					                    var fromId = (UUID) row.get(RELATION_FROM_ID_PROPERTY);
 | 
				
			||||||
 | 
					                    var fromType = (String) row.get(RELATION_FROM_TYPE_PROPERTY);
 | 
				
			||||||
 | 
					                    var toId = (UUID) row.get(RELATION_TO_ID_PROPERTY);
 | 
				
			||||||
 | 
					                    var toType = (String) row.get(RELATION_TO_TYPE_PROPERTY);
 | 
				
			||||||
 | 
					                    var grp = (String) row.get(RELATION_TYPE_GROUP_PROPERTY);
 | 
				
			||||||
 | 
					                    var type = (String) row.get(RELATION_TYPE_PROPERTY);
 | 
				
			||||||
 | 
					                    var version = (Long) row.get(VERSION_COLUMN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    entityRelation.setFrom(EntityIdFactory.getByTypeAndUuid(fromType, fromId));
 | 
				
			||||||
 | 
					                    entityRelation.setTo(EntityIdFactory.getByTypeAndUuid(toType, toId));
 | 
				
			||||||
 | 
					                    entityRelation.setType(type);
 | 
				
			||||||
 | 
					                    entityRelation.setTypeGroup(RelationTypeGroup.valueOf(grp));
 | 
				
			||||||
 | 
					                    entityRelation.setVersion(version);
 | 
				
			||||||
 | 
					                    return entityRelation;
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .collect(Collectors.toList());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private Object[] buildRelationPathParams(EntityRelationPathQuery query) {
 | 
				
			||||||
 | 
					        final List<Object> params = new ArrayList<>();
 | 
				
			||||||
 | 
					        // seed
 | 
				
			||||||
 | 
					        params.add(query.rootEntityId().getId());
 | 
				
			||||||
 | 
					        params.add(query.rootEntityId().getEntityType().name());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // levels
 | 
				
			||||||
 | 
					        for (var lvl : query.levels()) {
 | 
				
			||||||
 | 
					            params.add(lvl.relationType());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return params.toArray();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static String buildRelationPathSql(EntityRelationPathQuery query) {
 | 
				
			||||||
 | 
					        List<RelationPathLevel> levels = query.levels();
 | 
				
			||||||
 | 
					        StringBuilder sb = new StringBuilder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sb.append("WITH seed AS (\n")
 | 
				
			||||||
 | 
					                .append("  SELECT ?::uuid AS id, ?::varchar AS type\n")
 | 
				
			||||||
 | 
					                .append(")");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String prev = "seed";
 | 
				
			||||||
 | 
					        for (int i = 0; i < levels.size() - 1; i++) {
 | 
				
			||||||
 | 
					            RelationPathLevel lvl = levels.get(i);
 | 
				
			||||||
 | 
					            boolean down = lvl.direction() == EntitySearchDirection.FROM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            String cur = "lvl" + (i + 1);
 | 
				
			||||||
 | 
					            String joinCond = down
 | 
				
			||||||
 | 
					                    ? "r.from_id = p.id AND r.from_type = p.type"
 | 
				
			||||||
 | 
					                    : "r.to_id   = p.id AND r.to_type   = p.type";
 | 
				
			||||||
 | 
					            String selectNext = down
 | 
				
			||||||
 | 
					                    ? "r.to_id   AS id, r.to_type   AS type"
 | 
				
			||||||
 | 
					                    : "r.from_id AS id, r.from_type AS type";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            sb.append(",\n").append(cur).append(" AS (\n")
 | 
				
			||||||
 | 
					                    .append("  SELECT ").append(selectNext).append("\n")
 | 
				
			||||||
 | 
					                    .append("  FROM ").append(RELATION_TABLE_NAME).append(" r\n")
 | 
				
			||||||
 | 
					                    .append("  JOIN ").append(prev).append(" p ON ").append(joinCond).append("\n")
 | 
				
			||||||
 | 
					                    .append("  WHERE r.relation_type_group = '").append(RelationTypeGroup.COMMON).append("'\n")
 | 
				
			||||||
 | 
					                    .append("    AND r.relation_type = ?\n")
 | 
				
			||||||
 | 
					                    .append(")");
 | 
				
			||||||
 | 
					            prev = cur;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        RelationPathLevel last = levels.get(levels.size() - 1);
 | 
				
			||||||
 | 
					        boolean lastDown = last.direction() == EntitySearchDirection.FROM;
 | 
				
			||||||
 | 
					        String prevForLast = (levels.size() == 1) ? "seed" : prev;
 | 
				
			||||||
 | 
					        String lastJoin = lastDown
 | 
				
			||||||
 | 
					                ? "r.from_id = p.id AND r.from_type = p.type"
 | 
				
			||||||
 | 
					                : "r.to_id   = p.id AND r.to_type   = p.type";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sb.append("\n")
 | 
				
			||||||
 | 
					                .append("SELECT r.from_id, r.from_type, r.to_id, r.to_type,\n")
 | 
				
			||||||
 | 
					                .append("       r.relation_type_group, r.relation_type, r.version\n")
 | 
				
			||||||
 | 
					                .append("FROM ").append(RELATION_TABLE_NAME).append(" r\n")
 | 
				
			||||||
 | 
					                .append("JOIN ").append(prevForLast).append(" p ON ").append(lastJoin).append("\n")
 | 
				
			||||||
 | 
					                .append("WHERE r.relation_type_group = '").append(RelationTypeGroup.COMMON).append("'\n")
 | 
				
			||||||
 | 
					                .append("  AND r.relation_type = ?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sb.toString();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user