Browse Source

Merge remote-tracking branch 'origin/feature/entities-version-control' into vc-improvements

# Conflicts:
#	application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
#	application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
#	common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportSettings.java
pull/6730/head
Viacheslav Klimov 4 years ago
parent
commit
6e27ed46d6
  1. 2
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  2. 6
      application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java
  3. 6
      application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java
  4. 4
      application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java
  5. 6
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java
  6. 2
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  8. 64
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
  9. 6
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java
  10. 6
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java
  11. 14
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
  12. 13
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java
  13. 14
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/ReimportTask.java
  14. 5
      application/src/main/resources/thingsboard.yml
  15. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
  16. 182
      application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
  17. 72
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  18. 229
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
  19. 104
      application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java
  20. 16
      application/src/test/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaServiceTest.java
  21. 26
      common/dao-api/src/main/java/org/thingsboard/server/dao/util/HsqlDao.java
  22. 26
      common/dao-api/src/main/java/org/thingsboard/server/dao/util/PsqlDao.java
  23. 2
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  24. 37
      dao/src/main/java/org/thingsboard/server/dao/HsqlTsLatestDaoConfig.java
  25. 37
      dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java
  26. 8
      dao/src/main/java/org/thingsboard/server/dao/SqlTsDaoConfig.java
  27. 8
      dao/src/main/java/org/thingsboard/server/dao/SqlTsLatestDaoConfig.java
  28. 2
      dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java
  29. 4
      dao/src/main/java/org/thingsboard/server/dao/TimescaleTsLatestDaoConfig.java
  30. 48
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  31. 8
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java
  32. 27
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  33. 3
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java
  34. 76
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/HsqlAttributesInsertRepository.java
  35. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/SqlAttributesInsertRepository.java
  36. 65
      dao/src/main/java/org/thingsboard/server/dao/sql/component/HsqlComponentDescriptorInsertRepository.java
  37. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/component/SqlComponentDescriptorInsertRepository.java
  38. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventInsertRepository.java
  39. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java
  40. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/event/SqlEventCleanupRepository.java
  41. 46
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java
  42. 11
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java
  43. 60
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/SqlRelationInsertRepository.java
  44. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java
  45. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java
  46. 62
      dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java
  47. 87
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/hsql/HsqlInsertTsRepository.java
  48. 85
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/hsql/HsqlLatestInsertTsRepository.java
  49. 8
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/sql/SqlLatestInsertTsRepository.java
  50. 6
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlInsertTsRepository.java
  51. 10
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java
  52. 2
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java
  53. 32
      dao/src/main/java/org/thingsboard/server/dao/sqlts/sql/JpaSqlTimeseriesDao.java
  54. 4
      dao/src/main/java/org/thingsboard/server/dao/timeseries/SqlPartition.java
  55. 2
      dao/src/test/java/org/thingsboard/server/dao/AbstractDaoServiceTest.java
  56. 2
      dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java
  57. 40
      dao/src/test/java/org/thingsboard/server/dao/util/DaoTestUtil.java
  58. 1
      dao/src/test/resources/nosql-test.properties
  59. 2
      dao/src/test/resources/sql-test.properties
  60. 1
      docker/tb-node.hybrid.env
  61. 1
      docker/tb-node.postgres.env
  62. 35
      msa/js-executor/package.json
  63. 4
      msa/js-executor/pom.xml
  64. 53
      msa/js-executor/queue/serviceBusTemplate.js
  65. 2317
      msa/js-executor/yarn.lock
  66. 1
      msa/tb/docker-cassandra/Dockerfile
  67. 1
      msa/tb/docker-postgres/Dockerfile
  68. 61
      msa/tb/docker-tb/Dockerfile
  69. 18
      msa/tb/docker-tb/start-db.sh
  70. 18
      msa/tb/docker-tb/stop-db.sh
  71. 132
      msa/tb/pom.xml
  72. 21
      msa/web-ui/package.json
  73. 4
      msa/web-ui/pom.xml
  74. 966
      msa/web-ui/yarn.lock
  75. 6
      pom.xml
  76. 6
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java
  77. 1
      ui-ngx/package.json
  78. 16
      ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.html
  79. 21
      ui-ngx/src/app/modules/home/pages/security/authentication-dialog/sms-auth-dialog.component.html
  80. 12
      ui-ngx/src/app/shared/components/contact.component.html
  81. 50
      ui-ngx/src/app/shared/components/phone-input.component.html
  82. 44
      ui-ngx/src/app/shared/components/phone-input.component.scss
  83. 222
      ui-ngx/src/app/shared/components/phone-input.component.ts
  84. 520
      ui-ngx/src/app/shared/models/country.models.ts
  85. 7
      ui-ngx/src/app/shared/shared.module.ts
  86. 7
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  87. 5
      ui-ngx/yarn.lock

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

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

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

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

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

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

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

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

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

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

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

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

2
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -160,7 +160,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
TbActorMsg actorMsg = actorMsgOpt.get();
if (actorMsg instanceof ComponentLifecycleMsg) {
ComponentLifecycleMsg componentLifecycleMsg = (ComponentLifecycleMsg) actorMsg;
log.info("[{}][{}][{}] Received Lifecycle event: {}", componentLifecycleMsg.getTenantId(), componentLifecycleMsg.getEntityId().getEntityType(),
log.debug("[{}][{}][{}] Received Lifecycle event: {}", componentLifecycleMsg.getTenantId(), componentLifecycleMsg.getEntityId().getEntityType(),
componentLifecycleMsg.getEntityId(), componentLifecycleMsg.getEvent());
if (EntityType.TENANT_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
TenantProfileId tenantProfileId = new TenantProfileId(componentLifecycleMsg.getEntityId().getId());

64
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java

@ -23,6 +23,8 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StopWatch;
import org.thingsboard.common.util.TbStopWatch;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
@ -60,6 +62,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@ -83,6 +86,7 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
@Transactional(rollbackFor = Exception.class)
@Override
public EntityImportResult<E> importEntity(EntitiesImportCtx ctx, D exportData) throws ThingsboardException {
// TbStopWatch sw = TbStopWatch.create("find");
EntityImportResult<E> importResult = new EntityImportResult<>();
IdProvider idProvider = new IdProvider(ctx, importResult);
@ -99,16 +103,22 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
entity.setCreatedTime(existingEntity.getCreatedTime());
}
// sw.startNew("prepareAndSave");
E savedEntity = prepareAndSave(ctx, entity, exportData, idProvider);
importResult.setSavedEntity(savedEntity);
importResult.setOldEntity(existingEntity);
importResult.setEntityType(getEntityType());
// sw.startNew("afterSaved");
processAfterSaved(ctx, importResult, exportData, idProvider);
ctx.putInternalId(exportData.getExternalId(), savedEntity.getId());
// sw.stop();
// for (var task : sw.getTaskInfo()) {
// log.info("[{}][{}] Executed: {} in {}ms", exportData.getEntityType(), exportData.getEntity().getId(), task.getTaskName(), task.getTimeMillis());
// }
// log.info("[{}][{}] Total time: {}ms", exportData.getEntityType(), exportData.getEntity().getId(), sw.getTotalTimeMillis());
return importResult;
}
@ -280,33 +290,31 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
return entity.getId();
}
public Optional<EntityId> getInternalIdByUuid(UUID externalUuid) {
public Optional<EntityId> getInternalIdByUuid(UUID externalUuid, boolean fetchAllUUIDs, Set<EntityType> hints) {
if (externalUuid.equals(EntityId.NULL_UUID)) return Optional.empty();
for (EntityType entityType : EntityType.values()) {
EntityId externalId;
try {
externalId = EntityIdFactory.getByTypeAndUuid(entityType, externalUuid);
} catch (Exception e) {
Optional<EntityId> externalIdOpt = buildEntityId(entityType, externalUuid);
if (!externalIdOpt.isPresent()) {
continue;
}
EntityId internalId = ctx.getInternalId(externalId);
EntityId internalId = ctx.getInternalId(externalIdOpt.get());
if (internalId != null) {
return Optional.of(internalId);
}
}
for (EntityType entityType : EntityType.values()) {
EntityId externalId;
try {
externalId = EntityIdFactory.getByTypeAndUuid(entityType, externalUuid);
} catch (Exception e) {
continue;
if (fetchAllUUIDs) {
for (EntityType entityType : hints) {
Optional<EntityId> internalId = lookupInDb(externalUuid, entityType);
if (internalId.isPresent()) return internalId;
}
EntityId internalId = getInternalId(externalId, false);
if (internalId != null) {
return Optional.of(internalId);
for (EntityType entityType : EntityType.values()) {
if (hints.contains(entityType)) {
continue;
}
Optional<EntityId> internalId = lookupInDb(externalUuid, entityType);
if (internalId.isPresent()) return internalId;
}
}
@ -314,6 +322,28 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
return Optional.empty();
}
private Optional<EntityId> lookupInDb(UUID externalUuid, EntityType entityType) {
Optional<EntityId> externalIdOpt = buildEntityId(entityType, externalUuid);
if (externalIdOpt.isEmpty() || ctx.isNotFound(externalIdOpt.get())) {
return Optional.empty();
}
EntityId internalId = getInternalId(externalIdOpt.get(), false);
if (internalId != null) {
return Optional.of(internalId);
} else {
ctx.registerNotFound(externalIdOpt.get());
}
return Optional.empty();
}
private Optional<EntityId> buildEntityId(EntityType entityType, UUID externalUuid) {
try {
return Optional.of(EntityIdFactory.getByTypeAndUuid(entityType, externalUuid));
} catch (Exception e) {
return Optional.empty();
}
}
}
}

6
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java

@ -37,8 +37,10 @@ import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
import org.thingsboard.server.utils.RegexUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@ -49,6 +51,8 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class DashboardImportService extends BaseEntityImportService<DashboardId, Dashboard, EntityExportData<Dashboard>> {
private static final LinkedHashSet<EntityType> HINTS = new LinkedHashSet<>(Arrays.asList(EntityType.DASHBOARD, EntityType.DEVICE, EntityType.ASSET));
private final DashboardService dashboardService;
@ -78,7 +82,7 @@ public class DashboardImportService extends BaseEntityImportService<DashboardId,
if (field.equals("id")) continue;
JsonNode oldFieldValue = entityAlias.get(field);
JsonNode newFieldValue = JacksonUtil.toJsonNode(RegexUtils.replace(oldFieldValue.toString(), RegexUtils.UUID_PATTERN, uuid -> {
return idProvider.getInternalIdByUuid(UUID.fromString(uuid))
return idProvider.getInternalIdByUuid(UUID.fromString(uuid), ctx.isFetchAllUUIDs(), HINTS)
.map(entityId -> entityId.getId().toString()).orElse(uuid);
}));
((ObjectNode) entityAlias).set(field, newFieldValue);

6
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java

@ -35,7 +35,9 @@ import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
import org.thingsboard.server.utils.RegexUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.UUID;
@ -44,6 +46,8 @@ import java.util.UUID;
@RequiredArgsConstructor
public class RuleChainImportService extends BaseEntityImportService<RuleChainId, RuleChain, RuleChainExportData> {
private static final LinkedHashSet<EntityType> HINTS = new LinkedHashSet<>(Arrays.asList(EntityType.RULE_CHAIN, EntityType.DEVICE, EntityType.ASSET));
private final RuleChainService ruleChainService;
@Override
@ -70,7 +74,7 @@ public class RuleChainImportService extends BaseEntityImportService<RuleChainId,
JsonNode ruleNodeConfig = ruleNode.getConfiguration();
String newRuleNodeConfigJson = RegexUtils.replace(ruleNodeConfig.toString(), RegexUtils.UUID_PATTERN, uuid -> {
return idProvider.getInternalIdByUuid(UUID.fromString(uuid))
return idProvider.getInternalIdByUuid(UUID.fromString(uuid), ctx.isFetchAllUUIDs(), HINTS)
.map(entityId -> entityId.getId().toString())
.orElse(uuid);
});

14
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java

@ -77,6 +77,7 @@ import org.thingsboard.server.service.sync.vc.data.ComplexEntitiesExportCtx;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
import org.thingsboard.server.service.sync.vc.data.EntityTypeExportCtx;
import org.thingsboard.server.service.sync.vc.data.ReimportTask;
import org.thingsboard.server.service.sync.vc.data.SimpleEntitiesExportCtx;
import org.thingsboard.server.service.sync.vc.repository.TbRepositorySettingsService;
@ -282,8 +283,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
for (EntityType entityType : entityTypes) {
log.debug("[{}] Loading {} entities", ctx.getTenantId(), entityType);
sw.startNew("Entities " + entityType.name());
EntityImportSettings settings = getEntityImportSettings(request, entityType);
ctx.setSettings(settings);
ctx.setSettings(getEntityImportSettings(request, entityType));
importEntities(ctx, entityType);
}
@ -337,7 +337,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
throw new LoadEntityException(entityData, e);
}
if (importResult.getUpdatedAllExternalIds() != null && !importResult.getUpdatedAllExternalIds()) {
ctx.getToReimport().put(entityData.getEntity().getExternalId(), ctx.getSettings());
ctx.getToReimport().put(entityData.getEntity().getExternalId(), new ReimportTask(entityData, ctx.getSettings()));
continue;
}
@ -351,10 +351,12 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
@SuppressWarnings({"rawtypes", "unchecked"})
private void reimport(EntitiesImportCtx ctx) {
ctx.getToReimport().forEach((externalId, importSettings) -> {
ctx.setFetchAllUUIDs(true);
ctx.getToReimport().forEach((externalId, task) -> {
try {
EntityExportData entityData = gitServiceQueue.getEntity(ctx.getTenantId(), ctx.getVersionId(), externalId).get();
ctx.setSettings(importSettings);
EntityExportData entityData = task.getData();
var settings = task.getSettings();
ctx.setSettings(settings);
EntityImportResult<?> importResult = exportImportService.importEntity(ctx, entityData);
ctx.registerResult(externalId.getEntityType(), importResult.getOldEntity() == null);

13
application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java

@ -29,6 +29,7 @@ import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@ -43,14 +44,15 @@ public class EntitiesImportCtx {
private final Map<EntityType, EntityTypeLoadResult> results = new HashMap<>();
private final Map<EntityType, Set<EntityId>> importedEntities = new HashMap<>();
private final Map<EntityId, EntityImportSettings> toReimport = new HashMap<>();
private final Map<EntityId, ReimportTask> toReimport = new HashMap<>();
private final List<ThrowingRunnable> referenceCallbacks = new ArrayList<>();
private final List<ThrowingRunnable> eventCallbacks = new ArrayList<>();
private final Map<EntityId, EntityId> externalToInternalIdMap = new HashMap<>();
private final Set<EntityId> notFoundIds = new HashSet<>();
private final Set<EntityRelation> relations = new LinkedHashSet<>();
private boolean fetchAllUUIDs = false;
private EntityImportSettings settings;
public EntitiesImportCtx(SecurityUser user, String versionId) {
@ -124,5 +126,12 @@ public class EntitiesImportCtx {
}
}
public void registerNotFound(EntityId externalId) {
notFoundIds.add(externalId);
}
public boolean isNotFound(EntityId externalId) {
return notFoundIds.contains(externalId);
}
}

14
dao/src/test/java/org/thingsboard/server/dao/util/SqlDbType.java → application/src/main/java/org/thingsboard/server/service/sync/vc/data/ReimportTask.java

@ -13,8 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.util;
package org.thingsboard.server.service.sync.vc.data;
import lombok.Data;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
@Data
public class ReimportTask {
private final EntityExportData data;
private final EntityImportSettings settings;
public enum SqlDbType {
POSTGRES, H2;
}

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

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

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

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

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

@ -0,0 +1,182 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.model.ModelConstants;
import java.util.Locale;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.thingsboard.server.service.entitiy.DefaultTbNotificationEntityService.edgeTypeByActionType;
@Slf4j
public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
@SpyBean
protected TbClusterService tbClusterService;
@SpyBean
protected AuditLogService auditLogService;
protected void testNotifyEntityAllOneTime(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testSendNotificationMsgToEdgeServiceOneTime(entityId, tenantId, actionType);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOneTime(originatorId, tenantId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityDeleteOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOneTime(entityId, tenantId);
testBroadcastEntityStateChangeEventOneTime(entityId, tenantId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityNeverMsgToEdgeServiceOneTime(HasName entity, EntityId entityId, TenantId tenantId, ActionType actionType) {
testSendNotificationMsgToEdgeServiceOneTime(entityId, tenantId, actionType);
testLogEntityActionNever(entityId, entity);
testPushMsgToRuleEngineNever(entityId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOneTime(originatorId, tenantId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOneTime(originatorId, tenantId);
testBroadcastEntityStateChangeEventOneTime(entityId, tenantId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityError(HasName entity, TenantId tenantId,
UserId userId, String userName, ActionType actionType, Exception exp,
Object... additionalInfo) {
CustomerId customer_NULL_UUID = (CustomerId) EntityIdFactory.getByTypeAndUuid(EntityType.CUSTOMER, ModelConstants.NULL_UUID);
EntityId entity_NULL_UUID = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(entity.getClass().toString()
.substring(entity.getClass().toString().lastIndexOf(".") + 1).toUpperCase(Locale.ENGLISH)),
ModelConstants.NULL_UUID);
testNotificationMsgToEdgeServiceNever(entity_NULL_UUID);
if (additionalInfo.length > 0) {
Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId),
Mockito.eq(customer_NULL_UUID), Mockito.eq(userId), Mockito.eq(userName),
Mockito.eq(entity_NULL_UUID), Mockito.any(entity.getClass()), Mockito.eq(actionType),
Mockito.argThat(argument ->
argument.getMessage().equals(exp.getMessage())), Mockito.eq(additionalInfo));
} else {
Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId),
Mockito.eq(customer_NULL_UUID), Mockito.eq(userId), Mockito.eq(userName),
Mockito.eq(entity_NULL_UUID), Mockito.any(entity.getClass()), Mockito.eq(actionType),
Mockito.argThat(argument ->
argument.getMessage().equals(exp.getMessage())));
}
testPushMsgToRuleEngineNever(entity_NULL_UUID);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityNever(EntityId entityId, HasName entity) {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionNever(entityId, entity);
testPushMsgToRuleEngineNever(entityId);
Mockito.reset(tbClusterService, auditLogService);
}
private void testNotificationMsgToEdgeServiceNever(EntityId entityId) {
Mockito.verify(tbClusterService, never()).sendNotificationMsgToEdgeService(Mockito.any(),
Mockito.any(), Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any(), Mockito.any());
}
private void testLogEntityActionNever(EntityId entityId, HasName entity) {
Mockito.verify(auditLogService, never()).logEntityAction(Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(entityId.getClass()), Mockito.any(entity.getClass()),
Mockito.any(), Mockito.any());
}
private void testPushMsgToRuleEngineNever(EntityId entityId) {
Mockito.verify(tbClusterService, never()).pushMsgToRuleEngine(Mockito.any(),
Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any());
}
private void testLogEntityActionOneTime(HasName entity, EntityId originatorId, TenantId tenantId, CustomerId customerId,
UserId userId, String userName, ActionType actionType, Object... additionalInfo) {
if (additionalInfo.length == 0) {
Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), Mockito.eq(customerId),
Mockito.eq(userId), Mockito.eq(userName), Mockito.eq(originatorId),
Mockito.eq(entity), Mockito.eq(actionType), Mockito.isNull());
} else {
String additionalInfoStr = extractParameter(String.class, 0, additionalInfo);
Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), Mockito.eq(customerId),
Mockito.eq(userId), Mockito.eq(userName), Mockito.eq(originatorId),
Mockito.eq(entity), Mockito.eq(actionType), Mockito.isNull(), Mockito.eq(additionalInfoStr));
}
}
private void testPushMsgToRuleEngineOneTime(EntityId originatorId, TenantId tenantId) {
Mockito.verify(tbClusterService, times(1)).pushMsgToRuleEngine(Mockito.eq(tenantId),
Mockito.eq(originatorId), Mockito.any(TbMsg.class), Mockito.isNull());
}
private void testSendNotificationMsgToEdgeServiceOneTime(EntityId entityId, TenantId tenantId, ActionType actionType) {
Mockito.verify(tbClusterService, times(1)).sendNotificationMsgToEdgeService(Mockito.eq(tenantId),
Mockito.isNull(), Mockito.eq(entityId), Mockito.isNull(), Mockito.isNull(),
Mockito.eq(edgeTypeByActionType(actionType)));
}
private void testBroadcastEntityStateChangeEventOneTime(EntityId entityId, TenantId tenantId) {
Mockito.verify(tbClusterService, times(1)).broadcastEntityStateChangeEvent(Mockito.eq(tenantId),
Mockito.any(entityId.getClass()), Mockito.any(ComponentLifecycleEvent.class));
}
private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) {
T result = null;
if (additionalInfo != null && additionalInfo.length > index) {
Object paramObject = additionalInfo[index];
if (clazz.isInstance(paramObject)) {
result = clazz.cast(paramObject);
}
}
return result;
}
}

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

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

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

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

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

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

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

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

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

@ -1,26 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnProperty(prefix = "spring.jpa", value = "database-platform", havingValue = "org.hibernate.dialect.HSQLDialect")
public @interface HsqlDao {
}

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

@ -1,26 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnProperty(prefix = "spring.jpa", value = "database-platform", havingValue = "org.hibernate.dialect.PostgreSQLDialect")
public @interface PsqlDao {
}

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -901,7 +901,7 @@ public class DefaultTransportService implements TransportService {
if (EntityType.DEVICE_PROFILE.equals(entityType)) {
DeviceProfile deviceProfile = deviceProfileCache.put(msg.getData());
if (deviceProfile != null) {
log.info("On device profile update: {}", deviceProfile);
log.debug("On device profile update: {}", deviceProfile);
onProfileUpdate(deviceProfile);
}
} else if (EntityType.TENANT_PROFILE.equals(entityType)) {

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

@ -1,37 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlTsLatestDao;
import org.thingsboard.server.dao.util.TbAutoConfiguration;
@Configuration
@TbAutoConfiguration
@ComponentScan({"org.thingsboard.server.dao.sqlts.hsql"})
@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.insert.latest.hsql", "org.thingsboard.server.dao.sqlts.latest"})
@EntityScan({"org.thingsboard.server.dao.model.sqlts.latest"})
@EnableTransactionManagement
@SqlTsLatestDao
@HsqlDao
public class HsqlTsLatestDaoConfig {
}

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

@ -1,37 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
import org.thingsboard.server.dao.util.TbAutoConfiguration;
@Configuration
@TbAutoConfiguration
@ComponentScan({"org.thingsboard.server.dao.sqlts.psql", "org.thingsboard.server.dao.sqlts.insert.psql"})
@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.ts", "org.thingsboard.server.dao.sqlts.insert.psql"})
@EntityScan({"org.thingsboard.server.dao.model.sqlts.ts"})
@EnableTransactionManagement
@PsqlDao
@SqlTsDao
public class PsqlTsDaoConfig {
}

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

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

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

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

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

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

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

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

48
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java

@ -25,8 +25,6 @@ import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.thingsboard.server.cache.TbTransactionalCache;
@ -41,14 +39,12 @@ import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.sql.JpaExecutorService;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -188,26 +184,28 @@ public class BaseRelationService implements RelationService {
public void deleteEntityRelations(TenantId tenantId, EntityId entityId) {
log.trace("Executing deleteEntityRelations [{}]", entityId);
validate(entityId);
List<EntityRelation> inboundRelations = new ArrayList<>();
for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
inboundRelations.addAll(relationDao.findAllByTo(tenantId, entityId, typeGroup));
}
List<EntityRelation> outboundRelations = new ArrayList<>();
for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
outboundRelations.addAll(relationDao.findAllByFrom(tenantId, entityId, typeGroup));
}
List<EntityRelation> inboundRelations = new ArrayList<>(relationDao.findAllByTo(tenantId, entityId));
List<EntityRelation> outboundRelations = new ArrayList<>(relationDao.findAllByFrom(tenantId, entityId));
for (EntityRelation relation : inboundRelations) {
delete(tenantId, relation, true);
}
if (!inboundRelations.isEmpty()) {
try {
relationDao.deleteInboundRelations(tenantId, entityId);
} catch (ConcurrencyFailureException e) {
log.debug("Concurrency exception while deleting relations [{}]", inboundRelations, e);
}
for (EntityRelation relation : outboundRelations) {
delete(tenantId, relation, false);
for (EntityRelation relation : inboundRelations) {
eventPublisher.publishEvent(EntityRelationEvent.from(relation));
}
}
relationDao.deleteOutboundRelations(tenantId, entityId);
if (!outboundRelations.isEmpty()) {
relationDao.deleteOutboundRelations(tenantId, entityId);
for (EntityRelation relation : outboundRelations) {
eventPublisher.publishEvent(EntityRelationEvent.from(relation));
}
}
}
@Override
@ -269,18 +267,6 @@ public class BaseRelationService implements RelationService {
}
}
boolean delete(TenantId tenantId, EntityRelation relation, boolean deleteFromDb) {
eventPublisher.publishEvent(EntityRelationEvent.from(relation));
if (deleteFromDb) {
try {
return relationDao.deleteRelation(tenantId, relation);
} catch (ConcurrencyFailureException e) {
log.debug("Concurrency exception while deleting relations [{}]", relation, e);
}
}
return false;
}
@Override
public List<EntityRelation> findByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup) {
validate(from);

8
dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java

@ -32,10 +32,14 @@ public interface RelationDao {
List<EntityRelation> findAllByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup);
List<EntityRelation> findAllByFrom(TenantId tenantId, EntityId from);
List<EntityRelation> findAllByFromAndType(TenantId tenantId, EntityId from, String relationType, RelationTypeGroup typeGroup);
List<EntityRelation> findAllByTo(TenantId tenantId, EntityId to, RelationTypeGroup typeGroup);
List<EntityRelation> findAllByTo(TenantId tenantId, EntityId to);
List<EntityRelation> findAllByToAndType(TenantId tenantId, EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
@ -56,7 +60,9 @@ public interface RelationDao {
ListenableFuture<Boolean> deleteRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
boolean deleteOutboundRelations(TenantId tenantId, EntityId entity);
void deleteOutboundRelations(TenantId tenantId, EntityId entity);
void deleteInboundRelations(TenantId tenantId, EntityId entity);
ListenableFuture<Boolean> deleteOutboundRelationsAsync(TenantId tenantId, EntityId entity);

27
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -141,6 +141,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
List<RuleNode> nodes = ruleChainMetaData.getNodes();
List<RuleNode> toAddOrUpdate = new ArrayList<>();
List<RuleNode> toDelete = new ArrayList<>();
List<EntityRelation> relations = new ArrayList<>();
Map<RuleNodeId, Integer> ruleNodeIndexMap = new HashMap<>();
if (nodes != null) {
@ -171,15 +172,15 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
for (RuleNode node : toAddOrUpdate) {
node.setRuleChainId(ruleChain.getId());
RuleNode savedNode = ruleNodeDao.save(tenantId, node);
createRelation(tenantId, new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(),
relations.add(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
int index = nodes.indexOf(node);
nodes.set(index, savedNode);
ruleNodeIndexMap.put(savedNode.getId(), index);
}
}
for (RuleNode node : toDelete) {
deleteRuleNode(tenantId, node.getId());
if (!toDelete.isEmpty()) {
deleteRuleNodes(tenantId, toDelete);
}
RuleNodeId firstRuleNodeId = null;
if (nodes != null) {
@ -196,7 +197,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
EntityId from = nodes.get(nodeConnection.getFromIndex()).getId();
EntityId to = nodes.get(nodeConnection.getToIndex()).getId();
String type = nodeConnection.getType();
createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE));
relations.add(new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE));
}
}
if (ruleChainMetaData.getRuleChainConnections() != null) {
@ -222,7 +223,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
sourceRuleChainToRuleNode.setTo(targetNode.getId());
sourceRuleChainToRuleNode.setType(EntityRelation.CONTAINS_TYPE);
sourceRuleChainToRuleNode.setTypeGroup(RelationTypeGroup.RULE_CHAIN);
relationService.saveRelation(tenantId, sourceRuleChainToRuleNode);
relations.add(sourceRuleChainToRuleNode);
EntityRelation sourceRuleNodeToTargetRuleNode = new EntityRelation();
EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId();
@ -230,11 +231,15 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
sourceRuleNodeToTargetRuleNode.setTo(targetNode.getId());
sourceRuleNodeToTargetRuleNode.setType(nodeToRuleChainConnection.getType());
sourceRuleNodeToTargetRuleNode.setTypeGroup(RelationTypeGroup.RULE_NODE);
relationService.saveRelation(tenantId, sourceRuleNodeToTargetRuleNode);
}
relations.add(sourceRuleNodeToTargetRuleNode);
}
}
}
if (!relations.isEmpty()) {
relationService.saveRelations(tenantId, relations);
}
return RuleChainUpdateResult.successful(updatedRuleNodes);
}
@ -710,6 +715,14 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
deleteRuleNodes(tenantId, ruleChainId);
}
private void deleteRuleNodes(TenantId tenantId, List<RuleNode> ruleNodes) {
List<RuleNodeId> ruleNodeIds = ruleNodes.stream().map(RuleNode::getId).collect(Collectors.toList());
for (var node : ruleNodes) {
deleteEntityRelations(tenantId, node.getId());
}
ruleNodeDao.deleteByIdIn(ruleNodeIds);
}
@Override
public void deleteRuleNodes(TenantId tenantId, RuleChainId ruleChainId) {
List<EntityRelation> nodeRelations = getRuleChainToNodeRelations(tenantId, ruleChainId);

3
dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.rule;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -31,4 +32,6 @@ public interface RuleNodeDao extends Dao<RuleNode> {
List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String search);
PageData<RuleNode> findAllRuleNodesByType(String type, PageLink pageLink);
void deleteByIdIn(List<RuleNodeId> ruleNodeIds);
}

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

@ -1,76 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.attributes;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.AttributeKvEntity;
import org.thingsboard.server.dao.util.HsqlDao;
import java.sql.Types;
import java.util.List;
@HsqlDao
@Repository
@Transactional
public class HsqlAttributesInsertRepository extends AttributeKvInsertRepository {
private static final String INSERT_OR_UPDATE =
"MERGE INTO attribute_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
"A (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, json_v, last_update_ts) " +
"ON (attribute_kv.entity_type=A.entity_type " +
"AND attribute_kv.entity_id=A.entity_id " +
"AND attribute_kv.attribute_type=A.attribute_type " +
"AND attribute_kv.attribute_key=A.attribute_key) " +
"WHEN MATCHED THEN UPDATE SET attribute_kv.str_v = A.str_v, attribute_kv.long_v = A.long_v, attribute_kv.dbl_v = A.dbl_v, attribute_kv.bool_v = A.bool_v, attribute_kv.json_v = A.json_v, attribute_kv.last_update_ts = A.last_update_ts " +
"WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, json_v, last_update_ts) " +
"VALUES (A.entity_type, A.entity_id, A.attribute_type, A.attribute_key, A.str_v, A.long_v, A.dbl_v, A.bool_v, A.json_v, A.last_update_ts)";
@Override
protected void saveOrUpdate(List<AttributeKvEntity> entities) {
entities.forEach(entity -> {
jdbcTemplate.update(INSERT_OR_UPDATE, ps -> {
ps.setString(1, entity.getId().getEntityType().name());
ps.setObject(2, entity.getId().getEntityId());
ps.setString(3, entity.getId().getAttributeType());
ps.setString(4, entity.getId().getAttributeKey());
ps.setString(5, entity.getStrValue());
if (entity.getLongValue() != null) {
ps.setLong(6, entity.getLongValue());
} else {
ps.setNull(6, Types.BIGINT);
}
if (entity.getDoubleValue() != null) {
ps.setDouble(7, entity.getDoubleValue());
} else {
ps.setNull(7, Types.DOUBLE);
}
if (entity.getBooleanValue() != null) {
ps.setBoolean(8, entity.getBooleanValue());
} else {
ps.setNull(8, Types.BOOLEAN);
}
ps.setString(9, entity.getJsonValue());
ps.setLong(10, entity.getLastUpdateTs());
});
});
}
}

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

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

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

@ -1,65 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.component;
import org.springframework.stereotype.Repository;
import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity;
import org.thingsboard.server.dao.util.HsqlDao;
import javax.persistence.Query;
@HsqlDao
@Repository
public class HsqlComponentDescriptorInsertRepository extends AbstractComponentDescriptorInsertRepository {
private static final String P_KEY_CONFLICT_STATEMENT = "(component_descriptor.id=UUID(I.id))";
private static final String UNQ_KEY_CONFLICT_STATEMENT = "(component_descriptor.clazz=I.clazz)";
private static final String INSERT_OR_UPDATE_ON_P_KEY_CONFLICT = getInsertString(P_KEY_CONFLICT_STATEMENT);
private static final String INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT = getInsertString(UNQ_KEY_CONFLICT_STATEMENT);
@Override
public ComponentDescriptorEntity saveOrUpdate(ComponentDescriptorEntity entity) {
return saveAndGet(entity, INSERT_OR_UPDATE_ON_P_KEY_CONFLICT, INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT);
}
@Override
protected Query getQuery(ComponentDescriptorEntity entity, String query) {
return entityManager.createNativeQuery(query, ComponentDescriptorEntity.class)
.setParameter("id", entity.getUuid().toString())
.setParameter("created_time", entity.getCreatedTime())
.setParameter("actions", entity.getActions())
.setParameter("clazz", entity.getClazz())
.setParameter("configuration_descriptor", entity.getConfigurationDescriptor().toString())
.setParameter("name", entity.getName())
.setParameter("scope", entity.getScope().name())
.setParameter("search_text", entity.getSearchText())
.setParameter("type", entity.getType().name());
}
@Override
protected ComponentDescriptorEntity doProcessSaveOrUpdate(ComponentDescriptorEntity entity, String query) {
getQuery(entity, query).executeUpdate();
return entityManager.find(ComponentDescriptorEntity.class, entity.getUuid());
}
private static String getInsertString(String conflictStatement) {
return "MERGE INTO component_descriptor USING (VALUES :id, :created_time, :actions, :clazz, :configuration_descriptor, :name, :scope, :search_text, :type) I (id, created_time, actions, clazz, configuration_descriptor, name, scope, search_text, type) ON "
+ conflictStatement
+ " WHEN MATCHED THEN UPDATE SET component_descriptor.id = UUID(I.id), component_descriptor.actions = I.actions, component_descriptor.clazz = I.clazz, component_descriptor.configuration_descriptor = I.configuration_descriptor, component_descriptor.name = I.name, component_descriptor.scope = I.scope, component_descriptor.search_text = I.search_text, component_descriptor.type = I.type" +
" WHEN NOT MATCHED THEN INSERT (id, created_time, actions, clazz, configuration_descriptor, name, scope, search_text, type) VALUES (UUID(I.id), I.created_time, I.actions, I.clazz, I.configuration_descriptor, I.name, I.scope, I.search_text, I.type)";
}
}

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

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

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

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

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

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

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

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

46
dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java

@ -33,6 +33,8 @@ import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@ -44,6 +46,12 @@ import java.util.stream.Collectors;
@Component
public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService implements RelationDao {
private static final List<String> ALL_TYPE_GROUP_NAMES = new ArrayList<>();
static {
Arrays.stream(RelationTypeGroup.values()).map(RelationTypeGroup::name).forEach(ALL_TYPE_GROUP_NAMES::add);
}
@Autowired
private RelationRepository relationRepository;
@ -59,6 +67,15 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
typeGroup.name()));
}
@Override
public List<EntityRelation> findAllByFrom(TenantId tenantId, EntityId from) {
return DaoUtil.convertDataList(
relationRepository.findAllByFromIdAndFromTypeAndRelationTypeGroupIn(
from.getId(),
from.getEntityType().name(),
ALL_TYPE_GROUP_NAMES));
}
@Override
public List<EntityRelation> findAllByFromAndType(TenantId tenantId, EntityId from, String relationType, RelationTypeGroup typeGroup) {
return DaoUtil.convertDataList(
@ -78,6 +95,15 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
typeGroup.name()));
}
@Override
public List<EntityRelation> findAllByTo(TenantId tenantId, EntityId to) {
return DaoUtil.convertDataList(
relationRepository.findAllByToIdAndToTypeAndRelationTypeGroupIn(
to.getId(),
to.getEntityType().name(),
ALL_TYPE_GROUP_NAMES));
}
@Override
public List<EntityRelation> findAllByToAndType(TenantId tenantId, EntityId to, String relationType, RelationTypeGroup typeGroup) {
return DaoUtil.convertDataList(
@ -164,19 +190,21 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
}
@Override
public boolean deleteOutboundRelations(TenantId tenantId, EntityId entity) {
boolean relationExistsBeforeDelete = false;
public void deleteOutboundRelations(TenantId tenantId, EntityId entity) {
try {
relationExistsBeforeDelete = relationRepository
.findAllByFromIdAndFromType(entity.getId(), entity.getEntityType().name())
.size() > 0;
if (relationExistsBeforeDelete) {
relationRepository.deleteByFromIdAndFromType(entity.getId(), entity.getEntityType().name());
}
relationRepository.deleteByFromIdAndFromType(entity.getId(), entity.getEntityType().name());
} catch (ConcurrencyFailureException e) {
log.debug("Concurrency exception while deleting relations [{}]", entity, e);
}
}
@Override
public void deleteInboundRelations(TenantId tenantId, EntityId entity) {
try {
relationRepository.deleteByToIdAndToTypeAndRelationTypeGroupIn(entity.getId(), entity.getEntityType().name(), ALL_TYPE_GROUP_NAMES);
} catch (ConcurrencyFailureException e) {
log.debug("Concurrency exception while deleting relations [{}]", entity, e);
}
return relationExistsBeforeDelete;
}
@Override

11
dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java

@ -35,6 +35,10 @@ public interface RelationRepository
String fromType,
String relationTypeGroup);
List<RelationEntity> findAllByFromIdAndFromTypeAndRelationTypeGroupIn(UUID fromId,
String fromType,
List<String> relationTypeGroups);
List<RelationEntity> findAllByFromIdAndFromTypeAndRelationTypeAndRelationTypeGroup(UUID fromId,
String fromType,
String relationType,
@ -44,6 +48,10 @@ public interface RelationRepository
String toType,
String relationTypeGroup);
List<RelationEntity> findAllByToIdAndToTypeAndRelationTypeGroupIn(UUID toId,
String toType,
List<String> relationTypeGroups);
List<RelationEntity> findAllByToIdAndToTypeAndRelationTypeAndRelationTypeGroup(UUID toId,
String toType,
String relationType,
@ -66,4 +74,7 @@ public interface RelationRepository
@Transactional
void deleteByFromIdAndFromType(UUID fromId, String fromType);
@Transactional
void deleteByToIdAndToTypeAndRelationTypeGroupIn(UUID fromId, String fromType, List<String> relationTypeGroups);
}

60
dao/src/main/java/org/thingsboard/server/dao/sql/relation/SqlRelationInsertRepository.java

@ -19,23 +19,17 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.util.PsqlDao;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
@PsqlDao
@Repository
@Transactional
public class SqlRelationInsertRepository implements RelationInsertRepository {
@ -55,9 +49,6 @@ public class SqlRelationInsertRepository implements RelationInsertRepository {
@Autowired
protected JdbcTemplate jdbcTemplate;
@Autowired
private TransactionTemplate transactionTemplate;
protected Query getQuery(RelationEntity entity, String query) {
Query nativeQuery = entityManager.createNativeQuery(query, RelationEntity.class);
if (entity.getAdditionalInfo() == null) {
@ -81,36 +72,31 @@ public class SqlRelationInsertRepository implements RelationInsertRepository {
@Override
public void saveOrUpdate(List<RelationEntity> entities) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
jdbcTemplate.batchUpdate(INSERT_ON_CONFLICT_DO_UPDATE_JDBC, new BatchPreparedStatementSetter() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.batchUpdate(INSERT_ON_CONFLICT_DO_UPDATE_JDBC, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
RelationEntity relation = entities.get(i);
ps.setObject(1, relation.getFromId());
ps.setString(2, relation.getFromType());
ps.setObject(3, relation.getToId());
ps.setString(4, relation.getToType());
ps.setString(5, relation.getRelationTypeGroup());
ps.setString(6, relation.getRelationType());
if (relation.getAdditionalInfo() == null) {
ps.setString(7, null);
ps.setString(8, null);
} else {
String json = JacksonUtil.toString(relation.getAdditionalInfo());
ps.setString(7, json);
ps.setString(8, json);
}
}
public void setValues(PreparedStatement ps, int i) throws SQLException {
RelationEntity relation = entities.get(i);
ps.setObject(1, relation.getFromId());
ps.setString(2, relation.getFromType());
ps.setObject(3, relation.getToId());
ps.setString(4, relation.getToType());
ps.setString(5, relation.getRelationTypeGroup());
ps.setString(6, relation.getRelationType());
if (relation.getAdditionalInfo() == null) {
ps.setString(7, null);
ps.setString(8, null);
} else {
String json = JacksonUtil.toString(relation.getAdditionalInfo());
ps.setString(7, json);
ps.setString(8, json);
}
}
@Override
public int getBatchSize() {
return entities.size();
}
});
@Override
public int getBatchSize() {
return entities.size();
}
});
}

7
dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java

@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -32,6 +33,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
@Slf4j
@Component
@ -64,6 +66,11 @@ public class JpaRuleNodeDao extends JpaAbstractSearchTextDao<RuleNodeEntity, Rul
DaoUtil.toPageable(pageLink)));
}
@Override
public void deleteByIdIn(List<RuleNodeId> ruleNodeIds) {
ruleNodeRepository.deleteAllById(ruleNodeIds.stream().map(RuleNodeId::getId).collect(Collectors.toList()));
}
@Override
public EntityType getEntityType() {
return EntityType.RULE_NODE;

2
dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java

@ -39,4 +39,6 @@ public interface RuleNodeRepository extends JpaRepository<RuleNodeEntity, UUID>
@Param("searchText") String searchText,
Pageable pageable);
void deleteByIdIn(List<UUID> ids);
}

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

@ -1,62 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sqlts.hsql;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.sqlts.AbstractChunkedAggregationTimeseriesDao;
import org.thingsboard.server.dao.timeseries.TimeseriesDao;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
@Component
@Slf4j
@SqlTsDao
@HsqlDao
public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao implements TimeseriesDao {
@Override
public ListenableFuture<Integer> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) {
int dataPointDays = getDataPointDays(tsKvEntry, computeTtl(ttl));
String strKey = tsKvEntry.getKey();
Integer keyId = getOrSaveKeyId(strKey);
TsKvEntity entity = new TsKvEntity();
entity.setEntityId(entityId.getId());
entity.setTs(tsKvEntry.getTs());
entity.setKey(keyId);
entity.setStrValue(tsKvEntry.getStrValue().orElse(null));
entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null));
entity.setLongValue(tsKvEntry.getLongValue().orElse(null));
entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null));
entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null));
log.trace("Saving entity: {}", entity);
return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor());
}
@Override
public void cleanup(long systemTtl) {
}
}

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

@ -1,87 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sqlts.insert.hsql;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository;
import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
@SqlTsDao
@HsqlDao
@Repository
@Transactional
public class HsqlInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository<TsKvEntity> {
private static final String INSERT_OR_UPDATE =
"MERGE INTO ts_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " +
"T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
"ON (ts_kv.entity_id=T.entity_id " +
"AND ts_kv.key=T.key " +
"AND ts_kv.ts=T.ts) " +
"WHEN MATCHED THEN UPDATE SET ts_kv.bool_v = T.bool_v, ts_kv.str_v = T.str_v, ts_kv.long_v = T.long_v, ts_kv.dbl_v = T.dbl_v ,ts_kv.json_v = T.json_v " +
"WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
"VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v, T.json_v);";
@Override
public void saveOrUpdate(List<TsKvEntity> entities) {
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
TsKvEntity tsKvEntity = entities.get(i);
ps.setObject(1, tsKvEntity.getEntityId());
ps.setInt(2, tsKvEntity.getKey());
ps.setLong(3, tsKvEntity.getTs());
if (tsKvEntity.getBooleanValue() != null) {
ps.setBoolean(4, tsKvEntity.getBooleanValue());
} else {
ps.setNull(4, Types.BOOLEAN);
}
ps.setString(5, tsKvEntity.getStrValue());
if (tsKvEntity.getLongValue() != null) {
ps.setLong(6, tsKvEntity.getLongValue());
} else {
ps.setNull(6, Types.BIGINT);
}
if (tsKvEntity.getDoubleValue() != null) {
ps.setDouble(7, tsKvEntity.getDoubleValue());
} else {
ps.setNull(7, Types.DOUBLE);
}
ps.setString(8, tsKvEntity.getJsonValue());
}
@Override
public int getBatchSize() {
return entities.size();
}
});
}
}

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

@ -1,85 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sqlts.insert.latest.hsql;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity;
import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository;
import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlTsLatestDao;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
@SqlTsLatestDao
@HsqlDao
@Repository
@Transactional
public class HsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository {
private static final String INSERT_OR_UPDATE =
"MERGE INTO ts_kv_latest USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " +
"T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
"ON (ts_kv_latest.entity_id=T.entity_id " +
"AND ts_kv_latest.key=T.key) " +
"WHEN MATCHED THEN UPDATE SET ts_kv_latest.ts = T.ts, ts_kv_latest.bool_v = T.bool_v, ts_kv_latest.str_v = T.str_v, ts_kv_latest.long_v = T.long_v, ts_kv_latest.dbl_v = T.dbl_v, ts_kv_latest.json_v = T.json_v " +
"WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
"VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v, T.json_v);";
@Override
public void saveOrUpdate(List<TsKvLatestEntity> entities) {
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setObject(1, entities.get(i).getEntityId());
ps.setInt(2, entities.get(i).getKey());
ps.setLong(3, entities.get(i).getTs());
if (entities.get(i).getBooleanValue() != null) {
ps.setBoolean(4, entities.get(i).getBooleanValue());
} else {
ps.setNull(4, Types.BOOLEAN);
}
ps.setString(5, entities.get(i).getStrValue());
if (entities.get(i).getLongValue() != null) {
ps.setLong(6, entities.get(i).getLongValue());
} else {
ps.setNull(6, Types.BIGINT);
}
if (entities.get(i).getDoubleValue() != null) {
ps.setDouble(7, entities.get(i).getDoubleValue());
} else {
ps.setNull(7, Types.DOUBLE);
}
ps.setString(8, entities.get(i).getJsonValue());
}
@Override
public int getBatchSize() {
return entities.size();
}
});
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,40 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.util;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.DriverManager;
public class DaoTestUtil {
private static final String POSTGRES_DRIVER_CLASS = "org.postgresql.Driver";
private static final String H2_DRIVER_CLASS = "org.hsqldb.jdbc.JDBCDriver";
public static SqlDbType getSqlDbType(JdbcTemplate template){
try {
String driverName = DriverManager.getDriver(template.getDataSource().getConnection().getMetaData().getURL()).getClass().getName();
if (POSTGRES_DRIVER_CLASS.equals(driverName)) {
return SqlDbType.POSTGRES;
} else if (H2_DRIVER_CLASS.equals(driverName)) {
return SqlDbType.H2;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

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

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

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

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

1
docker/tb-node.hybrid.env

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

1
docker/tb-node.postgres.env

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

35
msa/js-executor/package.json

@ -12,20 +12,19 @@
"start-prod": "NODE_ENV=production nodemon server.js"
},
"dependencies": {
"@azure/service-bus": "^1.1.9",
"@google-cloud/pubsub": "^2.5.0",
"amqplib": "^0.6.0",
"aws-sdk": "^2.741.0",
"azure-sb": "^0.11.1",
"config": "^3.3.1",
"express": "^4.17.1",
"js-yaml": "^3.14.0",
"kafkajs": "^1.15.0",
"long": "^4.0.0",
"@azure/service-bus": "^7.5.1",
"@google-cloud/pubsub": "^3.0.1",
"amqplib": "^0.10.0",
"aws-sdk": "^2.1152.0",
"config": "^3.3.7",
"express": "^4.18.1",
"js-yaml": "^4.1.0",
"kafkajs": "^2.0.2",
"long": "^5.2.0",
"uuid-parse": "^1.1.0",
"uuid-random": "^1.3.2",
"winston": "^3.3.3",
"winston-daily-rotate-file": "^4.5.0"
"winston": "^3.7.2",
"winston-daily-rotate-file": "^4.7.1"
},
"nyc": {
"exclude": [
@ -36,13 +35,19 @@
]
},
"devDependencies": {
"fs-extra": "^10.0.0",
"nodemon": "^2.0.12",
"pkg": "^5.3.1"
"fs-extra": "^10.1.0",
"nodemon": "^2.0.16",
"pkg": "^5.7.0"
},
"pkg": {
"assets": [
"node_modules/config/**/*.*"
]
},
"resolutions": {
"ansi-regex": "^5.0.1",
"color-string": "^1.5.5",
"minimist": "^1.2.6",
"node-fetch": "^2.6.7"
}
}

4
msa/js-executor/pom.xml

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

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

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

2317
msa/js-executor/yarn.lock

File diff suppressed because it is too large

1
msa/tb/docker-cassandra/Dockerfile

@ -54,7 +54,6 @@ ENV DATABASE_TS_TYPE=cassandra
ENV PGDATA=/data/db
ENV CASSANDRA_DATA=/data/cassandra
ENV SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect
ENV SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver
ENV SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/thingsboard
ENV SPRING_DATASOURCE_USERNAME=${pkg.user}

1
msa/tb/docker-postgres/Dockerfile

@ -50,7 +50,6 @@ ENV DATABASE_TS_TYPE=sql
ENV PGDATA=/data/db
ENV PATH=$PATH:/usr/lib/postgresql/$PG_MAJOR/bin
ENV SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect
ENV SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver
ENV SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/thingsboard
ENV SPRING_DATASOURCE_USERNAME=${pkg.user}

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

132
msa/tb/pom.xml

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

21
msa/web-ui/package.json

@ -13,14 +13,14 @@
},
"dependencies": {
"compression": "^1.7.4",
"config": "^3.3.1",
"config": "^3.3.7",
"connect-history-api-fallback": "^1.6.0",
"express": "^4.17.1",
"express": "^4.18.1",
"http": "0.0.0",
"http-proxy": "^1.18.1",
"js-yaml": "^3.14.0",
"winston": "^3.3.3",
"winston-daily-rotate-file": "^4.5.0"
"js-yaml": "^4.1.0",
"winston": "^3.7.2",
"winston-daily-rotate-file": "^4.7.1"
},
"nyc": {
"exclude": [
@ -31,13 +31,18 @@
]
},
"devDependencies": {
"fs-extra": "^10.0.0",
"nodemon": "^2.0.12",
"pkg": "^5.3.1"
"fs-extra": "^10.1.0",
"nodemon": "^2.0.16",
"pkg": "^5.7.0"
},
"pkg": {
"assets": [
"node_modules/config/**/*.*"
]
},
"resolutions": {
"color-string": "^1.5.5",
"follow-redirects": "^1.14.8",
"minimist": "^1.2.6"
}
}

4
msa/web-ui/pom.xml

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

966
msa/web-ui/yarn.lock

File diff suppressed because it is too large

6
pom.xml

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

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

@ -40,12 +40,12 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.CustomTimeSchedule;
import org.thingsboard.server.common.data.device.profile.CustomTimeScheduleItem;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.CustomTimeSchedule;
import org.thingsboard.server.common.data.device.profile.CustomTimeScheduleItem;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -71,10 +71,10 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Optional;
import java.util.TreeMap;
import java.util.UUID;

1
ui-ngx/package.json

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

16
ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.html

@ -30,17 +30,11 @@
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content tb-toast toastTarget="sendTestSmsDialogContent">
<fieldset [disabled]="(isLoading$ | async)">
<mat-form-field class="mat-block">
<mat-label translate>admin.number-to</mat-label>
<input type="tel" required [pattern]="phoneNumberPattern" matInput formControlName="numberTo">
<mat-error *ngIf="sendTestSmsFormGroup.get('numberTo').hasError('required')">
{{ 'admin.number-to-required' | translate }}
</mat-error>
<mat-error *ngIf="sendTestSmsFormGroup.get('numberTo').hasError('pattern')">
{{ 'admin.phone-number-pattern' | translate }}
</mat-error>
<mat-hint innerHTML="{{ 'admin.phone-number-hint' | translate }}"></mat-hint>
</mat-form-field>
<tb-phone-input required
formControlName="numberTo"
[enableFlagsSelect]="false"
[label]="'admin.number-to'">
</tb-phone-input>
<mat-form-field class="mat-block">
<mat-label translate>admin.sms-message</mat-label>
<textarea required matInput rows="3" [maxLength]="1600" formControlName="message"></textarea>

21
ui-ngx/src/app/modules/home/pages/security/authentication-dialog/sms-auth-dialog.component.html

@ -36,21 +36,12 @@
<ng-template matStepLabel>{{ 'security.2fa.dialog.sms-step-label' | translate }}</ng-template>
<form [formGroup]="smsConfigForm" (ngSubmit)="nextStep()">
<p class="mat-body step-description input" translate>security.2fa.dialog.sms-step-description</p>
<div fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="8px">
<mat-form-field fxFlex class="mat-block input-container" floatLabel="always" hideRequiredMarker>
<mat-label></mat-label>
<input type="tel" required
[pattern]="phoneNumberPattern"
matInput formControlName="phone"
placeholder="{{ 'security.2fa.dialog.sms-step-label' | translate }}">
<mat-error *ngIf="smsConfigForm.get('phone').hasError('required')">
{{ 'admin.number-to-required' | translate }}
</mat-error>
<mat-error *ngIf="smsConfigForm.get('phone').hasError('pattern')">
{{ 'admin.phone-number-pattern' | translate }}
</mat-error>
<mat-hint innerHTML="{{ 'admin.phone-number-hint' | translate }}"></mat-hint>
</mat-form-field>
<div fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="15px">
<tb-phone-input fxFlex
formControlName="phone"
[floatLabel]="'never'"
[placeholder]="'security.2fa.dialog.sms-step-label'">
</tb-phone-input>
<button mat-raised-button
type="submit"
color="primary"

12
ui-ngx/src/app/shared/components/contact.component.html

@ -55,13 +55,11 @@
<mat-label translate>contact.address2</mat-label>
<input matInput formControlName="address2">
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>contact.phone</mat-label>
<input matInput formControlName="phone">
<mat-error *ngIf="parentForm.get('phone').hasError('maxlength')">
{{ 'contact.phone-max-length' | translate }}
</mat-error>
</mat-form-field>
<tb-phone-input [required]="false"
[label]="'contact.phone'"
[enableFlagsSelect]="false"
formControlName="phone">
</tb-phone-input>
<mat-form-field class="mat-block">
<mat-label translate>contact.email</mat-label>
<input matInput formControlName="email">

50
ui-ngx/src/app/shared/components/phone-input.component.html

@ -0,0 +1,50 @@
<!--
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.
-->
<form [formGroup]="phoneFormGroup">
<div class="phone-input-container">
<div class="flags-select-container" *ngIf="enableFlagsSelect">
<span class="flag-container">{{ flagIcon }}</span>
<mat-select class="country-select" formControlName="country">
<mat-option *ngFor="let country of allCountries" [value]="country.iso2">
<span style="font-size: 20px;">{{country.flag}}</span>
<span>{{' ' + country.name + ' +' + country.dialCode }}</span>
</mat-option>
</mat-select>
</div>
<mat-form-field class="phone-input" [appearance]="appearance" [floatLabel]="floatLabel">
<mat-label>{{ label | translate }}</mat-label>
<input
formControlName="phoneNumber"
type="tel"
matInput
placeholder="{{ placeholder | translate }}"
[pattern]="phoneNumberPattern"
(focus)="focus()"
autocomplete="off"
[required]="required">
<mat-hint innerHTML="{{ 'phone-input.phone-input-hint' | translate: {phoneNumber: phonePlaceholder} }}"></mat-hint>
<mat-error *ngIf="phoneFormGroup.get('phoneNumber').hasError('required')">
{{ 'phone-input.phone-input-required' | translate }}
</mat-error>
<mat-error *ngIf="phoneFormGroup.get('phoneNumber').hasError('invalidPhoneNumber')">
{{ 'phone-input.phone-input-validation' | translate }}
</mat-error>
</mat-form-field>
</div>
</form>

44
common/dao-api/src/main/java/org/thingsboard/server/dao/util/PsqlTsLatestAnyDao.java → ui-ngx/src/app/shared/components/phone-input.component.scss

@ -13,15 +13,43 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.util;
:host ::ng-deep {
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
.phone-input-container {
display: flex;
align-items: center;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
.phone-input {
width: 100%;
}
}
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnExpression("('${database.ts_latest.type}'=='sql' || '${database.ts_latest.type}'=='timescale') " +
"&& '${spring.jpa.database-platform}'=='org.hibernate.dialect.PostgreSQLDialect'")
public @interface PsqlTsLatestAnyDao {
.flags-select-container {
display: inline-block;
position: relative;
width: 50px;
height: 100%;
margin-right: 5px;
}
.flag-container {
position: absolute;
font-size: 20px;
top: 50%;
left: 0;
transform: translate(0, -50%);
}
.country-select {
width: 45px;
height: 30px;
.mat-select-trigger {
height: 100%;
width: 100%;
}
.mat-select-value {
visibility: hidden;
}
}
}

222
ui-ngx/src/app/shared/components/phone-input.component.ts

@ -0,0 +1,222 @@
///
/// 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,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
ValidatorFn,
Validators
} from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { Country, CountryData } from '@shared/models/country.models';
import examples from 'libphonenumber-js/examples.mobile.json';
import { CountryCode, getExampleNumber, parsePhoneNumberFromString } from 'libphonenumber-js';
import { phoneNumberPattern } from '@shared/models/settings.models';
import { Subscription } from 'rxjs';
import { FloatLabelType, MatFormFieldAppearance } from '@angular/material/form-field/form-field';
@Component({
selector: 'tb-phone-input',
templateUrl: './phone-input.component.html',
styleUrls: ['./phone-input.component.scss'],
providers: [
CountryData,
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => PhoneInputComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => PhoneInputComponent),
multi: true
}
]
})
export class PhoneInputComponent implements OnInit, ControlValueAccessor, Validator {
@Input()
disabled: boolean;
@Input()
defaultCountry: CountryCode = 'US';
@Input()
enableFlagsSelect = true;
@Input()
required = true;
@Input()
floatLabel: FloatLabelType = 'auto';
@Input()
appearance: MatFormFieldAppearance = 'legacy';
@Input()
placeholder;
@Input()
label = 'phone-input.phone-input-label';
allCountries: Array<Country> = this.countryCodeData.allCountries;
phonePlaceholder: string;
flagIcon: string;
phoneFormGroup: FormGroup;
phoneNumberPattern = phoneNumberPattern;
private baseCode = 127397;
private countryCallingCode: string;
private modelValue: string;
private valueChange$: Subscription = null;
private propagateChange = (v: any) => { };
constructor(private translate: TranslateService,
private fb: FormBuilder,
private countryCodeData: CountryData) {
}
ngOnInit(): void {
const validators: ValidatorFn[] = [Validators.pattern(phoneNumberPattern), this.validatePhoneNumber()];
if (this.required) {
validators.push(Validators.required);
}
this.phoneFormGroup = this.fb.group({
country: [this.defaultCountry, []],
phoneNumber: [null, validators]
});
this.valueChange$ = this.phoneFormGroup.get('phoneNumber').valueChanges.subscribe(value => {
this.updateModel();
if (value) {
const parsedPhoneNumber = parsePhoneNumberFromString(value);
const country = this.phoneFormGroup.get('country').value;
if (parsedPhoneNumber?.country && parsedPhoneNumber?.country !== country) {
this.phoneFormGroup.get('country').patchValue(parsedPhoneNumber.country, {emitEvent: true});
}
}
});
this.phoneFormGroup.get('country').valueChanges.subscribe(value => {
if (value) {
const code = this.countryCallingCode;
this.getFlagAndPhoneNumberData(value);
let phoneNumber = this.phoneFormGroup.get('phoneNumber').value;
if (phoneNumber) {
if (code !== this.countryCallingCode && phoneNumber.includes(code)) {
phoneNumber = phoneNumber.replace(code, this.countryCallingCode);
this.phoneFormGroup.get('phoneNumber').patchValue(phoneNumber);
}
}
}
});
}
ngOnDestroy() {
if (this.valueChange$) {
this.valueChange$.unsubscribe();
}
}
focus() {
const phoneNumber = this.phoneFormGroup.get('phoneNumber');
this.phoneFormGroup.markAsPristine();
this.phoneFormGroup.markAsUntouched();
if (!phoneNumber.value) {
phoneNumber.patchValue(this.countryCallingCode);
}
}
private getFlagAndPhoneNumberData(country) {
if (this.enableFlagsSelect) {
this.flagIcon = this.getFlagIcon(country);
}
this.getPhoneNumberData(country);
}
private getPhoneNumberData(country): void {
const phoneData = getExampleNumber(country, examples);
this.phonePlaceholder = phoneData.number;
this.countryCallingCode = '+' + phoneData.countryCallingCode;
}
private getFlagIcon(countryCode) {
return String.fromCodePoint(...countryCode.split('').map(country => this.baseCode + country.charCodeAt(0)));
}
validatePhoneNumber(): ValidatorFn {
return (c: FormControl) => {
const phoneNumber = c.value;
if (phoneNumber) {
const parsedPhoneNumber = parsePhoneNumberFromString(phoneNumber);
if (!parsedPhoneNumber?.isValid() || !parsedPhoneNumber?.isPossible()) {
return {
invalidPhoneNumber: {
valid: false
}
};
}
}
return null;
};
}
validate(): ValidationErrors | null {
return this.phoneFormGroup.get('phoneNumber').valid ? null : {
phoneFormGroup: false
};
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.phoneFormGroup.disable({emitEvent: false});
} else {
this.phoneFormGroup.enable({emitEvent: false});
}
}
writeValue(phoneNumber): void {
this.modelValue = phoneNumber;
const country = phoneNumber ? parsePhoneNumberFromString(phoneNumber)?.country : this.defaultCountry;
this.getFlagAndPhoneNumberData(country);
this.phoneFormGroup.patchValue({phoneNumber, country}, {emitEvent: !phoneNumber});
}
private updateModel() {
const phoneNumber = this.phoneFormGroup.get('phoneNumber');
if (phoneNumber.valid && phoneNumber.value) {
this.modelValue = phoneNumber.value;
this.propagateChange(this.modelValue);
} else {
this.propagateChange(null);
}
}
}

520
ui-ngx/src/app/shared/models/country.models.ts

@ -0,0 +1,520 @@
///
/// 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 { Injectable } from '@angular/core';
export interface Country {
name: string;
iso2: string;
dialCode: string;
areaCodes?: string[];
flag: string;
}
export enum CountryISO {
Afghanistan = 'AF',
Albania = 'AL',
Algeria = 'DZ',
AmericanSamoa = 'AS',
Andorra = 'AD',
Angola = 'AO',
Anguilla = 'AI',
AntiguaAndBarbuda = 'AG',
Argentina = 'AR',
Armenia = 'AM',
Aruba = 'AW',
Australia = 'AU',
Austria = 'AT',
Azerbaijan = 'AZ',
Bahamas = 'BS',
Bahrain = 'BH',
Bangladesh = 'BD',
Barbados = 'BB',
Belarus = 'BY',
Belgium = 'BE',
Belize = 'BZ',
Benin = 'BJ',
Bermuda = 'BM',
Bhutan = 'BT',
Bolivia = 'BO',
BosniaAndHerzegovina = 'BA',
Botswana = 'BW',
Brazil = 'BR',
BritishIndianOceanTerritory = 'IO',
BritishVirginIslands = 'VG',
Brunei = 'BN',
Bulgaria = 'BG',
BurkinaFaso = 'BF',
Burundi = 'BI',
Cambodia = 'KH',
Cameroon = 'CM',
Canada = 'CA',
CapeVerde = 'CV',
CaribbeanNetherlands = 'BQ',
CaymanIslands = 'KY',
CentralAfricanRepublic = 'CF',
Chad = 'TD',
Chile = 'CL',
China = 'CN',
ChristmasIsland = 'CX',
Cocos = 'CC',
Colombia = 'CC',
Comoros = 'KM',
CongoDRCJamhuriYaKidemokrasiaYaKongo = 'CD',
CongoRepublicCongoBrazzaville = 'CG',
CookIslands = 'CK',
CostaRica = 'CR',
CôteDIvoire = 'CI',
Croatia = 'HR',
Cuba = 'CU',
Curaçao = 'CW',
Cyprus = 'CY',
CzechRepublic = 'CZ',
Denmark = 'DK',
Djibouti = 'DJ',
Dominica = 'DM',
DominicanRepublic = 'DO',
Ecuador = 'EC',
Egypt = 'EG',
ElSalvador = 'SV',
EquatorialGuinea = 'GQ',
Eritrea = 'ER',
Estonia = 'EE',
Ethiopia = 'ET',
FalklandIslands = 'FK',
FaroeIslands = 'FO',
Fiji = 'FJ',
Finland = 'FI',
France = 'FR',
FrenchGuiana = 'GF',
FrenchPolynesia = 'PF',
Gabon = 'GA',
Gambia = 'GM',
Georgia = 'GE',
Germany = 'DE',
Ghana = 'GH',
Gibraltar = 'GI',
Greece = 'GR',
Greenland = 'GL',
Grenada = 'GD',
Guadeloupe = 'GP',
Guam = 'GU',
Guatemala = 'GT',
Guernsey = 'GG',
Guinea = 'GN',
GuineaBissau = 'GW',
Guyana = 'GY',
Haiti = 'HT',
Honduras = 'HN',
HongKong = 'HK',
Hungary = 'HU',
Iceland = 'IS',
India = 'IN',
Indonesia = 'ID',
Iran = 'IR',
Iraq = 'IQ',
Ireland = 'IE',
IsleOfMan = 'IM',
Israel = 'IL',
Italy = 'IT',
Jamaica = 'JM',
Japan = 'JP',
Jersey = 'JE',
Jordan = 'JO',
Kazakhstan = 'KZ',
Kenya = 'KE',
Kiribati = 'KI',
Kosovo = 'XK',
Kuwait = 'KW',
Kyrgyzstan = 'KG',
Laos = 'LA',
Latvia = 'LV',
Lebanon = 'LB',
Lesotho = 'LS',
Liberia = 'LR',
Libya = 'LY',
Liechtenstein = 'LI',
Lithuania = 'LT',
Luxembourg = 'LU',
Macau = 'MO',
Macedonia = 'MK',
Madagascar = 'MG',
Malawi = 'MW',
Malaysia = 'MY',
Maldives = 'MV',
Mali = 'ML',
Malta = 'MT',
MarshallIslands = 'MH',
Martinique = 'MQ',
Mauritania = 'MR',
Mauritius = 'MU',
Mayotte = 'YT',
Mexico = 'MX',
Micronesia = 'FM',
Moldova = 'MD',
Monaco = 'MC',
Mongolia = 'MN',
Montenegro = 'ME',
Montserrat = 'MS',
Morocco = 'MA',
Mozambique = 'MZ',
Myanmar = 'MM',
Namibia = 'NA',
Nauru = 'NR',
Nepal = 'NP',
Netherlands = 'NL',
NewCaledonia = 'NC',
NewZealand = 'NZ',
Nicaragua = 'NI',
Niger = 'NE',
Nigeria = 'NG',
Niue = 'NU',
NorfolkIsland = 'NF',
NorthKorea = 'KP',
NorthernMarianaIslands = 'MP',
Norway = 'NO',
Oman = 'OM',
Pakistan = 'PK',
Palau = 'PW',
Palestine = 'PS',
Panama = 'PA',
PapuaNewGuinea = 'PG',
Paraguay = 'PY',
Peru = 'PE',
Philippines = 'PH',
Poland = 'PL',
Portugal = 'PT',
PuertoRico = 'PR',
Qatar = 'QA',
Réunion = 'RE',
Romania = 'RO',
Russia = 'RU',
Rwanda = 'RW',
SaintBarthélemy = 'BL',
SaintHelena = 'SH',
SaintKittsAndNevis = 'KN',
SaintLucia = 'LC',
SaintMartin = 'MF',
SaintPierreAndMiquelon = 'PM',
SaintVincentAndTheGrenadines = 'VC',
Samoa = 'WS',
SanMarino = 'SM',
SãoToméAndPríncipe = 'ST',
SaudiArabia = 'SA',
Senegal = 'SN',
Serbia = 'RS',
Seychelles = 'SC',
SierraLeone = 'SL',
Singapore = 'SG',
SintMaarten = 'SX',
Slovakia = 'SK',
Slovenia = 'SI',
SolomonIslands = 'SB',
Somalia = 'SO',
SouthAfrica = 'ZA',
SouthKorea = 'KR',
SouthSudan = 'SS',
Spain = 'ES',
SriLanka = 'LK',
Sudan = 'SD',
Suriname = 'SR',
SvalbardAndJanMayen = 'SJ',
Swaziland = 'SZ',
Sweden = 'SE',
Switzerland = 'CH',
Syria = 'SY',
Taiwan = 'TW',
Tajikistan = 'TJ',
Tanzania = 'TZ',
Thailand = 'TH',
TimorLeste = 'TL',
Togo = 'TG',
Tokelau = 'TK',
Tonga = 'TO',
TrinidadAndTobago = 'TT',
Tunisia = 'TN',
Turkey = 'TR',
Turkmenistan = 'TM',
TurksAndCaicosIslands = 'TC',
Tuvalu = 'TV',
USVirginIslands = 'VI',
Uganda = 'UG',
Ukraine = 'UA',
UnitedArabEmirates = 'AE',
UnitedKingdom = 'GB',
UnitedStates = 'US',
Uruguay = 'UY',
Uzbekistan = 'UZ',
Vanuatu = 'VU',
VaticanCity = 'VA',
Venezuela = 'VE',
Vietnam = 'VN',
WallisAndFutuna = 'WF',
WesternSahara = 'EH',
Yemen = 'YE',
Zambia = 'ZM',
Zimbabwe = 'ZW',
ÅlandIslands = 'AX',
}
@Injectable()
export class CountryData {
public allCountries: Array<Country> = [
{name: 'Afghanistan', iso2: CountryISO.Afghanistan, dialCode: '93', flag: '🇦🇫'},
{name: 'Albania', iso2: CountryISO.Albania, dialCode: '355', flag: '🇦🇱'},
{name: 'Algeria', iso2: CountryISO.Algeria, dialCode: '213', flag: '🇩🇿'},
{name: 'American Samoa', iso2: CountryISO.AmericanSamoa, dialCode: '1', flag: '🇦🇸'},
{name: 'Andorra', iso2: CountryISO.Andorra, dialCode: '376', flag: '🇦🇩'},
{name: 'Angola', iso2: CountryISO.Angola, dialCode: '244', flag: '🇦🇴'},
{name: 'Anguilla', iso2: CountryISO.Anguilla, dialCode: '1', flag: '🇦🇮'},
{name: 'Antigua and Barbuda', iso2: CountryISO.AntiguaAndBarbuda, dialCode: '1', flag: '🇦🇬'},
{name: 'Argentina', iso2: CountryISO.Argentina, dialCode: '54', flag: '🇦🇷'},
{name: 'Armenia', iso2: CountryISO.Armenia, dialCode: '374', flag: '🇦🇲'},
{name: 'Aruba', iso2: CountryISO.Aruba, dialCode: '297', flag: '🇦🇼'},
{name: 'Australia', iso2: CountryISO.Australia, dialCode: '61', flag: '🇦🇺'},
{name: 'Austria', iso2: CountryISO.Austria, dialCode: '43', flag: '🇦🇹'},
{name: 'Azerbaijan', iso2: CountryISO.Azerbaijan, dialCode: '994', flag: '🇦🇿'},
{name: 'Bahamas', iso2: CountryISO.Bahamas, dialCode: '1', flag: '🇧🇸'},
{name: 'Bahrain', iso2: CountryISO.Bahrain, dialCode: '973', flag: '🇧🇭'},
{name: 'Bangladesh', iso2: CountryISO.Bangladesh, dialCode: '880', flag: '🇧🇩'},
{name: 'Barbados', iso2: CountryISO.Barbados, dialCode: '1', flag: '🇧🇧'},
{name: 'Belarus', iso2: CountryISO.Belarus, dialCode: '375', flag: '🇧🇾'},
{name: 'Belgium', iso2: CountryISO.Belgium, dialCode: '32', flag: '🇧🇪'},
{name: 'Belize', iso2: CountryISO.Belize, dialCode: '501', flag: '🇧🇿'},
{name: 'Benin', iso2: CountryISO.Benin, dialCode: '229', flag: '🇧🇯'},
{name: 'Bermuda', iso2: CountryISO.Bermuda, dialCode: '1', flag: '🇧🇲'},
{name: 'Bhutan', iso2: CountryISO.Bhutan, dialCode: '975', flag: '🇧🇹'},
{name: 'Bolivia', iso2: CountryISO.Bolivia, dialCode: '591', flag: '🇧🇴'},
{name: 'Bosnia and Herzegovina', iso2: CountryISO.BosniaAndHerzegovina, dialCode: '387', flag: '🇧🇦'},
{name: 'Botswana', iso2: CountryISO.Botswana, dialCode: '267', flag: '🇧🇼'},
{name: 'Brazil', iso2: CountryISO.Brazil, dialCode: '55', flag: '🇧🇷'},
{name: 'British Indian Ocean Territory', iso2: CountryISO.BritishIndianOceanTerritory, dialCode: '246', flag: '🇮🇴'},
{name: 'British Virgin Islands', iso2: CountryISO.BritishVirginIslands, dialCode: '1', flag: '🇻🇬'},
{name: 'Brunei', iso2: CountryISO.Brunei, dialCode: '673', flag: '🇧🇳'},
{name: 'Bulgaria', iso2: CountryISO.Bulgaria, dialCode: '359', flag: '🇧🇬'},
{name: 'Burkina Faso', iso2: CountryISO.BurkinaFaso, dialCode: '226', flag: '🇧🇫'},
{name: 'Burundi', iso2: CountryISO.Burundi, dialCode: '257', flag: '🇧🇮'},
{name: 'Cambodia', iso2: CountryISO.Cambodia, dialCode: '855', flag: '🇰🇭'},
{name: 'Cameroon', iso2: CountryISO.Cameroon, dialCode: '237', flag: '🇨🇲'},
{name: 'Canada', iso2: CountryISO.Canada, dialCode: '1', flag: '🇨🇦'},
{name: 'Cape Verde', iso2: CountryISO.CapeVerde, dialCode: '238', flag: '🇨🇻'},
{name: 'Caribbean Netherlands', iso2: CountryISO.CaribbeanNetherlands, dialCode: '599', flag: '🇧🇶'},
{name: 'Cayman Islands', iso2: CountryISO.CaymanIslands, dialCode: '1', flag: '🇰🇾'},
{name: 'Central African Republic', iso2: CountryISO.CentralAfricanRepublic, dialCode: '236', flag: '🇨🇫'},
{name: 'Chad', iso2: CountryISO.Chad, dialCode: '235', flag: '🇹🇩'},
{name: 'Chile', iso2: CountryISO.Chile, dialCode: '56', flag: '🇨🇱'},
{name: 'China', iso2: CountryISO.China, dialCode: '86', flag: '🇨🇳'},
{name: 'Christmas Island', iso2: CountryISO.ChristmasIsland, dialCode: '61', flag: '🇨🇽'},
{name: 'Cocos Islands', iso2: CountryISO.Cocos, dialCode: '61', flag: '🇨🇨'},
{name: 'Colombia', iso2: CountryISO.Colombia, dialCode: '57', flag: '🇨🇨'},
{name: 'Comoros', iso2: CountryISO.Comoros, dialCode: '269', flag: '🇰🇲'},
{name: 'Congo-Kinshasa', iso2: CountryISO.CongoDRCJamhuriYaKidemokrasiaYaKongo, dialCode: '243', flag: '🇨🇩'},
{name: 'Congo-Brazzaville', iso2: CountryISO.CongoRepublicCongoBrazzaville, dialCode: '242', flag: '🇨🇬'},
{name: 'Cook Islands', iso2: CountryISO.CookIslands, dialCode: '682', flag: '🇨🇰'},
{name: 'Costa Rica', iso2: CountryISO.CostaRica, dialCode: '506', flag: '🇨🇷'},
{name: 'Côte d’Ivoire', iso2: CountryISO.CôteDIvoire, dialCode: '225', flag: '🇨🇮'},
{name: 'Croatia', iso2: CountryISO.Croatia, dialCode: '385', flag: '🇭🇷'},
{name: 'Cuba', iso2: CountryISO.Cuba, dialCode: '53', flag: '🇨🇺'},
{name: 'Curaçao', iso2: CountryISO.Curaçao, dialCode: '599', flag: '🇨🇼'},
{name: 'Cyprus', iso2: CountryISO.Cyprus, dialCode: '357', flag: '🇨🇾'},
{name: 'Czech Republic', iso2: CountryISO.CzechRepublic, dialCode: '420', flag: '🇨🇿'},
{name: 'Denmark', iso2: CountryISO.Denmark, dialCode: '45', flag: '🇩🇰'},
{name: 'Djibouti', iso2: CountryISO.Djibouti, dialCode: '253', flag: '🇩🇯'},
{name: 'Dominica', iso2: CountryISO.Dominica, dialCode: '1767', flag: '🇩🇲'},
{name: 'Dominican Republic', iso2: CountryISO.DominicanRepublic, dialCode: '1', flag: '🇩🇴'},
{name: 'Ecuador', iso2: CountryISO.Ecuador, dialCode: '593', flag: '🇪🇨'},
{name: 'Egypt', iso2: CountryISO.Egypt, dialCode: '20', flag: '🇪🇬'},
{name: 'El Salvador', iso2: CountryISO.ElSalvador, dialCode: '503', flag: '🇸🇻'},
{name: 'Equatorial Guinea', iso2: CountryISO.EquatorialGuinea, dialCode: '240', flag: '🇬🇶'},
{name: 'Eritrea', iso2: CountryISO.Eritrea, dialCode: '291', flag: '🇪🇷'},
{name: 'Estonia', iso2: CountryISO.Estonia, dialCode: '372', flag: '🇪🇪'},
{name: 'Ethiopia', iso2: CountryISO.Ethiopia, dialCode: '251', flag: '🇪🇹'},
{name: 'Falkland Islands', iso2: CountryISO.FalklandIslands, dialCode: '500', flag: '🇫🇰'},
{name: 'Faroe Islands', iso2: CountryISO.FaroeIslands, dialCode: '298', flag: '🇫🇴'},
{name: 'Fiji', iso2: CountryISO.Fiji, dialCode: '679', flag: '🇫🇯'},
{name: 'Finland', iso2: CountryISO.Finland, dialCode: '358', flag: '🇫🇮'},
{name: 'France', iso2: CountryISO.France, dialCode: '33', flag: '🇫🇷'},
{name: 'French Guiana', iso2: CountryISO.FrenchGuiana, dialCode: '594', flag: '🇬🇫'},
{name: 'French Polynesia', iso2: CountryISO.FrenchPolynesia, dialCode: '689', flag: '🇵🇫'},
{name: 'Gabon', iso2: CountryISO.Gabon, dialCode: '241', flag: '🇬🇦'},
{name: 'Gambia', iso2: CountryISO.Gambia, dialCode: '220', flag: '🇬🇲'},
{name: 'Georgia', iso2: CountryISO.Georgia, dialCode: '995', flag: '🇬🇪'},
{name: 'Germany', iso2: CountryISO.Germany, dialCode: '49', flag: '🇩🇪'},
{name: 'Ghana', iso2: CountryISO.Ghana, dialCode: '233', flag: '🇬🇭'},
{name: 'Gibraltar', iso2: CountryISO.Gibraltar, dialCode: '350', flag: '🇬🇮'},
{name: 'Greece', iso2: CountryISO.Greece, dialCode: '30', flag: '🇬🇷'},
{name: 'Greenland', iso2: CountryISO.Greenland, dialCode: '299', flag: '🇬🇱'},
{name: 'Grenada', iso2: CountryISO.Grenada, dialCode: '1473', flag: '🇬🇩'},
{name: 'Guadeloupe', iso2: CountryISO.Guadeloupe, dialCode: '590', flag: '🇬🇵'},
{name: 'Guam', iso2: CountryISO.Guam, dialCode: '1', flag: '🇬🇺'},
{name: 'Guatemala', iso2: CountryISO.Guatemala, dialCode: '502', flag: '🇬🇹'},
{name: 'Guernsey', iso2: CountryISO.Guernsey, dialCode: '44', flag: '🇬🇬'},
{name: 'Guinea', iso2: CountryISO.Guinea, dialCode: '224', flag: '🇬🇳'},
{name: 'Guinea-Bissau', iso2: CountryISO.GuineaBissau, dialCode: '245', flag: '🇬🇼'},
{name: 'Guyana', iso2: CountryISO.Guyana, dialCode: '592', flag: '🇬🇾'},
{name: 'Haiti', iso2: CountryISO.Haiti, dialCode: '509', flag: '🇭🇹'},
{name: 'Honduras', iso2: CountryISO.Honduras, dialCode: '504', flag: '🇭🇳'},
{name: 'Hong Kong', iso2: CountryISO.HongKong, dialCode: '852', flag: '🇭🇰'},
{name: 'Hungary', iso2: CountryISO.Hungary, dialCode: '36', flag: '🇭🇺'},
{name: 'Iceland', iso2: CountryISO.Iceland, dialCode: '354', flag: '🇮🇸'},
{name: 'India', iso2: CountryISO.India, dialCode: '91', flag: '🇮🇳'},
{name: 'Indonesia', iso2: CountryISO.Indonesia, dialCode: '62', flag: '🇮🇩'},
{name: 'Iran', iso2: CountryISO.Iran, dialCode: '98', flag: '🇮🇷'},
{name: 'Iraq', iso2: CountryISO.Iraq, dialCode: '964', flag: '🇮🇶'},
{name: 'Ireland', iso2: CountryISO.Ireland, dialCode: '353', flag: '🇮🇪'},
{name: 'Isle of Man', iso2: CountryISO.IsleOfMan, dialCode: '44', flag: '🇮🇲'},
{name: 'Israel', iso2: CountryISO.Israel, dialCode: '972', flag: '🇮🇱'},
{name: 'Italy', iso2: CountryISO.Italy, dialCode: '39', flag: '🇮🇹'},
{name: 'Jamaica', iso2: CountryISO.Jamaica, dialCode: '1', flag: '🇯🇲'},
{name: 'Japan', iso2: CountryISO.Japan, dialCode: '81', flag: '🇯🇵'},
{name: 'Jersey', iso2: CountryISO.Jersey, dialCode: '44', flag: '🇯🇪'},
{name: 'Jordan', iso2: CountryISO.Jordan, dialCode: '962', flag: '🇯🇴'},
{name: 'Kazakhstan', iso2: CountryISO.Kazakhstan, dialCode: '7', flag: '🇰🇿'},
{name: 'Kenya', iso2: CountryISO.Kenya, dialCode: '254', flag: '🇰🇪'},
{name: 'Kiribati', iso2: CountryISO.Kiribati, dialCode: '686', flag: '🇰🇮'},
{name: 'Kosovo', iso2: CountryISO.Kosovo, dialCode: '383', flag: '🇽🇰'},
{name: 'Kuwait', iso2: CountryISO.Kuwait, dialCode: '965', flag: '🇰🇼'},
{name: 'Kyrgyzstan', iso2: CountryISO.Kyrgyzstan, dialCode: '996', flag: '🇰🇬'},
{name: 'Laos', iso2: CountryISO.Laos, dialCode: '856', flag: '🇱🇦'},
{name: 'Latvia', iso2: CountryISO.Latvia, dialCode: '371', flag: '🇱🇻'},
{name: 'Lebanon', iso2: CountryISO.Lebanon, dialCode: '961', flag: '🇱🇧'},
{name: 'Lesotho', iso2: CountryISO.Lesotho, dialCode: '266', flag: '🇱🇸'},
{name: 'Liberia', iso2: CountryISO.Liberia, dialCode: '231', flag: '🇱🇷'},
{name: 'Libya', iso2: CountryISO.Libya, dialCode: '218', flag: '🇱🇾'},
{name: 'Liechtenstein', iso2: CountryISO.Liechtenstein, dialCode: '423', flag: '🇱🇮'},
{name: 'Lithuania', iso2: CountryISO.Lithuania, dialCode: '370', flag: '🇱🇹'},
{name: 'Luxembourg', iso2: CountryISO.Luxembourg, dialCode: '352', flag: '🇱🇺'},
{name: 'Macau', iso2: CountryISO.Macau, dialCode: '853', flag: '🇲🇴'},
{name: 'Macedonia', iso2: CountryISO.Macedonia, dialCode: '389', flag: '🇲🇰'},
{name: 'Madagascar', iso2: CountryISO.Madagascar, dialCode: '261', flag: '🇲🇬'},
{name: 'Malawi', iso2: CountryISO.Malawi, dialCode: '265', flag: '🇲🇼'},
{name: 'Malaysia', iso2: CountryISO.Malaysia, dialCode: '60', flag: '🇲🇾'},
{name: 'Maldives', iso2: CountryISO.Maldives, dialCode: '960', flag: '🇲🇻'},
{name: 'Mali', iso2: CountryISO.Mali, dialCode: '223', flag: '🇲🇱'},
{name: 'Malta', iso2: CountryISO.Malta, dialCode: '356', flag: '🇲🇹'},
{name: 'Marshall Islands', iso2: CountryISO.MarshallIslands, dialCode: '692', flag: '🇲🇭'},
{name: 'Martinique', iso2: CountryISO.Martinique, dialCode: '596', flag: '🇲🇶'},
{name: 'Mauritania', iso2: CountryISO.Mauritania, dialCode: '222', flag: '🇲🇷'},
{name: 'Mauritius', iso2: CountryISO.Mauritius, dialCode: '230', flag: '🇲🇺'},
{name: 'Mayotte', iso2: CountryISO.Mayotte, dialCode: '262', flag: '🇾🇹'},
{name: 'Mexico', iso2: CountryISO.Mexico, dialCode: '52', flag: '🇲🇽'},
{name: 'Micronesia', iso2: CountryISO.Micronesia, dialCode: '691', flag: '🇫🇲'},
{name: 'Moldova', iso2: CountryISO.Moldova, dialCode: '373', flag: '🇲🇩'},
{name: 'Monaco', iso2: CountryISO.Monaco, dialCode: '377', flag: '🇲🇨'},
{name: 'Mongolia', iso2: CountryISO.Mongolia, dialCode: '976', flag: '🇲🇳'},
{name: 'Montenegro', iso2: CountryISO.Montenegro, dialCode: '382', flag: '🇲🇪'},
{name: 'Montserrat', iso2: CountryISO.Montserrat, dialCode: '1', flag: '🇲🇸'},
{name: 'Morocco', iso2: CountryISO.Morocco, dialCode: '212', flag: '🇲🇦'},
{name: 'Mozambique', iso2: CountryISO.Mozambique, dialCode: '258', flag: '🇲🇿'},
{name: 'Myanmar', iso2: CountryISO.Myanmar, dialCode: '95', flag: '🇲🇲'},
{name: 'Namibia', iso2: CountryISO.Namibia, dialCode: '264', flag: '🇳🇦'},
{name: 'Nauru', iso2: CountryISO.Nauru, dialCode: '674', flag: '🇳🇷'},
{name: 'Nepal', iso2: CountryISO.Nepal, dialCode: '977', flag: '🇳🇵'},
{name: 'Netherlands', iso2: CountryISO.Netherlands, dialCode: '31', flag: '🇳🇱'},
{name: 'New Caledonia', iso2: CountryISO.NewCaledonia, dialCode: '687', flag: '🇳🇨'},
{name: 'New Zealand', iso2: CountryISO.NewZealand, dialCode: '64', flag: '🇳🇿'},
{name: 'Nicaragua', iso2: CountryISO.Nicaragua, dialCode: '505', flag: '🇳🇮'},
{name: 'Niger', iso2: CountryISO.Niger, dialCode: '227', flag: '🇳🇪'},
{name: 'Nigeria', iso2: CountryISO.Nigeria, dialCode: '234', flag: '🇳🇬'},
{name: 'Niue', iso2: CountryISO.Niue, dialCode: '683', flag: '🇳🇺'},
{name: 'Norfolk Island', iso2: CountryISO.NorfolkIsland, dialCode: '672', flag: '🇳🇫'},
{name: 'North Korea', iso2: CountryISO.NorthKorea, dialCode: '850', flag: '🇰🇵'},
{name: 'Northern Mariana Islands', iso2: CountryISO.NorthernMarianaIslands, dialCode: '1670', flag: '🇲🇵'},
{name: 'Norway', iso2: CountryISO.Norway, dialCode: '47', flag: '🇳🇴'},
{name: 'Oman', iso2: CountryISO.Oman, dialCode: '968', flag: '🇴🇲'},
{name: 'Pakistan', iso2: CountryISO.Pakistan, dialCode: '92', flag: '🇵🇰'},
{name: 'Palau', iso2: CountryISO.Palau, dialCode: '680', flag: '🇵🇼'},
{name: 'Palestine', iso2: CountryISO.Palestine, dialCode: '970', flag: '🇵🇸'},
{name: 'Panama', iso2: CountryISO.Panama, dialCode: '507', flag: '🇵🇦'},
{name: 'Papua New Guinea', iso2: CountryISO.PapuaNewGuinea, dialCode: '675', flag: '🇵🇬'},
{name: 'Paraguay', iso2: CountryISO.Paraguay, dialCode: '595', flag: '🇵🇾'},
{name: 'Peru', iso2: CountryISO.Peru, dialCode: '51', flag: '🇵🇪'},
{name: 'Philippines', iso2: CountryISO.Philippines, dialCode: '63', flag: '🇵🇭'},
{name: 'Poland', iso2: CountryISO.Poland, dialCode: '48', flag: '🇵🇱'},
{name: 'Portugal', iso2: CountryISO.Portugal, dialCode: '351', flag: '🇵🇹'},
{name: 'Puerto Rico', iso2: CountryISO.PuertoRico, dialCode: '1', flag: '🇵🇷'},
{name: 'Qatar', iso2: CountryISO.Qatar, dialCode: '974', flag: '🇶🇦'},
{name: 'Réunion', iso2: CountryISO.Réunion, dialCode: '262', flag: '🇷🇪'},
{name: 'Romania', iso2: CountryISO.Romania, dialCode: '40', flag: '🇷🇴'},
{name: 'Russia', iso2: CountryISO.Russia, dialCode: '7', flag: '🇷🇺'},
{name: 'Rwanda', iso2: CountryISO.Rwanda, dialCode: '250', flag: '🇷🇼'},
{name: 'Saint Barthélemy', iso2: CountryISO.SaintBarthélemy, dialCode: '590', flag: '🇧🇱'},
{name: 'Saint Helena', iso2: CountryISO.SaintHelena, dialCode: '290', flag: '🇸🇭'},
{name: 'Saint Kitts and Nevis', iso2: CountryISO.SaintKittsAndNevis, dialCode: '1869', flag: '🇰🇳'},
{name: 'Saint Lucia', iso2: CountryISO.SaintLucia, dialCode: '1', flag: '🇱🇨'},
{name: 'Saint Martin', iso2: CountryISO.SaintMartin, dialCode: '590', flag: '🇲🇫'},
{name: 'Saint Pierre and Miquelon', iso2: CountryISO.SaintPierreAndMiquelon, dialCode: '508', flag: '🇵🇲'},
{name: 'Saint Vincent and the Grenadines', iso2: CountryISO.SaintVincentAndTheGrenadines, dialCode: '1', flag: '🇻🇨'},
{name: 'Samoa', iso2: CountryISO.Samoa, dialCode: '685', flag: '🇼🇸'},
{name: 'San Marino', iso2: CountryISO.SanMarino, dialCode: '378', flag: '🇸🇲'},
{name: 'São Tomé and Príncipe', iso2: CountryISO.SãoToméAndPríncipe, dialCode: '239', flag: '🇸🇹'},
{name: 'Saudi Arabia', iso2: CountryISO.SaudiArabia, dialCode: '966', flag: '🇸🇦'},
{name: 'Senegal', iso2: CountryISO.Senegal, dialCode: '221', flag: '🇸🇳'},
{name: 'Serbia', iso2: CountryISO.Serbia, dialCode: '381', flag: '🇷🇸'},
{name: 'Seychelles', iso2: CountryISO.Seychelles, dialCode: '248', flag: '🇸🇨'},
{name: 'Sierra Leone', iso2: CountryISO.SierraLeone, dialCode: '232', flag: '🇸🇱'},
{name: 'Singapore', iso2: CountryISO.Singapore, dialCode: '65', flag: '🇸🇬'},
{name: 'Sint Maarten', iso2: CountryISO.SintMaarten, dialCode: '1', flag: '🇸🇽'},
{name: 'Slovakia', iso2: CountryISO.Slovakia, dialCode: '421', flag: '🇸🇰'},
{name: 'Slovenia', iso2: CountryISO.Slovenia, dialCode: '386', flag: '🇸🇮'},
{name: 'Solomon Islands', iso2: CountryISO.SolomonIslands, dialCode: '677', flag: '🇸🇧'},
{name: 'Somalia', iso2: CountryISO.Somalia, dialCode: '252', flag: '🇸🇴'},
{name: 'South Africa', iso2: CountryISO.SouthAfrica, dialCode: '27', flag: '🇿🇦'},
{name: 'South Korea', iso2: CountryISO.SouthKorea, dialCode: '82', flag: '🇰🇷'},
{name: 'South Sudan', iso2: CountryISO.SouthSudan, dialCode: '211', flag: '🇸🇸'},
{name: 'Spain', iso2: CountryISO.Spain, dialCode: '34', flag: '🇪🇸'},
{name: 'Sri Lanka', iso2: CountryISO.SriLanka, dialCode: '94', flag: '🇱🇰'},
{name: 'Sudan', iso2: CountryISO.Sudan, dialCode: '249', flag: '🇸🇩'},
{name: 'Suriname: ', iso2: CountryISO.Suriname, dialCode: '597', flag: '🇸🇷'},
{name: 'Svalbard and Jan Mayen', iso2: CountryISO.SvalbardAndJanMayen, dialCode: '47', flag: '🇸🇯'},
{name: 'Swaziland', iso2: CountryISO.Swaziland, dialCode: '268', flag: '🇸🇿'},
{name: 'Sweden', iso2: CountryISO.Sweden, dialCode: '46', flag: '🇸🇪'},
{name: 'Switzerland', iso2: CountryISO.Switzerland, dialCode: '41', flag: '🇨🇭'},
{name: 'Syria', iso2: CountryISO.Syria, dialCode: '963', flag: '🇸🇾'},
{name: 'Taiwan', iso2: CountryISO.Taiwan, dialCode: '886', flag: '🇹🇼'},
{name: 'Tajikistan', iso2: CountryISO.Tajikistan, dialCode: '992', flag: '🇹🇯'},
{name: 'Tanzania', iso2: CountryISO.Tanzania, dialCode: '255', flag: '🇹🇿'},
{name: 'Thailand', iso2: CountryISO.Thailand, dialCode: '66', flag: '🇹🇭'},
{name: 'Timor-Leste', iso2: CountryISO.TimorLeste, dialCode: '670', flag: '🇹🇱'},
{name: 'Togo', iso2: CountryISO.Togo, dialCode: '228', flag: '🇹🇬'},
{name: 'Tokelau', iso2: CountryISO.Tokelau, dialCode: '690', flag: '🇹🇰'},
{name: 'Tonga', iso2: CountryISO.Tonga, dialCode: '676', flag: '🇹🇴'},
{name: 'Trinidad and Tobago', iso2: CountryISO.TrinidadAndTobago, dialCode: '1', flag: '🇹🇹'},
{name: 'Tunisia', iso2: CountryISO.Tunisia, dialCode: '216', flag: '🇹🇳'},
{name: 'Turkey', iso2: CountryISO.Turkey, dialCode: '90', flag: '🇹🇷'},
{name: 'Turkmenistan', iso2: CountryISO.Turkmenistan, dialCode: '993', flag: '🇹🇲'},
{name: 'Turks and Caicos Islands', iso2: CountryISO.TurksAndCaicosIslands, dialCode: '1649', flag: '🇹🇨'},
{name: 'Tuvalu', iso2: CountryISO.Tuvalu, dialCode: '688', flag: '🇹🇻'},
{name: 'U.S. Virgin Islands', iso2: CountryISO.USVirginIslands, dialCode: '1', flag: '🇻🇮'},
{name: 'Uganda', iso2: CountryISO.Uganda, dialCode: '256', flag: '🇺🇬'},
{name: 'Ukraine', iso2: CountryISO.Ukraine, dialCode: '380', flag: '🇺🇦'},
{name: 'United Arab Emirates', iso2: CountryISO.UnitedArabEmirates, dialCode: '971', flag: '🇦🇪'},
{name: 'United Kingdom', iso2: CountryISO.UnitedKingdom, dialCode: '44', flag: '🇬🇧'},
{name: 'United States', iso2: CountryISO.UnitedStates, dialCode: '1', flag: '🇺🇸'},
{name: 'Uruguay', iso2: CountryISO.Uruguay, dialCode: '598', flag: '🇺🇾'},
{name: 'Uzbekistan', iso2: CountryISO.Uzbekistan, dialCode: '998', flag: '🇺🇿'},
{name: 'Vanuatu', iso2: CountryISO.Vanuatu, dialCode: '678', flag: '🇻🇺'},
{name: 'Vatican City', iso2: CountryISO.VaticanCity, dialCode: '39', flag: '🇻🇦'},
{name: 'Venezuela', iso2: CountryISO.Venezuela, dialCode: '58', flag: '🇻🇪'},
{name: 'Vietnam', iso2: CountryISO.Vietnam, dialCode: '84', flag: '🇻🇳'},
{name: 'Wallis and Futuna', iso2: CountryISO.WallisAndFutuna, dialCode: '681', flag: '🇼🇫'},
{name: 'Western Sahara', iso2: CountryISO.WesternSahara, dialCode: '212', flag: '🇪🇭'},
{name: 'Yemen', iso2: CountryISO.Yemen, dialCode: '967', flag: '🇾🇪'},
{name: 'Zambia', iso2: CountryISO.Zambia, dialCode: '260', flag: '🇿🇲'},
{name: 'Zimbabwe', iso2: CountryISO.Zimbabwe, dialCode: '263', flag: '🇿🇼'},
{name: 'Åland Islands', iso2: CountryISO.ÅlandIslands, dialCode: '358', flag: '🇦🇽'}
];
}

7
ui-ngx/src/app/shared/shared.module.ts

@ -164,6 +164,7 @@ import { SafePipe } from '@shared/pipe/safe.pipe';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { MultipleImageInputComponent } from '@shared/components/multiple-image-input.component';
import { BranchAutocompleteComponent } from '@shared/components/vc/branch-autocomplete.component';
import { PhoneInputComponent } from '@shared/components/phone-input.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@ -286,7 +287,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
CopyButtonComponent,
TogglePasswordComponent,
ProtobufContentComponent,
BranchAutocompleteComponent
BranchAutocompleteComponent,
PhoneInputComponent
],
imports: [
CommonModule,
@ -487,7 +489,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
CopyButtonComponent,
TogglePasswordComponent,
ProtobufContentComponent,
BranchAutocompleteComponent
BranchAutocompleteComponent,
PhoneInputComponent
]
})
export class SharedModule { }

7
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -4542,6 +4542,13 @@
"material-icons": "Material icons",
"show-all": "Show all icons"
},
"phone-input": {
"phone-input-label": "Phone number",
"phone-input-required": "Phone number is required",
"phone-input-validation": "Phone number is invalid or not possible",
"phone-input-pattern": "Invalid phone number. Should be in E.164 format, ex. {{phoneNumber}}",
"phone-input-hint": "Phone Number in E.164 format, ex. {{phoneNumber}}"
},
"custom": {
"widget-action": {
"action-cell-button": "Action cell button",

5
ui-ngx/yarn.lock

@ -6039,6 +6039,11 @@ less@4.1.1:
needle "^2.5.2"
source-map "~0.6.0"
libphonenumber-js@^1.10.4:
version "1.10.4"
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.4.tgz#90397f0ed620262570a32244c9fbc389cc417ce4"
integrity sha512-9QWxEk4GW5RDnFzt8UtyRENfFpAN8u7Sbf9wf32tcXY9tdtnz1dKHIBwW2Wnfx8ypXJb9zUnTpK9aQJ/B8AlnA==
license-webpack-plugin@2.3.20:
version "2.3.20"
resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-2.3.20.tgz#f51fb674ca31519dbedbe1c7aabc036e5a7f2858"

Loading…
Cancel
Save