From 95bd1fb23cb2a8b1ce4a1d1595889adad59a6314 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 22 May 2025 19:02:19 +0300 Subject: [PATCH] AI rule node: implement data validator for AI settings --- .../data/ai/provider/AiProviderConfig.java | 2 + .../server/dao/ai/AiSettingsServiceImpl.java | 4 + .../validator/AiSettingsDataValidator.java | 109 ++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/service/validator/AiSettingsDataValidator.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AiProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AiProviderConfig.java index 06b82cdef2..f350ed75ec 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AiProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AiProviderConfig.java @@ -36,6 +36,8 @@ import lombok.NoArgsConstructor; }) public abstract class AiProviderConfig { + public abstract AiProvider getProvider(); + @Schema( requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_WRITE, diff --git a/dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsServiceImpl.java index ca23b6bc56..24571458ec 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsServiceImpl.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.HasId; 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.dao.service.DataValidator; import java.util.Optional; @@ -37,8 +38,11 @@ class AiSettingsServiceImpl implements AiSettingsService { private final AiSettingsDao aiSettingsDao; + private final DataValidator aiSettingsValidator; + @Override public AiSettings save(AiSettings aiSettings) { + aiSettingsValidator.validate(aiSettings, AiSettings::getTenantId); try { return aiSettingsDao.saveAndFlush(aiSettings.getTenantId(), aiSettings); } catch (Exception e) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiSettingsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiSettingsDataValidator.java new file mode 100644 index 0000000000..ecfe9ad92a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiSettingsDataValidator.java @@ -0,0 +1,109 @@ +/** + * 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.dao.service.validator; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ai.AiSettings; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.ai.AiSettingsDao; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantService; + +import java.util.Objects; +import java.util.Optional; + +@Component +@RequiredArgsConstructor +class AiSettingsDataValidator extends DataValidator { + + private final TenantService tenantService; + private final AiSettingsDao aiSettingsDao; + + @Override + protected void validateCreate(TenantId tenantId, AiSettings aiSettings) { + validateNumberOfEntitiesPerTenant(tenantId, EntityType.AI_SETTINGS); + } + + @Override + protected AiSettings validateUpdate(TenantId tenantId, AiSettings aiSettings) { + Optional old = aiSettingsDao.findByTenantIdAndId(tenantId, aiSettings.getId()); + if (old.isEmpty()) { + throw new DataValidationException("Can't update non existing AI settings!"); + } + return old.get(); + } + + @Override + protected void validateDataImpl(TenantId tenantId, AiSettings aiSettings) { + // ID validation + if (aiSettings.getId() != null) { + if (aiSettings.getUuidId() == null) { + throw new DataValidationException("AI settings UUID should be specified!"); + } + if (aiSettings.getId().isNullUid()) { + throw new DataValidationException("AI settings UUID must not be the reserved null value!"); + } + } + + // tenant ID validation + if (aiSettings.getTenantId() == null || aiSettings.getTenantId().getId() == null) { + throw new DataValidationException("AI settings should be assigned to tenant!"); + } + if (aiSettings.getTenantId().isSysTenantId()) { + throw new DataValidationException("AI settings cannot be assigned to the system tenant!"); + } + if (!tenantService.tenantExists(tenantId)) { + throw new DataValidationException("AI settings reference a non-existent tenant!"); + } + + // name validation + validateString("AI settings name", aiSettings.getName()); + if (aiSettings.getName().length() > 255) { + throw new DataValidationException("AI settings name should be between 1 and 255 symbols!"); + } + + // provider validation + if (aiSettings.getProvider() == null) { + throw new DataValidationException("AI provider should be specified!"); + } + + // provider config validation + if (aiSettings.getProviderConfig() == null) { + throw new DataValidationException("AI provider config should be specified!"); + } + if (aiSettings.getProviderConfig().getProvider() != aiSettings.getProvider()) { + throw new DataValidationException("AI provider configuration should match the selected AI provider!"); + } + validateString("AI provider API key", aiSettings.getProviderConfig().getApiKey()); + + // model identifier validation + validateString("AI model identifier", aiSettings.getModel()); + if (aiSettings.getModel().length() > 255) { + throw new DataValidationException("AI model identifier should be between 1 and 255 symbols!"); + } + + // model config validation + if (aiSettings.getModelConfig() != null) { + if (!Objects.equals(aiSettings.getModelConfig().getModel(), aiSettings.getModel())) { + throw new DataValidationException("AI model configuration should match the selected AI model!"); + } + } + } + +}