From f2278f86c9e4f56679d84c2932c66a2bc59a2543 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 3 Jul 2025 14:57:32 +0300 Subject: [PATCH] AI rule node: add validation using annotations --- .../controller/AiModelSettingsController.java | 5 ++- .../common/data/ai/AiModelSettings.java | 10 +++++ .../ai/model/chat/AmazonBedrockChatModel.java | 22 +++++++---- .../ai/model/chat/AnthropicChatModel.java | 24 +++++++----- .../ai/model/chat/AzureOpenAiChatModel.java | 22 +++++++---- .../ai/model/chat/GitHubModelsChatModel.java | 22 +++++++---- .../model/chat/GoogleAiGeminiChatModel.java | 24 +++++++----- .../chat/GoogleVertexAiGeminiChatModel.java | 24 +++++++----- .../ai/model/chat/MistralAiChatModel.java | 22 +++++++---- .../data/ai/model/chat/OpenAiChatModel.java | 22 +++++++---- .../provider/AmazonBedrockProviderConfig.java | 8 +++- .../ai/provider/AnthropicProviderConfig.java | 6 ++- .../provider/AzureOpenAiProviderConfig.java | 8 +++- .../provider/GitHubModelsProviderConfig.java | 6 ++- .../GoogleAiGeminiProviderConfig.java | 6 ++- .../GoogleVertexAiGeminiProviderConfig.java | 10 +++-- .../ai/provider/MistralAiProviderConfig.java | 6 ++- .../ai/provider/OpenAiProviderConfig.java | 6 ++- .../common/data/validation/NoNullChar.java | 39 +++++++++++++++++++ .../dao/service/ConstraintValidator.java | 8 ++-- .../dao/service/NoNullCharValidator.java | 29 ++++++++++++++ .../AiModelSettingsDataValidator.java | 11 ------ 22 files changed, 247 insertions(+), 93 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/validation/NoNullChar.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/service/NoNullCharValidator.java diff --git a/application/src/main/java/org/thingsboard/server/controller/AiModelSettingsController.java b/application/src/main/java/org/thingsboard/server/controller/AiModelSettingsController.java index 178d7017a0..b729d9f11e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AiModelSettingsController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AiModelSettingsController.java @@ -17,7 +17,9 @@ package org.thingsboard.server.controller; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -45,6 +47,7 @@ import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_D import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; +@Validated @RestController @RequestMapping("/api/ai/model/settings") class AiModelSettingsController extends BaseController { @@ -59,7 +62,7 @@ class AiModelSettingsController extends BaseController { ) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @PostMapping - public AiModelSettings saveAiModelSettings(@RequestBody AiModelSettings settings) throws ThingsboardException { + public AiModelSettings saveAiModelSettings(@RequestBody @Valid AiModelSettings settings) throws ThingsboardException { var user = getCurrentUser(); settings.setTenantId(user.getTenantId()); checkEntity(settings.getId(), settings, Resource.AI_MODEL_SETTINGS); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModelSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModelSettings.java index 750f779e68..63e94f1974 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModelSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModelSettings.java @@ -16,6 +16,9 @@ package org.thingsboard.server.common.data.ai; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -27,6 +30,8 @@ import org.thingsboard.server.common.data.HasVersion; import org.thingsboard.server.common.data.ai.model.AiModel; import org.thingsboard.server.common.data.id.AiModelSettingsId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoNullChar; import java.io.Serial; @@ -56,6 +61,9 @@ public final class AiModelSettings extends BaseData implement ) private Long version; + @NotBlank + @NoNullChar + @Length(min = 1, max = 255) @Schema( requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_WRITE, @@ -64,6 +72,8 @@ public final class AiModelSettings extends BaseData implement ) private String name; + @NotNull + @Valid @Schema( requiredMode = Schema.RequiredMode.NOT_REQUIRED, accessMode = Schema.AccessMode.READ_WRITE, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModel.java index 7d380a0dfd..d5fde0b373 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModel.java @@ -16,6 +16,12 @@ package org.thingsboard.server.common.data.ai.model.chat; import dev.langchain4j.model.chat.ChatModel; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import lombok.With; import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; @@ -25,8 +31,8 @@ import java.util.List; public record AmazonBedrockChatModel( AiModelType modelType, - AmazonBedrockProviderConfig providerConfig, - @With Config modelConfig + @NotNull @Valid AmazonBedrockProviderConfig providerConfig, + @With @NotNull @Valid Config modelConfig ) implements AiChatModel { @Override @@ -36,13 +42,13 @@ public record AmazonBedrockChatModel( @With public record Config( - String modelId, - Double temperature, - Double topP, - Integer maxOutputTokens, + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, + @Positive Integer maxOutputTokens, List stopSequences, - Integer timeoutSeconds, - Integer maxRetries + @Positive Integer timeoutSeconds, + @PositiveOrZero Integer maxRetries ) implements AiChatModelConfig {} @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModel.java index cdfb4842be..6359f6bf71 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModel.java @@ -16,6 +16,12 @@ package org.thingsboard.server.common.data.ai.model.chat; import dev.langchain4j.model.chat.ChatModel; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import lombok.With; import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; @@ -25,8 +31,8 @@ import java.util.List; public record AnthropicChatModel( AiModelType modelType, - AnthropicProviderConfig providerConfig, - @With Config modelConfig + @NotNull @Valid AnthropicProviderConfig providerConfig, + @With @NotNull @Valid Config modelConfig ) implements AiChatModel { @Override @@ -36,14 +42,14 @@ public record AnthropicChatModel( @With public record Config( - String modelId, - Double temperature, - Double topP, - Integer topK, - Integer maxOutputTokens, + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, + @Positive Integer topK, + @Positive Integer maxOutputTokens, List stopSequences, - Integer timeoutSeconds, - Integer maxRetries + @Positive Integer timeoutSeconds, + @PositiveOrZero Integer maxRetries ) implements AiChatModelConfig {} @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModel.java index a70ac7329b..fe58667f52 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModel.java @@ -16,6 +16,12 @@ package org.thingsboard.server.common.data.ai.model.chat; import dev.langchain4j.model.chat.ChatModel; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import lombok.With; import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; @@ -25,8 +31,8 @@ import java.util.List; public record AzureOpenAiChatModel( AiModelType modelType, - AzureOpenAiProviderConfig providerConfig, - @With Config modelConfig + @NotNull @Valid AzureOpenAiProviderConfig providerConfig, + @With @NotNull @Valid Config modelConfig ) implements AiChatModel { @Override @@ -36,15 +42,15 @@ public record AzureOpenAiChatModel( @With public record Config( - String modelId, - Double temperature, - Double topP, + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, Double frequencyPenalty, Double presencePenalty, - Integer maxOutputTokens, + @Positive Integer maxOutputTokens, List stopSequences, - Integer timeoutSeconds, - Integer maxRetries + @Positive Integer timeoutSeconds, + @PositiveOrZero Integer maxRetries ) implements AiChatModelConfig {} @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModel.java index a3ad41ff3c..26bc9d956e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModel.java @@ -16,6 +16,12 @@ package org.thingsboard.server.common.data.ai.model.chat; import dev.langchain4j.model.chat.ChatModel; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import lombok.With; import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; @@ -25,8 +31,8 @@ import java.util.List; public record GitHubModelsChatModel( AiModelType modelType, - GitHubModelsProviderConfig providerConfig, - @With Config modelConfig + @NotNull @Valid GitHubModelsProviderConfig providerConfig, + @With @NotNull @Valid Config modelConfig ) implements AiChatModel { @Override @@ -36,15 +42,15 @@ public record GitHubModelsChatModel( @With public record Config( - String modelId, - Double temperature, - Double topP, + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, Double frequencyPenalty, Double presencePenalty, - Integer maxOutputTokens, + @Positive Integer maxOutputTokens, List stopSequences, - Integer timeoutSeconds, - Integer maxRetries + @Positive Integer timeoutSeconds, + @PositiveOrZero Integer maxRetries ) implements AiChatModelConfig {} @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModel.java index 6dce59e248..87e0b07016 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModel.java @@ -16,6 +16,12 @@ package org.thingsboard.server.common.data.ai.model.chat; import dev.langchain4j.model.chat.ChatModel; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import lombok.With; import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; @@ -25,8 +31,8 @@ import java.util.List; public record GoogleAiGeminiChatModel( AiModelType modelType, - GoogleAiGeminiProviderConfig providerConfig, - @With Config modelConfig + @NotNull @Valid GoogleAiGeminiProviderConfig providerConfig, + @With @NotNull @Valid Config modelConfig ) implements AiChatModel { @Override @@ -36,16 +42,16 @@ public record GoogleAiGeminiChatModel( @With public record Config( - String modelId, - Double temperature, - Double topP, - Integer topK, + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, + @Positive Integer topK, Double frequencyPenalty, Double presencePenalty, - Integer maxOutputTokens, + @Positive Integer maxOutputTokens, List stopSequences, - Integer timeoutSeconds, - Integer maxRetries + @Positive Integer timeoutSeconds, + @PositiveOrZero Integer maxRetries ) implements AiChatModelConfig {} @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModel.java index f921b977d9..c1c9597ba8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModel.java @@ -16,6 +16,12 @@ package org.thingsboard.server.common.data.ai.model.chat; import dev.langchain4j.model.chat.ChatModel; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import lombok.With; import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; @@ -25,8 +31,8 @@ import java.util.List; public record GoogleVertexAiGeminiChatModel( AiModelType modelType, - GoogleVertexAiGeminiProviderConfig providerConfig, - @With Config modelConfig + @NotNull @Valid GoogleVertexAiGeminiProviderConfig providerConfig, + @With @NotNull @Valid Config modelConfig ) implements AiChatModel { @Override @@ -36,16 +42,16 @@ public record GoogleVertexAiGeminiChatModel( @With public record Config( - String modelId, - Double temperature, - Double topP, - Integer topK, + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, + @Positive Integer topK, Double frequencyPenalty, Double presencePenalty, - Integer maxOutputTokens, + @Positive Integer maxOutputTokens, List stopSequences, - Integer timeoutSeconds, - Integer maxRetries + @Positive Integer timeoutSeconds, + @PositiveOrZero Integer maxRetries ) implements AiChatModelConfig {} @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModel.java index a1fce27ff7..41f57a8dc9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModel.java @@ -16,6 +16,12 @@ package org.thingsboard.server.common.data.ai.model.chat; import dev.langchain4j.model.chat.ChatModel; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import lombok.With; import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; @@ -25,8 +31,8 @@ import java.util.List; public record MistralAiChatModel( AiModelType modelType, - MistralAiProviderConfig providerConfig, - @With Config modelConfig + @NotNull @Valid MistralAiProviderConfig providerConfig, + @With @NotNull @Valid Config modelConfig ) implements AiChatModel { @Override @@ -36,15 +42,15 @@ public record MistralAiChatModel( @With public record Config( - String modelId, - Double temperature, - Double topP, + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, Double frequencyPenalty, Double presencePenalty, - Integer maxOutputTokens, + @Positive Integer maxOutputTokens, List stopSequences, - Integer timeoutSeconds, - Integer maxRetries + @Positive Integer timeoutSeconds, + @PositiveOrZero Integer maxRetries ) implements AiChatModelConfig {} @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModel.java index a6cb659d6c..12ea88f8b9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModel.java @@ -16,6 +16,12 @@ package org.thingsboard.server.common.data.ai.model.chat; import dev.langchain4j.model.chat.ChatModel; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import lombok.With; import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; @@ -25,8 +31,8 @@ import java.util.List; public record OpenAiChatModel( AiModelType modelType, - OpenAiProviderConfig providerConfig, - @With Config modelConfig + @NotNull @Valid OpenAiProviderConfig providerConfig, + @With @NotNull @Valid Config modelConfig ) implements AiChatModel { @Override @@ -36,15 +42,15 @@ public record OpenAiChatModel( @With public record Config( - String modelId, - Double temperature, - Double topP, + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, Double frequencyPenalty, Double presencePenalty, - Integer maxOutputTokens, + @Positive Integer maxOutputTokens, List stopSequences, - Integer timeoutSeconds, - Integer maxRetries + @Positive Integer timeoutSeconds, + @PositiveOrZero Integer maxRetries ) implements AiChatModelConfig {} @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AmazonBedrockProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AmazonBedrockProviderConfig.java index 866e25c803..097d82ea0f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AmazonBedrockProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AmazonBedrockProviderConfig.java @@ -15,4 +15,10 @@ */ package org.thingsboard.server.common.data.ai.provider; -public record AmazonBedrockProviderConfig(String region, String accessKeyId, String secretAccessKey) implements AiProviderConfig {} +import jakarta.validation.constraints.NotBlank; + +public record AmazonBedrockProviderConfig( + @NotBlank String region, + @NotBlank String accessKeyId, + @NotBlank String secretAccessKey +) implements AiProviderConfig {} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AnthropicProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AnthropicProviderConfig.java index de48120bda..f1a345fd47 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AnthropicProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AnthropicProviderConfig.java @@ -15,4 +15,8 @@ */ package org.thingsboard.server.common.data.ai.provider; -public record AnthropicProviderConfig(String apiKey) implements AiProviderConfig {} +import jakarta.validation.constraints.NotBlank; + +public record AnthropicProviderConfig( + @NotBlank String apiKey +) implements AiProviderConfig {} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AzureOpenAiProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AzureOpenAiProviderConfig.java index d80da83225..3c31549181 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AzureOpenAiProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AzureOpenAiProviderConfig.java @@ -15,4 +15,10 @@ */ package org.thingsboard.server.common.data.ai.provider; -public record AzureOpenAiProviderConfig(String endpoint, String serviceVersion, String apiKey) implements AiProviderConfig {} +import jakarta.validation.constraints.NotBlank; + +public record AzureOpenAiProviderConfig( + @NotBlank String endpoint, + String serviceVersion, + @NotBlank String apiKey +) implements AiProviderConfig {} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GitHubModelsProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GitHubModelsProviderConfig.java index 278fd9544a..076078d253 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GitHubModelsProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GitHubModelsProviderConfig.java @@ -15,4 +15,8 @@ */ package org.thingsboard.server.common.data.ai.provider; -public record GitHubModelsProviderConfig(String personalAccessToken) implements AiProviderConfig {} +import jakarta.validation.constraints.NotBlank; + +public record GitHubModelsProviderConfig( + @NotBlank String personalAccessToken +) implements AiProviderConfig {} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GoogleAiGeminiProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GoogleAiGeminiProviderConfig.java index 668d98dc71..8a07636aac 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GoogleAiGeminiProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GoogleAiGeminiProviderConfig.java @@ -15,4 +15,8 @@ */ package org.thingsboard.server.common.data.ai.provider; -public record GoogleAiGeminiProviderConfig(String apiKey) implements AiProviderConfig {} +import jakarta.validation.constraints.NotBlank; + +public record GoogleAiGeminiProviderConfig( + @NotBlank String apiKey +) implements AiProviderConfig {} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GoogleVertexAiGeminiProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GoogleVertexAiGeminiProviderConfig.java index dad3200e9e..a16b891c3a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GoogleVertexAiGeminiProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GoogleVertexAiGeminiProviderConfig.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.common.data.ai.provider; +import jakarta.validation.constraints.NotBlank; + public record GoogleVertexAiGeminiProviderConfig( - String fileName, // not used on BE, but needed for UI - String projectId, - String location, - String serviceAccountKey + @NotBlank String fileName, // not used on BE, but needed for UI + @NotBlank String projectId, + @NotBlank String location, + @NotBlank String serviceAccountKey ) implements AiProviderConfig {} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/MistralAiProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/MistralAiProviderConfig.java index 45417bbcd2..0060e4779b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/MistralAiProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/MistralAiProviderConfig.java @@ -15,4 +15,8 @@ */ package org.thingsboard.server.common.data.ai.provider; -public record MistralAiProviderConfig(String apiKey) implements AiProviderConfig {} +import jakarta.validation.constraints.NotBlank; + +public record MistralAiProviderConfig( + @NotBlank String apiKey +) implements AiProviderConfig {} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/OpenAiProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/OpenAiProviderConfig.java index c943517175..1e0653f3b5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/OpenAiProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/OpenAiProviderConfig.java @@ -15,4 +15,8 @@ */ package org.thingsboard.server.common.data.ai.provider; -public record OpenAiProviderConfig(String apiKey) implements AiProviderConfig {} +import jakarta.validation.constraints.NotBlank; + +public record OpenAiProviderConfig( + @NotBlank String apiKey +) implements AiProviderConfig {} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoNullChar.java b/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoNullChar.java new file mode 100644 index 0000000000..51399a9634 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoNullChar.java @@ -0,0 +1,39 @@ +/** + * 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.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Constraint(validatedBy = {}) +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.RECORD_COMPONENT}) +@Retention(RetentionPolicy.RUNTIME) +public @interface NoNullChar { + + String message() default "should not contain 0x00 symbol"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java index f836f229ea..d220762f1b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java @@ -21,7 +21,6 @@ import jakarta.validation.Validation; import jakarta.validation.Validator; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.metadata.ConstraintDescriptor; -import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hibernate.validator.HibernateValidator; import org.hibernate.validator.HibernateValidatorConfiguration; @@ -32,6 +31,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoNullChar; import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.RateLimit; import org.thingsboard.server.dao.exception.DataValidationException; @@ -40,7 +40,6 @@ import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; -@Slf4j @Configuration public class ConstraintValidator { @@ -88,7 +87,9 @@ public class ConstraintValidator { ConstraintMapping constraintMapping = getCustomConstraintMapping(); validatorConfiguration.addMapping(constraintMapping); - fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator(); + try (var validatorFactory = validatorConfiguration.buildValidatorFactory()) { + fieldsValidator = validatorFactory.getValidator(); + } } @Bean @@ -105,6 +106,7 @@ public class ConstraintValidator { constraintMapping.constraintDefinition(NoXss.class).validatedBy(NoXssValidator.class); constraintMapping.constraintDefinition(Length.class).validatedBy(StringLengthValidator.class); constraintMapping.constraintDefinition(RateLimit.class).validatedBy(RateLimitValidator.class); + constraintMapping.constraintDefinition(NoNullChar.class).validatedBy(NoNullCharValidator.class); return constraintMapping; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/NoNullCharValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/NoNullCharValidator.java new file mode 100644 index 0000000000..afbcabe91b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/NoNullCharValidator.java @@ -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.dao.service; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import org.thingsboard.server.common.data.validation.NoNullChar; + +public final class NoNullCharValidator implements ConstraintValidator { + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + return value == null || !value.contains("\u0000"); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelSettingsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelSettingsDataValidator.java index 726fb6331f..fdba1f4e0c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelSettingsDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelSettingsDataValidator.java @@ -64,17 +64,6 @@ class AiModelSettingsDataValidator extends DataValidator { if (!tenantService.tenantExists(tenantId)) { throw new DataValidationException("AI model settings reference a non-existent tenant!"); } - - // name validation - validateString("AI model settings name", settings.getName()); - if (settings.getName().length() > 255) { - throw new DataValidationException("AI model settings name should be between 1 and 255 symbols!"); - } - - // model config validation - if (settings.getConfiguration() == null) { - throw new DataValidationException("AI model settings configuration should be specified!"); - } } }