Browse Source

AI rule node: support sorting on provider and model ID

pull/13371/head
Dmytro Skarzhynets 1 year ago
parent
commit
d732e31370
No known key found for this signature in database GPG Key ID: 2B51652F224037DF
  1. 26
      application/src/main/java/org/thingsboard/server/controller/AiModelSettingsController.java
  2. 23
      dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
  3. 3
      dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsServiceImpl.java
  4. 12
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AiModelSettingsEntity.java
  5. 56
      dao/src/main/java/org/thingsboard/server/dao/service/Validator.java
  6. 24
      dao/src/main/java/org/thingsboard/server/dao/sql/ai/JpaAiModelSettingsDao.java

26
application/src/main/java/org/thingsboard/server/controller/AiModelSettingsController.java

@ -35,7 +35,6 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static org.thingsboard.server.controller.ControllerConstants.AI_MODEL_SETTINGS_TEXT_SEARCH_DESCRIPTION;
@ -48,9 +47,7 @@ import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHO
@RestController
@RequestMapping("/api/ai-model-settings")
public class AiModelSettingsController extends BaseController {
private static final Set<String> ALLOWED_SORT_PROPERTIES = Set.of("createdTime", "name");
class AiModelSettingsController extends BaseController {
@ApiOperation(
value = "Create or update AI model settings (saveAiModelSettings)",
@ -75,16 +72,16 @@ public class AiModelSettingsController extends BaseController {
TENANT_AUTHORITY_PARAGRAPH
)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@GetMapping("/{aiModelSettingsId}")
@GetMapping("/{settingsUuid}")
public AiModelSettings getAiModelSettingsById(
@Parameter(
description = "ID of the AI model settings record",
required = true,
example = "de7900d4-30e2-11f0-9cd2-0242ac120002"
)
@PathVariable("aiModelSettingsId") UUID aiModelSettingsUuid
@PathVariable UUID settingsUuid
) throws ThingsboardException {
return checkAiModelSettingsId(new AiModelSettingsId(aiModelSettingsUuid), Operation.READ);
return checkAiModelSettingsId(new AiModelSettingsId(settingsUuid), Operation.READ);
}
@ApiOperation(
@ -101,24 +98,17 @@ public class AiModelSettingsController extends BaseController {
@RequestParam int page,
@Parameter(description = AI_MODEL_SETTINGS_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name"}))
@Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "provider", "modelId"}))
@RequestParam(required = false) String sortProperty,
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}))
@RequestParam(required = false) String sortOrder
) throws ThingsboardException {
var user = getCurrentUser();
accessControlService.checkPermission(user, Resource.AI_MODEL_SETTINGS, Operation.READ);
validateSortProperty(sortProperty);
var pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return aiModelSettingsService.findAiModelSettingsByTenantId(user.getTenantId(), pageLink);
}
private static void validateSortProperty(String sortProperty) {
if (sortProperty != null && !ALLOWED_SORT_PROPERTIES.contains(sortProperty)) {
throw new IllegalArgumentException("Unsupported sort property '" + sortProperty + "'! Only '" + String.join("', '", ALLOWED_SORT_PROPERTIES) + "' are allowed.");
}
}
@ApiOperation(
value = "Delete AI model settings by ID (deleteAiModelSettingsById)",
notes = "Deletes the AI model settings record by its `id`. " +
@ -127,17 +117,17 @@ public class AiModelSettingsController extends BaseController {
TENANT_AUTHORITY_PARAGRAPH
)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@DeleteMapping("/{aiModelSettingsId}")
@DeleteMapping("/{settingsUuid}")
public boolean deleteAiModelSettingsById(
@Parameter(
description = "ID of the AI model settings record",
required = true,
example = "de7900d4-30e2-11f0-9cd2-0242ac120002"
)
@PathVariable("aiModelSettingsId") UUID aiModelSettingsUuid
@PathVariable UUID settingsUuid
) throws ThingsboardException {
var user = getCurrentUser();
var settingsId = new AiModelSettingsId(aiModelSettingsUuid);
var settingsId = new AiModelSettingsId(settingsUuid);
accessControlService.checkPermission(user, Resource.AI_MODEL_SETTINGS, Operation.DELETE);
Optional<AiModelSettings> toDelete = aiModelSettingsService.findAiModelSettingsByTenantIdAndId(user.getTenantId(), settingsId);
if (toDelete.isEmpty()) {

23
dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java

@ -43,10 +43,9 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
public abstract class DaoUtil {
public final class DaoUtil {
private DaoUtil() {
}
private DaoUtil() {}
public static <T> PageData<T> toPageData(Page<? extends ToData<T>> page) {
List<T> data = convertDataList(page.getContent());
@ -98,17 +97,17 @@ public abstract class DaoUtil {
return PageRequest.of(pageLink.getPage(), pageLink.getPageSize(), pageLink.toSort(sortOrders, columnMap, addDefaultSorting));
}
public static <T> List<T> convertDataList(Collection<? extends ToData<T>> toDataList) {
List<T> list = Collections.emptyList();
if (toDataList != null && !toDataList.isEmpty()) {
list = new ArrayList<>();
for (ToData<T> object : toDataList) {
if (object != null) {
list.add(object.toData());
}
public static <T> List<T> convertDataList(Collection<? extends ToData<T>> toConvert) {
if (CollectionUtils.isEmpty(toConvert)) {
return Collections.emptyList();
}
List<T> converted = new ArrayList<>(toConvert.size());
for (ToData<T> object : toConvert) {
if (object != null) {
converted.add(object.toData());
}
}
return list;
return converted;
}
public static <T> T getData(ToData<T> data) {

3
dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsServiceImpl.java

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.CachedVersionedEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.model.sql.AiModelSettingsEntity;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.sql.JpaExecutorService;
@ -92,7 +93,7 @@ class AiModelSettingsServiceImpl extends CachedVersionedEntityService<AiModelSet
@Override
public PageData<AiModelSettings> findAiModelSettingsByTenantId(TenantId tenantId, PageLink pageLink) {
validatePageLink(pageLink);
validatePageLink(pageLink, AiModelSettingsEntity.ALLOWED_SORT_PROPERTIES);
return aiModelSettingsDao.findAllByTenantId(tenantId, pageLink);
}

12
dao/src/main/java/org/thingsboard/server/dao/model/sql/AiModelSettingsEntity.java

@ -31,8 +31,12 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseVersionedEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
@Getter
@ -43,7 +47,13 @@ import java.util.UUID;
public class AiModelSettingsEntity extends BaseVersionedEntity<AiModelSettings> {
public static final Map<String, String> COLUMN_MAP = Map.of(
"createdTime", "created_time"
"createdTime", "created_time",
"provider", "(configuration -> 'providerConfig' ->> 'provider')",
"modelId", "(configuration -> 'modelConfig' ->> 'modelId')"
);
public static final Set<String> ALLOWED_SORT_PROPERTIES = Collections.unmodifiableSet(
new LinkedHashSet<>(List.of("createdTime", "name", "provider", "modelId"))
);
@Column(name = ModelConstants.AI_MODEL_SETTINGS_TENANT_ID_COLUMN_NAME, nullable = false, columnDefinition = "UUID")

56
dao/src/main/java/org/thingsboard/server/dao/service/Validator.java

@ -26,11 +26,14 @@ import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.regex.Pattern;
public class Validator {
public final class Validator {
private Validator() {}
public static final Pattern PROPERTY_PATTERN = Pattern.compile("^[\\p{L}0-9_-]+$"); // Unicode letters, numbers, '_' and '-' allowed
@ -204,22 +207,61 @@ public class Validator {
}
/**
* This method validate <code>PageLink</code> page link. If pageLink is invalid than throw
* <code>IncorrectParameterException</code> exception
* Validates the specified PageLink object delegating to {@link #validatePageLink(PageLink, Set)}
* with no restrictions on allowed sort properties.
*
* @param pageLink the page link
* @param pageLink the PageLink object to validate
* @throws IncorrectParameterException if the pageLink is null, has invalid page size,
* invalid page number, or invalid sort property
* @see #validatePageLink(PageLink, Set)
*/
public static void validatePageLink(PageLink pageLink) {
validatePageLink(pageLink, null);
}
/**
* Validates the specified PageLink object ensuring that:
* <ul>
* <li>The PageLink object is not null</li>
* <li>The page size is greater than zero</li>
* <li>The page number is non-negative</li>
* <li>If sorting is specified, the sort property is valid and allowed</li>
* </ul>
*
* <p>When {@code allowedSortProperties} is provided, the sort property
* must be contained within this set. If {@code allowedSortProperties} is null,
* only basic sort property validation is performed.
*
* @param pageLink the PageLink object to validate.
* @param allowedSortProperties a Set of allowed sort property names, or null to skip
* this validation. If provided and the PageLink contains
* a sort order, the sort property must be in this set.
* @throws IncorrectParameterException if any of the following conditions are met:
* <ul>
* <li>{@code pageLink} is null</li>
* <li>page size is less than 1</li>
* <li>page number is negative</li>
* <li>sort property is malformed</li>
* <li>sort property is not in the {@code allowedSortProperties} set (when the set is provided and not null)</li>
* </ul>
*/
public static void validatePageLink(PageLink pageLink, Set<String> allowedSortProperties) {
if (pageLink == null) {
throw new IncorrectParameterException("Page link must be specified.");
} else if (pageLink.getPageSize() < 1) {
throw new IncorrectParameterException("Incorrect page link page size '"+pageLink.getPageSize()+"'. Page size must be greater than zero.");
throw new IncorrectParameterException("Incorrect page link page size '" + pageLink.getPageSize() + "'. Page size must be greater than zero.");
} else if (pageLink.getPage() < 0) {
throw new IncorrectParameterException("Incorrect page link page '"+pageLink.getPage()+"'. Page must be positive integer.");
throw new IncorrectParameterException("Incorrect page link page '" + pageLink.getPage() + "'. Page must be positive integer.");
} else if (pageLink.getSortOrder() != null) {
if (!isValidProperty(pageLink.getSortOrder().getProperty())) {
String sortProperty = pageLink.getSortOrder().getProperty();
if (!isValidProperty(sortProperty)) {
throw new IncorrectParameterException("Invalid page link sort property");
}
if (allowedSortProperties != null && !allowedSortProperties.contains(sortProperty)) {
throw new IncorrectParameterException(
"Unsupported sort property '" + sortProperty + "'. Only '" + String.join("', '", allowedSortProperties) + "' are allowed."
);
}
}
}

24
dao/src/main/java/org/thingsboard/server/dao/sql/ai/JpaAiModelSettingsDao.java

@ -17,6 +17,9 @@ package org.thingsboard.server.dao.sql.ai;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.JpaSort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
@ -25,6 +28,7 @@ import org.thingsboard.server.common.data.id.AiModelSettingsId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.ai.AiModelSettingsDao;
import org.thingsboard.server.dao.model.sql.AiModelSettingsEntity;
@ -65,15 +69,27 @@ class JpaAiModelSettingsDao extends JpaAbstractDao<AiModelSettingsEntity, AiMode
@Override
public PageData<AiModelSettings> findByTenantId(UUID tenantId, PageLink pageLink) {
return DaoUtil.toPageData(aiModelSettingsRepository.findByTenantId(
tenantId, StringUtils.defaultIfEmpty(pageLink.getTextSearch(), null), DaoUtil.toPageable(pageLink, AiModelSettingsEntity.COLUMN_MAP))
tenantId, StringUtils.defaultIfEmpty(pageLink.getTextSearch(), null), toPageRequest(pageLink))
);
}
@Override
public PageData<AiModelSettingsId> findIdsByTenantId(UUID tenantId, PageLink pageLink) {
return DaoUtil.pageToPageData(
aiModelSettingsRepository.findIdsByTenantId(tenantId, DaoUtil.toPageable(pageLink, AiModelSettingsEntity.COLUMN_MAP)).map(AiModelSettingsId::new)
);
return DaoUtil.pageToPageData(aiModelSettingsRepository.findIdsByTenantId(tenantId, toPageRequest(pageLink)).map(AiModelSettingsId::new));
}
private static PageRequest toPageRequest(PageLink pageLink) {
Sort sort;
SortOrder sortOrder = pageLink.getSortOrder();
if (sortOrder == null) {
sort = Sort.by(Sort.Direction.ASC, "id");
} else {
sort = JpaSort.unsafe(
Sort.Direction.fromString(sortOrder.getDirection().name()),
AiModelSettingsEntity.COLUMN_MAP.getOrDefault(sortOrder.getProperty(), sortOrder.getProperty())
);
}
return PageRequest.of(pageLink.getPage(), pageLink.getPageSize(), sort);
}
@Override

Loading…
Cancel
Save