Merge pull request #6846 from YevhenBondarenko/feature/queue-validation

[3.4] queue validation
This commit is contained in:
Andrew Shvayka 2022-06-29 11:41:28 +03:00 committed by GitHub
commit 0b8701b16f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 69 additions and 14 deletions

View File

@ -22,11 +22,17 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@Data @Data
public class Queue extends SearchTextBasedWithAdditionalInfo<QueueId> implements HasName, HasTenantId { public class Queue extends SearchTextBasedWithAdditionalInfo<QueueId> implements HasName, HasTenantId {
private TenantId tenantId; private TenantId tenantId;
@NoXss
@Length(fieldName = "name")
private String name; private String name;
@NoXss
@Length(fieldName = "topic")
private String topic; private String topic;
private int pollInterval; private int pollInterval;
private int partitions; private int partitions;

View File

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -36,6 +37,11 @@ public abstract class DataValidator<D extends BaseData<?>> {
private static final Pattern EMAIL_PATTERN = private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE); Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE);
private static final Pattern QUEUE_PATTERN = Pattern.compile("^[a-zA-Z0-9_.\\-]+$");
private static final String NAME = "name";
private static final String TOPIC = "topic";
// Returns old instance of the same object that is fetched during validation. // Returns old instance of the same object that is fetched during validation.
public D validate(D data, Function<D, TenantId> tenantIdFunction) { public D validate(D data, Function<D, TenantId> tenantIdFunction) {
try { try {
@ -134,4 +140,22 @@ public abstract class DataValidator<D extends BaseData<?>> {
} }
} }
protected static void validateQueueName(String name) {
validateQueueNameOrTopic(name, NAME);
}
protected static void validateQueueTopic(String topic) {
validateQueueNameOrTopic(topic, TOPIC);
}
private static void validateQueueNameOrTopic(String value, String fieldName) {
if (StringUtils.isEmpty(value)) {
throw new DataValidationException(String.format("Queue %s should be specified!", fieldName));
}
if (!QUEUE_PATTERN.matcher(value).matches()) {
throw new DataValidationException(
String.format("Queue %s contains a character other than ASCII alphanumerics, '.', '_' and '-'!", fieldName));
}
}
} }

View File

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.dao.service.validator; package org.thingsboard.server.dao.service.validator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
@ -73,12 +72,9 @@ public class QueueValidator extends DataValidator<Queue> {
} }
} }
if (StringUtils.isEmpty(queue.getName())) { validateQueueName(queue.getName());
throw new DataValidationException("Queue name should be specified!"); validateQueueTopic(queue.getTopic());
}
if (StringUtils.isBlank(queue.getTopic())) {
throw new DataValidationException("Queue topic should be non empty and without spaces!");
}
if (queue.getPollInterval() < 1) { if (queue.getPollInterval() < 1) {
throw new DataValidationException("Queue poll interval should be more then 0!"); throw new DataValidationException("Queue poll interval should be more then 0!");
} }

View File

@ -107,12 +107,9 @@ public class TenantProfileDataValidator extends DataValidator<TenantProfile> {
} }
private void validateQueueConfiguration(TenantProfileQueueConfiguration queue) { private void validateQueueConfiguration(TenantProfileQueueConfiguration queue) {
if (StringUtils.isEmpty(queue.getName())) { validateQueueName(queue.getName());
throw new DataValidationException("Queue name should be specified!"); validateQueueTopic(queue.getTopic());
}
if (StringUtils.isBlank(queue.getTopic())) {
throw new DataValidationException("Queue topic should be non empty and without spaces!");
}
if (queue.getPollInterval() < 1) { if (queue.getPollInterval() < 1) {
throw new DataValidationException("Queue poll interval should be more then 0!"); throw new DataValidationException("Queue poll interval should be more then 0!");
} }

View File

@ -152,6 +152,20 @@ public abstract class BaseQueueServiceTest extends AbstractServiceTest {
queueService.saveQueue(queue); queueService.saveQueue(queue);
} }
@Test(expected = DataValidationException.class)
public void testSaveQueueWithInvalidName() {
Queue queue = new Queue();
queue.setTenantId(tenantId);
queue.setName("Test 1");
queue.setTopic("tb_rule_engine.test");
queue.setPollInterval(25);
queue.setPartitions(1);
queue.setPackProcessingTimeout(2000);
queue.setSubmitStrategy(createTestSubmitStrategy());
queue.setProcessingStrategy(createTestProcessingStrategy());
queueService.saveQueue(queue);
}
@Test(expected = DataValidationException.class) @Test(expected = DataValidationException.class)
public void testSaveQueueWithEmptyTopic() { public void testSaveQueueWithEmptyTopic() {
Queue queue = new Queue(); Queue queue = new Queue();
@ -165,6 +179,20 @@ public abstract class BaseQueueServiceTest extends AbstractServiceTest {
queueService.saveQueue(queue); queueService.saveQueue(queue);
} }
@Test(expected = DataValidationException.class)
public void testSaveQueueWithInvalidTopic() {
Queue queue = new Queue();
queue.setTenantId(tenantId);
queue.setName("Test");
queue.setTopic("tb rule engine test");
queue.setPollInterval(25);
queue.setPartitions(1);
queue.setPackProcessingTimeout(2000);
queue.setSubmitStrategy(createTestSubmitStrategy());
queue.setProcessingStrategy(createTestProcessingStrategy());
queueService.saveQueue(queue);
}
@Test(expected = DataValidationException.class) @Test(expected = DataValidationException.class)
public void testSaveQueueWithEmptyPollInterval() { public void testSaveQueueWithEmptyPollInterval() {
Queue queue = new Queue(); Queue queue = new Queue();

View File

@ -26,6 +26,9 @@
<mat-error *ngIf="queueFormGroup.get('name').hasError('unique')"> <mat-error *ngIf="queueFormGroup.get('name').hasError('unique')">
{{ 'queue.name-unique' | translate }} {{ 'queue.name-unique' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="queueFormGroup.get('name').hasError('pattern')">
{{ 'queue.name-pattern' | translate }}
</mat-error>
</mat-form-field> </mat-form-field>
<mat-accordion class="queue-strategy" [multi]="true"> <mat-accordion class="queue-strategy" [multi]="true">
<mat-expansion-panel [expanded]="true"> <mat-expansion-panel [expanded]="true">

View File

@ -99,7 +99,7 @@ export class QueueFormComponent implements ControlValueAccessor, OnInit, OnDestr
ngOnInit() { ngOnInit() {
this.queueFormGroup = this.fb.group( this.queueFormGroup = this.fb.group(
{ {
name: ['', [Validators.required]], name: ['', [Validators.required, Validators.pattern(/^[a-zA-Z0-9_.\-]+$/)]],
pollInterval: [25, [Validators.min(1), Validators.required]], pollInterval: [25, [Validators.min(1), Validators.required]],
partitions: [10, [Validators.min(1), Validators.required]], partitions: [10, [Validators.min(1), Validators.required]],
consumerPerPartition: [false, []], consumerPerPartition: [false, []],

View File

@ -2928,6 +2928,7 @@
"name": "Name", "name": "Name",
"name-required": "Queue name is required!", "name-required": "Queue name is required!",
"name-unique": "Queue name is not unique!", "name-unique": "Queue name is not unique!",
"name-pattern": "Queue name contains a character other than ASCII alphanumerics, '.', '_' and '-'!",
"queue-required": "Queue is required!", "queue-required": "Queue is required!",
"topic-required": "Queue topic is required!", "topic-required": "Queue topic is required!",
"poll-interval-required": "Poll interval is required!", "poll-interval-required": "Poll interval is required!",