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; return null;
} }
private EdgeEventActionType edgeTypeByActionType(ActionType actionType) { public static EdgeEventActionType edgeTypeByActionType(ActionType actionType) {
switch (actionType) { switch (actionType) {
case ADDED: case ADDED:
return EdgeEventActionType.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 lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao;
@Service @Service
@PsqlDao
@Profile("install") @Profile("install")
@Slf4j @Slf4j
public class PsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
implements EntityDatabaseSchemaService { implements EntityDatabaseSchemaService {
public static final String SCHEMA_ENTITIES_SQL = "schema-entities.sql"; 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_SQL = "schema-entities-idx.sql";
public static final String SCHEMA_ENTITIES_IDX_PSQL_ADDON_SQL = "schema-entities-idx-psql-addon.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); 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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao; import org.thingsboard.server.dao.util.SqlTsDao;
@Service @Service
@SqlTsDao @SqlTsDao
@PsqlDao
@Profile("install") @Profile("install")
public class PsqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService implements TsDatabaseSchemaService { public class SqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService implements TsDatabaseSchemaService {
@Value("${sql.postgres.ts_key_value_partitioning:MONTHS}") @Value("${sql.postgres.ts_key_value_partitioning:MONTHS}")
private String partitionType; private String partitionType;
public PsqlTsDatabaseSchemaService() { public SqlTsDatabaseSchemaService() {
super("schema-ts-psql.sql", null); 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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao; import org.thingsboard.server.dao.util.SqlTsDao;
import java.io.File; import java.io.File;
@ -36,8 +35,7 @@ import java.sql.DriverManager;
@Profile("install") @Profile("install")
@Slf4j @Slf4j
@SqlTsDao @SqlTsDao
@PsqlDao public class SqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService {
public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService {
@Value("${sql.postgres.ts_key_value_partitioning:MONTHS}") @Value("${sql.postgres.ts_key_value_partitioning:MONTHS}")
private String partitionType; 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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.TimescaleDBTsDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
@Service @Service
@TimescaleDBTsDao @TimescaleDBTsDao
@PsqlDao
@Profile("install") @Profile("install")
@Slf4j @Slf4j
public class TimescaleTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService implements TsDatabaseSchemaService { 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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.TimescaleDBTsDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao;
import java.io.File; import java.io.File;
@ -37,7 +36,6 @@ import java.sql.DriverManager;
@Profile("install") @Profile("install")
@Slf4j @Slf4j
@TimescaleDBTsDao @TimescaleDBTsDao
@PsqlDao
public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService {
@Value("${sql.timescale.chunk_time_interval:86400000}") @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 @Override
public Optional<AccountTwoFaSettings> getAccountTwoFaSettings(TenantId tenantId, UserId userId) { public Optional<AccountTwoFaSettings> getAccountTwoFaSettings(TenantId tenantId, UserId userId) {
PlatformTwoFaSettings platformTwoFaSettings = getPlatformTwoFaSettings(tenantId, true).orElse(null);
return Optional.ofNullable(userAuthSettingsDao.findByUserId(userId)) return Optional.ofNullable(userAuthSettingsDao.findByUserId(userId))
.flatMap(userAuthSettings -> Optional.ofNullable(userAuthSettings.getTwoFaSettings())) .map(userAuthSettings -> {
.map(twoFaSettings -> { AccountTwoFaSettings twoFaSettings = userAuthSettings.getTwoFaSettings();
twoFaSettings.getConfigs().keySet().removeIf(providerType -> { if (twoFaSettings == null) return null;
return getTwoFaProviderConfig(tenantId, providerType).isEmpty(); 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; return twoFaSettings;
}); });
} }

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

@ -19,6 +19,8 @@ server:
address: "${HTTP_BIND_ADDRESS:0.0.0.0}" address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
# Server bind port # Server bind port
port: "${HTTP_BIND_PORT:8080}" port: "${HTTP_BIND_PORT:8080}"
# Server forward headers strategy
forward_headers_strategy: "${HTTP_FORWARD_HEADERS_STRATEGY:NONE}"
# Server SSL configuration # Server SSL configuration
ssl: ssl:
# Enable/disable SSL support # Enable/disable SSL support
@ -174,8 +176,6 @@ database:
ts_latest: ts_latest:
type: "${DATABASE_TS_LATEST_TYPE:sql}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale) 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 driver configuration parameters
cassandra: cassandra:
# Thingsboard cluster name # Thingsboard cluster name
@ -533,7 +533,6 @@ spring:
open-in-view: "false" open-in-view: "false"
hibernate: hibernate:
ddl-auto: "none" ddl-auto: "none"
database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}"
datasource: datasource:
driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}" driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}"
url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" 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 @EnableWebSocket
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Slf4j @Slf4j
public abstract class AbstractControllerTest extends AbstractWebTest { public abstract class AbstractControllerTest extends AbstractNotifyEntityTest {
public static final String WS_URL = "ws://localhost:"; 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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink; 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.common.data.security.Authority;
import org.thingsboard.server.config.ThingsboardSecurityConfiguration; import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantProfileService;
@ -112,6 +107,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected ObjectMapper mapper = new ObjectMapper(); protected ObjectMapper mapper = new ObjectMapper();
protected static final String TEST_TENANT_NAME = "TEST TENANT"; 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"; protected static final String SYS_ADMIN_EMAIL = "sysadmin@thingsboard.org";
private static final String SYS_ADMIN_PASSWORD = "sysadmin"; 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_EMAIL = "testtenant@thingsboard.org";
protected static final String TENANT_ADMIN_PASSWORD = "tenant"; 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"; protected static final String CUSTOMER_USER_EMAIL = "testcustomer@thingsboard.org";
private static final String CUSTOMER_USER_PASSWORD = "customer"; 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)} /** See {@link org.springframework.test.web.servlet.DefaultMvcResult#getAsyncResult(long)}
* and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()} * and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()}
*/ */
@ -137,7 +139,11 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected TenantId tenantId; protected TenantId tenantId;
protected UserId tenantAdminUserId; protected UserId tenantAdminUserId;
protected CustomerId tenantAdminCustomerId;
protected CustomerId customerId; protected CustomerId customerId;
protected TenantId differentTenantId;
protected CustomerId differentCustomerId;
protected UserId customerUserId;
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private HttpMessageConverter mappingJackson2HttpMessageConverter; private HttpMessageConverter mappingJackson2HttpMessageConverter;
@ -202,6 +208,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
tenantAdmin = createUserAndLogin(tenantAdmin, TENANT_ADMIN_PASSWORD); tenantAdmin = createUserAndLogin(tenantAdmin, TENANT_ADMIN_PASSWORD);
tenantAdminUserId = tenantAdmin.getId(); tenantAdminUserId = tenantAdmin.getId();
tenantAdminCustomerId = tenantAdmin.getCustomerId();
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("Customer"); customer.setTitle("Customer");
@ -215,7 +222,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
customerUser.setCustomerId(savedCustomer.getId()); customerUser.setCustomerId(savedCustomer.getId());
customerUser.setEmail(CUSTOMER_USER_EMAIL); customerUser.setEmail(CUSTOMER_USER_EMAIL);
createUserAndLogin(customerUser, CUSTOMER_USER_PASSWORD); customerUser = createUserAndLogin(customerUser, CUSTOMER_USER_PASSWORD);
customerUserId = customerUser.getId();
logout(); logout();
@ -273,36 +281,52 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
private Customer savedDifferentCustomer; private Customer savedDifferentCustomer;
protected void loginDifferentTenant() throws Exception { protected void loginDifferentTenant() throws Exception {
loginSysAdmin();
if (savedDifferentTenant != null) { 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(); protected void loginDifferentCustomer() throws Exception {
tenant.setTitle("Different tenant"); if (savedDifferentCustomer != null) {
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class); login(savedDifferentCustomer.getEmail(), CUSTOMER_USER_PASSWORD);
Assert.assertNotNull(savedDifferentTenant); } else {
User differentTenantAdmin = new User(); createDifferentCustomer();
differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN);
differentTenantAdmin.setTenantId(savedDifferentTenant.getId());
differentTenantAdmin.setEmail("different_tenant@thingsboard.org");
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(); loginTenantAdmin();
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("Different customer"); customer.setTitle("Different customer");
savedDifferentCustomer = doPost("/api/customer", customer, Customer.class); savedDifferentCustomer = doPost("/api/customer", customer, Customer.class);
Assert.assertNotNull(savedDifferentCustomer); Assert.assertNotNull(savedDifferentCustomer);
User differentCustomerUser = new User(); differentCustomerId = savedDifferentCustomer.getId();
differentCustomerUser.setAuthority(Authority.CUSTOMER_USER);
differentCustomerUser.setTenantId(tenantId);
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
differentCustomerUser.setEmail("different_customer@thingsboard.org");
createUserAndLogin(differentCustomerUser, "testPassword"); logout();
} }
protected void deleteDifferentTenant() throws Exception { 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; 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.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; 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.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm; 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.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; 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; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class BaseAlarmControllerTest extends AbstractControllerTest { public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
public static final String TEST_ALARM_TYPE = "Test"; public static final String TEST_ALARM_TYPE = "Test";
@ -56,160 +69,338 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
@Test @Test
public void testCreateAlarmViaCustomer() throws Exception { public void testCreateAlarmViaCustomer() throws Exception {
loginCustomerUser(); 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 @Test
public void testCreateAlarmViaTenant() throws Exception { public void testCreateAlarmViaTenant() throws Exception {
loginTenantAdmin(); 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 @Test
public void testUpdateAlarmViaCustomer() throws Exception { public void testUpdateAlarmViaCustomer() throws Exception {
loginCustomerUser(); loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
alarm.setSeverity(AlarmSeverity.MAJOR); alarm.setSeverity(AlarmSeverity.MAJOR);
Alarm updatedAlarm = doPost("/api/alarm", alarm, Alarm.class); Alarm updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(updatedAlarm); Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity()); Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
logout();
testNotifyEntityAllOneTime(updatedAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.UPDATED);
} }
@Test @Test
public void testUpdateAlarmViaTenant() throws Exception { public void testUpdateAlarmViaTenant() throws Exception {
loginTenantAdmin(); loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
alarm.setSeverity(AlarmSeverity.MAJOR); alarm.setSeverity(AlarmSeverity.MAJOR);
Alarm updatedAlarm = doPost("/api/alarm", alarm, Alarm.class); Alarm updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(updatedAlarm); Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity()); Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
logout();
testNotifyEntityAllOneTime(updatedAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.UPDATED);
} }
@Test @Test
public void testUpdateAlarmViaDifferentTenant() throws Exception { public void testUpdateAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin(); loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
alarm.setSeverity(AlarmSeverity.MAJOR); alarm.setSeverity(AlarmSeverity.MAJOR);
loginDifferentTenant(); loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm", alarm).andExpect(status().isForbidden()); doPost("/api/alarm", alarm).andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
} }
@Test @Test
public void testUpdateAlarmViaDifferentCustomer() throws Exception { public void testUpdateAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser(); loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer(); loginDifferentCustomer();
alarm.setSeverity(AlarmSeverity.MAJOR); alarm.setSeverity(AlarmSeverity.MAJOR);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm", alarm).andExpect(status().isForbidden()); doPost("/api/alarm", alarm).andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
} }
@Test @Test
public void testDeleteAlarmViaCustomer() throws Exception { public void testDeleteAlarmViaCustomer() throws Exception {
loginCustomerUser(); loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk()); doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
logout();
testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.DELETED);
} }
@Test @Test
public void testDeleteAlarmViaTenant() throws Exception { public void testDeleteAlarmViaTenant() throws Exception {
loginTenantAdmin(); loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk()); doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
logout();
testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED);
} }
@Test @Test
public void testDeleteAlarmViaDifferentTenant() throws Exception { public void testDeleteAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin(); loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant(); loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden()); doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
} }
@Test @Test
public void testDeleteAlarmViaAnotherCustomer() throws Exception { public void testDeleteAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser(); loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer(); loginDifferentCustomer();
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden()); doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
} }
@Test @Test
public void testClearAlarmViaCustomer() throws Exception { public void testClearAlarmViaCustomer() throws Exception {
loginCustomerUser(); loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk()); doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class); Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm); Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus()); Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
logout();
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_CLEAR);
} }
@Test @Test
public void testClearAlarmViaTenant() throws Exception { public void testClearAlarmViaTenant() throws Exception {
loginTenantAdmin(); loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk()); doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class); Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm); Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus()); Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
logout();
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_CLEAR);
} }
@Test @Test
public void testAcknowledgeAlarmViaCustomer() throws Exception { public void testAcknowledgeAlarmViaCustomer() throws Exception {
loginCustomerUser(); loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isOk()); doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class); Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm); Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.ACTIVE_ACK, foundAlarm.getStatus()); Assert.assertEquals(AlarmStatus.ACTIVE_ACK, foundAlarm.getStatus());
logout();
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_ACK);
} }
@Test @Test
public void testClearAlarmViaDifferentCustomer() throws Exception { public void testClearAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser(); loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer(); loginDifferentCustomer();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden()); doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
} }
@Test @Test
public void testClearAlarmViaDifferentTenant() throws Exception { public void testClearAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin(); loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant(); loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden()); doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
} }
@Test @Test
public void testAcknowledgeAlarmViaDifferentCustomer() throws Exception { public void testAcknowledgeAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser(); loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer(); loginDifferentCustomer();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden()); doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden());
logout();
testNotifyEntityNever(alarm.getId(), alarm);
} }
@Test @Test
public void testAcknowledgeAlarmViaDifferentTenant() throws Exception { public void testAcknowledgeAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin(); loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant(); loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden()); 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(); 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 { 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.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; 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.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -86,14 +89,28 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
public void testSaveCustomer() throws Exception { public void testSaveCustomer() throws Exception {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
Mockito.reset(tbClusterService, auditLogService);
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); 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);
Assert.assertNotNull(savedCustomer.getId()); Assert.assertNotNull(savedCustomer.getId());
Assert.assertTrue(savedCustomer.getCreatedTime() > 0); Assert.assertTrue(savedCustomer.getCreatedTime() > 0);
Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle()); Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle());
savedCustomer.setTitle("My new customer"); savedCustomer.setTitle("My new customer");
doPost("/api/customer", savedCustomer, Customer.class); 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); Customer foundCustomer = doGet("/api/customer/" + savedCustomer.getId().getId().toString(), Customer.class);
Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle()); Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle());
@ -104,23 +121,57 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
@Test @Test
public void testSaveCustomerWithViolationOfValidation() throws Exception { public void testSaveCustomerWithViolationOfValidation() throws Exception {
Customer customer = new Customer(); Customer customer = new Customer();
String validationError = "Validation error: ";
customer.setTitle(RandomStringUtils.randomAlphabetic(300)); 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.setTitle("Normal title");
customer.setCity(RandomStringUtils.randomAlphabetic(300)); 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.setCity("Normal city");
customer.setCountry(RandomStringUtils.randomAlphabetic(300)); 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.setCountry("Ukraine");
customer.setPhone(RandomStringUtils.randomAlphabetic(300)); 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.setPhone("+3892555554512");
customer.setState(RandomStringUtils.randomAlphabetic(300)); 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.setState("Normal state");
customer.setZip(RandomStringUtils.randomAlphabetic(300)); 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 @Test
@ -131,12 +182,22 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
doPost("/api/customer", savedCustomer, Customer.class); doPost("/api/customer", savedCustomer, Customer.class);
loginDifferentTenant(); loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/customer", savedCustomer, Customer.class, status().isForbidden()); doPost("/api/customer", savedCustomer, Customer.class, status().isForbidden());
deleteDifferentTenant();
testNotifyEntityNever(savedCustomer.getId(), savedCustomer);
deleteDifferentTenant();
login(tenantAdmin.getName(), "testPassword1"); login(tenantAdmin.getName(), "testPassword1");
doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(),
savedCustomer.getId(), savedCustomer.getTenantId(), savedCustomer.getId(), tenantAdmin.getId(),
tenantAdmin.getEmail(), ActionType.DELETED, savedCustomer.getId().getId().toString());
} }
@Test @Test
@ -149,6 +210,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
Assert.assertNotNull(foundCustomer); Assert.assertNotNull(foundCustomer);
Assert.assertEquals(savedCustomer, foundCustomer); Assert.assertEquals(savedCustomer, foundCustomer);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@ -159,9 +222,15 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk()); .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()) doGet("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
} }
@ -169,24 +238,33 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
@Test @Test
public void testSaveCustomerWithEmptyTitle() throws Exception { public void testSaveCustomerWithEmptyTitle() throws Exception {
Customer customer = new Customer(); Customer customer = new Customer();
String msgError = "Customer title should be specified";
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/customer", customer) doPost("/api/customer", customer)
.andExpect(status().isBadRequest()) .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 @Test
public void testSaveCustomerWithInvalidEmail() throws Exception { public void testSaveCustomerWithInvalidEmail() throws Exception {
Customer customer = new Customer(); Customer customer = new Customer();
String msgError = "Invalid email address format 'invalid@mail'";
customer.setTitle("My customer"); customer.setTitle("My customer");
customer.setEmail("invalid@mail"); customer.setEmail("invalid@mail");
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/customer", customer) doPost("/api/customer", customer)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Invalid email address format 'invalid@mail'"))); .andExpect(statusReason(containsString(msgError)));
// loginSysAdmin(); testNotifyEntityError(customer, savedTenant.getId(),
// tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError + "!"));
// doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
// .andExpect(status().isOk());
} }
@Test @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(); PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
twoFaSettings.setProviders(List.of(totpTwoFaProviderConfig, smsTwoFaProviderConfig)); twoFaSettings.setProviders(List.of(totpTwoFaProviderConfig, smsTwoFaProviderConfig));
twoFaSettings.setMinVerificationCodeSendPeriod(5);
twoFaSettings.setVerificationCodeCheckRateLimit("3:900"); twoFaSettings.setVerificationCodeCheckRateLimit("3:900");
twoFaSettings.setMaxVerificationFailuresBeforeUserLockout(10); twoFaSettings.setMaxVerificationFailuresBeforeUserLockout(10);
twoFaSettings.setTotalAllowedTimeForVerification(3600); twoFaSettings.setTotalAllowedTimeForVerification(3600);
@ -117,6 +118,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest {
twoFaSettings.setVerificationCodeCheckRateLimit("0:12"); twoFaSettings.setVerificationCodeCheckRateLimit("0:12");
twoFaSettings.setMaxVerificationFailuresBeforeUserLockout(-1); twoFaSettings.setMaxVerificationFailuresBeforeUserLockout(-1);
twoFaSettings.setTotalAllowedTimeForVerification(0); twoFaSettings.setTotalAllowedTimeForVerification(0);
twoFaSettings.setMinVerificationCodeSendPeriod(5);
String errorMessage = getErrorMessage(doPost("/api/2fa/settings", twoFaSettings) String errorMessage = getErrorMessage(doPost("/api/2fa/settings", twoFaSettings)
.andExpect(status().isBadRequest())); .andExpect(status().isBadRequest()));
@ -156,6 +158,8 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest {
private String savePlatformTwoFaSettingsAndGetError(TwoFaProviderConfig invalidTwoFaProviderConfig) throws Exception { private String savePlatformTwoFaSettingsAndGetError(TwoFaProviderConfig invalidTwoFaProviderConfig) throws Exception {
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings(); PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
twoFaSettings.setProviders(Collections.singletonList(invalidTwoFaProviderConfig)); twoFaSettings.setProviders(Collections.singletonList(invalidTwoFaProviderConfig));
twoFaSettings.setMinVerificationCodeSendPeriod(5);
twoFaSettings.setTotalAllowedTimeForVerification(100);
return getErrorMessage(doPost("/api/2fa/settings", twoFaSettings) return getErrorMessage(doPost("/api/2fa/settings", twoFaSettings)
.andExpect(status().isBadRequest())); .andExpect(status().isBadRequest()));
@ -432,8 +436,9 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest {
private void saveProvidersConfigs(TwoFaProviderConfig... providerConfigs) throws Exception { private void saveProvidersConfigs(TwoFaProviderConfig... providerConfigs) throws Exception {
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings(); PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
twoFaSettings.setProviders(Arrays.stream(providerConfigs).collect(Collectors.toList())); twoFaSettings.setProviders(Arrays.stream(providerConfigs).collect(Collectors.toList()));
twoFaSettings.setMinVerificationCodeSendPeriod(5);
twoFaSettings.setTotalAllowedTimeForVerification(100);
doPost("/api/2fa/settings", twoFaSettings).andExpect(status().isOk()); 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); emailTwoFaProviderConfig.setVerificationCodeLifetime(60);
platformTwoFaSettings.setProviders(List.of(totpTwoFaProviderConfig, smsTwoFaProviderConfig, emailTwoFaProviderConfig)); platformTwoFaSettings.setProviders(List.of(totpTwoFaProviderConfig, smsTwoFaProviderConfig, emailTwoFaProviderConfig));
platformTwoFaSettings.setMinVerificationCodeSendPeriod(5);
platformTwoFaSettings.setTotalAllowedTimeForVerification(100);
twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, platformTwoFaSettings); twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, platformTwoFaSettings);
User twoFaUser = new User(); User twoFaUser = new User();
@ -409,6 +411,8 @@ public abstract class TwoFactorAuthTest extends AbstractControllerTest {
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings(); PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
twoFaSettings.setProviders(Arrays.stream(new TwoFaProviderConfig[]{totpTwoFaProviderConfig}).collect(Collectors.toList())); twoFaSettings.setProviders(Arrays.stream(new TwoFaProviderConfig[]{totpTwoFaProviderConfig}).collect(Collectors.toList()));
twoFaSettings.setMinVerificationCodeSendPeriod(5);
twoFaSettings.setTotalAllowedTimeForVerification(100);
Arrays.stream(customizer).forEach(c -> c.accept(twoFaSettings)); Arrays.stream(customizer).forEach(c -> c.accept(twoFaSettings));
twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, twoFaSettings); twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, twoFaSettings);
@ -425,6 +429,8 @@ public abstract class TwoFactorAuthTest extends AbstractControllerTest {
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings(); PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
twoFaSettings.setProviders(Arrays.stream(new TwoFaProviderConfig[]{smsTwoFaProviderConfig}).collect(Collectors.toList())); twoFaSettings.setProviders(Arrays.stream(new TwoFaProviderConfig[]{smsTwoFaProviderConfig}).collect(Collectors.toList()));
twoFaSettings.setMinVerificationCodeSendPeriod(5);
twoFaSettings.setTotalAllowedTimeForVerification(100);
twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, twoFaSettings); twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, twoFaSettings);
SmsTwoFaAccountConfig smsTwoFaAccountConfig = new SmsTwoFaAccountConfig(); 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.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
public class PsqlEntityDatabaseSchemaServiceTest { public class SqlEntityDatabaseSchemaServiceTest {
@Test @Test
public void givenPsqlDbSchemaService_whenCreateDatabaseSchema_thenVerifyPsqlIndexSpecificCall() throws Exception { public void givenPsqlDbSchemaService_whenCreateDatabaseSchema_thenVerifyPsqlIndexSpecificCall() throws Exception {
PsqlEntityDatabaseSchemaService service = spy(new PsqlEntityDatabaseSchemaService()); SqlEntityDatabaseSchemaService service = spy(new SqlEntityDatabaseSchemaService());
willDoNothing().given(service).executeQueryFromFile(anyString()); willDoNothing().given(service).executeQueryFromFile(anyString());
service.createDatabaseSchema(); service.createDatabaseSchema();
verify(service, times(1)).createDatabaseIndexes(); verify(service, times(1)).createDatabaseIndexes();
verify(service, times(1)).executeQueryFromFile(PsqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_SQL); verify(service, times(1)).executeQueryFromFile(SqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_SQL);
verify(service, times(1)).executeQueryFromFile(PsqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_IDX_SQL); verify(service, times(1)).executeQueryFromFile(SqlEntityDatabaseSchemaService.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_PSQL_ADDON_SQL);
verify(service, times(3)).executeQueryFromFile(anyString()); verify(service, times(3)).executeQueryFromFile(anyString());
} }
@Test @Test
public void givenPsqlDbSchemaService_whenCreateDatabaseIndexes_thenVerifyPsqlIndexSpecificCall() throws Exception { public void givenPsqlDbSchemaService_whenCreateDatabaseIndexes_thenVerifyPsqlIndexSpecificCall() throws Exception {
PsqlEntityDatabaseSchemaService service = spy(new PsqlEntityDatabaseSchemaService()); SqlEntityDatabaseSchemaService service = spy(new SqlEntityDatabaseSchemaService());
willDoNothing().given(service).executeQueryFromFile(anyString()); willDoNothing().given(service).executeQueryFromFile(anyString());
service.createDatabaseIndexes(); service.createDatabaseIndexes();
verify(service, times(1)).executeQueryFromFile(PsqlEntityDatabaseSchemaService.SCHEMA_ENTITIES_IDX_SQL); verify(service, times(1)).executeQueryFromFile(SqlEntityDatabaseSchemaService.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_PSQL_ADDON_SQL);
verify(service, times(2)).executeQueryFromFile(anyString()); 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.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.thingsboard.server.common.data.query.DynamicValue;
import java.io.Serializable; import java.io.Serializable;
@ -34,4 +35,6 @@ public interface AlarmSchedule extends Serializable {
AlarmScheduleType getType(); 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; package org.thingsboard.server.common.data.device.profile;
import org.thingsboard.server.common.data.query.DynamicValue;
public class AnyTimeSchedule implements AlarmSchedule { public class AnyTimeSchedule implements AlarmSchedule {
@Override @Override
@ -22,4 +24,9 @@ public class AnyTimeSchedule implements AlarmSchedule {
return AlarmScheduleType.ANY_TIME; 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; package org.thingsboard.server.common.data.device.profile;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.query.DynamicValue;
import java.util.List; import java.util.List;
@ -25,6 +26,8 @@ public class CustomTimeSchedule implements AlarmSchedule {
private String timezone; private String timezone;
private List<CustomTimeScheduleItem> items; private List<CustomTimeScheduleItem> items;
private DynamicValue<String> dynamicValue;
@Override @Override
public AlarmScheduleType getType() { public AlarmScheduleType getType() {
return AlarmScheduleType.CUSTOM; 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; package org.thingsboard.server.common.data.device.profile;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.query.DynamicValue;
import java.util.List;
import java.util.Set; import java.util.Set;
@Data @Data
@ -28,6 +28,8 @@ public class SpecificTimeSchedule implements AlarmSchedule {
private long startsOn; private long startsOn;
private long endsOn; private long endsOn;
private DynamicValue<String> dynamicValue;
@Override @Override
public AlarmScheduleType getType() { public AlarmScheduleType getType() {
return AlarmScheduleType.SPECIFIC_TIME; 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 @NotNull
private List<TwoFaProviderConfig> providers; private List<TwoFaProviderConfig> providers;
@NotNull
@Min(value = 5, message = "minimum verification code sent period must be greater than or equal 5") @Min(value = 5, message = "minimum verification code sent period must be greater than or equal 5")
private Integer minVerificationCodeSendPeriod; private Integer minVerificationCodeSendPeriod;
@Pattern(regexp = "[1-9]\\d*:[1-9]\\d*", message = "verification code check rate limit configuration is invalid") @Pattern(regexp = "[1-9]\\d*:[1-9]\\d*", message = "verification code check rate limit configuration is invalid")
private String verificationCodeCheckRateLimit; private String verificationCodeCheckRateLimit;
@Min(value = 0, message = "maximum number of verification failure before user lockout must be positive") @Min(value = 0, message = "maximum number of verification failure before user lockout must be positive")
private Integer maxVerificationFailuresBeforeUserLockout; private Integer maxVerificationFailuresBeforeUserLockout;
@NotNull
@Min(value = 60, message = "total amount of time allotted for verification must be greater than or equal 60") @Min(value = 60, message = "total amount of time allotted for verification must be greater than or equal 60")
private Integer totalAllowedTimeForVerification; 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); 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) { public static String toJson(JsonElement element) {
return GSON.toJson(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.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement; 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.SqlTsDao;
import org.thingsboard.server.dao.util.TbAutoConfiguration; import org.thingsboard.server.dao.util.TbAutoConfiguration;
@Configuration @Configuration
@TbAutoConfiguration @TbAutoConfiguration
@ComponentScan({"org.thingsboard.server.dao.sqlts.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.hsql"}) @EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.ts", "org.thingsboard.server.dao.sqlts.insert.sql"})
@EntityScan({"org.thingsboard.server.dao.model.sqlts.ts"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.ts"})
@EnableTransactionManagement @EnableTransactionManagement
@SqlTsDao @SqlTsDao
@HsqlDao public class SqlTsDaoConfig {
public class HsqlTsDaoConfig {
} }

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.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement; 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.SqlTsLatestDao;
import org.thingsboard.server.dao.util.TbAutoConfiguration; import org.thingsboard.server.dao.util.TbAutoConfiguration;
@Configuration @Configuration
@TbAutoConfiguration @TbAutoConfiguration
@ComponentScan({"org.thingsboard.server.dao.sqlts.psql"}) @ComponentScan({"org.thingsboard.server.dao.sqlts.sql"})
@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"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.latest"})
@EnableTransactionManagement @EnableTransactionManagement
@SqlTsLatestDao @SqlTsLatestDao
@PsqlDao public class SqlTsLatestDaoConfig {
public class PsqlTsLatestDaoConfig {
} }

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.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement; 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.TbAutoConfiguration;
import org.thingsboard.server.dao.util.TimescaleDBTsDao; 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"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.timescale"})
@EnableTransactionManagement @EnableTransactionManagement
@TimescaleDBTsDao @TimescaleDBTsDao
@PsqlDao
public class TimescaleDaoConfig { 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.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement; 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.TbAutoConfiguration;
import org.thingsboard.server.dao.util.TimescaleDBTsLatestDao; import org.thingsboard.server.dao.util.TimescaleDBTsLatestDao;
@Configuration @Configuration
@TbAutoConfiguration @TbAutoConfiguration
@ComponentScan({"org.thingsboard.server.dao.sqlts.timescale"}) @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"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.latest"})
@EnableTransactionManagement @EnableTransactionManagement
@TimescaleDBTsLatestDao @TimescaleDBTsLatestDao
@PsqlDao
public class TimescaleTsLatestDaoConfig { 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.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.util.PsqlDao;
@PsqlDao
@Repository @Repository
@Transactional @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.springframework.stereotype.Repository;
import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity; import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity;
import org.thingsboard.server.dao.util.PsqlDao;
@PsqlDao
@Repository @Repository
public class PsqlComponentDescriptorInsertRepository extends AbstractComponentDescriptorInsertRepository { public class SqlComponentDescriptorInsertRepository extends AbstractComponentDescriptorInsertRepository {
private static final String ID = "id = :id"; private static final String ID = "id = :id";
private static final String CLAZZ_CLAZZ = "clazz = :clazz"; 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.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.dao.model.sql.EdgeEventEntity; import org.thingsboard.server.dao.model.sql.EdgeEventEntity;
import org.thingsboard.server.dao.util.PsqlDao;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
@PsqlDao
@Repository @Repository
@Transactional @Transactional
public class EdgeEventInsertRepository { 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.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.dao.model.sql.EventEntity; import org.thingsboard.server.dao.model.sql.EventEntity;
import org.thingsboard.server.dao.util.PsqlDao;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@PsqlDao
@Repository @Repository
@Transactional @Transactional
public class EventInsertRepository { 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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
import org.thingsboard.server.dao.util.PsqlDao;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
@ -27,9 +26,8 @@ import java.sql.SQLException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
@PsqlDao
@Repository @Repository
public class PsqlEventCleanupRepository extends JpaAbstractDaoListeningExecutorService implements EventCleanupRepository { public class SqlEventCleanupRepository extends JpaAbstractDaoListeningExecutorService implements EventCleanupRepository {
@Override @Override
public void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs) { 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 { try {
return jdbcTemplate.queryForObject(countQuery, ctx, Long.class); return jdbcTemplate.queryForObject(countQuery, ctx, Long.class);
} finally { } 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 { try {
rows = jdbcTemplate.queryForList(dataQuery, ctx); rows = jdbcTemplate.queryForList(dataQuery, ctx);
} finally { } finally {
queryLog.logQuery(ctx, countQuery, System.currentTimeMillis() - startTs); queryLog.logQuery(ctx, dataQuery, System.currentTimeMillis() - startTs);
} }
return EntityDataAdapter.createEntityData(pageLink, selectionMapping, rows, totalElements); 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.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.RelationEntity; import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.util.PsqlDao;
@PsqlDao
@Repository @Repository
@Transactional @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)" + 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) " + " 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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.beans.factory.annotation.Value;
import org.springframework.jdbc.core.BatchPreparedStatementSetter; 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.model.sqlts.latest.TsKvLatestEntity;
import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository; import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository;
import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository; 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.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
@ -33,10 +33,10 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
@PsqlTsLatestAnyDao @SqlTsLatestAnyDao
@Repository @Repository
@Transactional @Transactional
public class PsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository { public class SqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository {
@Value("${sql.ts_latest.update_by_latest_ts:true}") @Value("${sql.ts_latest.update_by_latest_ts:true}")
private Boolean updateByLatestTs; 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.stereotype.Repository; 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.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository; import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository;
import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository; import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao; import org.thingsboard.server.dao.util.SqlTsDao;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
@ -30,10 +29,9 @@ import java.sql.Types;
import java.util.List; import java.util.List;
@SqlTsDao @SqlTsDao
@PsqlDao
@Repository @Repository
@Transactional @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)) " + 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);"; "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 * See the License for the specific language governing permissions and
* limitations under the License. * 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.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.timeseries.PsqlPartition; import org.thingsboard.server.dao.timeseries.SqlPartition;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao; import org.thingsboard.server.dao.util.SqlTsDao;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContext;
@SqlTsDao @SqlTsDao
@PsqlDao
@Repository @Repository
@Transactional @Transactional
public class PsqlPartitioningRepository { public class SqlPartitioningRepository {
@PersistenceContext @PersistenceContext
private EntityManager entityManager; private EntityManager entityManager;
public void save(PsqlPartition partition) { public void save(SqlPartition partition) {
entityManager.createNativeQuery(partition.getQuery()) entityManager.createNativeQuery(partition.getQuery())
.executeUpdate(); .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.model.sqlts.timescale.ts.TimescaleTsKvEntity;
import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository; import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository;
import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository; import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.TimescaleDBTsDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
@ -30,7 +29,6 @@ import java.sql.Types;
import java.util.List; import java.util.List;
@TimescaleDBTsDao @TimescaleDBTsDao
@PsqlDao
@Repository @Repository
@Transactional @Transactional
public class TimescaleInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository<TimescaleTsKvEntity> { 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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.Futures;
import com.google.common.util.concurrent.ListenableFuture; 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.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.sqlts.AbstractChunkedAggregationTimeseriesDao; import org.thingsboard.server.dao.sqlts.AbstractChunkedAggregationTimeseriesDao;
import org.thingsboard.server.dao.sqlts.insert.psql.PsqlPartitioningRepository; import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
import org.thingsboard.server.dao.timeseries.PsqlPartition; import org.thingsboard.server.dao.timeseries.SqlPartition;
import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate; import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao; import org.thingsboard.server.dao.util.SqlTsDao;
import java.sql.Connection; import java.sql.Connection;
@ -52,15 +51,14 @@ import java.util.concurrent.locks.ReentrantLock;
@Component @Component
@Slf4j @Slf4j
@PsqlDao
@SqlTsDao @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(); private static final ReentrantLock partitionCreationLock = new ReentrantLock();
@Autowired @Autowired
private PsqlPartitioningRepository partitioningRepository; private SqlPartitioningRepository partitioningRepository;
private SqlTsPartitionDate tsFormat; private SqlTsPartitionDate tsFormat;
@ -134,24 +132,24 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa
long partitionEndTs = toMills(localDateTimeEnd); long partitionEndTs = toMills(localDateTimeEnd);
ZonedDateTime zonedDateTime = localDateTimeStart.atZone(ZoneOffset.UTC); ZonedDateTime zonedDateTime = localDateTimeStart.atZone(ZoneOffset.UTC);
String partitionDate = zonedDateTime.format(DateTimeFormatter.ofPattern(tsFormat.getPattern())); 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) { private void savePartition(SqlPartition sqlPartition) {
if (!partitions.containsKey(psqlPartition.getStart())) { if (!partitions.containsKey(sqlPartition.getStart())) {
partitionCreationLock.lock(); partitionCreationLock.lock();
try { try {
log.trace("Saving partition: {}", psqlPartition); log.trace("Saving partition: {}", sqlPartition);
partitioningRepository.save(psqlPartition); partitioningRepository.save(sqlPartition);
log.trace("Adding partition to Set: {}", psqlPartition); log.trace("Adding partition to Set: {}", sqlPartition);
partitions.put(psqlPartition.getStart(), psqlPartition); partitions.put(sqlPartition.getStart(), sqlPartition);
} catch (DataIntegrityViolationException ex) { } catch (DataIntegrityViolationException ex) {
log.trace("Error occurred during partition save:", ex); log.trace("Error occurred during partition save:", ex);
if (ex.getCause() instanceof ConstraintViolationException) { if (ex.getCause() instanceof ConstraintViolationException) {
log.warn("Saving partition [{}] rejected. Timeseries data will save to the ts_kv_indefinite (DEFAULT) partition.", psqlPartition.getPartitionDate()); log.warn("Saving partition [{}] rejected. Timeseries data will save to the ts_kv_indefinite (DEFAULT) partition.", sqlPartition.getPartitionDate());
partitions.put(psqlPartition.getStart(), psqlPartition); partitions.put(sqlPartition.getStart(), sqlPartition);
} else { } else {
throw new RuntimeException(ex); 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; import lombok.Data;
@Data @Data
public class PsqlPartition { public class SqlPartition {
private static final String TABLE_REGEX = "ts_kv_"; private static final String TABLE_REGEX = "ts_kv_";
@ -27,7 +27,7 @@ public class PsqlPartition {
private String partitionDate; private String partitionDate;
private String query; private String query;
public PsqlPartition(long start, long end, String partitionDate) { public SqlPartition(long start, long end, String partitionDate) {
this.start = start; this.start = start;
this.end = end; this.end = end;
this.partitionDate = partitionDate; 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; import org.thingsboard.server.dao.service.DaoSqlTest;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@ContextConfiguration(classes = {JpaDaoConfig.class, PsqlTsDaoConfig.class, PsqlTsLatestDaoConfig.class, SqlTimeseriesDaoConfig.class}) @ContextConfiguration(classes = {JpaDaoConfig.class, SqlTsDaoConfig.class, SqlTsLatestDaoConfig.class, SqlTimeseriesDaoConfig.class})
@DaoSqlTest @DaoSqlTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@TestExecutionListeners({ @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. * Created by Valerii Sosliuk on 4/22/2017.
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@ContextConfiguration(classes = {JpaDaoConfig.class, PsqlTsDaoConfig.class, PsqlTsLatestDaoConfig.class, SqlTimeseriesDaoConfig.class}) @ContextConfiguration(classes = {JpaDaoConfig.class, SqlTsDaoConfig.class, SqlTsLatestDaoConfig.class, SqlTimeseriesDaoConfig.class})
@DaoSqlTest @DaoSqlTest
@TestExecutionListeners({ @TestExecutionListeners({
DependencyInjectionTestExecutionListener.class, 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.show-sql=false
spring.jpa.hibernate.ddl-auto=none spring.jpa.hibernate.ddl-auto=none
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.username=postgres spring.datasource.username=postgres
spring.datasource.password=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 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.show-sql=false
spring.jpa.hibernate.ddl-auto=none spring.jpa.hibernate.ddl-auto=none
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.username=postgres spring.datasource.username=postgres
spring.datasource.password=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 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.properties.hibernate.jdbc.lob.non_contextual_creation=true
#spring.jpa.show-sql=false #spring.jpa.show-sql=false
#spring.jpa.hibernate.ddl-auto=none #spring.jpa.hibernate.ddl-auto=none
#spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
# #
#spring.datasource.username=postgres #spring.datasource.username=postgres
#spring.datasource.password=postgres #spring.datasource.password=postgres

1
docker/tb-node.hybrid.env

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

1
docker/tb-node.postgres.env

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

16
msa/js-executor/docker/Dockerfile

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

37
msa/js-executor/package.json

@ -6,26 +6,25 @@
"main": "server.js", "main": "server.js",
"bin": "server.js", "bin": "server.js",
"scripts": { "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", "test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon server.js", "start": "nodemon server.js",
"start-prod": "NODE_ENV=production nodemon server.js" "start-prod": "NODE_ENV=production nodemon server.js"
}, },
"dependencies": { "dependencies": {
"@azure/service-bus": "^1.1.9", "@azure/service-bus": "^7.5.1",
"@google-cloud/pubsub": "^2.5.0", "@google-cloud/pubsub": "^3.0.1",
"amqplib": "^0.6.0", "amqplib": "^0.10.0",
"aws-sdk": "^2.741.0", "aws-sdk": "^2.1152.0",
"azure-sb": "^0.11.1", "config": "^3.3.7",
"config": "^3.3.1", "express": "^4.18.1",
"express": "^4.17.1", "js-yaml": "^4.1.0",
"js-yaml": "^3.14.0", "kafkajs": "^2.0.2",
"kafkajs": "^1.15.0", "long": "^5.2.0",
"long": "^4.0.0",
"uuid-parse": "^1.1.0", "uuid-parse": "^1.1.0",
"uuid-random": "^1.3.2", "uuid-random": "^1.3.2",
"winston": "^3.3.3", "winston": "^3.7.2",
"winston-daily-rotate-file": "^4.5.0" "winston-daily-rotate-file": "^4.7.1"
}, },
"nyc": { "nyc": {
"exclude": [ "exclude": [
@ -36,13 +35,19 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"fs-extra": "^10.0.0", "fs-extra": "^10.1.0",
"nodemon": "^2.0.12", "nodemon": "^2.0.16",
"pkg": "^5.3.1" "pkg": "^5.7.0"
}, },
"pkg": { "pkg": {
"assets": [ "assets": [
"node_modules/config/**/*.*" "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> <goal>install-node-and-yarn</goal>
</goals> </goals>
<configuration> <configuration>
<nodeVersion>v12.16.1</nodeVersion> <nodeVersion>v16.15.1</nodeVersion>
<yarnVersion>v1.22.4</yarnVersion> <yarnVersion>v1.22.17</yarnVersion>
</configuration> </configuration>
</execution> </execution>
<execution> <execution>

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

@ -18,8 +18,7 @@
const config = require('config'), const config = require('config'),
JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'), JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'),
logger = require('../config/logger')._logger('serviceBusTemplate'); logger = require('../config/logger')._logger('serviceBusTemplate');
const {ServiceBusClient, ReceiveMode} = require("@azure/service-bus"); const {ServiceBusClient, ServiceBusAdministrationClient} = require("@azure/service-bus");
const azure = require('azure-sb');
const requestTopic = config.get('request_topic'); const requestTopic = config.get('request_topic');
const namespaceName = config.get('service_bus.namespace_name'); 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'); const queueProperties = config.get('service_bus.queue_properties');
let sbClient; let sbClient;
let receiverClient;
let receiver; let receiver;
let serviceBusService; let serviceBusService;
@ -61,11 +59,10 @@ function ServiceBusProducer() {
} }
function CustomSender(topic) { function CustomSender(topic) {
this.queueClient = sbClient.createQueueClient(topic); this.sender = sbClient.createSender(topic);
this.sender = this.queueClient.createSender();
this.send = async (message) => { 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...'); logger.info('Starting ThingsBoard JavaScript Executor Microservice...');
const connectionString = `Endpoint=sb://${namespaceName}.servicebus.windows.net/;SharedAccessKeyName=${sasKeyName};SharedAccessKey=${sasKey}`; const connectionString = `Endpoint=sb://${namespaceName}.servicebus.windows.net/;SharedAccessKeyName=${sasKeyName};SharedAccessKey=${sasKey}`;
sbClient = ServiceBusClient.createFromConnectionString(connectionString); sbClient = new ServiceBusClient(connectionString)
serviceBusService = azure.createServiceBusService(connectionString); serviceBusService = new ServiceBusAdministrationClient(connectionString);
parseQueueProperties(); parseQueueProperties();
@ -84,9 +81,9 @@ function CustomSender(topic) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
data.forEach(queue => { for (const queue of data) {
queues.push(queue.QueueName); queues.push(queue.name);
}); }
resolve(); resolve();
} }
}); });
@ -97,8 +94,7 @@ function CustomSender(topic) {
queues.push(requestTopic); queues.push(requestTopic);
} }
receiverClient = sbClient.createQueueClient(requestTopic); receiver = sbClient.createReceiver(requestTopic, {receiveMode: 'peekLock'});
receiver = receiverClient.createReceiver(ReceiveMode.peekLock);
const messageProcessor = new JsInvokeMessageProcessor(new ServiceBusProducer()); const messageProcessor = new JsInvokeMessageProcessor(new ServiceBusProducer());
@ -111,18 +107,18 @@ function CustomSender(topic) {
const errorHandler = (error) => { const errorHandler = (error) => {
logger.error('Failed to receive message from queue.', error); logger.error('Failed to receive message from queue.', error);
}; };
receiver.registerMessageHandler(messageHandler, errorHandler); receiver.subscribe({processMessage: messageHandler, processError: errorHandler})
} catch (e) { } catch (e) {
logger.error('Failed to start ThingsBoard JavaScript Executor Microservice: %s', e.message); logger.error('Failed to start ThingsBoard JavaScript Executor Microservice: %s', e.message);
logger.error(e.stack); logger.error(e.stack);
exit(-1); await exit(-1);
} }
})(); })();
async function createQueueIfNotExist(topic) { async function createQueueIfNotExist(topic) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
serviceBusService.createQueueIfNotExists(topic, queueOptions, (err) => { serviceBusService.createQueue(topic, queueOptions, (err) => {
if (err) { if (err && err.code !== "MessageEntityAlreadyExistsError") {
reject(err); reject(err);
} else { } else {
resolve(); resolve();
@ -139,10 +135,10 @@ function parseQueueProperties() {
properties[p.substring(0, delimiterPosition)] = p.substring(delimiterPosition + 1); properties[p.substring(0, delimiterPosition)] = p.substring(delimiterPosition + 1);
}); });
queueOptions = { queueOptions = {
DuplicateDetection: 'false', requiresDuplicateDetection: false,
MaxSizeInMegabytes: properties['maxSizeInMb'], maxSizeInMegabytes: properties['maxSizeInMb'],
DefaultMessageTimeToLive: `PT${properties['messageTimeToLiveInSec']}S`, defaultMessageTimeToLive: `PT${properties['messageTimeToLiveInSec']}S`,
LockDuration: `PT${properties['lockDurationInSec']}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) => { senderMap.forEach((k, v) => {
try { try {
v.sender.close(); v.sender.close();
} catch (e) { } catch (e) {
}
try {
v.queueClient.close();
} catch (e) {
} }
}); });
@ -191,4 +174,4 @@ async function exit(status) {
} }
logger.info('Azure Service Bus resources stopped.') logger.info('Azure Service Bus resources stopped.')
process.exit(status); 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. # limitations under the License.
# #
FROM thingsboard/openjdk11 FROM thingsboard/openjdk11:bullseye-slim
RUN echo 'networkaddress.cache.ttl=60' >> /etc/java-11-openjdk/security/java.security
COPY start-tb-node.sh ${pkg.name}.deb /tmp/ COPY start-tb-node.sh ${pkg.name}.deb /tmp/
RUN chmod a+x /tmp/*.sh \ RUN echo 'networkaddress.cache.ttl=60' >> /etc/java-11-openjdk/security/java.security \
&& mv /tmp/start-tb-node.sh /usr/bin && chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb-node.sh /usr/bin && \
RUN yes | dpkg -i /tmp/${pkg.name}.deb (yes | dpkg -i /tmp/${pkg.name}.deb) && \
RUN rm /tmp/${pkg.name}.deb rm /tmp/${pkg.name}.deb && \
(systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :) && \
RUN 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
RUN chown -R ${pkg.user}:${pkg.user} /tmp
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
USER ${pkg.user} USER ${pkg.user}

81
msa/tb/docker-cassandra/Dockerfile

@ -14,37 +14,9 @@
# limitations under the License. # 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 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 ENV DATA_FOLDER=/data
@ -54,7 +26,6 @@ ENV DATABASE_TS_TYPE=cassandra
ENV PGDATA=/data/db ENV PGDATA=/data/db
ENV CASSANDRA_DATA=/data/cassandra ENV CASSANDRA_DATA=/data/cassandra
ENV SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect
ENV SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver ENV SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver
ENV SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/thingsboard ENV SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/thingsboard
ENV SPRING_DATASOURCE_USERNAME=${pkg.user} 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 PGLOG=/var/log/postgres
ENV CASSANDRA_LOG=/var/log/cassandra ENV CASSANDRA_LOG=/var/log/cassandra
# postgres config 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 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
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 # cassandra config
RUN rm -rf /var/lib/cassandra && rm -rf /var/lib/cassandra \
RUN chmod a+w /var/lib && chmod a+w /var/lib \
RUN chown -R ${pkg.user}:${pkg.user} $CASSANDRA_LOG && chown -R ${pkg.user}:${pkg.user} $CASSANDRA_LOG \
&& mkdir -p $DATA_FOLDER \
&& chown -R ${pkg.user}:${pkg.user} $DATA_FOLDER \
RUN mkdir -p $DATA_FOLDER && chown -R ${pkg.user}:${pkg.user} /var/log/${pkg.name} \
RUN chown -R ${pkg.user}:${pkg.user} $DATA_FOLDER && chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
RUN chown -R ${pkg.user}:${pkg.user} /var/log/${pkg.name}
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar
USER ${pkg.user} USER ${pkg.user}

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

@ -24,16 +24,23 @@ if [ ! -d ${PGDATA} ]; then
${PG_CTL} initdb ${PG_CTL} initdb
fi 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 if [ ! -f ${firstlaunch} ]; then
sleep 2 echo "Creating database..."
while ! psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard"
do
sleep 1
done
fi fi
echo "Postgresql is ready"
cassandra_data_dir=${CASSANDRA_DATA} cassandra_data_dir=${CASSANDRA_DATA}
cassandra_data_link=/var/lib/cassandra cassandra_data_link=/var/lib/cassandra

62
msa/tb/docker-postgres/Dockerfile

@ -14,33 +14,9 @@
# limitations under the License. # 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 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 ENV DATA_FOLDER=/data
@ -50,24 +26,40 @@ ENV DATABASE_TS_TYPE=sql
ENV PGDATA=/data/db ENV PGDATA=/data/db
ENV PATH=$PATH:/usr/lib/postgresql/$PG_MAJOR/bin 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_DRIVER_CLASS_NAME=org.postgresql.Driver
ENV SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/thingsboard ENV SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/thingsboard
ENV SPRING_DATASOURCE_USERNAME=${pkg.user} ENV SPRING_DATASOURCE_USERNAME=${pkg.user}
ENV SPRING_DATASOURCE_PASSWORD=postgres ENV SPRING_DATASOURCE_PASSWORD=postgres
ENV PGLOG=/var/log/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 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 mkdir -p /data
RUN chown -R ${pkg.user}:${pkg.user} /data
RUN chown -R ${pkg.user}:${pkg.user} /var/log/${pkg.name} RUN apt-get update \
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar && 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} USER ${pkg.user}

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

@ -24,12 +24,19 @@ if [ ! -d ${PGDATA} ]; then
${PG_CTL} initdb ${PG_CTL} initdb
fi 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 if [ ! -f ${firstlaunch} ]; then
sleep 2 echo "Creating database..."
while ! psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard"
do fi
sleep 1
done echo "Postgresql is ready"
fi

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 ..." echo "Starting ThingsBoard installation ..."
set -e
java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \ java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
-Dinstall.load_demo=${loadDemo} \ -Dinstall.load_demo=${loadDemo} \
-Dspring.jpa.hibernate.ddl-auto=none \ -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}" source "${CONF_FOLDER}/${configfile}"
if [ ! -f ${firstlaunch} ]; then if [ ! -f ${firstlaunch} ]; then
install-tb.sh --loadDemo install-tb.sh --loadDemo && touch ${firstlaunch}
touch ${firstlaunch}
fi fi
echo "Starting ThingsBoard ..." if [ -f ${firstlaunch} ]; then
echo "Starting ThingsBoard ..."
java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardServerApplication \ java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardServerApplication \
-Dspring.jpa.hibernate.ddl-auto=none \ -Dspring.jpa.hibernate.ddl-auto=none \
-Dlogging.config=${CONF_FOLDER}/logback.xml \ -Dlogging.config=${CONF_FOLDER}/logback.xml \
org.springframework.boot.loader.PropertiesLauncher org.springframework.boot.loader.PropertiesLauncher
else
echo "ERROR: ThingsBoard is not installed"
fi
stop-db.sh stop-db.sh

134
msa/tb/pom.xml

@ -35,7 +35,6 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../..</main.dir> <main.dir>${basedir}/../..</main.dir>
<pkg.name>thingsboard</pkg.name> <pkg.name>thingsboard</pkg.name>
<tb.docker.name>tb</tb.docker.name>
<tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name> <tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name>
<tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name> <tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder> <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
@ -59,25 +58,6 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId> <artifactId>maven-dependency-plugin</artifactId>
<executions> <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> <execution>
<id>copy-tb-postgres-deb</id> <id>copy-tb-postgres-deb</id>
<phase>package</phase> <phase>package</phase>
@ -122,27 +102,7 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId> <artifactId>maven-resources-plugin</artifactId>
<executions> <executions>
<execution> <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>
<id>copy-docker-tb-postgres-config</id> <id>copy-docker-tb-postgres-config</id>
<phase>process-resources</phase> <phase>process-resources</phase>
<goals> <goals>
@ -188,32 +148,6 @@
<groupId>com.spotify</groupId> <groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId> <artifactId>dockerfile-maven-plugin</artifactId>
<executions> <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> <execution>
<id>build-docker-tb-postgres-image</id> <id>build-docker-tb-postgres-image</id>
<phase>pre-integration-test</phase> <phase>pre-integration-test</phase>
@ -226,6 +160,7 @@
<verbose>true</verbose> <verbose>true</verbose>
<googleContainerRegistryEnabled>false</googleContainerRegistryEnabled> <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
<contextDirectory>${project.build.directory}/docker-postgres</contextDirectory> <contextDirectory>${project.build.directory}/docker-postgres</contextDirectory>
<noCache>true</noCache>
</configuration> </configuration>
</execution> </execution>
<execution> <execution>
@ -252,6 +187,7 @@
<verbose>true</verbose> <verbose>true</verbose>
<googleContainerRegistryEnabled>false</googleContainerRegistryEnabled> <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
<contextDirectory>${project.build.directory}/docker-cassandra</contextDirectory> <contextDirectory>${project.build.directory}/docker-cassandra</contextDirectory>
<noCache>true</noCache>
</configuration> </configuration>
</execution> </execution>
<execution> <execution>
@ -284,28 +220,6 @@
<groupId>com.spotify</groupId> <groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId> <artifactId>dockerfile-maven-plugin</artifactId>
<executions> <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> <execution>
<id>push-latest-docker-tb-postgres-image</id> <id>push-latest-docker-tb-postgres-image</id>
<phase>pre-integration-test</phase> <phase>pre-integration-test</phase>
@ -368,48 +282,6 @@
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId> <artifactId>exec-maven-plugin</artifactId>
<executions> <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> <execution>
<id>push-latest-docker-amd-arm-tb-postgres-images</id> <id>push-latest-docker-amd-arm-tb-postgres-images</id>
<phase>pre-integration-test</phase> <phase>pre-integration-test</phase>

15
msa/transport/coap/docker/Dockerfile

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

15
msa/transport/http/docker/Dockerfile

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

15
msa/transport/lwm2m/docker/Dockerfile

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

15
msa/transport/mqtt/docker/Dockerfile

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

15
msa/transport/snmp/docker/Dockerfile

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

16
msa/web-ui/docker/Dockerfile

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

23
msa/web-ui/package.json

@ -6,21 +6,21 @@
"main": "server.js", "main": "server.js",
"bin": "server.js", "bin": "server.js",
"scripts": { "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", "test": "echo \"Error: no test specified\" && exit 1",
"start": "WEB_FOLDER=./target/web nodemon server.js", "start": "WEB_FOLDER=./target/web nodemon server.js",
"start-prod": "NODE_ENV=production nodemon server.js" "start-prod": "NODE_ENV=production nodemon server.js"
}, },
"dependencies": { "dependencies": {
"compression": "^1.7.4", "compression": "^1.7.4",
"config": "^3.3.1", "config": "^3.3.7",
"connect-history-api-fallback": "^1.6.0", "connect-history-api-fallback": "^1.6.0",
"express": "^4.17.1", "express": "^4.18.1",
"http": "0.0.0", "http": "0.0.0",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"js-yaml": "^3.14.0", "js-yaml": "^4.1.0",
"winston": "^3.3.3", "winston": "^3.7.2",
"winston-daily-rotate-file": "^4.5.0" "winston-daily-rotate-file": "^4.7.1"
}, },
"nyc": { "nyc": {
"exclude": [ "exclude": [
@ -31,13 +31,18 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"fs-extra": "^10.0.0", "fs-extra": "^10.1.0",
"nodemon": "^2.0.12", "nodemon": "^2.0.16",
"pkg": "^5.3.1" "pkg": "^5.7.0"
}, },
"pkg": { "pkg": {
"assets": [ "assets": [
"node_modules/config/**/*.*" "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> <goal>install-node-and-yarn</goal>
</goals> </goals>
<configuration> <configuration>
<nodeVersion>v12.16.1</nodeVersion> <nodeVersion>v16.15.1</nodeVersion>
<yarnVersion>v1.22.4</yarnVersion> <yarnVersion>v1.22.17</yarnVersion>
</configuration> </configuration>
</execution> </execution>
<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> <awaitility.version>4.1.0</awaitility.version>
<cassandra-unit.version>4.3.1.0</cassandra-unit.version> <cassandra-unit.version>4.3.1.0</cassandra-unit.version>
<dbunit.version>2.7.2</dbunit.version> <dbunit.version>2.7.2</dbunit.version>
<hsqldb.version>2.6.1</hsqldb.version>
<java-websocket.version>1.5.2</java-websocket.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--> <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> <json-path.version>2.6.0</json-path.version>
@ -1659,11 +1658,6 @@
<artifactId>bcpkix-jdk15on</artifactId> <artifactId>bcpkix-jdk15on</artifactId>
<version>${bouncycastle.version}</version> <version>${bouncycastle.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>${hsqldb.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.testcontainers</groupId> <groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId> <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.AlarmConditionSpecType;
import org.thingsboard.server.common.data.device.profile.AlarmRule; 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.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.CustomTimeScheduleItem;
import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec; 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.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.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.StringFilterPredicate; import org.thingsboard.server.common.data.query.StringFilterPredicate;
import org.thingsboard.server.common.msg.tools.SchedulerUtils; import org.thingsboard.server.common.msg.tools.SchedulerUtils;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
@ -115,7 +117,7 @@ class AlarmRuleState {
} }
public AlarmEvalResult eval(DataSnapshot data) { public AlarmEvalResult eval(DataSnapshot data) {
boolean active = isActive(data.getTs()); boolean active = isActive(data, data.getTs());
switch (spec.getType()) { switch (spec.getType()) {
case SIMPLE: case SIMPLE:
return (active && eval(alarmRule.getCondition(), data)) ? AlarmEvalResult.TRUE : AlarmEvalResult.FALSE; 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) { if (eventTs == 0L) {
eventTs = System.currentTimeMillis(); eventTs = System.currentTimeMillis();
} }
@ -139,14 +141,28 @@ class AlarmRuleState {
case ANY_TIME: case ANY_TIME:
return true; return true;
case SPECIFIC_TIME: case SPECIFIC_TIME:
return isActiveSpecific((SpecificTimeSchedule) alarmRule.getSchedule(), eventTs); return isActiveSpecific((SpecificTimeSchedule) getSchedule(data, alarmRule), eventTs);
case CUSTOM: case CUSTOM:
return isActiveCustom((CustomTimeSchedule) alarmRule.getSchedule(), eventTs); return isActiveCustom((CustomTimeSchedule) getSchedule(data, alarmRule), eventTs);
default: default:
throw new RuntimeException("Unsupported schedule type: " + alarmRule.getSchedule().getType()); 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) { private boolean isActiveSpecific(SpecificTimeSchedule schedule, long eventTs) {
ZoneId zoneId = SchedulerUtils.getZoneId(schedule.getTimezone()); ZoneId zoneId = SchedulerUtils.getZoneId(schedule.getTimezone());
ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(eventTs), zoneId); ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(eventTs), zoneId);
@ -156,7 +172,13 @@ class AlarmRuleState {
return false; 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) { private boolean isActiveCustom(CustomTimeSchedule schedule, long eventTs) {
@ -166,7 +188,12 @@ class AlarmRuleState {
for (CustomTimeScheduleItem item : schedule.getItems()) { for (CustomTimeScheduleItem item : schedule.getItems()) {
if (item.getDayOfWeek() == dayOfWeek) { if (item.getDayOfWeek() == dayOfWeek) {
if (item.isEnabled()) { 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 { } else {
return false; return false;
} }
@ -279,7 +306,7 @@ class AlarmRuleState {
long requiredDurationInMs = resolveRequiredDurationInMs(dataSnapshot); long requiredDurationInMs = resolveRequiredDurationInMs(dataSnapshot);
if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) { if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) {
long duration = state.getDuration() + (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; return duration > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE;
} else { } else {
return AlarmEvalResult.FALSE; 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.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec; 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.RepeatingAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.AlarmSchedule;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate; import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValue;
@ -77,6 +78,10 @@ class ProfileState {
addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys); addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys);
} }
addEntityKeysFromAlarmConditionSpec(alarmRule); addEntityKeysFromAlarmConditionSpec(alarmRule);
AlarmSchedule schedule = alarmRule.getSchedule();
if (schedule != null) {
addScheduleDynamicValues(schedule);
}
})); }));
if (alarm.getClearRule() != null) { if (alarm.getClearRule() != null) {
var clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>()); 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) { private void addEntityKeysFromAlarmConditionSpec(AlarmRule alarmRule) {
AlarmConditionSpec spec = alarmRule.getCondition().getSpec(); AlarmConditionSpec spec = alarmRule.getCondition().getSpec();
if (spec == null) { 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.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; 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.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.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec; 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.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; 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.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate; 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.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -1086,6 +1090,179 @@ public class TbDeviceProfileNodeTest {
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); 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 @Test
public void testCurrentCustomersAttributeForDynamicValue() throws Exception { public void testCurrentCustomersAttributeForDynamicValue() throws Exception {

1
ui-ngx/package.json

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

2
ui-ngx/pom.xml

@ -56,7 +56,7 @@
<goal>install-node-and-yarn</goal> <goal>install-node-and-yarn</goal>
</goals> </goals>
<configuration> <configuration>
<nodeVersion>v16.13.0</nodeVersion> <nodeVersion>v16.15.1</nodeVersion>
<yarnVersion>v1.22.17</yarnVersion> <yarnVersion>v1.22.17</yarnVersion>
</configuration> </configuration>
</execution> </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 { LoginRequest, LoginResponse, PublicLoginRequest } from '@shared/models/login.models';
import { ActivatedRoute, Router, UrlTree } from '@angular/router'; 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 { UserService } from '../http/user.service';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '../core.state'; 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 { OAuth2ClientInfo, PlatformType } from '@shared/models/oauth2.models';
import { isMobileApp } from '@core/utils'; import { isMobileApp } from '@core/utils';
import { TwoFactorAuthProviderType, TwoFaProviderInfo } from '@shared/models/two-factor-auth.models'; import { TwoFactorAuthProviderType, TwoFaProviderInfo } from '@shared/models/two-factor-auth.models';
import { UserPasswordPolicy } from '@shared/models/settings.models';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -163,14 +164,18 @@ export class AuthService {
)); ));
} }
public changePassword(currentPassword: string, newPassword: string) { public changePassword(currentPassword: string, newPassword: string, config?: RequestConfig) {
return this.http.post('/api/auth/changePassword', {currentPassword, newPassword}, defaultHttpOptions()).pipe( return this.http.post('/api/auth/changePassword', {currentPassword, newPassword}, defaultHttpOptionsFromConfig(config)).pipe(
tap((loginResponse: LoginResponse) => { tap((loginResponse: LoginResponse) => {
this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false); this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false);
} }
)); ));
} }
public getUserPasswordPolicy() {
return this.http.get<UserPasswordPolicy>(`/api/noauth/userPasswordPolicy`, defaultHttpOptions());
}
public activateByEmailCode(emailCode: string): Observable<LoginResponse> { public activateByEmailCode(emailCode: string): Observable<LoginResponse> {
return this.http.post<LoginResponse>(`/api/noauth/activateByEmailCode?emailCode=${emailCode}`, return this.http.post<LoginResponse>(`/api/noauth/activateByEmailCode?emailCode=${emailCode}`,
null, defaultHttpOptions()); null, defaultHttpOptions());

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

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

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

@ -28,7 +28,7 @@
</mat-toolbar> </mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar> </mat-progress-bar>
<div mat-dialog-content> <div mat-dialog-content style="padding: 0;">
<fieldset [disabled]="isLoading$ | async" style="position: relative; height: 600px;"> <fieldset [disabled]="isLoading$ | async" style="position: relative; height: 600px;">
<tb-widget-config <tb-widget-config
[aliasController]="aliasController" [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) { editWidget($event: Event, layoutCtx: DashboardPageLayoutContext, widget: Widget) {
$event.stopPropagation(); $event.stopPropagation();
if (this.isAddingWidget) {
this.onAddWidgetClosed();
this.isAddingWidgetClosed = true;
this.isEditingWidgetClosed = false;
}
if (this.editingWidgetOriginal === widget) { if (this.editingWidgetOriginal === widget) {
this.onEditWidgetClosed(); this.onEditWidgetClosed();
} else { } 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 { private updateEdgeEventStatus(createdTime: number): string {
if (this.queueStartTs && createdTime < this.queueStartTs) { if (this.queueStartTs && createdTime <= this.queueStartTs) {
return this.translate.instant('edge.deployed'); return this.translate.instant('edge.deployed');
} else { } else {
return this.translate.instant('edge.pending'); 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) { if (this.pageMode) {
this.route.queryParams.pipe(skip(1)).subscribe((params: PageQueryParam) => { this.route.queryParams.pipe(skip(1)).subscribe((params: PageQueryParam) => {
this.paginator.pageIndex = Number(params.page) || 0; if (this.displayPagination) {
this.paginator.pageSize = Number(params.pageSize) || this.defaultPageSize; 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.active = params.property || this.entitiesTableConfig.defaultSortOrder.property;
this.sort.direction = (params.direction || this.entitiesTableConfig.defaultSortOrder.direction).toLowerCase() as SortDirection; this.sort.direction = (params.direction || this.entitiesTableConfig.defaultSortOrder.direction).toLowerCase() as SortDirection;
if (params.hasOwnProperty('textSearch') && !isEmptyStr(params.textSearch)) { 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 HOME_COMPONENTS_MODULE_TOKEN
} from '@home/components/tokens'; } from '@home/components/tokens';
import { DashboardStateComponent } from '@home/components/dashboard-page/dashboard-state.component'; 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 { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { TenantProfileQueuesComponent } from '@home/components/profile/queue/tenant-profile-queues.component'; import { TenantProfileQueuesComponent } from '@home/components/profile/queue/tenant-profile-queues.component';
import { QueueFormComponent } from '@home/components/queue/queue-form.component'; import { QueueFormComponent } from '@home/components/queue/queue-form.component';
@ -254,6 +255,7 @@ import { WidgetSettingsComponent } from '@home/components/widget/widget-settings
AlarmScheduleInfoComponent, AlarmScheduleInfoComponent,
DeviceProfileProvisionConfigurationComponent, DeviceProfileProvisionConfigurationComponent,
AlarmScheduleComponent, AlarmScheduleComponent,
AlarmDynamicValue,
AlarmDurationPredicateValueComponent, AlarmDurationPredicateValueComponent,
DeviceWizardDialogComponent, DeviceWizardDialogComponent,
AlarmScheduleDialogComponent, AlarmScheduleDialogComponent,
@ -372,11 +374,11 @@ import { WidgetSettingsComponent } from '@home/components/widget/widget-settings
DeviceWizardDialogComponent, DeviceWizardDialogComponent,
AlarmScheduleInfoComponent, AlarmScheduleInfoComponent,
AlarmScheduleComponent, AlarmScheduleComponent,
AlarmDynamicValue,
AlarmScheduleDialogComponent, AlarmScheduleDialogComponent,
AlarmDurationPredicateValueComponent, AlarmDurationPredicateValueComponent,
EditAlarmDetailsDialogComponent, EditAlarmDetailsDialogComponent,
DeviceProfileProvisionConfigurationComponent, DeviceProfileProvisionConfigurationComponent,
AlarmScheduleComponent,
SmsProviderConfigurationComponent, SmsProviderConfigurationComponent,
AwsSnsProviderConfigurationComponent, AwsSnsProviderConfigurationComponent,
SmppSmsProviderConfigurationComponent, 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"> formControlName="timezone">
</tb-timezone-select> </tb-timezone-select>
<section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.SPECIFIC_TIME"> <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 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="column" fxLayout.gt-md="row" fxLayoutGap="16px">
<div fxLayout="row" fxLayoutGap="16px"> <div fxLayout="row" fxLayoutGap="16px">
@ -73,8 +74,8 @@
</div> </div>
</section> </section>
<section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.CUSTOM"> <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 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 *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"> <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)"> <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); alarmScheduleTypes = Object.keys(AlarmScheduleType);
alarmScheduleType = AlarmScheduleType; alarmScheduleType = AlarmScheduleType;
alarmScheduleTypeTranslate = AlarmScheduleTypeTranslationMap; alarmScheduleTypeTranslate = AlarmScheduleTypeTranslationMap;
dayOfWeekTranslationsArray = dayOfWeekTranslations; dayOfWeekTranslationsArray = dayOfWeekTranslations;
allDays = Array(7).fill(0).map((x, i) => i); 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), daysOfWeek: this.fb.array(new Array(7).fill(false), this.validateDayOfWeeks),
startsOn: [0, Validators.required], startsOn: [0, Validators.required],
endsOn: [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) => { this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => {
const defaultTimezone = getDefaultTimezone(); const defaultTimezone = getDefaultTimezone();
this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false}); 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, timezone: this.modelValue.timezone,
daysOfWeek, daysOfWeek,
startsOn: utcTimestampToTimeOfDay(this.modelValue.startsOn), startsOn: utcTimestampToTimeOfDay(this.modelValue.startsOn),
endsOn: utcTimestampToTimeOfDay(this.modelValue.endsOn) endsOn: utcTimestampToTimeOfDay(this.modelValue.endsOn),
dynamicValue: this.modelValue.dynamicValue
}, {emitEvent: false}); }, {emitEvent: false});
break; break;
case AlarmScheduleType.CUSTOM: case AlarmScheduleType.CUSTOM:
@ -177,7 +179,8 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
this.alarmScheduleForm.patchValue({ this.alarmScheduleForm.patchValue({
type: this.modelValue.type, type: this.modelValue.type,
timezone: this.modelValue.timezone, timezone: this.modelValue.timezone,
items: alarmDays items: alarmDays,
dynamicValue: this.modelValue.dynamicValue
}, {emitEvent: false}); }, {emitEvent: false});
} }
break; break;

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

Loading…
Cancel
Save