Browse Source

Add feature toggle, SystemParams field, and service-layer validation

- Add sql.query.key-filters-or-conditions.enabled property to thingsboard.yml
- Add allowKeyFiltersOrConditions field to SystemParams
- Wire config value into SystemInfoController for /api/system/params
- Add validateKeyFiltersOperation to DefaultEntityQueryService
- Add throws ThingsboardException to EntityQueryService interface methods
- Reject OR queries with 400 when feature is disabled
pull/15394/head
Viacheslav Klimov 6 months ago
parent
commit
4ae486a5f1
Failed to extract signature
  1. 4
      application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
  2. 22
      application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java
  3. 9
      application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java
  4. 4
      application/src/main/resources/thingsboard.yml
  5. 1
      common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java

4
application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java

@ -76,6 +76,9 @@ public class SystemInfoController extends BaseController {
@Value("${debug.settings.default_duration:15}") @Value("${debug.settings.default_duration:15}")
private int defaultDebugDurationMinutes; private int defaultDebugDurationMinutes;
@Value("${sql.query.key-filters-or-conditions.enabled:true}")
private boolean keyFiltersOrConditionsEnabled;
@Autowired(required = false) @Autowired(required = false)
private BuildProperties buildProperties; private BuildProperties buildProperties;
@ -173,6 +176,7 @@ public class SystemInfoController extends BaseController {
systemParams.setMobileQrEnabled(Optional.ofNullable(qrCodeSettingService.findQrCodeSettings(TenantId.SYS_TENANT_ID)) systemParams.setMobileQrEnabled(Optional.ofNullable(qrCodeSettingService.findQrCodeSettings(TenantId.SYS_TENANT_ID))
.map(QrCodeSettings::getQrCodeConfig).map(QRCodeConfig::isShowOnHomePage) .map(QrCodeSettings::getQrCodeConfig).map(QRCodeConfig::isShowOnHomePage)
.orElse(false)); .orElse(false));
systemParams.setAllowKeyFiltersOrConditions(keyFiltersOrConditionsEnabled);
return systemParams; return systemParams;
} }

22
application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java

@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.query.AvailableEntityKeysV2;
import org.thingsboard.server.common.data.query.AvailableEntityKeysV2.KeyInfo; import org.thingsboard.server.common.data.query.AvailableEntityKeysV2.KeyInfo;
import org.thingsboard.server.common.data.query.AvailableEntityKeysV2.KeySample; import org.thingsboard.server.common.data.query.AvailableEntityKeysV2.KeySample;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate; import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.ComplexOperation;
import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityData;
@ -93,6 +94,9 @@ public class DefaultEntityQueryService implements EntityQueryService {
@Value("${server.ws.max_entities_per_alarm_subscription:1000}") @Value("${server.ws.max_entities_per_alarm_subscription:1000}")
private int maxEntitiesPerAlarmSubscription; private int maxEntitiesPerAlarmSubscription;
@Value("${sql.query.key-filters-or-conditions.enabled:true}")
private boolean keyFiltersOrConditionsEnabled;
@Autowired @Autowired
private DbCallbackExecutorService dbCallbackExecutor; private DbCallbackExecutorService dbCallbackExecutor;
@ -103,12 +107,14 @@ public class DefaultEntityQueryService implements EntityQueryService {
private AttributesService attributesService; private AttributesService attributesService;
@Override @Override
public long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query) { public long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query) throws ThingsboardException {
validateKeyFiltersOperation(query);
return entityService.countEntitiesByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query); return entityService.countEntitiesByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query);
} }
@Override @Override
public PageData<EntityData> findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) { public PageData<EntityData> findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) throws ThingsboardException {
validateKeyFiltersOperation(query);
if (query.getKeyFilters() != null) { if (query.getKeyFilters() != null) {
resolveDynamicValuesInPredicates( resolveDynamicValuesInPredicates(
query.getKeyFilters().stream() query.getKeyFilters().stream()
@ -177,7 +183,8 @@ public class DefaultEntityQueryService implements EntityQueryService {
} }
@Override @Override
public PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) { public PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) throws ThingsboardException {
validateKeyFiltersOperation(query);
EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query); EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query);
PageData<EntityData> entities = entityService.findEntityDataByQuery(securityUser.getTenantId(), PageData<EntityData> entities = entityService.findEntityDataByQuery(securityUser.getTenantId(),
securityUser.getCustomerId(), entityDataQuery); securityUser.getCustomerId(), entityDataQuery);
@ -203,7 +210,8 @@ public class DefaultEntityQueryService implements EntityQueryService {
} }
@Override @Override
public long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query) { public long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query) throws ThingsboardException {
validateKeyFiltersOperation(query);
if (query.getEntityFilter() != null) { if (query.getEntityFilter() != null) {
EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query); EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query);
PageData<EntityData> entities = entityService.findEntityDataByQuery(securityUser.getTenantId(), PageData<EntityData> entities = entityService.findEntityDataByQuery(securityUser.getTenantId(),
@ -236,6 +244,12 @@ public class DefaultEntityQueryService implements EntityQueryService {
return new EntityDataQuery(query.getEntityFilter(), edpl, query.getEntityFields(), query.getLatestValues(), query.getKeyFilters(), query.getKeyFiltersOperationOrDefault()); return new EntityDataQuery(query.getEntityFilter(), edpl, query.getEntityFields(), query.getLatestValues(), query.getKeyFilters(), query.getKeyFiltersOperationOrDefault());
} }
private void validateKeyFiltersOperation(EntityCountQuery query) throws ThingsboardException {
if (!keyFiltersOrConditionsEnabled && query.getKeyFiltersOperation() == ComplexOperation.OR) {
throw new ThingsboardException("OR conditions between key filters are disabled", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
}
@Override @Override
public ListenableFuture<AvailableEntityKeys> getKeysByQuery(SecurityUser securityUser, TenantId tenantId, EntityDataQuery query, public ListenableFuture<AvailableEntityKeys> getKeysByQuery(SecurityUser securityUser, TenantId tenantId, EntityDataQuery query,
boolean isTimeseries, boolean isAttributes, AttributeScope scope) { boolean isTimeseries, boolean isAttributes, AttributeScope scope) {

9
application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.query;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmCountQuery; import org.thingsboard.server.common.data.query.AlarmCountQuery;
@ -33,13 +34,13 @@ import java.util.Set;
public interface EntityQueryService { public interface EntityQueryService {
long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query); long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query) throws ThingsboardException;
PageData<EntityData> findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query); PageData<EntityData> findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) throws ThingsboardException;
PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query); PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) throws ThingsboardException;
long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query); long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query) throws ThingsboardException;
ListenableFuture<AvailableEntityKeys> getKeysByQuery(SecurityUser securityUser, TenantId tenantId, EntityDataQuery query, ListenableFuture<AvailableEntityKeys> getKeysByQuery(SecurityUser securityUser, TenantId tenantId, EntityDataQuery query,
boolean isTimeseries, boolean isAttributes, AttributeScope scope); boolean isTimeseries, boolean isAttributes, AttributeScope scope);

4
application/src/main/resources/thingsboard.yml

@ -469,6 +469,10 @@ sql:
log_tenant_stats: "${SQL_LOG_TENANT_STATS:true}" log_tenant_stats: "${SQL_LOG_TENANT_STATS:true}"
# Interval in milliseconds for printing the latest statistic information about the tenant # Interval in milliseconds for printing the latest statistic information about the tenant
log_tenant_stats_interval_ms: "${SQL_LOG_TENANT_STATS_INTERVAL_MS:60000}" log_tenant_stats_interval_ms: "${SQL_LOG_TENANT_STATS_INTERVAL_MS:60000}"
query:
key-filters-or-conditions:
# Enable/disable OR conditions between key filters in entity data queries
enabled: "${SQL_QUERY_KEY_FILTERS_OR_CONDITIONS_ENABLED:true}"
postgres: postgres:
# Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE. # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE.
ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}" ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}"

1
common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java

@ -45,4 +45,5 @@ public class SystemParams {
long minAllowedAggregationIntervalInSecForCF; long minAllowedAggregationIntervalInSecForCF;
long intermediateAggregationIntervalInSecForCF; long intermediateAggregationIntervalInSecForCF;
TrendzSettings trendzSettings; TrendzSettings trendzSettings;
boolean allowKeyFiltersOrConditions;
} }

Loading…
Cancel
Save