Browse Source

Merge branch 'develop/3.4' of github.com:thingsboard/thingsboard into feature/6371

pull/6374/head
Dmitriymush 4 years ago
parent
commit
3114b40e32
  1. 4
      application/src/main/data/json/system/widget_bundles/charts.json
  2. 2
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  3. 6
      application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java
  4. 6
      application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java
  5. 4
      application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java
  6. 6
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java
  8. 27
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java
  9. 5
      application/src/main/resources/thingsboard.yml
  10. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
  11. 182
      application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
  12. 72
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  13. 229
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
  14. 104
      application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java
  15. 7
      application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthConfigTest.java
  16. 6
      application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java
  17. 16
      application/src/test/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaServiceTest.java
  18. 26
      common/dao-api/src/main/java/org/thingsboard/server/dao/util/HsqlDao.java
  19. 26
      common/dao-api/src/main/java/org/thingsboard/server/dao/util/PsqlDao.java
  20. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java
  21. 7
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java
  22. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java
  23. 4
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java
  24. 2
      common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/PlatformTwoFaSettings.java
  25. 4
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
  26. 37
      dao/src/main/java/org/thingsboard/server/dao/HsqlTsLatestDaoConfig.java
  27. 37
      dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java
  28. 8
      dao/src/main/java/org/thingsboard/server/dao/SqlTsDaoConfig.java
  29. 8
      dao/src/main/java/org/thingsboard/server/dao/SqlTsLatestDaoConfig.java
  30. 2
      dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java
  31. 4
      dao/src/main/java/org/thingsboard/server/dao/TimescaleTsLatestDaoConfig.java
  32. 76
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/HsqlAttributesInsertRepository.java
  33. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/SqlAttributesInsertRepository.java
  34. 65
      dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java
  35. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/component/SqlComponentDescriptorInsertRepository.java
  36. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventInsertRepository.java
  37. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java
  38. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/event/SqlEventCleanupRepository.java
  39. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  40. 63
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/HsqlRelationInsertRepository.java
  41. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/SqlRelationInsertRepository.java
  42. 62
      dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java
  43. 87
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/hsql/HsqlInsertTsRepository.java
  44. 85
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/hsql/HsqlLatestInsertTsRepository.java
  45. 8
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/sql/SqlLatestInsertTsRepository.java
  46. 6
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlInsertTsRepository.java
  47. 10
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java
  48. 2
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java
  49. 32
      dao/src/main/java/org/thingsboard/server/dao/sqlts/sql/JpaSqlTimeseriesDao.java
  50. 4
      dao/src/main/java/org/thingsboard/server/dao/timeseries/SqlPartition.java
  51. 2
      dao/src/test/java/org/thingsboard/server/dao/AbstractDaoServiceTest.java
  52. 2
      dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java
  53. 40
      dao/src/test/java/org/thingsboard/server/dao/util/DaoTestUtil.java
  54. 20
      dao/src/test/java/org/thingsboard/server/dao/util/SqlDbType.java
  55. 1
      dao/src/test/resources/nosql-test.properties
  56. 2
      dao/src/test/resources/sql-test.properties
  57. 1
      docker/tb-node.hybrid.env
  58. 1
      docker/tb-node.postgres.env
  59. 16
      msa/js-executor/docker/Dockerfile
  60. 37
      msa/js-executor/package.json
  61. 4
      msa/js-executor/pom.xml
  62. 53
      msa/js-executor/queue/serviceBusTemplate.js
  63. 2317
      msa/js-executor/yarn.lock
  64. 23
      msa/tb-node/docker/Dockerfile
  65. 81
      msa/tb/docker-cassandra/Dockerfile
  66. 19
      msa/tb/docker-cassandra/start-db.sh
  67. 62
      msa/tb/docker-postgres/Dockerfile
  68. 21
      msa/tb/docker-postgres/start-db.sh
  69. 61
      msa/tb/docker-tb/Dockerfile
  70. 18
      msa/tb/docker-tb/start-db.sh
  71. 18
      msa/tb/docker-tb/stop-db.sh
  72. 2
      msa/tb/docker/install-tb.sh
  73. 17
      msa/tb/docker/start-tb.sh
  74. 134
      msa/tb/pom.xml
  75. 15
      msa/transport/coap/docker/Dockerfile
  76. 15
      msa/transport/http/docker/Dockerfile
  77. 15
      msa/transport/lwm2m/docker/Dockerfile
  78. 15
      msa/transport/mqtt/docker/Dockerfile
  79. 15
      msa/transport/snmp/docker/Dockerfile
  80. 16
      msa/web-ui/docker/Dockerfile
  81. 23
      msa/web-ui/package.json
  82. 4
      msa/web-ui/pom.xml
  83. 966
      msa/web-ui/yarn.lock
  84. 6
      pom.xml
  85. 41
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java
  86. 15
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java
  87. 177
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java
  88. 1
      ui-ngx/package.json
  89. 2
      ui-ngx/pom.xml
  90. 11
      ui-ngx/src/app/core/auth/auth.service.ts
  91. 4
      ui-ngx/src/app/core/services/menu.service.ts
  92. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html
  93. 7
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  94. 2
      ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts
  95. 6
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts
  96. 4
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  97. 55
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html
  98. 99
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts
  99. 3
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html
  100. 11
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts

4
application/src/main/data/json/system/widget_bundles/charts.json

File diff suppressed because one or more lines are too long

2
application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java

@ -335,7 +335,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
return null;
}
private EdgeEventActionType edgeTypeByActionType(ActionType actionType) {
public static EdgeEventActionType edgeTypeByActionType(ActionType actionType) {
switch (actionType) {
case ADDED:
return EdgeEventActionType.ADDED;

6
application/src/main/java/org/thingsboard/server/service/install/PsqlEntityDatabaseSchemaService.java → application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java

@ -18,19 +18,17 @@ package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao;
@Service
@PsqlDao
@Profile("install")
@Slf4j
public class PsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
implements EntityDatabaseSchemaService {
public static final String SCHEMA_ENTITIES_SQL = "schema-entities.sql";
public static final String SCHEMA_ENTITIES_IDX_SQL = "schema-entities-idx.sql";
public static final String SCHEMA_ENTITIES_IDX_PSQL_ADDON_SQL = "schema-entities-idx-psql-addon.sql";
public PsqlEntityDatabaseSchemaService() {
public SqlEntityDatabaseSchemaService() {
super(SCHEMA_ENTITIES_SQL, SCHEMA_ENTITIES_IDX_SQL);
}

6
application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseSchemaService.java → application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java

@ -18,19 +18,17 @@ package org.thingsboard.server.service.install;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
@Service
@SqlTsDao
@PsqlDao
@Profile("install")
public class PsqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService implements TsDatabaseSchemaService {
public class SqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService implements TsDatabaseSchemaService {
@Value("${sql.postgres.ts_key_value_partitioning:MONTHS}")
private String partitionType;
public PsqlTsDatabaseSchemaService() {
public SqlTsDatabaseSchemaService() {
super("schema-ts-psql.sql", null);
}

4
application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java → application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java

@ -21,7 +21,6 @@ import org.apache.commons.lang3.SystemUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
import java.io.File;
@ -36,8 +35,7 @@ import java.sql.DriverManager;
@Profile("install")
@Slf4j
@SqlTsDao
@PsqlDao
public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService {
public class SqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService {
@Value("${sql.postgres.ts_key_value_partitioning:MONTHS}")
private String partitionType;

6
application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java

@ -19,16 +19,10 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.TimescaleDBTsDao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
@Service
@TimescaleDBTsDao
@PsqlDao
@Profile("install")
@Slf4j
public class TimescaleTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService implements TsDatabaseSchemaService {

2
application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java

@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.TimescaleDBTsDao;
import java.io.File;
@ -37,7 +36,6 @@ import java.sql.DriverManager;
@Profile("install")
@Slf4j
@TimescaleDBTsDao
@PsqlDao
public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService {
@Value("${sql.timescale.chunk_time_interval:86400000}")

27
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java

@ -56,12 +56,31 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
@Override
public Optional<AccountTwoFaSettings> getAccountTwoFaSettings(TenantId tenantId, UserId userId) {
PlatformTwoFaSettings platformTwoFaSettings = getPlatformTwoFaSettings(tenantId, true).orElse(null);
return Optional.ofNullable(userAuthSettingsDao.findByUserId(userId))
.flatMap(userAuthSettings -> Optional.ofNullable(userAuthSettings.getTwoFaSettings()))
.map(twoFaSettings -> {
twoFaSettings.getConfigs().keySet().removeIf(providerType -> {
return getTwoFaProviderConfig(tenantId, providerType).isEmpty();
.map(userAuthSettings -> {
AccountTwoFaSettings twoFaSettings = userAuthSettings.getTwoFaSettings();
if (twoFaSettings == null) return null;
boolean updateNeeded;
Map<TwoFaProviderType, TwoFaAccountConfig> configs = twoFaSettings.getConfigs();
updateNeeded = configs.keySet().removeIf(providerType -> {
return platformTwoFaSettings == null || platformTwoFaSettings.getProviderConfig(providerType).isEmpty();
});
if (configs.size() == 1 && configs.containsKey(TwoFaProviderType.BACKUP_CODE)) {
configs.remove(TwoFaProviderType.BACKUP_CODE);
updateNeeded = true;
}
if (!configs.isEmpty() && configs.values().stream().noneMatch(TwoFaAccountConfig::isUseByDefault)) {
configs.values().stream()
.filter(config -> config.getProviderType() != TwoFaProviderType.BACKUP_CODE)
.findFirst().ifPresent(config -> config.setUseByDefault(true));
updateNeeded = true;
}
if (updateNeeded) {
twoFaSettings = saveAccountTwoFaSettings(tenantId, userId, twoFaSettings);
}
return twoFaSettings;
});
}

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

@ -19,6 +19,8 @@ server:
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
# Server bind port
port: "${HTTP_BIND_PORT:8080}"
# Server forward headers strategy
forward_headers_strategy: "${HTTP_FORWARD_HEADERS_STRATEGY:NONE}"
# Server SSL configuration
ssl:
# Enable/disable SSL support
@ -174,8 +176,6 @@ database:
ts_latest:
type: "${DATABASE_TS_LATEST_TYPE:sql}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale)
# note: timescale works only with postgreSQL database for DATABASE_ENTITIES_TYPE.
# Cassandra driver configuration parameters
cassandra:
# Thingsboard cluster name
@ -533,7 +533,6 @@ spring:
open-in-view: "false"
hibernate:
ddl-auto: "none"
database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}"
datasource:
driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}"
url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}"

2
application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java

@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@EnableWebSocket
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Slf4j
public abstract class AbstractControllerTest extends AbstractWebTest {
public abstract class AbstractControllerTest extends AbstractNotifyEntityTest {
public static final String WS_URL = "ws://localhost:";

182
application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java

@ -0,0 +1,182 @@
/**
* Copyright © 2016-2022 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.controller;
import lombok.extern.slf4j.Slf4j;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.model.ModelConstants;
import java.util.Locale;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.thingsboard.server.service.entitiy.DefaultTbNotificationEntityService.edgeTypeByActionType;
@Slf4j
public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
@SpyBean
protected TbClusterService tbClusterService;
@SpyBean
protected AuditLogService auditLogService;
protected void testNotifyEntityAllOneTime(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testSendNotificationMsgToEdgeServiceOneTime(entityId, tenantId, actionType);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOneTime(originatorId, tenantId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityDeleteOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOneTime(entityId, tenantId);
testBroadcastEntityStateChangeEventOneTime(entityId, tenantId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityNeverMsgToEdgeServiceOneTime(HasName entity, EntityId entityId, TenantId tenantId, ActionType actionType) {
testSendNotificationMsgToEdgeServiceOneTime(entityId, tenantId, actionType);
testLogEntityActionNever(entityId, entity);
testPushMsgToRuleEngineNever(entityId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOneTime(originatorId, tenantId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOneTime(originatorId, tenantId);
testBroadcastEntityStateChangeEventOneTime(entityId, tenantId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityError(HasName entity, TenantId tenantId,
UserId userId, String userName, ActionType actionType, Exception exp,
Object... additionalInfo) {
CustomerId customer_NULL_UUID = (CustomerId) EntityIdFactory.getByTypeAndUuid(EntityType.CUSTOMER, ModelConstants.NULL_UUID);
EntityId entity_NULL_UUID = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(entity.getClass().toString()
.substring(entity.getClass().toString().lastIndexOf(".") + 1).toUpperCase(Locale.ENGLISH)),
ModelConstants.NULL_UUID);
testNotificationMsgToEdgeServiceNever(entity_NULL_UUID);
if (additionalInfo.length > 0) {
Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId),
Mockito.eq(customer_NULL_UUID), Mockito.eq(userId), Mockito.eq(userName),
Mockito.eq(entity_NULL_UUID), Mockito.any(entity.getClass()), Mockito.eq(actionType),
Mockito.argThat(argument ->
argument.getMessage().equals(exp.getMessage())), Mockito.eq(additionalInfo));
} else {
Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId),
Mockito.eq(customer_NULL_UUID), Mockito.eq(userId), Mockito.eq(userName),
Mockito.eq(entity_NULL_UUID), Mockito.any(entity.getClass()), Mockito.eq(actionType),
Mockito.argThat(argument ->
argument.getMessage().equals(exp.getMessage())));
}
testPushMsgToRuleEngineNever(entity_NULL_UUID);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityNever(EntityId entityId, HasName entity) {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionNever(entityId, entity);
testPushMsgToRuleEngineNever(entityId);
Mockito.reset(tbClusterService, auditLogService);
}
private void testNotificationMsgToEdgeServiceNever(EntityId entityId) {
Mockito.verify(tbClusterService, never()).sendNotificationMsgToEdgeService(Mockito.any(),
Mockito.any(), Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any(), Mockito.any());
}
private void testLogEntityActionNever(EntityId entityId, HasName entity) {
Mockito.verify(auditLogService, never()).logEntityAction(Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(entityId.getClass()), Mockito.any(entity.getClass()),
Mockito.any(), Mockito.any());
}
private void testPushMsgToRuleEngineNever(EntityId entityId) {
Mockito.verify(tbClusterService, never()).pushMsgToRuleEngine(Mockito.any(),
Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any());
}
private void testLogEntityActionOneTime(HasName entity, EntityId originatorId, TenantId tenantId, CustomerId customerId,
UserId userId, String userName, ActionType actionType, Object... additionalInfo) {
if (additionalInfo.length == 0) {
Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), Mockito.eq(customerId),
Mockito.eq(userId), Mockito.eq(userName), Mockito.eq(originatorId),
Mockito.eq(entity), Mockito.eq(actionType), Mockito.isNull());
} else {
String additionalInfoStr = extractParameter(String.class, 0, additionalInfo);
Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), Mockito.eq(customerId),
Mockito.eq(userId), Mockito.eq(userName), Mockito.eq(originatorId),
Mockito.eq(entity), Mockito.eq(actionType), Mockito.isNull(), Mockito.eq(additionalInfoStr));
}
}
private void testPushMsgToRuleEngineOneTime(EntityId originatorId, TenantId tenantId) {
Mockito.verify(tbClusterService, times(1)).pushMsgToRuleEngine(Mockito.eq(tenantId),
Mockito.eq(originatorId), Mockito.any(TbMsg.class), Mockito.isNull());
}
private void testSendNotificationMsgToEdgeServiceOneTime(EntityId entityId, TenantId tenantId, ActionType actionType) {
Mockito.verify(tbClusterService, times(1)).sendNotificationMsgToEdgeService(Mockito.eq(tenantId),
Mockito.isNull(), Mockito.eq(entityId), Mockito.isNull(), Mockito.isNull(),
Mockito.eq(edgeTypeByActionType(actionType)));
}
private void testBroadcastEntityStateChangeEventOneTime(EntityId entityId, TenantId tenantId) {
Mockito.verify(tbClusterService, times(1)).broadcastEntityStateChangeEvent(Mockito.eq(tenantId),
Mockito.any(entityId.getClass()), Mockito.any(ComponentLifecycleEvent.class));
}
private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) {
T result = null;
if (additionalInfo != null && additionalInfo.length > index) {
Object paramObject = additionalInfo[index];
if (clazz.isInstance(paramObject)) {
result = clazz.cast(paramObject);
}
}
return result;
}
}

72
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -75,11 +75,6 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.queue.ProcessingStrategy;
import org.thingsboard.server.common.data.queue.ProcessingStrategyType;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.queue.SubmitStrategy;
import org.thingsboard.server.common.data.queue.SubmitStrategyType;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
import org.thingsboard.server.dao.tenant.TenantProfileService;
@ -112,6 +107,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected ObjectMapper mapper = new ObjectMapper();
protected static final String TEST_TENANT_NAME = "TEST TENANT";
protected static final String TEST_DIFFERENT_TENANT_NAME = "TEST DIFFERENT TENANT";
protected static final String SYS_ADMIN_EMAIL = "sysadmin@thingsboard.org";
private static final String SYS_ADMIN_PASSWORD = "sysadmin";
@ -119,9 +115,15 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected static final String TENANT_ADMIN_EMAIL = "testtenant@thingsboard.org";
protected static final String TENANT_ADMIN_PASSWORD = "tenant";
protected static final String DIFFERENT_TENANT_ADMIN_EMAIL = "testdifftenant@thingsboard.org";
private static final String DIFFERENT_TENANT_ADMIN_PASSWORD = "difftenant";
protected static final String CUSTOMER_USER_EMAIL = "testcustomer@thingsboard.org";
private static final String CUSTOMER_USER_PASSWORD = "customer";
protected static final String DIFFERENT_CUSTOMER_USER_EMAIL = "testdifferentcustomer@thingsboard.org";
private static final String DIFFERENT_CUSTOMER_USER_PASSWORD = "diffcustomer";
/** See {@link org.springframework.test.web.servlet.DefaultMvcResult#getAsyncResult(long)}
* and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()}
*/
@ -137,7 +139,11 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected TenantId tenantId;
protected UserId tenantAdminUserId;
protected CustomerId tenantAdminCustomerId;
protected CustomerId customerId;
protected TenantId differentTenantId;
protected CustomerId differentCustomerId;
protected UserId customerUserId;
@SuppressWarnings("rawtypes")
private HttpMessageConverter mappingJackson2HttpMessageConverter;
@ -202,6 +208,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
tenantAdmin = createUserAndLogin(tenantAdmin, TENANT_ADMIN_PASSWORD);
tenantAdminUserId = tenantAdmin.getId();
tenantAdminCustomerId = tenantAdmin.getCustomerId();
Customer customer = new Customer();
customer.setTitle("Customer");
@ -215,7 +222,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
customerUser.setCustomerId(savedCustomer.getId());
customerUser.setEmail(CUSTOMER_USER_EMAIL);
createUserAndLogin(customerUser, CUSTOMER_USER_PASSWORD);
customerUser = createUserAndLogin(customerUser, CUSTOMER_USER_PASSWORD);
customerUserId = customerUser.getId();
logout();
@ -273,36 +281,52 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
private Customer savedDifferentCustomer;
protected void loginDifferentTenant() throws Exception {
loginSysAdmin();
if (savedDifferentTenant != null) {
deleteDifferentTenant();
login(savedDifferentTenant.getEmail(), TENANT_ADMIN_PASSWORD);
} else {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle(TEST_DIFFERENT_TENANT_NAME);
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
differentTenantId = savedDifferentTenant.getId();
Assert.assertNotNull(savedDifferentTenant);
User differentTenantAdmin = new User();
differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN);
differentTenantAdmin.setTenantId(savedDifferentTenant.getId());
differentTenantAdmin.setEmail(DIFFERENT_TENANT_ADMIN_EMAIL);
createUserAndLogin(differentTenantAdmin, DIFFERENT_TENANT_ADMIN_PASSWORD);
}
}
Tenant tenant = new Tenant();
tenant.setTitle("Different tenant");
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedDifferentTenant);
User differentTenantAdmin = new User();
differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN);
differentTenantAdmin.setTenantId(savedDifferentTenant.getId());
differentTenantAdmin.setEmail("different_tenant@thingsboard.org");
protected void loginDifferentCustomer() throws Exception {
if (savedDifferentCustomer != null) {
login(savedDifferentCustomer.getEmail(), CUSTOMER_USER_PASSWORD);
} else {
createDifferentCustomer();
createUserAndLogin(differentTenantAdmin, "testPassword");
loginTenantAdmin();
User differentCustomerUser = new User();
differentCustomerUser.setAuthority(Authority.CUSTOMER_USER);
differentCustomerUser.setTenantId(tenantId);
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
differentCustomerUser.setEmail(DIFFERENT_CUSTOMER_USER_EMAIL);
createUserAndLogin(differentCustomerUser, DIFFERENT_CUSTOMER_USER_PASSWORD);
}
}
protected void loginDifferentCustomer() throws Exception {
protected void createDifferentCustomer() throws Exception {
loginTenantAdmin();
Customer customer = new Customer();
customer.setTitle("Different customer");
savedDifferentCustomer = doPost("/api/customer", customer, Customer.class);
Assert.assertNotNull(savedDifferentCustomer);
User differentCustomerUser = new User();
differentCustomerUser.setAuthority(Authority.CUSTOMER_USER);
differentCustomerUser.setTenantId(tenantId);
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
differentCustomerUser.setEmail("different_customer@thingsboard.org");
differentCustomerId = savedDifferentCustomer.getId();
createUserAndLogin(differentCustomerUser, "testPassword");
logout();
}
protected void deleteDifferentTenant() throws Exception {

229
application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java

@ -15,17 +15,30 @@
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.page.PageData;
import java.util.LinkedList;
import java.util.List;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
public static final String TEST_ALARM_TYPE = "Test";
@ -56,160 +69,338 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
@Test
public void testCreateAlarmViaCustomer() throws Exception {
loginCustomerUser();
createAlarm(TEST_ALARM_TYPE);
logout();
Mockito.reset(tbClusterService, auditLogService);
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
testNotifyEntityAllOneTime(alarm, alarm.getId(), alarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ADDED);
}
@Test
public void testCreateAlarmViaTenant() throws Exception {
loginTenantAdmin();
createAlarm(TEST_ALARM_TYPE);
logout();
Mockito.reset(tbClusterService, auditLogService);
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
testNotifyEntityAllOneTime(alarm, alarm.getId(), alarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ADDED);
}
@Test
public void testUpdateAlarmViaCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
alarm.setSeverity(AlarmSeverity.MAJOR);
Alarm updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
logout();
testNotifyEntityAllOneTime(updatedAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.UPDATED);
}
@Test
public void testUpdateAlarmViaTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
alarm.setSeverity(AlarmSeverity.MAJOR);
Alarm updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
logout();
testNotifyEntityAllOneTime(updatedAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.UPDATED);
}
@Test
public void testUpdateAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
alarm.setSeverity(AlarmSeverity.MAJOR);
loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm", alarm).andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
}
@Test
public void testUpdateAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer();
alarm.setSeverity(AlarmSeverity.MAJOR);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm", alarm).andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
}
@Test
public void testDeleteAlarmViaCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
logout();
testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.DELETED);
}
@Test
public void testDeleteAlarmViaTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
logout();
testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED);
}
@Test
public void testDeleteAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
}
@Test
public void testDeleteAlarmViaAnotherCustomer() throws Exception {
public void testDeleteAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer();
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
}
@Test
public void testClearAlarmViaCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
logout();
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_CLEAR);
}
@Test
public void testClearAlarmViaTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
logout();
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_CLEAR);
}
@Test
public void testAcknowledgeAlarmViaCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.ACTIVE_ACK, foundAlarm.getStatus());
logout();
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_ACK);
}
@Test
public void testClearAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
}
@Test
public void testClearAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
}
@Test
public void testAcknowledgeAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
}
@Test
public void testAcknowledgeAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden());
}
@Test
public void testFindAlarmsViaCustomerUser() throws Exception {
loginCustomerUser();
List<Alarm> createdAlarms = new LinkedList<>();
final int size = 10;
for (int i = 0; i < size; i++) {
createdAlarms.add(
createAlarm(TEST_ALARM_TYPE + i)
);
}
var response = doGetTyped(
"/api/alarm/" + EntityType.DEVICE + "/"
+ customerDevice.getUuidId() + "?page=0&pageSize=" + size,
new TypeReference<PageData<AlarmInfo>>() {}
);
var foundAlarmInfos = response.getData();
Assert.assertNotNull("Found pageData is null", foundAlarmInfos);
Assert.assertNotEquals(
"Expected alarms are not found!",
0, foundAlarmInfos.size()
);
boolean allMatch = createdAlarms.stream()
.allMatch(alarm -> foundAlarmInfos.stream()
.map(Alarm::getType)
.anyMatch(type -> alarm.getType().equals(type))
);
Assert.assertTrue("Created alarm doesn't match any found!", allMatch);
}
@Test
public void testFindAlarmsViaDifferentCustomerUser() throws Exception {
loginCustomerUser();
final int size = 10;
for (int i = 0; i < size; i++) {
createAlarm(TEST_ALARM_TYPE + i);
}
loginDifferentCustomer();
doGet("/api/alarm/" + EntityType.DEVICE + "/"
+ customerDevice.getUuidId() + "?page=0&pageSize=" + size)
.andExpect(status().isForbidden());
}
@Test
public void testFindAlarmsViaPublicCustomer() throws Exception {
loginTenantAdmin();
Device device = new Device();
device.setName("Test Public Device");
device.setLabel("Label");
device.setCustomerId(customerId);
device = doPost("/api/device", device, Device.class);
device = doPost("/api/customer/public/device/" + device.getUuidId(), Device.class);
String publicId = device.getCustomerId().toString();
Alarm alarm = Alarm.builder()
.originator(device.getId())
.status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type("Test")
.build();
Mockito.reset(tbClusterService, auditLogService);
alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull("Saved alarm is null!", alarm);
testNotifyEntityNeverMsgToEdgeServiceOneTime(alarm, alarm.getId(), tenantId, ActionType.ADDED);
logout();
JsonNode publicLoginRequest = JacksonUtil.toJsonNode("{\"publicId\": \"" + publicId + "\"}");
JsonNode tokens = doPost("/api/auth/login/public", publicLoginRequest, JsonNode.class);
this.token = tokens.get("token").asText();
PageData<AlarmInfo> pageData = doGetTyped(
"/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=1", new TypeReference<PageData<AlarmInfo>>() {}
);
Assert.assertNotNull("Found pageData is null", pageData);
Assert.assertNotEquals("Expected alarms are not found!", 0, pageData.getTotalElements());
AlarmInfo alarmInfo = pageData.getData().get(0);
boolean equals = alarm.getId().equals(alarmInfo.getId()) && alarm.getType().equals(alarmInfo.getType());
Assert.assertTrue("Created alarm doesn't match the found one!", equals);
}
private Alarm createAlarm(String type) throws Exception {

104
application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java

@ -25,14 +25,17 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
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.security.Authority;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.ArrayList;
import java.util.List;
@ -86,14 +89,28 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
public void testSaveCustomer() throws Exception {
Customer customer = new Customer();
customer.setTitle("My customer");
Mockito.reset(tbClusterService, auditLogService);
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
testNotifyEntityOneTimeMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(), savedCustomer.getId(),
savedCustomer.getTenantId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED);
Assert.assertNotNull(savedCustomer);
Assert.assertNotNull(savedCustomer.getId());
Assert.assertTrue(savedCustomer.getCreatedTime() > 0);
Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle());
savedCustomer.setTitle("My new customer");
doPost("/api/customer", savedCustomer, Customer.class);
testNotifyEntityAllOneTime(savedCustomer, savedCustomer.getId(), savedCustomer.getId(), savedCustomer.getTenantId(),
savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.UPDATED);
Customer foundCustomer = doGet("/api/customer/" + savedCustomer.getId().getId().toString(), Customer.class);
Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle());
@ -104,23 +121,57 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
@Test
public void testSaveCustomerWithViolationOfValidation() throws Exception {
Customer customer = new Customer();
String validationError = "Validation error: ";
customer.setTitle(RandomStringUtils.randomAlphabetic(300));
doPost("/api/customer", customer).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
Mockito.reset(tbClusterService, auditLogService);
String msgError = "length of title must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError));
customer.setTitle("Normal title");
customer.setCity(RandomStringUtils.randomAlphabetic(300));
doPost("/api/customer", customer).andExpect(statusReason(containsString("length of city must be equal or less than 255")));
msgError = "length of city must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError));
customer.setCity("Normal city");
customer.setCountry(RandomStringUtils.randomAlphabetic(300));
doPost("/api/customer", customer).andExpect(statusReason(containsString("length of country must be equal or less than 255")));
msgError = "length of country must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError));
customer.setCountry("Ukraine");
customer.setPhone(RandomStringUtils.randomAlphabetic(300));
doPost("/api/customer", customer).andExpect(statusReason(containsString("length of phone must be equal or less than 255")));
msgError = "length of phone must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError));
customer.setPhone("+3892555554512");
customer.setState(RandomStringUtils.randomAlphabetic(300));
doPost("/api/customer", customer).andExpect(statusReason(containsString("length of state must be equal or less than 255")));
msgError = "length of state must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError));
customer.setState("Normal state");
customer.setZip(RandomStringUtils.randomAlphabetic(300));
doPost("/api/customer", customer).andExpect(statusReason(containsString("length of zip or postal code must be equal or less than 255")));
msgError = "length of zip or postal code must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError));
}
@Test
@ -131,12 +182,22 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
doPost("/api/customer", savedCustomer, Customer.class);
loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/customer", savedCustomer, Customer.class, status().isForbidden());
deleteDifferentTenant();
testNotifyEntityNever(savedCustomer.getId(), savedCustomer);
deleteDifferentTenant();
login(tenantAdmin.getName(), "testPassword1");
doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk());
testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(),
savedCustomer.getId(), savedCustomer.getTenantId(), savedCustomer.getId(), tenantAdmin.getId(),
tenantAdmin.getEmail(), ActionType.DELETED, savedCustomer.getId().getId().toString());
}
@Test
@ -149,6 +210,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
Assert.assertNotNull(foundCustomer);
Assert.assertEquals(savedCustomer, foundCustomer);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk());
}
@ -159,9 +222,15 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk());
testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(),
savedCustomer.getId(), savedCustomer.getTenantId(), savedCustomer.getId(), tenantAdmin.getId(),
tenantAdmin.getEmail(), ActionType.DELETED, savedCustomer.getId().getId().toString());
doGet("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isNotFound());
}
@ -169,24 +238,33 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
@Test
public void testSaveCustomerWithEmptyTitle() throws Exception {
Customer customer = new Customer();
String msgError = "Customer title should be specified";
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/customer", customer)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Customer title should be specified")));
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError + "!"));
}
@Test
public void testSaveCustomerWithInvalidEmail() throws Exception {
Customer customer = new Customer();
String msgError = "Invalid email address format 'invalid@mail'";
customer.setTitle("My customer");
customer.setEmail("invalid@mail");
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/customer", customer)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Invalid email address format 'invalid@mail'")));
.andExpect(statusReason(containsString(msgError)));
// loginSysAdmin();
//
// doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
// .andExpect(status().isOk());
testNotifyEntityError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError + "!"));
}
@Test

7
application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthConfigTest.java

@ -96,6 +96,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest {
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
twoFaSettings.setProviders(List.of(totpTwoFaProviderConfig, smsTwoFaProviderConfig));
twoFaSettings.setMinVerificationCodeSendPeriod(5);
twoFaSettings.setVerificationCodeCheckRateLimit("3:900");
twoFaSettings.setMaxVerificationFailuresBeforeUserLockout(10);
twoFaSettings.setTotalAllowedTimeForVerification(3600);
@ -117,6 +118,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest {
twoFaSettings.setVerificationCodeCheckRateLimit("0:12");
twoFaSettings.setMaxVerificationFailuresBeforeUserLockout(-1);
twoFaSettings.setTotalAllowedTimeForVerification(0);
twoFaSettings.setMinVerificationCodeSendPeriod(5);
String errorMessage = getErrorMessage(doPost("/api/2fa/settings", twoFaSettings)
.andExpect(status().isBadRequest()));
@ -156,6 +158,8 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest {
private String savePlatformTwoFaSettingsAndGetError(TwoFaProviderConfig invalidTwoFaProviderConfig) throws Exception {
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
twoFaSettings.setProviders(Collections.singletonList(invalidTwoFaProviderConfig));
twoFaSettings.setMinVerificationCodeSendPeriod(5);
twoFaSettings.setTotalAllowedTimeForVerification(100);
return getErrorMessage(doPost("/api/2fa/settings", twoFaSettings)
.andExpect(status().isBadRequest()));
@ -432,8 +436,9 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest {
private void saveProvidersConfigs(TwoFaProviderConfig... providerConfigs) throws Exception {
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
twoFaSettings.setProviders(Arrays.stream(providerConfigs).collect(Collectors.toList()));
twoFaSettings.setMinVerificationCodeSendPeriod(5);
twoFaSettings.setTotalAllowedTimeForVerification(100);
doPost("/api/2fa/settings", twoFaSettings).andExpect(status().isOk());
}

6
application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java

@ -355,6 +355,8 @@ public abstract class TwoFactorAuthTest extends AbstractControllerTest {
emailTwoFaProviderConfig.setVerificationCodeLifetime(60);
platformTwoFaSettings.setProviders(List.of(totpTwoFaProviderConfig, smsTwoFaProviderConfig, emailTwoFaProviderConfig));
platformTwoFaSettings.setMinVerificationCodeSendPeriod(5);
platformTwoFaSettings.setTotalAllowedTimeForVerification(100);
twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, platformTwoFaSettings);
User twoFaUser = new User();
@ -409,6 +411,8 @@ public abstract class TwoFactorAuthTest extends AbstractControllerTest {
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
twoFaSettings.setProviders(Arrays.stream(new TwoFaProviderConfig[]{totpTwoFaProviderConfig}).collect(Collectors.toList()));
twoFaSettings.setMinVerificationCodeSendPeriod(5);
twoFaSettings.setTotalAllowedTimeForVerification(100);
Arrays.stream(customizer).forEach(c -> c.accept(twoFaSettings));
twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, twoFaSettings);
@ -425,6 +429,8 @@ public abstract class TwoFactorAuthTest extends AbstractControllerTest {
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
twoFaSettings.setProviders(Arrays.stream(new TwoFaProviderConfig[]{smsTwoFaProviderConfig}).collect(Collectors.toList()));
twoFaSettings.setMinVerificationCodeSendPeriod(5);
twoFaSettings.setTotalAllowedTimeForVerification(100);
twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, twoFaSettings);
SmsTwoFaAccountConfig smsTwoFaAccountConfig = new SmsTwoFaAccountConfig();

16
application/src/test/java/org/thingsboard/server/service/install/PsqlEntityDatabaseSchemaServiceTest.java → application/src/test/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaServiceTest.java

@ -23,31 +23,31 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class PsqlEntityDatabaseSchemaServiceTest {
public class SqlEntityDatabaseSchemaServiceTest {
@Test
public void givenPsqlDbSchemaService_whenCreateDatabaseSchema_thenVerifyPsqlIndexSpecificCall() throws Exception {
PsqlEntityDatabaseSchemaService service = spy(new PsqlEntityDatabaseSchemaService());
SqlEntityDatabaseSchemaService service = spy(new SqlEntityDatabaseSchemaService());
willDoNothing().given(service).executeQueryFromFile(anyString());
service.createDatabaseSchema();
verify(service, times(1)).createDatabaseIndexes();
verify(service, times(1)).executeQueryFromFile(PsqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_SQL);
verify(service, times(1)).executeQueryFromFile(PsqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_IDX_SQL);
verify(service, times(1)).executeQueryFromFile(PsqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_IDX_PSQL_ADDON_SQL);
verify(service, times(1)).executeQueryFromFile(SqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_SQL);
verify(service, times(1)).executeQueryFromFile(SqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_IDX_SQL);
verify(service, times(1)).executeQueryFromFile(SqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_IDX_PSQL_ADDON_SQL);
verify(service, times(3)).executeQueryFromFile(anyString());
}
@Test
public void givenPsqlDbSchemaService_whenCreateDatabaseIndexes_thenVerifyPsqlIndexSpecificCall() throws Exception {
PsqlEntityDatabaseSchemaService service = spy(new PsqlEntityDatabaseSchemaService());
SqlEntityDatabaseSchemaService service = spy(new SqlEntityDatabaseSchemaService());
willDoNothing().given(service).executeQueryFromFile(anyString());
service.createDatabaseIndexes();
verify(service, times(1)).executeQueryFromFile(PsqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_IDX_SQL);
verify(service, times(1)).executeQueryFromFile(PsqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_IDX_PSQL_ADDON_SQL);
verify(service, times(1)).executeQueryFromFile(SqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_IDX_SQL);
verify(service, times(1)).executeQueryFromFile(SqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_IDX_PSQL_ADDON_SQL);
verify(service, times(2)).executeQueryFromFile(anyString());
}

26
common/dao-api/src/main/java/org/thingsboard/server/dao/util/HsqlDao.java

@ -1,26 +0,0 @@
/**
* Copyright © 2016-2022 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.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnProperty(prefix = "spring.jpa", value = "database-platform", havingValue = "org.hibernate.dialect.HSQLDialect")
public @interface HsqlDao {
}

26
common/dao-api/src/main/java/org/thingsboard/server/dao/util/PsqlDao.java

@ -1,26 +0,0 @@
/**
* Copyright © 2016-2022 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.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnProperty(prefix = "spring.jpa", value = "database-platform", havingValue = "org.hibernate.dialect.PostgreSQLDialect")
public @interface PsqlDao {
}

3
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.thingsboard.server.common.data.query.DynamicValue;
import java.io.Serializable;
@ -34,4 +35,6 @@ public interface AlarmSchedule extends Serializable {
AlarmScheduleType getType();
DynamicValue<String> getDynamicValue();
}

7
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data.device.profile;
import org.thingsboard.server.common.data.query.DynamicValue;
public class AnyTimeSchedule implements AlarmSchedule {
@Override
@ -22,4 +24,9 @@ public class AnyTimeSchedule implements AlarmSchedule {
return AlarmScheduleType.ANY_TIME;
}
@Override
public DynamicValue<String> getDynamicValue() {
return null;
}
}

3
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java

@ -16,6 +16,7 @@
package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.query.DynamicValue;
import java.util.List;
@ -25,6 +26,8 @@ public class CustomTimeSchedule implements AlarmSchedule {
private String timezone;
private List<CustomTimeScheduleItem> items;
private DynamicValue<String> dynamicValue;
@Override
public AlarmScheduleType getType() {
return AlarmScheduleType.CUSTOM;

4
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java

@ -16,8 +16,8 @@
package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.query.DynamicValue;
import java.util.List;
import java.util.Set;
@Data
@ -28,6 +28,8 @@ public class SpecificTimeSchedule implements AlarmSchedule {
private long startsOn;
private long endsOn;
private DynamicValue<String> dynamicValue;
@Override
public AlarmScheduleType getType() {
return AlarmScheduleType.SPECIFIC_TIME;

2
common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/PlatformTwoFaSettings.java

@ -35,12 +35,14 @@ public class PlatformTwoFaSettings {
@NotNull
private List<TwoFaProviderConfig> providers;
@NotNull
@Min(value = 5, message = "minimum verification code sent period must be greater than or equal 5")
private Integer minVerificationCodeSendPeriod;
@Pattern(regexp = "[1-9]\\d*:[1-9]\\d*", message = "verification code check rate limit configuration is invalid")
private String verificationCodeCheckRateLimit;
@Min(value = 0, message = "maximum number of verification failure before user lockout must be positive")
private Integer maxVerificationFailuresBeforeUserLockout;
@NotNull
@Min(value = 60, message = "total amount of time allotted for verification must be greater than or equal 60")
private Integer totalAllowedTimeForVerification;

4
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java

@ -575,6 +575,10 @@ public class JsonConverter {
return JSON_PARSER.parse(json);
}
public static <T> T parse(String json, Class<T> clazz) {
return fromJson(parse(json), clazz);
}
public static String toJson(JsonElement element) {
return GSON.toJson(element);
}

37
dao/src/main/java/org/thingsboard/server/dao/HsqlTsLatestDaoConfig.java

@ -1,37 +0,0 @@
/**
* Copyright © 2016-2022 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;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlTsLatestDao;
import org.thingsboard.server.dao.util.TbAutoConfiguration;
@Configuration
@TbAutoConfiguration
@ComponentScan({"org.thingsboard.server.dao.sqlts.hsql"})
@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.insert.latest.hsql", "org.thingsboard.server.dao.sqlts.latest"})
@EntityScan({"org.thingsboard.server.dao.model.sqlts.latest"})
@EnableTransactionManagement
@SqlTsLatestDao
@HsqlDao
public class HsqlTsLatestDaoConfig {
}

37
dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java

@ -1,37 +0,0 @@
/**
* Copyright © 2016-2022 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;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
import org.thingsboard.server.dao.util.TbAutoConfiguration;
@Configuration
@TbAutoConfiguration
@ComponentScan({"org.thingsboard.server.dao.sqlts.psql", "org.thingsboard.server.dao.sqlts.insert.psql"})
@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.ts", "org.thingsboard.server.dao.sqlts.insert.psql"})
@EntityScan({"org.thingsboard.server.dao.model.sqlts.ts"})
@EnableTransactionManagement
@PsqlDao
@SqlTsDao
public class PsqlTsDaoConfig {
}

8
dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java → dao/src/main/java/org/thingsboard/server/dao/SqlTsDaoConfig.java

@ -20,18 +20,16 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
import org.thingsboard.server.dao.util.TbAutoConfiguration;
@Configuration
@TbAutoConfiguration
@ComponentScan({"org.thingsboard.server.dao.sqlts.hsql"})
@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.ts", "org.thingsboard.server.dao.sqlts.insert.hsql"})
@ComponentScan({"org.thingsboard.server.dao.sqlts.sql", "org.thingsboard.server.dao.sqlts.insert.sql"})
@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.ts", "org.thingsboard.server.dao.sqlts.insert.sql"})
@EntityScan({"org.thingsboard.server.dao.model.sqlts.ts"})
@EnableTransactionManagement
@SqlTsDao
@HsqlDao
public class HsqlTsDaoConfig {
public class SqlTsDaoConfig {
}

8
dao/src/main/java/org/thingsboard/server/dao/PsqlTsLatestDaoConfig.java → dao/src/main/java/org/thingsboard/server/dao/SqlTsLatestDaoConfig.java

@ -20,18 +20,16 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsLatestDao;
import org.thingsboard.server.dao.util.TbAutoConfiguration;
@Configuration
@TbAutoConfiguration
@ComponentScan({"org.thingsboard.server.dao.sqlts.psql"})
@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.insert.latest.psql", "org.thingsboard.server.dao.sqlts.latest"})
@ComponentScan({"org.thingsboard.server.dao.sqlts.sql"})
@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.insert.latest.sql", "org.thingsboard.server.dao.sqlts.latest"})
@EntityScan({"org.thingsboard.server.dao.model.sqlts.latest"})
@EnableTransactionManagement
@SqlTsLatestDao
@PsqlDao
public class PsqlTsLatestDaoConfig {
public class SqlTsLatestDaoConfig {
}

2
dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java

@ -20,7 +20,6 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.TbAutoConfiguration;
import org.thingsboard.server.dao.util.TimescaleDBTsDao;
@ -31,7 +30,6 @@ import org.thingsboard.server.dao.util.TimescaleDBTsDao;
@EntityScan({"org.thingsboard.server.dao.model.sqlts.timescale"})
@EnableTransactionManagement
@TimescaleDBTsDao
@PsqlDao
public class TimescaleDaoConfig {
}

4
dao/src/main/java/org/thingsboard/server/dao/TimescaleTsLatestDaoConfig.java

@ -20,18 +20,16 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.TbAutoConfiguration;
import org.thingsboard.server.dao.util.TimescaleDBTsLatestDao;
@Configuration
@TbAutoConfiguration
@ComponentScan({"org.thingsboard.server.dao.sqlts.timescale"})
@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.insert.latest.psql", "org.thingsboard.server.dao.sqlts.latest"})
@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.insert.latest.sql", "org.thingsboard.server.dao.sqlts.latest"})
@EntityScan({"org.thingsboard.server.dao.model.sqlts.latest"})
@EnableTransactionManagement
@TimescaleDBTsLatestDao
@PsqlDao
public class TimescaleTsLatestDaoConfig {
}

76
dao/src/main/java/org/thingsboard/server/dao/sql/attributes/HsqlAttributesInsertRepository.java

@ -1,76 +0,0 @@
/**
* Copyright © 2016-2022 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.sql.attributes;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.AttributeKvEntity;
import org.thingsboard.server.dao.util.HsqlDao;
import java.sql.Types;
import java.util.List;
@HsqlDao
@Repository
@Transactional
public class HsqlAttributesInsertRepository extends AttributeKvInsertRepository {
private static final String INSERT_OR_UPDATE =
"MERGE INTO attribute_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
"A (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, json_v, last_update_ts) " +
"ON (attribute_kv.entity_type=A.entity_type " +
"AND attribute_kv.entity_id=A.entity_id " +
"AND attribute_kv.attribute_type=A.attribute_type " +
"AND attribute_kv.attribute_key=A.attribute_key) " +
"WHEN MATCHED THEN UPDATE SET attribute_kv.str_v = A.str_v, attribute_kv.long_v = A.long_v, attribute_kv.dbl_v = A.dbl_v, attribute_kv.bool_v = A.bool_v, attribute_kv.json_v = A.json_v, attribute_kv.last_update_ts = A.last_update_ts " +
"WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, json_v, last_update_ts) " +
"VALUES (A.entity_type, A.entity_id, A.attribute_type, A.attribute_key, A.str_v, A.long_v, A.dbl_v, A.bool_v, A.json_v, A.last_update_ts)";
@Override
protected void saveOrUpdate(List<AttributeKvEntity> entities) {
entities.forEach(entity -> {
jdbcTemplate.update(INSERT_OR_UPDATE, ps -> {
ps.setString(1, entity.getId().getEntityType().name());
ps.setObject(2, entity.getId().getEntityId());
ps.setString(3, entity.getId().getAttributeType());
ps.setString(4, entity.getId().getAttributeKey());
ps.setString(5, entity.getStrValue());
if (entity.getLongValue() != null) {
ps.setLong(6, entity.getLongValue());
} else {
ps.setNull(6, Types.BIGINT);
}
if (entity.getDoubleValue() != null) {
ps.setDouble(7, entity.getDoubleValue());
} else {
ps.setNull(7, Types.DOUBLE);
}
if (entity.getBooleanValue() != null) {
ps.setBoolean(8, entity.getBooleanValue());
} else {
ps.setNull(8, Types.BOOLEAN);
}
ps.setString(9, entity.getJsonValue());
ps.setLong(10, entity.getLastUpdateTs());
});
});
}
}

4
dao/src/main/java/org/thingsboard/server/dao/sql/attributes/PsqlAttributesInsertRepository.java → dao/src/main/java/org/thingsboard/server/dao/sql/attributes/SqlAttributesInsertRepository.java

@ -17,11 +17,9 @@ package org.thingsboard.server.dao.sql.attributes;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.util.PsqlDao;
@PsqlDao
@Repository
@Transactional
public class PsqlAttributesInsertRepository extends AttributeKvInsertRepository {
public class SqlAttributesInsertRepository extends AttributeKvInsertRepository {
}

65
dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java

@ -1,65 +0,0 @@
/**
* Copyright © 2016-2022 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.sql.component;
import org.springframework.stereotype.Repository;
import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity;
import org.thingsboard.server.dao.util.HsqlDao;
import javax.persistence.Query;
@HsqlDao
@Repository
public class HsqlComponentDescriptorInsertRepository extends AbstractComponentDescriptorInsertRepository {
private static final String P_KEY_CONFLICT_STATEMENT = "(component_descriptor.id=UUID(I.id))";
private static final String UNQ_KEY_CONFLICT_STATEMENT = "(component_descriptor.clazz=I.clazz)";
private static final String INSERT_OR_UPDATE_ON_P_KEY_CONFLICT = getInsertString(P_KEY_CONFLICT_STATEMENT);
private static final String INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT = getInsertString(UNQ_KEY_CONFLICT_STATEMENT);
@Override
public ComponentDescriptorEntity saveOrUpdate(ComponentDescriptorEntity entity) {
return saveAndGet(entity, INSERT_OR_UPDATE_ON_P_KEY_CONFLICT, INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT);
}
@Override
protected Query getQuery(ComponentDescriptorEntity entity, String query) {
return entityManager.createNativeQuery(query, ComponentDescriptorEntity.class)
.setParameter("id", entity.getUuid().toString())
.setParameter("created_time", entity.getCreatedTime())
.setParameter("actions", entity.getActions())
.setParameter("clazz", entity.getClazz())
.setParameter("configuration_descriptor", entity.getConfigurationDescriptor().toString())
.setParameter("name", entity.getName())
.setParameter("scope", entity.getScope().name())
.setParameter("search_text", entity.getSearchText())
.setParameter("type", entity.getType().name());
}
@Override
protected ComponentDescriptorEntity doProcessSaveOrUpdate(ComponentDescriptorEntity entity, String query) {
getQuery(entity, query).executeUpdate();
return entityManager.find(ComponentDescriptorEntity.class, entity.getUuid());
}
private static String getInsertString(String conflictStatement) {
return "MERGE INTO component_descriptor USING (VALUES :id, :created_time, :actions, :clazz, :configuration_descriptor, :name, :scope, :search_text, :type) I (id, created_time, actions, clazz, configuration_descriptor, name, scope, search_text, type) ON "
+ conflictStatement
+ " WHEN MATCHED THEN UPDATE SET component_descriptor.id = UUID(I.id), component_descriptor.actions = I.actions, component_descriptor.clazz = I.clazz, component_descriptor.configuration_descriptor = I.configuration_descriptor, component_descriptor.name = I.name, component_descriptor.scope = I.scope, component_descriptor.search_text = I.search_text, component_descriptor.type = I.type" +
" WHEN NOT MATCHED THEN INSERT (id, created_time, actions, clazz, configuration_descriptor, name, scope, search_text, type) VALUES (UUID(I.id), I.created_time, I.actions, I.clazz, I.configuration_descriptor, I.name, I.scope, I.search_text, I.type)";
}
}

4
dao/src/main/java/org/thingsboard/server/dao/sql/component/PsqlComponentDescriptorInsertRepository.java → dao/src/main/java/org/thingsboard/server/dao/sql/component/SqlComponentDescriptorInsertRepository.java

@ -17,11 +17,9 @@ package org.thingsboard.server.dao.sql.component;
import org.springframework.stereotype.Repository;
import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity;
import org.thingsboard.server.dao.util.PsqlDao;
@PsqlDao
@Repository
public class PsqlComponentDescriptorInsertRepository extends AbstractComponentDescriptorInsertRepository {
public class SqlComponentDescriptorInsertRepository extends AbstractComponentDescriptorInsertRepository {
private static final String ID = "id = :id";
private static final String CLAZZ_CLAZZ = "clazz = :clazz";

3
dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventInsertRepository.java

@ -24,13 +24,12 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.dao.model.sql.EdgeEventEntity;
import org.thingsboard.server.dao.util.PsqlDao;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
@PsqlDao
@Repository
@Transactional
public class EdgeEventInsertRepository {

2
dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java

@ -25,14 +25,12 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.dao.model.sql.EventEntity;
import org.thingsboard.server.dao.util.PsqlDao;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.regex.Pattern;
@PsqlDao
@Repository
@Transactional
public class EventInsertRepository {

4
dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventCleanupRepository.java → dao/src/main/java/org/thingsboard/server/dao/sql/event/SqlEventCleanupRepository.java

@ -18,7 +18,6 @@ package org.thingsboard.server.dao.sql.event;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
import org.thingsboard.server.dao.util.PsqlDao;
import java.sql.Connection;
import java.sql.PreparedStatement;
@ -27,9 +26,8 @@ import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
@Slf4j
@PsqlDao
@Repository
public class PsqlEventCleanupRepository extends JpaAbstractDaoListeningExecutorService implements EventCleanupRepository {
public class SqlEventCleanupRepository extends JpaAbstractDaoListeningExecutorService implements EventCleanupRepository {
@Override
public void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs) {

4
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java

@ -369,7 +369,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
try {
return jdbcTemplate.queryForObject(countQuery, ctx, Long.class);
} finally {
queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs);
queryLog.logQuery(ctx, countQuery, System.currentTimeMillis() - startTs);
}
});
}
@ -481,7 +481,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
try {
rows = jdbcTemplate.queryForList(dataQuery, ctx);
} finally {
queryLog.logQuery(ctx, countQuery, System.currentTimeMillis() - startTs);
queryLog.logQuery(ctx, dataQuery, System.currentTimeMillis() - startTs);
}
return EntityDataAdapter.createEntityData(pageLink, selectionMapping, rows, totalElements);
});

63
dao/src/main/java/org/thingsboard/server/dao/sql/relation/HsqlRelationInsertRepository.java

@ -1,63 +0,0 @@
/**
* Copyright © 2016-2022 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.sql.relation;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.RelationCompositeKey;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.util.HsqlDao;
import javax.persistence.Query;
@HsqlDao
@Repository
@Transactional
public class HsqlRelationInsertRepository extends AbstractRelationInsertRepository implements RelationInsertRepository {
private static final String INSERT_ON_CONFLICT_DO_UPDATE = "MERGE INTO relation USING (VALUES :fromId, :fromType, :toId, :toType, :relationTypeGroup, :relationType, :additionalInfo) R " +
"(from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info) " +
"ON (relation.from_id = UUID(R.from_id) AND relation.from_type = R.from_type AND relation.relation_type_group = R.relation_type_group AND relation.relation_type = R.relation_type AND relation.to_id = UUID(R.to_id) AND relation.to_type = R.to_type) " +
"WHEN MATCHED THEN UPDATE SET relation.additional_info = R.additional_info " +
"WHEN NOT MATCHED THEN INSERT (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info) VALUES (UUID(R.from_id), R.from_type, UUID(R.to_id), R.to_type, R.relation_type_group, R.relation_type, R.additional_info)";
protected Query getQuery(RelationEntity entity, String query) {
Query nativeQuery = entityManager.createNativeQuery(query, RelationEntity.class);
if (entity.getAdditionalInfo() == null) {
nativeQuery.setParameter("additionalInfo", null);
} else {
nativeQuery.setParameter("additionalInfo", entity.getAdditionalInfo().toString());
}
return nativeQuery
.setParameter("fromId", entity.getFromId().toString())
.setParameter("fromType", entity.getFromType())
.setParameter("toId", entity.getToId().toString())
.setParameter("toType", entity.getToType())
.setParameter("relationTypeGroup", entity.getRelationTypeGroup())
.setParameter("relationType", entity.getRelationType());
}
@Override
public RelationEntity saveOrUpdate(RelationEntity entity) {
return processSaveOrUpdate(entity);
}
@Override
protected RelationEntity processSaveOrUpdate(RelationEntity entity) {
getQuery(entity, INSERT_ON_CONFLICT_DO_UPDATE).executeUpdate();
return entityManager.find(RelationEntity.class, new RelationCompositeKey(entity.toData()));
}
}

4
dao/src/main/java/org/thingsboard/server/dao/sql/relation/PsqlRelationInsertRepository.java → dao/src/main/java/org/thingsboard/server/dao/sql/relation/SqlRelationInsertRepository.java

@ -18,12 +18,10 @@ package org.thingsboard.server.dao.sql.relation;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.util.PsqlDao;
@PsqlDao
@Repository
@Transactional
public class PsqlRelationInsertRepository extends AbstractRelationInsertRepository implements RelationInsertRepository {
public class SqlRelationInsertRepository extends AbstractRelationInsertRepository implements RelationInsertRepository {
private static final String INSERT_ON_CONFLICT_DO_UPDATE = "INSERT INTO relation (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info)" +
" VALUES (:fromId, :fromType, :toId, :toType, :relationTypeGroup, :relationType, :additionalInfo) " +

62
dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java

@ -1,62 +0,0 @@
/**
* Copyright © 2016-2022 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.sqlts.hsql;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.sqlts.AbstractChunkedAggregationTimeseriesDao;
import org.thingsboard.server.dao.timeseries.TimeseriesDao;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
@Component
@Slf4j
@SqlTsDao
@HsqlDao
public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao implements TimeseriesDao {
@Override
public ListenableFuture<Integer> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) {
int dataPointDays = getDataPointDays(tsKvEntry, computeTtl(ttl));
String strKey = tsKvEntry.getKey();
Integer keyId = getOrSaveKeyId(strKey);
TsKvEntity entity = new TsKvEntity();
entity.setEntityId(entityId.getId());
entity.setTs(tsKvEntry.getTs());
entity.setKey(keyId);
entity.setStrValue(tsKvEntry.getStrValue().orElse(null));
entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null));
entity.setLongValue(tsKvEntry.getLongValue().orElse(null));
entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null));
entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null));
log.trace("Saving entity: {}", entity);
return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor());
}
@Override
public void cleanup(long systemTtl) {
}
}

87
dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/hsql/HsqlInsertTsRepository.java

@ -1,87 +0,0 @@
/**
* Copyright © 2016-2022 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.sqlts.insert.hsql;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository;
import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
@SqlTsDao
@HsqlDao
@Repository
@Transactional
public class HsqlInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository<TsKvEntity> {
private static final String INSERT_OR_UPDATE =
"MERGE INTO ts_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " +
"T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
"ON (ts_kv.entity_id=T.entity_id " +
"AND ts_kv.key=T.key " +
"AND ts_kv.ts=T.ts) " +
"WHEN MATCHED THEN UPDATE SET ts_kv.bool_v = T.bool_v, ts_kv.str_v = T.str_v, ts_kv.long_v = T.long_v, ts_kv.dbl_v = T.dbl_v ,ts_kv.json_v = T.json_v " +
"WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
"VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v, T.json_v);";
@Override
public void saveOrUpdate(List<TsKvEntity> entities) {
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
TsKvEntity tsKvEntity = entities.get(i);
ps.setObject(1, tsKvEntity.getEntityId());
ps.setInt(2, tsKvEntity.getKey());
ps.setLong(3, tsKvEntity.getTs());
if (tsKvEntity.getBooleanValue() != null) {
ps.setBoolean(4, tsKvEntity.getBooleanValue());
} else {
ps.setNull(4, Types.BOOLEAN);
}
ps.setString(5, tsKvEntity.getStrValue());
if (tsKvEntity.getLongValue() != null) {
ps.setLong(6, tsKvEntity.getLongValue());
} else {
ps.setNull(6, Types.BIGINT);
}
if (tsKvEntity.getDoubleValue() != null) {
ps.setDouble(7, tsKvEntity.getDoubleValue());
} else {
ps.setNull(7, Types.DOUBLE);
}
ps.setString(8, tsKvEntity.getJsonValue());
}
@Override
public int getBatchSize() {
return entities.size();
}
});
}
}

85
dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/hsql/HsqlLatestInsertTsRepository.java

@ -1,85 +0,0 @@
/**
* Copyright © 2016-2022 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.sqlts.insert.latest.hsql;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity;
import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository;
import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlTsLatestDao;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
@SqlTsLatestDao
@HsqlDao
@Repository
@Transactional
public class HsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository {
private static final String INSERT_OR_UPDATE =
"MERGE INTO ts_kv_latest USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " +
"T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
"ON (ts_kv_latest.entity_id=T.entity_id " +
"AND ts_kv_latest.key=T.key) " +
"WHEN MATCHED THEN UPDATE SET ts_kv_latest.ts = T.ts, ts_kv_latest.bool_v = T.bool_v, ts_kv_latest.str_v = T.str_v, ts_kv_latest.long_v = T.long_v, ts_kv_latest.dbl_v = T.dbl_v, ts_kv_latest.json_v = T.json_v " +
"WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
"VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v, T.json_v);";
@Override
public void saveOrUpdate(List<TsKvLatestEntity> entities) {
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setObject(1, entities.get(i).getEntityId());
ps.setInt(2, entities.get(i).getKey());
ps.setLong(3, entities.get(i).getTs());
if (entities.get(i).getBooleanValue() != null) {
ps.setBoolean(4, entities.get(i).getBooleanValue());
} else {
ps.setNull(4, Types.BOOLEAN);
}
ps.setString(5, entities.get(i).getStrValue());
if (entities.get(i).getLongValue() != null) {
ps.setLong(6, entities.get(i).getLongValue());
} else {
ps.setNull(6, Types.BIGINT);
}
if (entities.get(i).getDoubleValue() != null) {
ps.setDouble(7, entities.get(i).getDoubleValue());
} else {
ps.setNull(7, Types.DOUBLE);
}
ps.setString(8, entities.get(i).getJsonValue());
}
@Override
public int getBatchSize() {
return entities.size();
}
});
}
}

8
dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/psql/PsqlLatestInsertTsRepository.java → dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/sql/SqlLatestInsertTsRepository.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sqlts.insert.latest.psql;
package org.thingsboard.server.dao.sqlts.insert.latest.sql;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
@ -24,7 +24,7 @@ import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity;
import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository;
import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository;
import org.thingsboard.server.dao.util.PsqlTsLatestAnyDao;
import org.thingsboard.server.dao.util.SqlTsLatestAnyDao;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@ -33,10 +33,10 @@ import java.util.ArrayList;
import java.util.List;
@PsqlTsLatestAnyDao
@SqlTsLatestAnyDao
@Repository
@Transactional
public class PsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository {
public class SqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository {
@Value("${sql.ts_latest.update_by_latest_ts:true}")
private Boolean updateByLatestTs;

6
dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/psql/PsqlInsertTsRepository.java → dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlInsertTsRepository.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sqlts.insert.psql;
package org.thingsboard.server.dao.sqlts.insert.sql;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.stereotype.Repository;
@ -21,7 +21,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository;
import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
import java.sql.PreparedStatement;
@ -30,10 +29,9 @@ import java.sql.Types;
import java.util.List;
@SqlTsDao
@PsqlDao
@Repository
@Transactional
public class PsqlInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository<TsKvEntity> {
public class SqlInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository<TsKvEntity> {
private static final String INSERT_ON_CONFLICT_DO_UPDATE = "INSERT INTO ts_kv (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES (?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " +
"ON CONFLICT (entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);";

10
dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/psql/PsqlPartitioningRepository.java → dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java

@ -13,27 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sqlts.insert.psql;
package org.thingsboard.server.dao.sqlts.insert.sql;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.timeseries.PsqlPartition;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.timeseries.SqlPartition;
import org.thingsboard.server.dao.util.SqlTsDao;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@SqlTsDao
@PsqlDao
@Repository
@Transactional
public class PsqlPartitioningRepository {
public class SqlPartitioningRepository {
@PersistenceContext
private EntityManager entityManager;
public void save(PsqlPartition partition) {
public void save(SqlPartition partition) {
entityManager.createNativeQuery(partition.getQuery())
.executeUpdate();
}

2
dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java

@ -21,7 +21,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity;
import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository;
import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.TimescaleDBTsDao;
import java.sql.PreparedStatement;
@ -30,7 +29,6 @@ import java.sql.Types;
import java.util.List;
@TimescaleDBTsDao
@PsqlDao
@Repository
@Transactional
public class TimescaleInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository<TimescaleTsKvEntity> {

32
dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java → dao/src/main/java/org/thingsboard/server/dao/sqlts/sql/JpaSqlTimeseriesDao.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sqlts.psql;
package org.thingsboard.server.dao.sqlts.sql;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@ -29,10 +29,9 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.sqlts.AbstractChunkedAggregationTimeseriesDao;
import org.thingsboard.server.dao.sqlts.insert.psql.PsqlPartitioningRepository;
import org.thingsboard.server.dao.timeseries.PsqlPartition;
import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
import org.thingsboard.server.dao.timeseries.SqlPartition;
import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
import java.sql.Connection;
@ -52,15 +51,14 @@ import java.util.concurrent.locks.ReentrantLock;
@Component
@Slf4j
@PsqlDao
@SqlTsDao
public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao {
public class JpaSqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao {
private final Map<Long, PsqlPartition> partitions = new ConcurrentHashMap<>();
private final Map<Long, SqlPartition> partitions = new ConcurrentHashMap<>();
private static final ReentrantLock partitionCreationLock = new ReentrantLock();
@Autowired
private PsqlPartitioningRepository partitioningRepository;
private SqlPartitioningRepository partitioningRepository;
private SqlTsPartitionDate tsFormat;
@ -134,24 +132,24 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa
long partitionEndTs = toMills(localDateTimeEnd);
ZonedDateTime zonedDateTime = localDateTimeStart.atZone(ZoneOffset.UTC);
String partitionDate = zonedDateTime.format(DateTimeFormatter.ofPattern(tsFormat.getPattern()));
savePartition(new PsqlPartition(partitionStartTs, partitionEndTs, partitionDate));
savePartition(new SqlPartition(partitionStartTs, partitionEndTs, partitionDate));
}
}
}
private void savePartition(PsqlPartition psqlPartition) {
if (!partitions.containsKey(psqlPartition.getStart())) {
private void savePartition(SqlPartition sqlPartition) {
if (!partitions.containsKey(sqlPartition.getStart())) {
partitionCreationLock.lock();
try {
log.trace("Saving partition: {}", psqlPartition);
partitioningRepository.save(psqlPartition);
log.trace("Adding partition to Set: {}", psqlPartition);
partitions.put(psqlPartition.getStart(), psqlPartition);
log.trace("Saving partition: {}", sqlPartition);
partitioningRepository.save(sqlPartition);
log.trace("Adding partition to Set: {}", sqlPartition);
partitions.put(sqlPartition.getStart(), sqlPartition);
} catch (DataIntegrityViolationException ex) {
log.trace("Error occurred during partition save:", ex);
if (ex.getCause() instanceof ConstraintViolationException) {
log.warn("Saving partition [{}] rejected. Timeseries data will save to the ts_kv_indefinite (DEFAULT) partition.", psqlPartition.getPartitionDate());
partitions.put(psqlPartition.getStart(), psqlPartition);
log.warn("Saving partition [{}] rejected. Timeseries data will save to the ts_kv_indefinite (DEFAULT) partition.", sqlPartition.getPartitionDate());
partitions.put(sqlPartition.getStart(), sqlPartition);
} else {
throw new RuntimeException(ex);
}

4
dao/src/main/java/org/thingsboard/server/dao/timeseries/PsqlPartition.java → dao/src/main/java/org/thingsboard/server/dao/timeseries/SqlPartition.java

@ -18,7 +18,7 @@ package org.thingsboard.server.dao.timeseries;
import lombok.Data;
@Data
public class PsqlPartition {
public class SqlPartition {
private static final String TABLE_REGEX = "ts_kv_";
@ -27,7 +27,7 @@ public class PsqlPartition {
private String partitionDate;
private String query;
public PsqlPartition(long start, long end, String partitionDate) {
public SqlPartition(long start, long end, String partitionDate) {
this.start = start;
this.end = end;
this.partitionDate = partitionDate;

2
dao/src/test/java/org/thingsboard/server/dao/AbstractDaoServiceTest.java

@ -28,7 +28,7 @@ import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.dao.service.DaoSqlTest;
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {JpaDaoConfig.class, PsqlTsDaoConfig.class, PsqlTsLatestDaoConfig.class, SqlTimeseriesDaoConfig.class})
@ContextConfiguration(classes = {JpaDaoConfig.class, SqlTsDaoConfig.class, SqlTsLatestDaoConfig.class, SqlTimeseriesDaoConfig.class})
@DaoSqlTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@TestExecutionListeners({

2
dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java

@ -30,7 +30,7 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
* Created by Valerii Sosliuk on 4/22/2017.
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {JpaDaoConfig.class, PsqlTsDaoConfig.class, PsqlTsLatestDaoConfig.class, SqlTimeseriesDaoConfig.class})
@ContextConfiguration(classes = {JpaDaoConfig.class, SqlTsDaoConfig.class, SqlTsLatestDaoConfig.class, SqlTimeseriesDaoConfig.class})
@DaoSqlTest
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,

40
dao/src/test/java/org/thingsboard/server/dao/util/DaoTestUtil.java

@ -1,40 +0,0 @@
/**
* Copyright © 2016-2022 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.util;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.DriverManager;
public class DaoTestUtil {
private static final String POSTGRES_DRIVER_CLASS = "org.postgresql.Driver";
private static final String H2_DRIVER_CLASS = "org.hsqldb.jdbc.JDBCDriver";
public static SqlDbType getSqlDbType(JdbcTemplate template){
try {
String driverName = DriverManager.getDriver(template.getDataSource().getConnection().getMetaData().getURL()).getClass().getName();
if (POSTGRES_DRIVER_CLASS.equals(driverName)) {
return SqlDbType.POSTGRES;
} else if (H2_DRIVER_CLASS.equals(driverName)) {
return SqlDbType.H2;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

20
dao/src/test/java/org/thingsboard/server/dao/util/SqlDbType.java

@ -1,20 +0,0 @@
/**
* Copyright © 2016-2022 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.util;
public enum SqlDbType {
POSTGRES, H2;
}

1
dao/src/test/resources/nosql-test.properties

@ -11,7 +11,6 @@ spring.jpa.properties.hibernate.jdbc.log.warnings=false
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.url=jdbc:tc:postgresql:12.8:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb

2
dao/src/test/resources/sql-test.properties

@ -12,7 +12,6 @@ spring.jpa.properties.hibernate.jdbc.log.warnings=false
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.url=jdbc:tc:postgresql:12.8:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb
@ -32,7 +31,6 @@ service.type=monolith
#spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
#spring.jpa.show-sql=false
#spring.jpa.hibernate.ddl-auto=none
#spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
#
#spring.datasource.username=postgres
#spring.datasource.password=postgres

1
docker/tb-node.hybrid.env

@ -2,7 +2,6 @@
DATABASE_TS_TYPE=cassandra
CASSANDRA_URL=cassandra:9042
SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect
SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver
SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/thingsboard
SPRING_DATASOURCE_USERNAME=postgres

1
docker/tb-node.postgres.env

@ -1,7 +1,6 @@
# ThingsBoard server configuration for PostgreSQL database
DATABASE_TS_TYPE=sql
SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect
SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver
SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/thingsboard
SPRING_DATASOURCE_USERNAME=postgres

16
msa/js-executor/docker/Dockerfile

@ -14,16 +14,13 @@
# limitations under the License.
#
FROM node:16.13.1-bullseye-slim
COPY start-js-executor.sh /tmp/
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-js-executor.sh /usr/bin
FROM node:16.15.1-bullseye-slim
ENV NODE_ENV production
ENV DOCKER_MODE true
COPY start-js-executor.sh /tmp/
WORKDIR ${pkg.installFolder}
COPY ["src/package.json", "src/yarn.lock", "./"]
@ -34,9 +31,10 @@ COPY src/api ./api
COPY src/queue ./queue
COPY src/server.js ./
RUN chown -R node:node ${pkg.installFolder}
RUN yarn install --production && yarn cache clean --all
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-js-executor.sh /usr/bin \
&& chown -R node:node ${pkg.installFolder} \
&& yarn install --production && yarn cache clean --all
USER node

37
msa/js-executor/package.json

@ -6,26 +6,25 @@
"main": "server.js",
"bin": "server.js",
"scripts": {
"pkg": "pkg -t node12-linux-x64,node12-win-x64 --out-path ./target . && node install.js",
"pkg": "pkg -t node16-linux-x64,node16-win-x64 --out-path ./target . && node install.js",
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon server.js",
"start-prod": "NODE_ENV=production nodemon server.js"
},
"dependencies": {
"@azure/service-bus": "^1.1.9",
"@google-cloud/pubsub": "^2.5.0",
"amqplib": "^0.6.0",
"aws-sdk": "^2.741.0",
"azure-sb": "^0.11.1",
"config": "^3.3.1",
"express": "^4.17.1",
"js-yaml": "^3.14.0",
"kafkajs": "^1.15.0",
"long": "^4.0.0",
"@azure/service-bus": "^7.5.1",
"@google-cloud/pubsub": "^3.0.1",
"amqplib": "^0.10.0",
"aws-sdk": "^2.1152.0",
"config": "^3.3.7",
"express": "^4.18.1",
"js-yaml": "^4.1.0",
"kafkajs": "^2.0.2",
"long": "^5.2.0",
"uuid-parse": "^1.1.0",
"uuid-random": "^1.3.2",
"winston": "^3.3.3",
"winston-daily-rotate-file": "^4.5.0"
"winston": "^3.7.2",
"winston-daily-rotate-file": "^4.7.1"
},
"nyc": {
"exclude": [
@ -36,13 +35,19 @@
]
},
"devDependencies": {
"fs-extra": "^10.0.0",
"nodemon": "^2.0.12",
"pkg": "^5.3.1"
"fs-extra": "^10.1.0",
"nodemon": "^2.0.16",
"pkg": "^5.7.0"
},
"pkg": {
"assets": [
"node_modules/config/**/*.*"
]
},
"resolutions": {
"ansi-regex": "^5.0.1",
"color-string": "^1.5.5",
"minimist": "^1.2.6",
"node-fetch": "^2.6.7"
}
}

4
msa/js-executor/pom.xml

@ -71,8 +71,8 @@
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>v12.16.1</nodeVersion>
<yarnVersion>v1.22.4</yarnVersion>
<nodeVersion>v16.15.1</nodeVersion>
<yarnVersion>v1.22.17</yarnVersion>
</configuration>
</execution>
<execution>

53
msa/js-executor/queue/serviceBusTemplate.js

@ -18,8 +18,7 @@
const config = require('config'),
JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'),
logger = require('../config/logger')._logger('serviceBusTemplate');
const {ServiceBusClient, ReceiveMode} = require("@azure/service-bus");
const azure = require('azure-sb');
const {ServiceBusClient, ServiceBusAdministrationClient} = require("@azure/service-bus");
const requestTopic = config.get('request_topic');
const namespaceName = config.get('service_bus.namespace_name');
@ -28,7 +27,6 @@ const sasKey = config.get('service_bus.sas_key');
const queueProperties = config.get('service_bus.queue_properties');
let sbClient;
let receiverClient;
let receiver;
let serviceBusService;
@ -61,11 +59,10 @@ function ServiceBusProducer() {
}
function CustomSender(topic) {
this.queueClient = sbClient.createQueueClient(topic);
this.sender = this.queueClient.createSender();
this.sender = sbClient.createSender(topic);
this.send = async (message) => {
return this.sender.send(message);
return this.sender.sendMessages(message);
}
}
@ -74,8 +71,8 @@ function CustomSender(topic) {
logger.info('Starting ThingsBoard JavaScript Executor Microservice...');
const connectionString = `Endpoint=sb://${namespaceName}.servicebus.windows.net/;SharedAccessKeyName=${sasKeyName};SharedAccessKey=${sasKey}`;
sbClient = ServiceBusClient.createFromConnectionString(connectionString);
serviceBusService = azure.createServiceBusService(connectionString);
sbClient = new ServiceBusClient(connectionString)
serviceBusService = new ServiceBusAdministrationClient(connectionString);
parseQueueProperties();
@ -84,9 +81,9 @@ function CustomSender(topic) {
if (err) {
reject(err);
} else {
data.forEach(queue => {
queues.push(queue.QueueName);
});
for (const queue of data) {
queues.push(queue.name);
}
resolve();
}
});
@ -97,8 +94,7 @@ function CustomSender(topic) {
queues.push(requestTopic);
}
receiverClient = sbClient.createQueueClient(requestTopic);
receiver = receiverClient.createReceiver(ReceiveMode.peekLock);
receiver = sbClient.createReceiver(requestTopic, {receiveMode: 'peekLock'});
const messageProcessor = new JsInvokeMessageProcessor(new ServiceBusProducer());
@ -111,18 +107,18 @@ function CustomSender(topic) {
const errorHandler = (error) => {
logger.error('Failed to receive message from queue.', error);
};
receiver.registerMessageHandler(messageHandler, errorHandler);
receiver.subscribe({processMessage: messageHandler, processError: errorHandler})
} catch (e) {
logger.error('Failed to start ThingsBoard JavaScript Executor Microservice: %s', e.message);
logger.error(e.stack);
exit(-1);
await exit(-1);
}
})();
async function createQueueIfNotExist(topic) {
return new Promise((resolve, reject) => {
serviceBusService.createQueueIfNotExists(topic, queueOptions, (err) => {
if (err) {
serviceBusService.createQueue(topic, queueOptions, (err) => {
if (err && err.code !== "MessageEntityAlreadyExistsError") {
reject(err);
} else {
resolve();
@ -139,10 +135,10 @@ function parseQueueProperties() {
properties[p.substring(0, delimiterPosition)] = p.substring(delimiterPosition + 1);
});
queueOptions = {
DuplicateDetection: 'false',
MaxSizeInMegabytes: properties['maxSizeInMb'],
DefaultMessageTimeToLive: `PT${properties['messageTimeToLiveInSec']}S`,
LockDuration: `PT${properties['lockDurationInSec']}S`
requiresDuplicateDetection: false,
maxSizeInMegabytes: properties['maxSizeInMb'],
defaultMessageTimeToLive: `PT${properties['messageTimeToLiveInSec']}S`,
lockDuration: `PT${properties['lockDurationInSec']}S`
};
}
@ -161,24 +157,11 @@ async function exit(status) {
}
}
if (receiverClient) {
try {
await receiverClient.close();
} catch (e) {
}
}
senderMap.forEach((k, v) => {
try {
v.sender.close();
} catch (e) {
}
try {
v.queueClient.close();
} catch (e) {
}
});
@ -191,4 +174,4 @@ async function exit(status) {
}
logger.info('Azure Service Bus resources stopped.')
process.exit(status);
}
}

2317
msa/js-executor/yarn.lock

File diff suppressed because it is too large

23
msa/tb-node/docker/Dockerfile

@ -14,23 +14,18 @@
# limitations under the License.
#
FROM thingsboard/openjdk11
RUN echo 'networkaddress.cache.ttl=60' >> /etc/java-11-openjdk/security/java.security
FROM thingsboard/openjdk11:bullseye-slim
COPY start-tb-node.sh ${pkg.name}.deb /tmp/
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb-node.sh /usr/bin
RUN yes | dpkg -i /tmp/${pkg.name}.deb
RUN rm /tmp/${pkg.name}.deb
RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
RUN chown -R ${pkg.user}:${pkg.user} /tmp
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
RUN echo 'networkaddress.cache.ttl=60' >> /etc/java-11-openjdk/security/java.security \
&& chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb-node.sh /usr/bin && \
(yes | dpkg -i /tmp/${pkg.name}.deb) && \
rm /tmp/${pkg.name}.deb && \
(systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :) && \
chown -R ${pkg.user}:${pkg.user} /tmp && \
chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
USER ${pkg.user}

81
msa/tb/docker-cassandra/Dockerfile

@ -14,37 +14,9 @@
# limitations under the License.
#
FROM thingsboard/openjdk11
FROM thingsboard/openjdk11:bullseye-slim
RUN apt-get update
RUN apt-get install -y curl nmap procps gnupg2
RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ buster-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null
RUN curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo 'deb http://downloads.apache.org/cassandra/debian 40x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null
RUN curl -L https://downloads.apache.org/cassandra/KEYS | apt-key add -
ENV PG_MAJOR=12
RUN apt-get update
RUN apt-get install -y cassandra cassandra-tools postgresql-12
RUN update-rc.d cassandra disable
RUN update-rc.d postgresql disable
RUN sed -i.old '/ulimit/d' /etc/init.d/cassandra
COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb.sh /usr/bin \
&& mv /tmp/upgrade-tb.sh /usr/bin \
&& mv /tmp/install-tb.sh /usr/bin \
&& mv /tmp/start-db.sh /usr/bin \
&& mv /tmp/stop-db.sh /usr/bin
RUN dpkg -i /tmp/${pkg.name}.deb
RUN rm /tmp/${pkg.name}.deb
RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \
&& mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf
ENV DATA_FOLDER=/data
@ -54,7 +26,6 @@ ENV DATABASE_TS_TYPE=cassandra
ENV PGDATA=/data/db
ENV CASSANDRA_DATA=/data/cassandra
ENV SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect
ENV SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver
ENV SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/thingsboard
ENV SPRING_DATASOURCE_USERNAME=${pkg.user}
@ -70,23 +41,43 @@ ENV PATH=$PATH:/usr/lib/postgresql/$PG_MAJOR/bin
ENV PGLOG=/var/log/postgres
ENV CASSANDRA_LOG=/var/log/cassandra
# postgres config
RUN mkdir -p $PGLOG
RUN chown -R ${pkg.user}:${pkg.user} $PGLOG
RUN chown -R ${pkg.user}:${pkg.user} /var/log/postgresql
RUN chown -R ${pkg.user}:${pkg.user} /var/run/postgresql
COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/
RUN apt-get update \
&& apt-get install -y curl nmap procps gnupg2 \
&& echo "deb http://apt.postgresql.org/pub/repos/apt/ $(. /etc/os-release && echo -n $VERSION_CODENAME)-pgdg main" | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null \
&& curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
&& echo 'deb http://downloads.apache.org/cassandra/debian 40x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null \
&& curl -L https://downloads.apache.org/cassandra/KEYS | apt-key add - \
&& apt-get update \
&& apt-get install -y cassandra cassandra-tools postgresql-12 \
&& update-rc.d cassandra disable \
&& update-rc.d postgresql disable \
&& sed -i.old '/ulimit/d' /etc/init.d/cassandra \
&& chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb.sh /usr/bin \
&& mv /tmp/upgrade-tb.sh /usr/bin \
&& mv /tmp/install-tb.sh /usr/bin \
&& mv /tmp/start-db.sh /usr/bin \
&& mv /tmp/stop-db.sh /usr/bin \
&& dpkg -i /tmp/${pkg.name}.deb \
&& rm /tmp/${pkg.name}.deb \
&& (systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :) \
&& mv /tmp/logback.xml ${pkg.installFolder}/conf \
&& mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf \
# postgres config
&& mkdir -p $PGLOG \
&& chown -R ${pkg.user}:${pkg.user} $PGLOG \
&& chown -R ${pkg.user}:${pkg.user} /var/log/postgresql \
&& chown -R ${pkg.user}:${pkg.user} /var/run/postgresql \
# cassandra config
RUN rm -rf /var/lib/cassandra
RUN chmod a+w /var/lib
RUN chown -R ${pkg.user}:${pkg.user} $CASSANDRA_LOG
RUN mkdir -p $DATA_FOLDER
RUN chown -R ${pkg.user}:${pkg.user} $DATA_FOLDER
RUN chown -R ${pkg.user}:${pkg.user} /var/log/${pkg.name}
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
&& rm -rf /var/lib/cassandra \
&& chmod a+w /var/lib \
&& chown -R ${pkg.user}:${pkg.user} $CASSANDRA_LOG \
&& mkdir -p $DATA_FOLDER \
&& chown -R ${pkg.user}:${pkg.user} $DATA_FOLDER \
&& chown -R ${pkg.user}:${pkg.user} /var/log/${pkg.name} \
&& chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
USER ${pkg.user}

19
msa/tb/docker-cassandra/start-db.sh

@ -24,16 +24,23 @@ if [ ! -d ${PGDATA} ]; then
${PG_CTL} initdb
fi
exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 &
echo "Starting Postgresql..."
${PG_CTL} start
RETRIES="${PG_ISREADY_RETRIES:-300}"
until pg_isready -U ${pkg.user} -d postgres --quiet || [ $RETRIES -eq 0 ]
do
echo "Connecting to Postgres, $((RETRIES--)) attempts left..."
sleep 1
done
if [ ! -f ${firstlaunch} ]; then
sleep 2
while ! psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard"
do
sleep 1
done
echo "Creating database..."
psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard"
fi
echo "Postgresql is ready"
cassandra_data_dir=${CASSANDRA_DATA}
cassandra_data_link=/var/lib/cassandra

62
msa/tb/docker-postgres/Dockerfile

@ -14,33 +14,9 @@
# limitations under the License.
#
FROM thingsboard/openjdk11
FROM thingsboard/openjdk11:bullseye-slim
RUN apt-get update
RUN apt-get install -y curl gnupg2
RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ buster-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null
RUN curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
ENV PG_MAJOR 12
RUN apt-get update
RUN apt-get install -y postgresql-12
RUN update-rc.d postgresql disable
COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb.sh /usr/bin \
&& mv /tmp/upgrade-tb.sh /usr/bin \
&& mv /tmp/install-tb.sh /usr/bin \
&& mv /tmp/start-db.sh /usr/bin \
&& mv /tmp/stop-db.sh /usr/bin
RUN dpkg -i /tmp/${pkg.name}.deb
RUN rm /tmp/${pkg.name}.deb
RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \
&& mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf
ENV DATA_FOLDER=/data
@ -50,24 +26,40 @@ ENV DATABASE_TS_TYPE=sql
ENV PGDATA=/data/db
ENV PATH=$PATH:/usr/lib/postgresql/$PG_MAJOR/bin
ENV SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect
ENV SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver
ENV SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/thingsboard
ENV SPRING_DATASOURCE_USERNAME=${pkg.user}
ENV SPRING_DATASOURCE_PASSWORD=postgres
ENV PGLOG=/var/log/postgres
RUN mkdir -p $PGLOG
RUN chown -R ${pkg.user}:${pkg.user} $PGLOG
RUN chown -R ${pkg.user}:${pkg.user} /var/run/postgresql
RUN mkdir -p /data
RUN chown -R ${pkg.user}:${pkg.user} /data
COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/
RUN chown -R ${pkg.user}:${pkg.user} /var/log/${pkg.name}
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
RUN apt-get update \
&& apt-get install -y curl gnupg2 \
&& echo "deb http://apt.postgresql.org/pub/repos/apt/ $(. /etc/os-release && echo -n $VERSION_CODENAME)-pgdg main" | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null \
&& curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
&& apt-get update \
&& apt-get install -y postgresql-12 \
&& update-rc.d postgresql disable \
&& chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb.sh /usr/bin \
&& mv /tmp/upgrade-tb.sh /usr/bin \
&& mv /tmp/install-tb.sh /usr/bin \
&& mv /tmp/start-db.sh /usr/bin \
&& mv /tmp/stop-db.sh /usr/bin \
&& dpkg -i /tmp/${pkg.name}.deb \
&& rm /tmp/${pkg.name}.deb \
&& (systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :) \
&& mv /tmp/logback.xml ${pkg.installFolder}/conf \
&& mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf \
&& mkdir -p $PGLOG \
&& chown -R ${pkg.user}:${pkg.user} $PGLOG \
&& chown -R ${pkg.user}:${pkg.user} /var/run/postgresql \
&& mkdir -p /data \
&& chown -R ${pkg.user}:${pkg.user} /data \
&& chown -R ${pkg.user}:${pkg.user} /var/log/${pkg.name} \
&& chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
USER ${pkg.user}

21
msa/tb/docker-postgres/start-db.sh

@ -24,12 +24,19 @@ if [ ! -d ${PGDATA} ]; then
${PG_CTL} initdb
fi
exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 &
echo "Starting Postgresql..."
${PG_CTL} start
RETRIES="${PG_ISREADY_RETRIES:-300}"
until pg_isready -U ${pkg.user} -d postgres --quiet || [ $RETRIES -eq 0 ]
do
echo "Connecting to Postgres, $((RETRIES--)) attempts left..."
sleep 1
done
if [ ! -f ${firstlaunch} ]; then
sleep 2
while ! psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard"
do
sleep 1
done
fi
echo "Creating database..."
psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard"
fi
echo "Postgresql is ready"

61
msa/tb/docker-tb/Dockerfile

@ -1,61 +0,0 @@
#
# Copyright © 2016-2022 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.
#
FROM thingsboard/openjdk11
COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb.sh /usr/bin \
&& mv /tmp/upgrade-tb.sh /usr/bin \
&& mv /tmp/install-tb.sh /usr/bin \
&& mv /tmp/start-db.sh /usr/bin \
&& mv /tmp/stop-db.sh /usr/bin
RUN dpkg -i /tmp/${pkg.name}.deb
RUN rm /tmp/${pkg.name}.deb
RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \
&& mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf
ENV DATA_FOLDER=/data
ENV HTTP_BIND_PORT=9090
ENV DATABASE_TS_TYPE=sql
ENV SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.HSQLDialect
ENV SPRING_DRIVER_CLASS_NAME=org.hsqldb.jdbc.JDBCDriver
ENV SPRING_DATASOURCE_URL=jdbc:hsqldb:file:/data/db/thingsboardDb;sql.enforce_size=false;hsqldb.log_size=5
ENV SPRING_DATASOURCE_USERNAME=sa
ENV SPRING_DATASOURCE_PASSWORD=
RUN mkdir -p /data
RUN chown -R ${pkg.user}:${pkg.user} /data
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
USER ${pkg.user}
EXPOSE 9090
EXPOSE 1883
EXPOSE 5683/udp
EXPOSE 5685/udp
VOLUME ["/data"]
CMD ["start-tb.sh"]

18
msa/tb/docker-tb/start-db.sh

@ -1,18 +0,0 @@
#!/bin/bash
#
# Copyright © 2016-2022 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.
#
# Do nothing

18
msa/tb/docker-tb/stop-db.sh

@ -1,18 +0,0 @@
#!/bin/bash
#
# Copyright © 2016-2022 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.
#
# Do nothing

2
msa/tb/docker/install-tb.sh

@ -46,6 +46,8 @@ source "${CONF_FOLDER}/${configfile}"
echo "Starting ThingsBoard installation ..."
set -e
java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
-Dinstall.load_demo=${loadDemo} \
-Dspring.jpa.hibernate.ddl-auto=none \

17
msa/tb/docker/start-tb.sh

@ -25,15 +25,18 @@ firstlaunch=${DATA_FOLDER}/.firstlaunch
source "${CONF_FOLDER}/${configfile}"
if [ ! -f ${firstlaunch} ]; then
install-tb.sh --loadDemo
touch ${firstlaunch}
install-tb.sh --loadDemo && touch ${firstlaunch}
fi
echo "Starting ThingsBoard ..."
if [ -f ${firstlaunch} ]; then
echo "Starting ThingsBoard ..."
java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardServerApplication \
-Dspring.jpa.hibernate.ddl-auto=none \
-Dlogging.config=${CONF_FOLDER}/logback.xml \
org.springframework.boot.loader.PropertiesLauncher
java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardServerApplication \
-Dspring.jpa.hibernate.ddl-auto=none \
-Dlogging.config=${CONF_FOLDER}/logback.xml \
org.springframework.boot.loader.PropertiesLauncher
else
echo "ERROR: ThingsBoard is not installed"
fi
stop-db.sh

134
msa/tb/pom.xml

@ -35,7 +35,6 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../..</main.dir>
<pkg.name>thingsboard</pkg.name>
<tb.docker.name>tb</tb.docker.name>
<tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name>
<tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
@ -59,25 +58,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-tb-deb</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.thingsboard</groupId>
<artifactId>application</artifactId>
<classifier>deb</classifier>
<type>deb</type>
<destFileName>${pkg.name}.deb</destFileName>
<outputDirectory>${project.build.directory}/docker-tb</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
<execution>
<id>copy-tb-postgres-deb</id>
<phase>package</phase>
@ -122,27 +102,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-docker-tb-config</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/docker-tb</outputDirectory>
<resources>
<resource>
<directory>docker</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>docker-tb</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
<execution>
<execution>
<id>copy-docker-tb-postgres-config</id>
<phase>process-resources</phase>
<goals>
@ -188,32 +148,6 @@
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<executions>
<execution>
<id>build-docker-tb-image</id>
<phase>pre-integration-test</phase>
<goals>
<goal>build</goal>
</goals>
<configuration>
<skip>${dockerfile.skip}</skip>
<repository>${docker.repo}/${tb.docker.name}</repository>
<verbose>true</verbose>
<googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
<contextDirectory>${project.build.directory}/docker-tb</contextDirectory>
</configuration>
</execution>
<execution>
<id>tag-docker-tb-image</id>
<phase>pre-integration-test</phase>
<goals>
<goal>tag</goal>
</goals>
<configuration>
<skip>${dockerfile.skip}</skip>
<repository>${docker.repo}/${tb.docker.name}</repository>
<tag>${project.version}</tag>
</configuration>
</execution>
<execution>
<id>build-docker-tb-postgres-image</id>
<phase>pre-integration-test</phase>
@ -226,6 +160,7 @@
<verbose>true</verbose>
<googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
<contextDirectory>${project.build.directory}/docker-postgres</contextDirectory>
<noCache>true</noCache>
</configuration>
</execution>
<execution>
@ -252,6 +187,7 @@
<verbose>true</verbose>
<googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
<contextDirectory>${project.build.directory}/docker-cassandra</contextDirectory>
<noCache>true</noCache>
</configuration>
</execution>
<execution>
@ -284,28 +220,6 @@
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<executions>
<execution>
<id>push-latest-docker-tb-image</id>
<phase>pre-integration-test</phase>
<goals>
<goal>push</goal>
</goals>
<configuration>
<tag>latest</tag>
<repository>${docker.repo}/${tb.docker.name}</repository>
</configuration>
</execution>
<execution>
<id>push-version-docker-tb-image</id>
<phase>pre-integration-test</phase>
<goals>
<goal>push</goal>
</goals>
<configuration>
<tag>${project.version}</tag>
<repository>${docker.repo}/${tb.docker.name}</repository>
</configuration>
</execution>
<execution>
<id>push-latest-docker-tb-postgres-image</id>
<phase>pre-integration-test</phase>
@ -368,48 +282,6 @@
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>push-latest-docker-amd-arm-tb-images</id>
<phase>pre-integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<workingDirectory>${project.build.directory}/docker-tb</workingDirectory>
<arguments>
<argument>buildx</argument>
<argument>build</argument>
<argument>-t</argument>
<argument>${docker.repo}/${tb.docker.name}:latest</argument>
<argument>--platform=linux/amd64,linux/arm64</argument>
<argument>-o</argument>
<argument>type=registry</argument>
<argument>.</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>push-version-docker-amd-arm-tb-images</id>
<phase>pre-integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<workingDirectory>${project.build.directory}/docker-tb</workingDirectory>
<arguments>
<argument>buildx</argument>
<argument>build</argument>
<argument>-t</argument>
<argument>${docker.repo}/${tb.docker.name}:${project.version}</argument>
<argument>--platform=linux/amd64,linux/arm64</argument>
<argument>-o</argument>
<argument>type=registry</argument>
<argument>.</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>push-latest-docker-amd-arm-tb-postgres-images</id>
<phase>pre-integration-test</phase>

15
msa/transport/coap/docker/Dockerfile

@ -14,19 +14,16 @@
# limitations under the License.
#
FROM thingsboard/openjdk11
FROM thingsboard/openjdk11:bullseye-slim
COPY start-tb-coap-transport.sh ${pkg.name}.deb /tmp/
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb-coap-transport.sh /usr/bin
RUN yes | dpkg -i /tmp/${pkg.name}.deb
RUN rm /tmp/${pkg.name}.deb
RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
&& mv /tmp/start-tb-coap-transport.sh /usr/bin && \
(yes | dpkg -i /tmp/${pkg.name}.deb) && \
rm /tmp/${pkg.name}.deb && \
(systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :) && \
chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
USER ${pkg.user}

15
msa/transport/http/docker/Dockerfile

@ -14,19 +14,16 @@
# limitations under the License.
#
FROM thingsboard/openjdk11
FROM thingsboard/openjdk11:bullseye-slim
COPY start-tb-http-transport.sh ${pkg.name}.deb /tmp/
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb-http-transport.sh /usr/bin
RUN yes | dpkg -i /tmp/${pkg.name}.deb
RUN rm /tmp/${pkg.name}.deb
RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
&& mv /tmp/start-tb-http-transport.sh /usr/bin && \
(yes | dpkg -i /tmp/${pkg.name}.deb) && \
rm /tmp/${pkg.name}.deb && \
(systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :) && \
chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
USER ${pkg.user}

15
msa/transport/lwm2m/docker/Dockerfile

@ -14,19 +14,16 @@
# limitations under the License.
#
FROM thingsboard/openjdk11
FROM thingsboard/openjdk11:bullseye-slim
COPY start-tb-lwm2m-transport.sh ${pkg.name}.deb /tmp/
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb-lwm2m-transport.sh /usr/bin
RUN yes | dpkg -i /tmp/${pkg.name}.deb
RUN rm /tmp/${pkg.name}.deb
RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
&& mv /tmp/start-tb-lwm2m-transport.sh /usr/bin && \
(yes | dpkg -i /tmp/${pkg.name}.deb) && \
rm /tmp/${pkg.name}.deb && \
(systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :) && \
chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
USER ${pkg.user}

15
msa/transport/mqtt/docker/Dockerfile

@ -14,19 +14,16 @@
# limitations under the License.
#
FROM thingsboard/openjdk11
FROM thingsboard/openjdk11:bullseye-slim
COPY start-tb-mqtt-transport.sh ${pkg.name}.deb /tmp/
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb-mqtt-transport.sh /usr/bin
RUN yes | dpkg -i /tmp/${pkg.name}.deb
RUN rm /tmp/${pkg.name}.deb
RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
&& mv /tmp/start-tb-mqtt-transport.sh /usr/bin && \
(yes | dpkg -i /tmp/${pkg.name}.deb) && \
rm /tmp/${pkg.name}.deb && \
(systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :) && \
chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
USER ${pkg.user}

15
msa/transport/snmp/docker/Dockerfile

@ -14,19 +14,16 @@
# limitations under the License.
#
FROM thingsboard/openjdk11
FROM thingsboard/openjdk11:bullseye-slim
COPY start-tb-snmp-transport.sh ${pkg.name}.deb /tmp/
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb-snmp-transport.sh /usr/bin
RUN yes | dpkg -i /tmp/${pkg.name}.deb
RUN rm /tmp/${pkg.name}.deb
RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
&& mv /tmp/start-tb-snmp-transport.sh /usr/bin && \
(yes | dpkg -i /tmp/${pkg.name}.deb) && \
rm /tmp/${pkg.name}.deb && \
(systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :) && \
chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
USER ${pkg.user}

16
msa/web-ui/docker/Dockerfile

@ -14,16 +14,13 @@
# limitations under the License.
#
FROM node:16.13.1-bullseye-slim
COPY start-web-ui.sh /tmp/
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-web-ui.sh /usr/bin
FROM node:16.15.1-bullseye-slim
ENV NODE_ENV production
ENV DOCKER_MODE true
COPY start-web-ui.sh /tmp/
WORKDIR ${pkg.installFolder}
COPY ["src/package.json", "src/yarn.lock", "./"]
@ -33,9 +30,10 @@ COPY package/linux/conf ./config
COPY web ./web
COPY src/server.js ./
RUN chown -R node:node ${pkg.installFolder}
RUN yarn install --production && yarn cache clean --all
RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-web-ui.sh /usr/bin \
&& chown -R node:node ${pkg.installFolder} \
&& yarn install --production && yarn cache clean --all
USER node

23
msa/web-ui/package.json

@ -6,21 +6,21 @@
"main": "server.js",
"bin": "server.js",
"scripts": {
"pkg": "pkg -t node12-linux-x64,node12-win-x64 --out-path ./target . && node install.js",
"pkg": "pkg -t node16-linux-x64,node16-win-x64 --out-path ./target . && node install.js",
"test": "echo \"Error: no test specified\" && exit 1",
"start": "WEB_FOLDER=./target/web nodemon server.js",
"start-prod": "NODE_ENV=production nodemon server.js"
},
"dependencies": {
"compression": "^1.7.4",
"config": "^3.3.1",
"config": "^3.3.7",
"connect-history-api-fallback": "^1.6.0",
"express": "^4.17.1",
"express": "^4.18.1",
"http": "0.0.0",
"http-proxy": "^1.18.1",
"js-yaml": "^3.14.0",
"winston": "^3.3.3",
"winston-daily-rotate-file": "^4.5.0"
"js-yaml": "^4.1.0",
"winston": "^3.7.2",
"winston-daily-rotate-file": "^4.7.1"
},
"nyc": {
"exclude": [
@ -31,13 +31,18 @@
]
},
"devDependencies": {
"fs-extra": "^10.0.0",
"nodemon": "^2.0.12",
"pkg": "^5.3.1"
"fs-extra": "^10.1.0",
"nodemon": "^2.0.16",
"pkg": "^5.7.0"
},
"pkg": {
"assets": [
"node_modules/config/**/*.*"
]
},
"resolutions": {
"color-string": "^1.5.5",
"follow-redirects": "^1.14.8",
"minimist": "^1.2.6"
}
}

4
msa/web-ui/pom.xml

@ -80,8 +80,8 @@
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>v12.16.1</nodeVersion>
<yarnVersion>v1.22.4</yarnVersion>
<nodeVersion>v16.15.1</nodeVersion>
<yarnVersion>v1.22.17</yarnVersion>
</configuration>
</execution>
<execution>

966
msa/web-ui/yarn.lock

File diff suppressed because it is too large

6
pom.xml

@ -125,7 +125,6 @@
<awaitility.version>4.1.0</awaitility.version>
<cassandra-unit.version>4.3.1.0</cassandra-unit.version>
<dbunit.version>2.7.2</dbunit.version>
<hsqldb.version>2.6.1</hsqldb.version>
<java-websocket.version>1.5.2</java-websocket.version>
<jupiter.version>5.7.2</jupiter.version> <!-- keep the same version as spring-boot-starter-test depend on jupiter-->
<json-path.version>2.6.0</json-path.version>
@ -1659,11 +1658,6 @@
<artifactId>bcpkix-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>${hsqldb.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>

41
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.CustomTimeSchedule;
import org.thingsboard.server.common.data.device.profile.AlarmSchedule;
import org.thingsboard.server.common.data.device.profile.CustomTimeScheduleItem;
import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec;
@ -40,6 +41,7 @@ import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import org.thingsboard.server.common.msg.tools.SchedulerUtils;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import java.time.Instant;
import java.time.ZoneId;
@ -115,7 +117,7 @@ class AlarmRuleState {
}
public AlarmEvalResult eval(DataSnapshot data) {
boolean active = isActive(data.getTs());
boolean active = isActive(data, data.getTs());
switch (spec.getType()) {
case SIMPLE:
return (active && eval(alarmRule.getCondition(), data)) ? AlarmEvalResult.TRUE : AlarmEvalResult.FALSE;
@ -128,7 +130,7 @@ class AlarmRuleState {
}
}
private boolean isActive(long eventTs) {
private boolean isActive(DataSnapshot data, long eventTs) {
if (eventTs == 0L) {
eventTs = System.currentTimeMillis();
}
@ -139,14 +141,28 @@ class AlarmRuleState {
case ANY_TIME:
return true;
case SPECIFIC_TIME:
return isActiveSpecific((SpecificTimeSchedule) alarmRule.getSchedule(), eventTs);
return isActiveSpecific((SpecificTimeSchedule) getSchedule(data, alarmRule), eventTs);
case CUSTOM:
return isActiveCustom((CustomTimeSchedule) alarmRule.getSchedule(), eventTs);
return isActiveCustom((CustomTimeSchedule) getSchedule(data, alarmRule), eventTs);
default:
throw new RuntimeException("Unsupported schedule type: " + alarmRule.getSchedule().getType());
}
}
private AlarmSchedule getSchedule(DataSnapshot data, AlarmRule alarmRule) {
AlarmSchedule schedule = alarmRule.getSchedule();
EntityKeyValue dynamicValue = getDynamicPredicateValue(data, schedule.getDynamicValue());
if (dynamicValue != null) {
try {
return JsonConverter.parse(dynamicValue.getJsonValue(), alarmRule.getSchedule().getClass());
} catch (Exception e) {
log.trace("Failed to parse AlarmSchedule from dynamicValue: {}", dynamicValue.getJsonValue(), e);
}
}
return schedule;
}
private boolean isActiveSpecific(SpecificTimeSchedule schedule, long eventTs) {
ZoneId zoneId = SchedulerUtils.getZoneId(schedule.getTimezone());
ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(eventTs), zoneId);
@ -156,7 +172,13 @@ class AlarmRuleState {
return false;
}
}
return isActive(eventTs, zoneId, zdt, schedule.getStartsOn(), schedule.getEndsOn());
long endsOn = schedule.getEndsOn();
if (endsOn == 0) {
// 24 hours in milliseconds
endsOn = 86400000;
}
return isActive(eventTs, zoneId, zdt, schedule.getStartsOn(), endsOn);
}
private boolean isActiveCustom(CustomTimeSchedule schedule, long eventTs) {
@ -166,7 +188,12 @@ class AlarmRuleState {
for (CustomTimeScheduleItem item : schedule.getItems()) {
if (item.getDayOfWeek() == dayOfWeek) {
if (item.isEnabled()) {
return isActive(eventTs, zoneId, zdt, item.getStartsOn(), item.getEndsOn());
long endsOn = item.getEndsOn();
if (endsOn == 0) {
// 24 hours in milliseconds
endsOn = 86400000;
}
return isActive(eventTs, zoneId, zdt, item.getStartsOn(), endsOn);
} else {
return false;
}
@ -279,7 +306,7 @@ class AlarmRuleState {
long requiredDurationInMs = resolveRequiredDurationInMs(dataSnapshot);
if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) {
long duration = state.getDuration() + (ts - state.getLastEventTs());
if (isActive(ts)) {
if (isActive(dataSnapshot, ts)) {
return duration > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE;
} else {
return AlarmEvalResult.FALSE;

15
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.AlarmSchedule;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
@ -77,6 +78,10 @@ class ProfileState {
addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys);
}
addEntityKeysFromAlarmConditionSpec(alarmRule);
AlarmSchedule schedule = alarmRule.getSchedule();
if (schedule != null) {
addScheduleDynamicValues(schedule);
}
}));
if (alarm.getClearRule() != null) {
var clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>());
@ -91,6 +96,16 @@ class ProfileState {
}
}
private void addScheduleDynamicValues(AlarmSchedule schedule) {
DynamicValue<String> dynamicValue = schedule.getDynamicValue();
if (dynamicValue != null) {
entityKeys.add(
new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE,
dynamicValue.getSourceAttribute())
);
}
}
private void addEntityKeysFromAlarmConditionSpec(AlarmRule alarmRule) {
AlarmConditionSpec spec = alarmRule.getCondition().getSpec();
if (spec == null) {

177
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java

@ -40,6 +40,8 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.CustomTimeSchedule;
import org.thingsboard.server.common.data.device.profile.CustomTimeScheduleItem;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec;
@ -47,6 +49,7 @@ import org.thingsboard.server.common.data.device.profile.RepeatingAlarmCondition
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
@ -68,6 +71,7 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -1086,6 +1090,179 @@ public class TbDeviceProfileNodeTest {
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testActiveAlarmScheduleFromDynamicValuesWhenDefaultScheduleIsInactive() throws Exception {
init();
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
Device device = new Device();
device.setId(deviceId);
device.setCustomerId(customerId);
AttributeKvCompositeKey compositeKeyActiveSchedule = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "dynamicValueActiveSchedule"
);
AttributeKvEntity attributeKvEntityActiveSchedule = new AttributeKvEntity();
attributeKvEntityActiveSchedule.setId(compositeKeyActiveSchedule);
attributeKvEntityActiveSchedule.setJsonValue(
"{\"timezone\":\"Europe/Kiev\",\"items\":[{\"enabled\":true,\"dayOfWeek\":1,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":2,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":3,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":4,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":5,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":6,\"startsOn\":8.64e+7,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":7,\"startsOn\":0,\"endsOn\":8.64e+7}],\"dynamicValue\":null}"
);
attributeKvEntityActiveSchedule.setLastUpdateTs(0L);
AttributeKvEntry entryActiveSchedule = attributeKvEntityActiveSchedule.toData();
ListenableFuture<List<AttributeKvEntry>> listListenableFutureActiveSchedule =
Futures.immediateFuture(Collections.singletonList(entryActiveSchedule));
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
null
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
CustomTimeSchedule schedule = new CustomTimeSchedule();
schedule.setItems(Collections.emptyList());
schedule.setDynamicValue(new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "dynamicValueActiveSchedule", false));
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
alarmRule.setSchedule(schedule);
DeviceProfileAlarm deviceProfileAlarmActiveSchedule = new DeviceProfileAlarm();
deviceProfileAlarmActiveSchedule.setId("highTemperatureAlarmID");
deviceProfileAlarmActiveSchedule.setAlarmType("highTemperatureAlarm");
deviceProfileAlarmActiveSchedule.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
deviceProfileData.setAlarms(Collections.singletonList(deviceProfileAlarmActiveSchedule));
deviceProfile.setProfileData(deviceProfileData);
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFutureActiveSchedule);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 35);
TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg);
verify(ctx).tellSuccess(msg);
verify(ctx).enqueueForTellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testInactiveAlarmScheduleFromDynamicValuesWhenDefaultScheduleIsActive() throws Exception {
init();
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
Device device = new Device();
device.setId(deviceId);
device.setCustomerId(customerId);
AttributeKvCompositeKey compositeKeyInactiveSchedule = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "dynamicValueInactiveSchedule"
);
AttributeKvEntity attributeKvEntityInactiveSchedule = new AttributeKvEntity();
attributeKvEntityInactiveSchedule.setId(compositeKeyInactiveSchedule);
attributeKvEntityInactiveSchedule.setJsonValue(
"{\"timezone\":\"Europe/Kiev\",\"items\":[{\"enabled\":false,\"dayOfWeek\":1,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":2,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":3,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":4,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":5,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":6,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":7,\"startsOn\":0,\"endsOn\":0}],\"dynamicValue\":null}"
);
attributeKvEntityInactiveSchedule.setLastUpdateTs(0L);
AttributeKvEntry entryInactiveSchedule = attributeKvEntityInactiveSchedule.toData();
ListenableFuture<List<AttributeKvEntry>> listListenableFutureInactiveSchedule =
Futures.immediateFuture(Collections.singletonList(entryInactiveSchedule));
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
null
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
CustomTimeSchedule schedule = new CustomTimeSchedule();
List<CustomTimeScheduleItem> items = new ArrayList<>();
for (int i = 0; i < 7; i++) {
CustomTimeScheduleItem item = new CustomTimeScheduleItem();
item.setEnabled(true);
item.setDayOfWeek(i + 1);
item.setEndsOn(0);
item.setStartsOn(0);
items.add(item);
}
schedule.setItems(items);
schedule.setDynamicValue(new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "dynamicValueInactiveSchedule", false));
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
alarmRule.setSchedule(schedule);
DeviceProfileAlarm deviceProfileAlarmNonactiveSchedule = new DeviceProfileAlarm();
deviceProfileAlarmNonactiveSchedule.setId("highTemperatureAlarmID");
deviceProfileAlarmNonactiveSchedule.setAlarmType("highTemperatureAlarm");
deviceProfileAlarmNonactiveSchedule.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
deviceProfileData.setAlarms(Collections.singletonList(deviceProfileAlarmNonactiveSchedule));
deviceProfile.setProfileData(deviceProfileData);
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFutureInactiveSchedule);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 35);
TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg);
verify(ctx).tellSuccess(msg);
verify(ctx, Mockito.never()).enqueueForTellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testCurrentCustomersAttributeForDynamicValue() throws Exception {

1
ui-ngx/package.json

@ -62,6 +62,7 @@
"leaflet-providers": "^1.13.0",
"leaflet.gridlayer.googlemutant": "^0.13.4",
"leaflet.markercluster": "^1.5.3",
"libphonenumber-js": "^1.10.4",
"messageformat": "^2.3.0",
"moment": "^2.29.1",
"moment-timezone": "^0.5.34",

2
ui-ngx/pom.xml

@ -56,7 +56,7 @@
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>v16.13.0</nodeVersion>
<nodeVersion>v16.15.1</nodeVersion>
<yarnVersion>v1.22.17</yarnVersion>
</configuration>
</execution>

11
ui-ngx/src/app/core/auth/auth.service.ts

@ -23,7 +23,7 @@ import { catchError, map, mergeMap, tap } from 'rxjs/operators';
import { LoginRequest, LoginResponse, PublicLoginRequest } from '@shared/models/login.models';
import { ActivatedRoute, Router, UrlTree } from '@angular/router';
import { defaultHttpOptions } from '../http/http-utils';
import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from '../http/http-utils';
import { UserService } from '../http/user.service';
import { Store } from '@ngrx/store';
import { AppState } from '../core.state';
@ -47,6 +47,7 @@ import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.com
import { OAuth2ClientInfo, PlatformType } from '@shared/models/oauth2.models';
import { isMobileApp } from '@core/utils';
import { TwoFactorAuthProviderType, TwoFaProviderInfo } from '@shared/models/two-factor-auth.models';
import { UserPasswordPolicy } from '@shared/models/settings.models';
@Injectable({
providedIn: 'root'
@ -163,14 +164,18 @@ export class AuthService {
));
}
public changePassword(currentPassword: string, newPassword: string) {
return this.http.post('/api/auth/changePassword', {currentPassword, newPassword}, defaultHttpOptions()).pipe(
public changePassword(currentPassword: string, newPassword: string, config?: RequestConfig) {
return this.http.post('/api/auth/changePassword', {currentPassword, newPassword}, defaultHttpOptionsFromConfig(config)).pipe(
tap((loginResponse: LoginResponse) => {
this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false);
}
));
}
public getUserPasswordPolicy() {
return this.http.get<UserPasswordPolicy>(`/api/noauth/userPasswordPolicy`, defaultHttpOptions());
}
public activateByEmailCode(emailCode: string): Observable<LoginResponse> {
return this.http.post<LoginResponse>(`/api/noauth/activateByEmailCode?emailCode=${emailCode}`,
null, defaultHttpOptions());

4
ui-ngx/src/app/core/services/menu.service.ts

@ -108,7 +108,7 @@ export class MenuService {
name: 'admin.system-settings',
type: 'toggle',
path: '/settings',
height: '280px',
height: '320px',
icon: 'settings',
pages: [
{
@ -376,7 +376,7 @@ export class MenuService {
name: 'admin.system-settings',
type: 'toggle',
path: '/settings',
height: '120px',
height: '80px',
icon: 'settings',
pages: [
{

2
ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html

@ -28,7 +28,7 @@
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div mat-dialog-content>
<div mat-dialog-content style="padding: 0;">
<fieldset [disabled]="isLoading$ | async" style="position: relative; height: 600px;">
<tb-widget-config
[aliasController]="aliasController"

7
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -1159,6 +1159,13 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
editWidget($event: Event, layoutCtx: DashboardPageLayoutContext, widget: Widget) {
$event.stopPropagation();
if (this.isAddingWidget) {
this.onAddWidgetClosed();
this.isAddingWidgetClosed = true;
this.isEditingWidgetClosed = false;
}
if (this.editingWidgetOriginal === widget) {
this.onEditWidgetClosed();
} else {

2
ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts

@ -138,7 +138,7 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig<EdgeEvent, TimePa
}
private updateEdgeEventStatus(createdTime: number): string {
if (this.queueStartTs && createdTime < this.queueStartTs) {
if (this.queueStartTs && createdTime <= this.queueStartTs) {
return this.translate.instant('edge.deployed');
} else {
return this.translate.instant('edge.pending');

6
ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts

@ -295,8 +295,10 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
if (this.pageMode) {
this.route.queryParams.pipe(skip(1)).subscribe((params: PageQueryParam) => {
this.paginator.pageIndex = Number(params.page) || 0;
this.paginator.pageSize = Number(params.pageSize) || this.defaultPageSize;
if (this.displayPagination) {
this.paginator.pageIndex = Number(params.page) || 0;
this.paginator.pageSize = Number(params.pageSize) || this.defaultPageSize;
}
this.sort.active = params.property || this.entitiesTableConfig.defaultSortOrder.property;
this.sort.direction = (params.direction || this.entitiesTableConfig.defaultSortOrder.direction).toLowerCase() as SortDirection;
if (params.hasOwnProperty('textSearch') && !isEmptyStr(params.textSearch)) {

4
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -149,6 +149,7 @@ import {
HOME_COMPONENTS_MODULE_TOKEN
} from '@home/components/tokens';
import { DashboardStateComponent } from '@home/components/dashboard-page/dashboard-state.component';
import { AlarmDynamicValue } from '@home/components/profile/alarm/alarm-dynamic-value.component';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { TenantProfileQueuesComponent } from '@home/components/profile/queue/tenant-profile-queues.component';
import { QueueFormComponent } from '@home/components/queue/queue-form.component';
@ -254,6 +255,7 @@ import { WidgetSettingsComponent } from '@home/components/widget/widget-settings
AlarmScheduleInfoComponent,
DeviceProfileProvisionConfigurationComponent,
AlarmScheduleComponent,
AlarmDynamicValue,
AlarmDurationPredicateValueComponent,
DeviceWizardDialogComponent,
AlarmScheduleDialogComponent,
@ -372,11 +374,11 @@ import { WidgetSettingsComponent } from '@home/components/widget/widget-settings
DeviceWizardDialogComponent,
AlarmScheduleInfoComponent,
AlarmScheduleComponent,
AlarmDynamicValue,
AlarmScheduleDialogComponent,
AlarmDurationPredicateValueComponent,
EditAlarmDetailsDialogComponent,
DeviceProfileProvisionConfigurationComponent,
AlarmScheduleComponent,
SmsProviderConfigurationComponent,
AwsSnsProviderConfigurationComponent,
SmppSmsProviderConfigurationComponent,

55
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html

@ -0,0 +1,55 @@
<!--
Copyright © 2016-2022 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.
-->
<mat-expansion-panel [formGroup] = "dynamicValue" class="device-profile-alarm" style = "margin-bottom: 26px;" fxFlex>
<mat-expansion-panel-header>
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<mat-panel-title>
<div fxLayout="row" fxFlex fxLayoutAlign="start center">
{{'filter.dynamic-value' | translate}}
</div>
</mat-panel-title>
<span fxFlex></span>
</div>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div fxFlex fxLayout="column">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div fxFlex="40" fxLayout="column">
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
<mat-label></mat-label>
<mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}">
<mat-option [value]="null">
{{'filter.no-dynamic-value' | translate}}
</mat-option>
<mat-option *ngFor="let sourceType of dynamicValueSourceTypes" [value]="sourceType">
{{dynamicValueSourceTypeTranslations.get(sourceType) | translate}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxFlex fxLayout="column">
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block source-attribute">
<mat-label></mat-label>
<input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}">
</mat-form-field>
</div>
<div [tb-help-popup]="helpId"></div>
</div>
</div>
</ng-template>
</mat-expansion-panel>

99
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts

@ -0,0 +1,99 @@
///
/// Copyright © 2016-2022 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.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormGroup,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import {
DynamicValueSourceType,
dynamicValueSourceTypeTranslationMap,
getDynamicSourcesForAllowUser
} from '@shared/models/query/query.models';
@Component({
selector: 'tb-alarm-dynamic-value',
templateUrl: './alarm-dynamic-value.component.html',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AlarmDynamicValue),
multi: true
}]
})
export class AlarmDynamicValue implements ControlValueAccessor, OnInit{
public dynamicValue: FormGroup;
public dynamicValueSourceTypes: DynamicValueSourceType[] = getDynamicSourcesForAllowUser(false);
public dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap;
private propagateChange = (v: any) => { };
@Input()
helpId: string;
@Input()
disabled: boolean;
constructor(private fb: FormBuilder) {
}
ngOnInit(): void {
this.dynamicValue = this.fb.group({
sourceType: [null, []],
sourceAttribute: [null]
})
this.dynamicValue.get('sourceType').valueChanges.subscribe(
(sourceType) => {
if (!sourceType) {
this.dynamicValue.get('sourceAttribute').patchValue(null, {emitEvent: false});
}
}
);
this.dynamicValue.valueChanges.subscribe(() => {
this.updateModel();
})
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
writeValue(dynamicValue: {sourceType: string, sourceAttribute: string}): void {
if(dynamicValue) {
this.dynamicValue.patchValue(dynamicValue, {emitEvent: false});
}
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.dynamicValue.disable({emitEvent: false});
} else {
this.dynamicValue.enable({emitEvent: false});
}
}
private updateModel() {
this.propagateChange(this.dynamicValue.value);
}
}

3
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html

@ -34,6 +34,7 @@
formControlName="timezone">
</tb-timezone-select>
<section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.SPECIFIC_TIME">
<tb-alarm-dynamic-value formControlName = 'dynamicValue' helpId = 'device-profile/alarm_specific_schedule_format'></tb-alarm-dynamic-value>
<div class="tb-small" style="margin-bottom: 0.5em" translate>device-profile.schedule-days</div>
<div fxLayout="column" fxLayout.gt-md="row" fxLayoutGap="16px">
<div fxLayout="row" fxLayoutGap="16px">
@ -73,8 +74,8 @@
</div>
</section>
<section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.CUSTOM">
<tb-alarm-dynamic-value formControlName = 'dynamicValue' helpId = 'device-profile/alarm_custom_schedule_format'></tb-alarm-dynamic-value>
<div class="tb-small" style="margin-bottom: 0.5em" translate>device-profile.schedule-days</div>
<div *ngFor="let day of allDays" fxLayout="column" formArrayName="items" fxLayoutGap="1em">
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px" [formGroupName]="''+day" fxLayoutAlign="start center" fxLayoutAlign.xs="center start">
<mat-checkbox formControlName="enabled" fxFlex="17" (change)="changeCustomScheduler($event, day)">

11
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts

@ -64,7 +64,6 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
alarmScheduleTypes = Object.keys(AlarmScheduleType);
alarmScheduleType = AlarmScheduleType;
alarmScheduleTypeTranslate = AlarmScheduleTypeTranslationMap;
dayOfWeekTranslationsArray = dayOfWeekTranslations;
allDays = Array(7).fill(0).map((x, i) => i);
@ -91,8 +90,10 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
daysOfWeek: this.fb.array(new Array(7).fill(false), this.validateDayOfWeeks),
startsOn: [0, Validators.required],
endsOn: [0, Validators.required],
items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems)
items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems),
dynamicValue: [null]
});
this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => {
const defaultTimezone = getDefaultTimezone();
this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false});
@ -158,7 +159,8 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
timezone: this.modelValue.timezone,
daysOfWeek,
startsOn: utcTimestampToTimeOfDay(this.modelValue.startsOn),
endsOn: utcTimestampToTimeOfDay(this.modelValue.endsOn)
endsOn: utcTimestampToTimeOfDay(this.modelValue.endsOn),
dynamicValue: this.modelValue.dynamicValue
}, {emitEvent: false});
break;
case AlarmScheduleType.CUSTOM:
@ -177,7 +179,8 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
this.alarmScheduleForm.patchValue({
type: this.modelValue.type,
timezone: this.modelValue.timezone,
items: alarmDays
items: alarmDays,
dynamicValue: this.modelValue.dynamicValue
}, {emitEvent: false});
}
break;

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save