Browse Source

Merge with develop/3.4

pull/6779/head
Andrii Shvaika 4 years ago
parent
commit
f5603c4f72
  1. 4
      application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java
  2. 15
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  3. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
  4. 6
      application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
  5. 6
      application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java
  6. 60
      application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java
  7. 3
      application/src/main/resources/thingsboard.yml
  8. 2
      application/src/test/resources/application-test.properties
  9. 41
      common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java
  10. 6
      common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java
  11. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
  12. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
  13. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java
  14. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  15. 14
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  16. 2
      dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeCacheKey.java
  17. 4
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  18. 20
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  19. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  20. 23
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  21. 2
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
  22. 22
      dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
  23. 28
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  24. 8
      dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java
  25. 11
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  26. 4
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java
  27. 4
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  28. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java
  29. 14
      dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java
  30. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
  31. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java
  32. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java
  33. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java
  34. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java
  35. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java
  36. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java
  37. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java
  38. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java
  39. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java
  40. 13
      dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java
  41. 6
      dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java
  42. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java
  43. 9
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java
  44. 45
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantCacheKey.java
  45. 33
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantCaffeineCache.java
  46. 26
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantEvictEvent.java
  47. 33
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsCaffeineCache.java
  48. 35
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java
  49. 35
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantRedisCache.java
  50. 71
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  51. 10
      dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java
  52. 16
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  53. 4
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  54. 36
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java
  55. 16
      dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java
  56. 434
      dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java
  57. 3
      dao/src/test/resources/application-test.properties
  58. 40
      msa/js-executor/api/jsInvokeMessageProcessor.js
  59. 20
      pom.xml
  60. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java
  61. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java
  62. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java
  63. 6
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java
  64. 2
      transport/coap/src/main/resources/tb-coap-transport.yml
  65. 2
      transport/http/src/main/resources/tb-http-transport.yml
  66. 2
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
  67. 2
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  68. 2
      transport/snmp/src/main/resources/tb-snmp-transport.yml
  69. 2
      ui-ngx/src/app/core/services/script/node-script-test.service.ts
  70. 5
      ui-ngx/src/app/core/services/utils.service.ts
  71. 2
      ui-ngx/src/app/core/utils.ts
  72. 37
      ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts
  73. 15
      ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts
  74. 8
      ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts
  75. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  76. 1
      ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts
  77. 4
      ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-header.component.ts
  78. 2
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html
  79. 8
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
  80. 2
      ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html
  81. 26
      ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.scss
  82. 2
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts
  83. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  84. 143
      ui-ngx/src/assets/locale/locale.constant-es_ES.json
  85. 4
      ui-ngx/src/styles.scss

4
application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java

@ -32,7 +32,9 @@ import java.util.Arrays;
"org.thingsboard.server.dao",
"org.thingsboard.server.common.stats",
"org.thingsboard.server.common.transport.config.ssl",
"org.thingsboard.server.cache"})
"org.thingsboard.server.cache",
"org.thingsboard.server.springfox"
})
public class ThingsboardInstallApplication {
private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";

15
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -181,13 +181,18 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
@Autowired
private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**")
.permitAll()
);
// http.authorizeHttpRequests((authorizeHttpRequests) ->
// authorizeHttpRequests
// .antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**")
// .permitAll()
// );
http.headers().cacheControl().and().frameOptions().disable()
.and()
.cors()

12
application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java

@ -378,7 +378,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
}
List<ListenableFuture<Void>> futures = new ArrayList<>();
for (EntityView entityView : entityViews) {
ListenableFuture<Boolean> future = relationService.checkRelation(tenantId, edge.getId(), entityView.getId(),
ListenableFuture<Boolean> future = relationService.checkRelationAsync(tenantId, edge.getId(), entityView.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE);
futures.add(Futures.transformAsync(future, result -> {
if (Boolean.TRUE.equals(result)) {
@ -413,11 +413,11 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
}
private ListenableFuture<Void> saveEdgeEvent(TenantId tenantId,
EdgeId edgeId,
EdgeEventType type,
EdgeEventActionType action,
EntityId entityId,
JsonNode body) {
EdgeId edgeId,
EdgeEventType type,
EdgeEventActionType action,
EntityId entityId,
JsonNode body) {
log.trace("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]",
tenantId, edgeId, type, action, entityId, body);

6
application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java

@ -32,7 +32,7 @@ import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.action.EntityActionService;
@ -50,7 +50,7 @@ public class AlarmsCleanUpService {
@Value("${sql.ttl.alarms.removal_batch_size}")
private Integer removalBatchSize;
private final TenantDao tenantDao;
private final TenantService tenantService;
private final AlarmDao alarmDao;
private final AlarmService alarmService;
private final RelationService relationService;
@ -64,7 +64,7 @@ public class AlarmsCleanUpService {
PageLink removalBatchRequest = new PageLink(removalBatchSize, 0 );
PageData<TenantId> tenantsIds;
do {
tenantsIds = tenantDao.findTenantsIds(tenantsBatchRequest);
tenantsIds = tenantService.findTenantsIds(tenantsBatchRequest);
for (TenantId tenantId : tenantsIds.getData()) {
if (!partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
continue;

6
application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java

@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.dao.rpc.RpcDao;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -43,7 +43,7 @@ public class RpcCleanUpService {
@Value("${sql.ttl.rpc.enabled}")
private boolean ttlTaskExecutionEnabled;
private final TenantDao tenantDao;
private final TenantService tenantService;
private final PartitionService partitionService;
private final TbTenantProfileCache tenantProfileCache;
private final RpcDao rpcDao;
@ -54,7 +54,7 @@ public class RpcCleanUpService {
PageLink tenantsBatchRequest = new PageLink(10_000, 0);
PageData<TenantId> tenantsIds;
do {
tenantsIds = tenantDao.findTenantsIds(tenantsBatchRequest);
tenantsIds = tenantService.findTenantsIds(tenantsBatchRequest);
for (TenantId tenantId : tenantsIds.getData()) {
if (!partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
continue;

60
application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java

@ -0,0 +1,60 @@
/**
* 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.springfox;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import org.thingsboard.server.queue.util.TbCoreComponent;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;
@Component
//TODO: remove after fixing issue https://github.com/springfox/springfox/issues/3462 or after migration from springfox to springdoc
public class SpringfoxHandlerProviderBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebMvcRequestHandlerProvider) {
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
}
return bean;
}
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
List<T> copy = mappings.stream()
.filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
mappings.clear();
mappings.addAll(copy);
}
@SuppressWarnings("unchecked")
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
try {
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
field.setAccessible(true);
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}

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

@ -394,6 +394,9 @@ cache:
tenantProfiles:
timeToLiveInMinutes: "${CACHE_SPECS_TENANT_PROFILES_TTL:1440}"
maxSize: "${CACHE_SPECS_TENANT_PROFILES_MAX_SIZE:10000}"
tenants:
timeToLiveInMinutes: "${CACHE_SPECS_TENANTS_TTL:1440}"
maxSize: "${CACHE_SPECS_TENANTS_MAX_SIZE:10000}"
deviceProfiles:
timeToLiveInMinutes: "${CACHE_SPECS_DEVICE_PROFILES_TTL:1440}"
maxSize: "${CACHE_SPECS_DEVICE_PROFILES_MAX_SIZE:10000}"

2
application/src/test/resources/application-test.properties

@ -55,3 +55,5 @@ queue.rule-engine.queues[2].partitions=2
queue.rule-engine.queues[2].processing-strategy.retries=1
queue.rule-engine.queues[2].processing-strategy.pause-between-retries=0
queue.rule-engine.queues[2].processing-strategy.max-pause-between-retries=0
usage.stats.report.enabled=false

41
common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java

@ -21,24 +21,32 @@ import org.springframework.cache.support.NullValue;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.jedis.JedisClusterConnection;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.util.JedisClusterCRC16;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Slf4j
public abstract class RedisTbTransactionalCache<K extends Serializable, V extends Serializable> implements TbTransactionalCache<K, V> {
private static final byte[] BINARY_NULL_VALUE = RedisSerializer.java().serialize(NullValue.INSTANCE);
static final JedisPool MOCK_POOL = new JedisPool(); //non-null pool required for JedisConnection to trigger closing jedis connection
@Getter
private final String cacheName;
private final RedisConnectionFactory connectionFactory;
private final JedisConnectionFactory connectionFactory;
private final RedisSerializer<String> keySerializer = StringRedisSerializer.UTF_8;
private final RedisSerializer<V> valueSerializer;
private final Expiration evictExpiration;
@ -50,15 +58,15 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
TBRedisCacheConfiguration configuration,
RedisSerializer<V> valueSerializer) {
this.cacheName = cacheName;
this.connectionFactory = connectionFactory;
this.connectionFactory = (JedisConnectionFactory) connectionFactory;
this.valueSerializer = valueSerializer;
this.evictExpiration = Expiration.from(configuration.getEvictTtlInMs(), TimeUnit.MILLISECONDS);
if (cacheSpecsMap.getSpecs() != null && cacheSpecsMap.getSpecs().get(cacheName) != null) {
CacheSpecs cacheSpecs = cacheSpecsMap.getSpecs().get(cacheName);
this.cacheTtl = Expiration.from(cacheSpecs.getTimeToLiveInMinutes(), TimeUnit.MINUTES);
} else {
this.cacheTtl = Expiration.persistent();
}
this.cacheTtl = Optional.ofNullable(cacheSpecsMap)
.map(CacheSpecsMap::getSpecs)
.map(x -> x.get(cacheName))
.map(CacheSpecs::getTimeToLiveInMinutes)
.map(t -> Expiration.from(t, TimeUnit.MINUTES))
.orElseGet(Expiration::persistent);
}
@Override
@ -130,8 +138,23 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
return new RedisTbCacheTransaction<>(this, connection);
}
private RedisConnection getConnection(byte[] rawKey) {
if (!connectionFactory.isRedisClusterAware()) {
return connectionFactory.getConnection();
}
RedisConnection connection = connectionFactory.getClusterConnection();
int slotNum = JedisClusterCRC16.getSlot(rawKey);
Jedis jedis = ((JedisClusterConnection) connection).getNativeConnection().getConnectionFromSlot(slotNum);
JedisConnection jedisConnection = new JedisConnection(jedis, MOCK_POOL, jedis.getDB());
jedisConnection.setConvertPipelineAndTxResults(connectionFactory.getConvertPipelineAndTxResults());
return jedisConnection;
}
private RedisConnection watch(byte[][] rawKeysList) {
var connection = connectionFactory.getConnection();
RedisConnection connection = getConnection(rawKeysList[0]);
try {
connection.watch(rawKeysList);
connection.multi();

6
common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java

@ -39,6 +39,12 @@ public interface TbTransactionalCache<K extends Serializable, V extends Serializ
TbCacheTransaction<K, V> newTransactionForKey(K key);
/**
* Note that all keys should be in the same cache slot for redis. You may control the cache slot using '{}' bracers.
* See CLUSTER KEYSLOT command for more details.
* @param keys - list of keys to use
* @return transaction object
*/
TbCacheTransaction<K, V> newTransactionForKeys(List<K> keys);
default V getAndPutInTransaction(K key, Supplier<V> dbCall, boolean cacheNullValue) {

2
common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java

@ -72,6 +72,8 @@ public interface EntityViewService {
ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId);
List<EntityView> findEntityViewsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId);
void deleteEntityView(TenantId tenantId, EntityViewId entityViewId);
void deleteEntityViewsByTenantId(TenantId tenantId);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java

@ -35,7 +35,9 @@ import java.util.List;
*/
public interface RelationService {
ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<Boolean> checkRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
boolean checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
EntityRelation getRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java

@ -35,6 +35,8 @@ public interface TenantService {
Tenant saveTenant(Tenant tenant);
boolean tenantExists(TenantId tenantId);
void deleteTenant(TenantId tenantId);
PageData<Tenant> findTenants(PageLink pageLink);
@ -44,4 +46,6 @@ public interface TenantService {
List<TenantId> findTenantIdsByTenantProfileId(TenantProfileId tenantProfileId);
void deleteTenants();
PageData<TenantId> findTenantsIds(PageLink pageLink);
}

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

@ -26,6 +26,7 @@ public class CacheConstants {
public static final String CLAIM_DEVICES_CACHE = "claimDevices";
public static final String SECURITY_SETTINGS_CACHE = "securitySettings";
public static final String TENANT_PROFILE_CACHE = "tenantProfiles";
public static final String TENANTS_CACHE = "tenants";
public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
public static final String ATTRIBUTES_CACHE = "attributes";
public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime";

14
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -23,8 +23,6 @@ import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
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.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
@ -53,7 +51,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
@ -160,14 +157,9 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
deleteEntityRelations(tenantId, assetId);
Asset asset = assetDao.findById(tenantId, assetId.getId());
try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(asset.getTenantId(), assetId).get();
if (entityViews != null && !entityViews.isEmpty()) {
throw new DataValidationException("Can't delete asset that has entity views!");
}
} catch (ExecutionException | InterruptedException e) {
log.error("Exception while finding entity views for assetId [{}]", assetId, e);
throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e);
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(asset.getTenantId(), assetId);
if (entityViews != null && !entityViews.isEmpty()) {
throw new DataValidationException("Can't delete asset that has entity views!");
}
publishEvictEvent(new AssetCacheEvictEvent(asset.getTenantId(), asset.getName(), null));

2
dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeCacheKey.java

@ -34,6 +34,6 @@ public class AttributeCacheKey implements Serializable {
@Override
public String toString() {
return entityId + "_" + scope + "_" + key;
return "{" + entityId + "}" + scope + "_" + key;
}
}

4
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -31,7 +31,6 @@ import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
@ -64,9 +63,6 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
@Autowired
private DeviceService deviceService;
@Autowired
private EntityViewService entityViewService;
@Autowired
private DashboardService dashboardService;

20
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -58,7 +58,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired
private CustomerDao customerDao;
@Autowired
private EdgeDao edgeDao;
@ -289,16 +289,16 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
private PaginatedRemover<TenantId, DashboardInfo> tenantDashboardsRemover =
new PaginatedRemover<TenantId, DashboardInfo>() {
@Override
protected PageData<DashboardInfo> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return dashboardInfoDao.findDashboardsByTenantId(id.getId(), pageLink);
}
@Override
protected PageData<DashboardInfo> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return dashboardInfoDao.findDashboardsByTenantId(id.getId(), pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, DashboardInfo entity) {
deleteDashboard(tenantId, new DashboardId(entity.getUuidId()));
}
};
@Override
protected void removeEntity(TenantId tenantId, DashboardInfo entity) {
deleteDashboard(tenantId, new DashboardId(entity.getUuidId()));
}
};
private class CustomerDashboardsUnassigner extends PaginatedRemover<Customer, DashboardInfo> {

6
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -19,8 +19,6 @@ import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
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.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
@ -47,8 +45,6 @@ import org.thingsboard.server.dao.service.Validator;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -76,8 +72,6 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
@Autowired
private QueueService queueService;
private final Lock findOrCreateLock = new ReentrantLock();
@TransactionalEventListener(classes = DeviceProfileEvictEvent.class)
@Override
public void handleEvictEvent(DeviceProfileEvictEvent event) {

23
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -78,7 +78,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
@ -313,14 +312,9 @@ public class DeviceServiceImpl extends AbstractCachedEntityService<DeviceCacheKe
Device device = deviceDao.findById(tenantId, deviceId.getId());
DeviceCacheEvictEvent deviceCacheEvictEvent = new DeviceCacheEvictEvent(device.getTenantId(), device.getId(), device.getName(), null);
try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), deviceId).get();
if (entityViews != null && !entityViews.isEmpty()) {
throw new DataValidationException("Can't delete device that has entity views!");
}
} catch (ExecutionException | InterruptedException e) {
log.error("Exception while finding entity views for deviceId [{}]", deviceId, e);
throw new RuntimeException("Exception while finding entity views for deviceId [" + deviceId + "]", e);
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(device.getTenantId(), deviceId);
if (entityViews != null && !entityViews.isEmpty()) {
throw new DataValidationException("Can't delete device that has entity views!");
}
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, deviceId);
@ -523,14 +517,9 @@ public class DeviceServiceImpl extends AbstractCachedEntityService<DeviceCacheKe
@Override
public Device assignDeviceToTenant(TenantId tenantId, Device device) {
log.trace("Executing assignDeviceToTenant [{}][{}]", tenantId, device);
try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), device.getId()).get();
if (!CollectionUtils.isEmpty(entityViews)) {
throw new DataValidationException("Can't assign device that has entity views to another tenant!");
}
} catch (ExecutionException | InterruptedException e) {
log.error("Exception while finding entity views for deviceId [{}]", device.getId(), e);
throw new RuntimeException("Exception while finding entity views for deviceId [" + device.getId() + "]", e);
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(device.getTenantId(), device.getId());
if (!CollectionUtils.isEmpty(entityViews)) {
throw new DataValidationException("Can't assign device that has entity views to another tenant!");
}
eventService.removeEvents(device.getTenantId(), device.getId());

2
dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java

@ -26,8 +26,6 @@ import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
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.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;

22
dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java

@ -84,20 +84,16 @@ public abstract class AbstractEntityService {
}
protected void checkAssignedEntityViewsToEdge(TenantId tenantId, EntityId entityId, EdgeId edgeId) {
try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId).get();
if (entityViews != null && !entityViews.isEmpty()) {
EntityView entityView = entityViews.get(0);
// TODO: @voba - refactor this blocking operation
Boolean relationExists = relationService.checkRelation(tenantId, edgeId, entityView.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE).get();
if (relationExists) {
throw new DataValidationException("Can't unassign device/asset from edge that is related to entity view and entity view is assigned to edge!");
}
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(tenantId, entityId);
if (entityViews != null && !entityViews.isEmpty()) {
EntityView entityView = entityViews.get(0);
Boolean relationExists = relationService.checkRelation(
tenantId, edgeId, entityView.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE
);
if (relationExists) {
throw new DataValidationException("Can't unassign device/asset from edge that is related to entity view and entity view is assigned to edge!");
}
} catch (Exception e) {
log.error("[{}] Exception while finding entity views for entityId [{}]", tenantId, entityId, e);
throw new RuntimeException("Exception while finding entity views for entityId [" + entityId + "]", e);
}
}

28
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java

@ -22,8 +22,6 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
@ -53,7 +51,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -68,7 +65,6 @@ import static org.thingsboard.server.dao.service.Validator.validateString;
public class EntityViewServiceImpl extends AbstractCachedEntityService<EntityViewCacheKey, EntityViewCacheValue, EntityViewEvictEvent> implements EntityViewService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String INCORRECT_PAGE_LINK = "Incorrect page link ";
public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
public static final String INCORRECT_ENTITY_VIEW_ID = "Incorrect entityViewId ";
public static final String INCORRECT_EDGE_ID = "Incorrect edgeId ";
@ -280,6 +276,17 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService<EntityVie
EntityViewCacheValue::getEntityViews, v -> new EntityViewCacheValue(null, v), true));
}
@Override
public List<EntityView> findEntityViewsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId) {
log.trace("Executing findEntityViewsByTenantIdAndEntityId, tenantId [{}], entityId [{}]", tenantId, entityId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(entityId.getId(), "Incorrect entityId" + entityId);
return cache.getAndPutInTransaction(EntityViewCacheKey.byEntityId(tenantId, entityId),
() -> entityViewDao.findEntityViewsByTenantIdAndEntityId(tenantId.getId(), entityId.getId()),
EntityViewCacheValue::getEntityViews, v -> new EntityViewCacheValue(null, v), true);
}
@Override
public void deleteEntityView(TenantId tenantId, EntityViewId entityViewId) {
log.trace("Executing deleteEntityView [{}]", entityViewId);
@ -320,15 +327,10 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService<EntityVie
throw new DataValidationException("Can't assign entityView to edge from different tenant!");
}
try {
Boolean relationExists = relationService.checkRelation(tenantId, edgeId, entityView.getEntityId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE).get();
if (!relationExists) {
throw new DataValidationException("Can't assign entity view to edge because related device/asset doesn't assigned to edge!");
}
} catch (ExecutionException | InterruptedException e) {
log.error("Exception during relation check", e);
throw new RuntimeException("Exception during relation check", e);
Boolean relationExists = relationService.checkRelation(tenantId, edgeId, entityView.getEntityId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE);
if (!relationExists) {
throw new DataValidationException("Can't assign entity view to edge because related device/asset doesn't assigned to edge!");
}
try {

8
dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java

@ -22,8 +22,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
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.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.OtaPackage;
@ -42,8 +40,6 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -248,8 +244,4 @@ public class BaseOtaPackageService extends AbstractCachedEntityService<OtaPackag
}
}
private static List<OtaPackageId> toOtaPackageInfoKey(OtaPackageId otaPackageId) {
return Collections.singletonList(otaPackageId);
}
}

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

@ -28,8 +28,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.StringUtils;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
@ -90,7 +90,14 @@ public class BaseRelationService implements RelationService {
}
@Override
public ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
public ListenableFuture<Boolean> checkRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
log.trace("Executing checkRelationAsync [{}][{}][{}][{}]", from, to, relationType, typeGroup);
validate(from, to, relationType, typeGroup);
return relationDao.checkRelationAsync(tenantId, from, to, relationType, typeGroup);
}
@Override
public boolean checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
log.trace("Executing checkRelation [{}][{}][{}][{}]", from, to, relationType, typeGroup);
validate(from, to, relationType, typeGroup);
return relationDao.checkRelation(tenantId, from, to, relationType, typeGroup);

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

@ -42,7 +42,9 @@ public interface RelationDao {
List<EntityRelation> findAllByToAndType(TenantId tenantId, EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<Boolean> checkRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
boolean checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
EntityRelation getRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);

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

@ -70,7 +70,9 @@ import java.util.Set;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.TENANT;
import static org.thingsboard.server.dao.service.Validator.*;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
import static org.thingsboard.server.dao.service.Validator.validateString;
/**
* Created by igor on 3/12/18.

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java

@ -18,18 +18,17 @@ package org.thingsboard.server.dao.service.validator;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
@Component
@AllArgsConstructor
public class AlarmDataValidator extends DataValidator<Alarm> {
private final TenantDao tenantDao;
private final TenantService tenantService;
@Override
protected void validateDataImpl(TenantId tenantId, Alarm alarm) {
@ -48,8 +47,7 @@ public class AlarmDataValidator extends DataValidator<Alarm> {
if (alarm.getTenantId() == null) {
throw new DataValidationException("Alarm should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(alarm.getTenantId(), alarm.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(alarm.getTenantId())) {
throw new DataValidationException("Alarm is referencing to non-existent tenant!");
}
}

14
dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java

@ -15,29 +15,29 @@
*/
package org.thingsboard.server.dao.service.validator;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
@Component
@AllArgsConstructor
public class ApiUsageDataValidator extends DataValidator<ApiUsageState> {
private final TenantDao tenantDao;
@Lazy
@Autowired
private TenantService tenantService;
@Override
protected void validateDataImpl(TenantId requestTenantId, ApiUsageState apiUsageState) {
if (apiUsageState.getTenantId() == null) {
throw new DataValidationException("ApiUsageState should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(requestTenantId, apiUsageState.getTenantId().getId());
if (tenant == null && !requestTenantId.equals(TenantId.SYS_TENANT_ID)) {
if (!tenantService.tenantExists(apiUsageState.getTenantId()) && !requestTenantId.equals(TenantId.SYS_TENANT_ID)) {
throw new DataValidationException("ApiUsageState is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java

@ -21,7 +21,6 @@ import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -32,7 +31,7 @@ import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -43,7 +42,7 @@ public class AssetDataValidator extends DataValidator<Asset> {
private AssetDao assetDao;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
private CustomerDao customerDao;
@ -82,8 +81,7 @@ public class AssetDataValidator extends DataValidator<Asset> {
if (asset.getTenantId() == null) {
throw new DataValidationException("Asset should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(tenantId, asset.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(asset.getTenantId())) {
throw new DataValidationException("Asset is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java

@ -20,18 +20,17 @@ import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.dao.device.DeviceProfileDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Objects;
public abstract class BaseOtaPackageDataValidator<D extends BaseData<?>> extends DataValidator<D> {
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
private DeviceProfileDao deviceProfileDao;
@ -40,8 +39,7 @@ public abstract class BaseOtaPackageDataValidator<D extends BaseData<?>> extends
if (otaPackageInfo.getTenantId() == null) {
throw new DataValidationException("OtaPackage should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(otaPackageInfo.getTenantId(), otaPackageInfo.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(otaPackageInfo.getTenantId())) {
throw new DataValidationException("OtaPackage is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java

@ -21,7 +21,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerDao;
@ -29,7 +28,7 @@ import org.thingsboard.server.dao.customer.CustomerServiceImpl;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Optional;
@ -40,7 +39,7 @@ public class CustomerDataValidator extends DataValidator<Customer> {
private CustomerDao customerDao;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
@Lazy
@ -87,8 +86,7 @@ public class CustomerDataValidator extends DataValidator<Customer> {
if (customer.getTenantId() == null) {
throw new DataValidationException("Customer should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(tenantId, customer.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(customer.getTenantId())) {
throw new DataValidationException("Customer is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java

@ -21,14 +21,13 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.dashboard.DashboardDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
@Component
public class DashboardDataValidator extends DataValidator<Dashboard> {
@ -37,7 +36,7 @@ public class DashboardDataValidator extends DataValidator<Dashboard> {
private DashboardDao dashboardDao;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
@Lazy
@ -59,8 +58,7 @@ public class DashboardDataValidator extends DataValidator<Dashboard> {
if (dashboard.getTenantId() == null) {
throw new DataValidationException("Dashboard should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(tenantId, dashboard.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(dashboard.getTenantId())) {
throw new DataValidationException("Dashboard is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java

@ -23,7 +23,6 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -35,7 +34,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Optional;
@ -48,7 +47,7 @@ public class DeviceDataValidator extends DataValidator<Device> {
private DeviceDao deviceDao;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
private CustomerDao customerDao;
@ -85,8 +84,7 @@ public class DeviceDataValidator extends DataValidator<Device> {
if (device.getTenantId() == null) {
throw new DataValidationException("Device should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(device.getTenantId(), device.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(device.getTenantId())) {
throw new DataValidationException("Device is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java

@ -37,7 +37,6 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration;
@ -67,7 +66,7 @@ import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.HashSet;
import java.util.List;
@ -90,7 +89,7 @@ public class DeviceProfileDataValidator extends DataValidator<DeviceProfile> {
@Autowired
private DeviceDao deviceDao;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
@Lazy
private QueueService queueService;
@ -119,8 +118,7 @@ public class DeviceProfileDataValidator extends DataValidator<DeviceProfile> {
if (deviceProfile.getTenantId() == null) {
throw new DataValidationException("Device profile should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(deviceProfile.getTenantId(), deviceProfile.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(deviceProfile.getTenantId())) {
throw new DataValidationException("Device profile is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java

@ -19,7 +19,6 @@ import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -27,7 +26,7 @@ import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.edge.EdgeDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -36,7 +35,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
public class EdgeDataValidator extends DataValidator<Edge> {
private final EdgeDao edgeDao;
private final TenantDao tenantDao;
private final TenantService tenantService;
private final CustomerDao customerDao;
@Override
@ -65,8 +64,7 @@ public class EdgeDataValidator extends DataValidator<Edge> {
if (edge.getTenantId() == null) {
throw new DataValidationException("Edge should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(edge.getTenantId(), edge.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(edge.getTenantId())) {
throw new DataValidationException("Edge is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java

@ -20,14 +20,13 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entityview.EntityViewDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -36,7 +35,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
public class EntityViewDataValidator extends DataValidator<EntityView> {
private final EntityViewDao entityViewDao;
private final TenantDao tenantDao;
private final TenantService tenantService;
private final CustomerDao customerDao;
@Override
@ -69,8 +68,7 @@ public class EntityViewDataValidator extends DataValidator<EntityView> {
if (entityView.getTenantId() == null) {
throw new DataValidationException("Entity view should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(tenantId, entityView.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(entityView.getTenantId())) {
throw new DataValidationException("Entity view is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java

@ -20,7 +20,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.exception.DataValidationException;
@ -28,7 +27,7 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.resource.TbResourceDao;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE;
@ -39,7 +38,7 @@ public class ResourceDataValidator extends DataValidator<TbResource> {
private TbResourceDao resourceDao;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
@Lazy
@ -73,8 +72,7 @@ public class ResourceDataValidator extends DataValidator<TbResource> {
resource.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
}
if (!resource.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
Tenant tenant = tenantDao.findById(tenantId, resource.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(resource.getTenantId())) {
throw new DataValidationException("Resource is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java

@ -20,7 +20,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
@ -30,7 +29,7 @@ import org.thingsboard.server.dao.rule.RuleChainDao;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
@Component
public class RuleChainDataValidator extends DataValidator<RuleChain> {
@ -43,7 +42,7 @@ public class RuleChainDataValidator extends DataValidator<RuleChain> {
private RuleChainService ruleChainService;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
@Lazy
@ -68,8 +67,7 @@ public class RuleChainDataValidator extends DataValidator<RuleChain> {
if (ruleChain.getTenantId() == null || ruleChain.getTenantId().isNullUid()) {
throw new DataValidationException("Rule chain should be assigned to tenant!");
}
Tenant tenant = tenantDao.findById(tenantId, ruleChain.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(ruleChain.getTenantId())) {
throw new DataValidationException("Rule chain is referencing to non-existent tenant!");
}
if (ruleChain.isRoot() && RuleChainType.CORE.equals(ruleChain.getType())) {

13
dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java

@ -21,7 +21,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -32,7 +31,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserDao;
import org.thingsboard.server.dao.user.UserService;
@ -46,9 +45,6 @@ public class UserDataValidator extends DataValidator<User> {
@Lazy
private UserService userService;
@Autowired
private TenantDao tenantDao;
@Autowired
private CustomerDao customerDao;
@ -56,6 +52,10 @@ public class UserDataValidator extends DataValidator<User> {
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Autowired
@Lazy
private TenantService tenantService;
@Override
protected void validateCreate(TenantId tenantId, User user) {
if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
@ -119,8 +119,7 @@ public class UserDataValidator extends DataValidator<User> {
+ " already present in database!");
}
if (!tenantId.getId().equals(ModelConstants.NULL_UUID)) {
Tenant tenant = tenantDao.findById(tenantId, user.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(user.getTenantId())) {
throw new DataValidationException("User is referencing to non-existent tenant!");
}
}

6
dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java

@ -27,6 +27,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.widget.WidgetTypeDao;
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
@ -35,8 +36,8 @@ import org.thingsboard.server.dao.widget.WidgetsBundleDao;
public class WidgetTypeDataValidator extends DataValidator<WidgetTypeDetails> {
private final WidgetTypeDao widgetTypeDao;
private final TenantDao tenantDao;
private final WidgetsBundleDao widgetsBundleDao;
private final TenantService tenantService;
@Override
protected void validateDataImpl(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) {
@ -53,8 +54,7 @@ public class WidgetTypeDataValidator extends DataValidator<WidgetTypeDetails> {
widgetTypeDetails.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
}
if (!widgetTypeDetails.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
Tenant tenant = tenantDao.findById(tenantId, widgetTypeDetails.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(widgetTypeDetails.getTenantId())) {
throw new DataValidationException("Widget type is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java

@ -18,13 +18,12 @@ package org.thingsboard.server.dao.service.validator;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
@Component
@ -32,7 +31,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleDao;
public class WidgetsBundleDataValidator extends DataValidator<WidgetsBundle> {
private final WidgetsBundleDao widgetsBundleDao;
private final TenantDao tenantDao;
private final TenantService tenantService;
@Override
protected void validateDataImpl(TenantId tenantId, WidgetsBundle widgetsBundle) {
@ -43,8 +42,7 @@ public class WidgetsBundleDataValidator extends DataValidator<WidgetsBundle> {
widgetsBundle.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
}
if (!widgetsBundle.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
Tenant tenant = tenantDao.findById(tenantId, widgetsBundle.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(widgetsBundle.getTenantId())) {
throw new DataValidationException("Widgets bundle is referencing to non-existent tenant!");
}
}

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

@ -115,9 +115,14 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
}
@Override
public ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
public ListenableFuture<Boolean> checkRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
return service.submit(() -> checkRelation(tenantId, from, to, relationType, typeGroup));
}
@Override
public boolean checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
RelationCompositeKey key = getRelationCompositeKey(from, to, relationType, typeGroup);
return service.submit(() -> relationRepository.existsById(key));
return relationRepository.existsById(key);
}
@Override

45
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantCacheKey.java

@ -0,0 +1,45 @@
/**
* 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.tenant;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import java.io.Serializable;
@Data
@AllArgsConstructor
public class TenantCacheKey implements Serializable {
private static final long serialVersionUID = -121787454251592384L;
private final TenantId tenantId;
private final TenantCacheKeyPrefix keyPrefix;
public static TenantCacheKey fromId(TenantId tenantId) {
return new TenantCacheKey(tenantId, TenantCacheKeyPrefix.TENANT);
}
public static TenantCacheKey fromIdExists(TenantId tenantId) {
return new TenantCacheKey(tenantId, TenantCacheKeyPrefix.EXISTS);
}
public enum TenantCacheKeyPrefix {
TENANT, EXISTS
}
}

33
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantCaffeineCache.java

@ -0,0 +1,33 @@
/**
* 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.tenant;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.Tenant;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("TenantCache")
public class TenantCaffeineCache extends CaffeineTbTransactionalCache<TenantCacheKey, Tenant> {
public TenantCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.TENANTS_CACHE);
}
}

26
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantEvictEvent.java

@ -0,0 +1,26 @@
/**
* 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.tenant;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
@Data
public class TenantEvictEvent {
private final TenantId tenantId;
// for exists tenant cache
private final boolean isExistsTenant;
}

33
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsCaffeineCache.java

@ -0,0 +1,33 @@
/**
* 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.tenant;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.Tenant;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("TenantExistsCache")
public class TenantExistsCaffeineCache extends CaffeineTbTransactionalCache<TenantCacheKey, Boolean> {
public TenantExistsCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.TENANTS_CACHE);
}
}

35
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java

@ -0,0 +1,35 @@
/**
* 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.tenant;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.Tenant;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("TenantExistsCache")
public class TenantExistsRedisCache extends RedisTbTransactionalCache<TenantCacheKey, Boolean> {
public TenantExistsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.TENANTS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>());
}
}

35
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantRedisCache.java

@ -0,0 +1,35 @@
/**
* 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.tenant;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.Tenant;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("TenantCache")
public class TenantRedisCache extends RedisTbTransactionalCache<TenantCacheKey, Tenant> {
public TenantRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.TENANTS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>());
}
}

71
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -18,7 +18,11 @@ package org.thingsboard.server.dao.tenant;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
@ -31,8 +35,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.resource.ResourceService;
@ -52,7 +55,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Service
@Slf4j
public class TenantServiceImpl extends AbstractEntityService implements TenantService {
public class TenantServiceImpl extends AbstractCachedEntityService<TenantCacheKey, Tenant, TenantEvictEvent> implements TenantService {
private static final String DEFAULT_TENANT_REGION = "Global";
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
@ -64,6 +67,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
private TenantProfileService tenantProfileService;
@Autowired
@Lazy
private UserService userService;
@Autowired
@ -78,12 +82,10 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
@Autowired
private DeviceProfileService deviceProfileService;
@Lazy
@Autowired
private ApiUsageStateService apiUsageStateService;
@Autowired
private EntityViewService entityViewService;
@Autowired
private WidgetsBundleService widgetsBundleService;
@ -111,11 +113,26 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
@Autowired
private AdminSettingsService adminSettingsService;
@Autowired
protected TbTransactionalCache<TenantCacheKey, Boolean> existsTenantCache;
@TransactionalEventListener(classes = TenantEvictEvent.class)
@Override
public void handleEvictEvent(TenantEvictEvent event) {
TenantId tenantId = event.getTenantId();
cache.evict(TenantCacheKey.fromId(tenantId));
if (event.isExistsTenant()) {
existsTenantCache.evict(TenantCacheKey.fromIdExists(tenantId));
}
}
@Override
public Tenant findTenantById(TenantId tenantId) {
log.trace("Executing findTenantById [{}]", tenantId);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
return tenantDao.findById(tenantId, tenantId.getId());
return cache.getAndPutInTransaction(TenantCacheKey.fromId(tenantId),
() -> tenantDao.findById(tenantId, tenantId.getId()), true);
}
@Override
@ -127,12 +144,13 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
@Override
public ListenableFuture<Tenant> findTenantByIdAsync(TenantId callerId, TenantId tenantId) {
log.trace("Executing TenantIdAsync [{}]", tenantId);
log.trace("Executing findTenantByIdAsync [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
return tenantDao.findByIdAsync(callerId, tenantId.getId());
}
@Override
@Transactional
public Tenant saveTenant(Tenant tenant) {
log.trace("Executing saveTenant [{}]", tenant);
tenant.setRegion(DEFAULT_TENANT_REGION);
@ -142,6 +160,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
}
tenantValidator.validate(tenant, Tenant::getId);
Tenant savedTenant = tenantDao.save(tenant.getId(), tenant);
publishEvictEvent(new TenantEvictEvent(savedTenant.getId(), false));
if (tenant.getId() == null) {
deviceProfileService.createDefaultDeviceProfile(savedTenant.getId());
apiUsageStateService.createDefaultApiUsageState(savedTenant.getId(), null);
@ -150,6 +169,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
}
@Override
@Transactional(timeout = 60 * 60)
public void deleteTenant(TenantId tenantId) {
log.trace("Executing deleteTenant [{}]", tenantId);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
@ -170,6 +190,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
queueService.deleteQueuesByTenantId(tenantId);
adminSettingsService.deleteAdminSettingsByTenantId(tenantId);
tenantDao.removeById(tenantId, tenantId.getId());
publishEvictEvent(new TenantEvictEvent(tenantId, true));
deleteEntityRelations(tenantId, tenantId);
}
@ -199,17 +220,29 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
tenantsRemover.removeEntities(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID);
}
private PaginatedRemover<TenantId, Tenant> tenantsRemover =
new PaginatedRemover<>() {
@Override
public PageData<TenantId> findTenantsIds(PageLink pageLink) {
log.trace("Executing findTenantsIds");
Validator.validatePageLink(pageLink);
return tenantDao.findTenantsIds(pageLink);
}
@Override
protected PageData<Tenant> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return tenantDao.findTenants(tenantId, pageLink);
}
@Override
public boolean tenantExists(TenantId tenantId) {
return existsTenantCache.getAndPutInTransaction(TenantCacheKey.fromIdExists(tenantId),
() -> tenantDao.existsById(tenantId, tenantId.getId()), false);
}
@Override
protected void removeEntity(TenantId tenantId, Tenant entity) {
deleteTenant(TenantId.fromUUID(entity.getUuidId()));
}
};
private PaginatedRemover<TenantId, Tenant> tenantsRemover = new PaginatedRemover<>() {
@Override
protected PageData<Tenant> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return tenantDao.findTenants(tenantId, pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, Tenant entity) {
deleteTenant(TenantId.fromUUID(entity.getUuidId()));
}
};
}

10
dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java

@ -35,8 +35,8 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantProfileDao;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.ArrayList;
@ -52,16 +52,16 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
private final ApiUsageStateDao apiUsageStateDao;
private final TenantProfileDao tenantProfileDao;
private final TenantDao tenantDao;
private final TenantService tenantService;
private final TimeseriesService tsService;
private final DataValidator<ApiUsageState> apiUsageStateValidator;
public ApiUsageStateServiceImpl(ApiUsageStateDao apiUsageStateDao, TenantProfileDao tenantProfileDao,
TenantDao tenantDao, @Lazy TimeseriesService tsService,
TenantService tenantService, @Lazy TimeseriesService tsService,
DataValidator<ApiUsageState> apiUsageStateValidator) {
this.apiUsageStateDao = apiUsageStateDao;
this.tenantProfileDao = tenantProfileDao;
this.tenantDao = tenantDao;
this.tenantService = tenantService;
this.tsService = tsService;
this.apiUsageStateValidator = apiUsageStateValidator;
}
@ -118,7 +118,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) {
tenantId = (TenantId) entityId;
Tenant tenant = tenantDao.findById(tenantId, tenantId.getId());
Tenant tenant = tenantService.findTenantById(tenantId);
TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId());
TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration();

16
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Value;
@ -50,6 +51,7 @@ import static org.thingsboard.server.dao.service.Validator.validateString;
@Service
@Slf4j
@RequiredArgsConstructor
public class UserServiceImpl extends AbstractEntityService implements UserService {
public static final String USER_PASSWORD_HISTORY = "userPasswordHistory";
@ -73,20 +75,6 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
private final DataValidator<UserCredentials> userCredentialsValidator;
private final ApplicationEventPublisher eventPublisher;
public UserServiceImpl(UserDao userDao,
UserCredentialsDao userCredentialsDao,
UserAuthSettingsDao userAuthSettingsDao,
DataValidator<User> userValidator,
DataValidator<UserCredentials> userCredentialsValidator,
ApplicationEventPublisher eventPublisher) {
this.userDao = userDao;
this.userCredentialsDao = userCredentialsDao;
this.userAuthSettingsDao = userAuthSettingsDao;
this.userValidator = userValidator;
this.userCredentialsValidator = userCredentialsValidator;
this.eventPublisher = eventPublisher;
}
@Override
public User findUserByEmail(TenantId tenantId, String email) {
log.trace("Executing findUserByEmail [{}]", email);

4
dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java

@ -65,6 +65,7 @@ import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rpc.RpcService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
@ -170,6 +171,9 @@ public abstract class AbstractServiceTest {
@Autowired
protected OtaPackageService otaPackageService;
@Autowired
protected RpcService rpcService;
@Autowired
protected QueueService queueService;

36
dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java

@ -97,26 +97,26 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId));
createFirmware(tenantId, "1");
createAndSaveFirmware(tenantId, "1");
Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId));
thrown.expect(DataValidationException.class);
thrown.expectMessage(String.format("Failed to create the ota package, files size limit is exhausted %d bytes!", DATA_SIZE));
createFirmware(tenantId, "2");
createAndSaveFirmware(tenantId, "2");
}
@Test
public void sumDataSizeByTenantId() {
Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId));
createFirmware(tenantId, "0.1");
createAndSaveFirmware(tenantId, "0.1");
Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId));
int maxSumDataSize = 8;
List<OtaPackage> packages = new ArrayList<>(maxSumDataSize);
for (int i = 2; i <= maxSumDataSize; i++) {
packages.add(createFirmware(tenantId, "0." + i));
packages.add(createAndSaveFirmware(tenantId, "0." + i));
Assert.assertEquals(i, otaPackageService.sumDataSizeByTenantId(tenantId));
}
@ -419,15 +419,15 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@Test
public void testSaveFirmwareWithExistingTitleAndVersion() {
createFirmware(tenantId, VERSION);
createAndSaveFirmware(tenantId, VERSION);
thrown.expect(DataValidationException.class);
thrown.expectMessage("OtaPackage with such title and version already exists!");
createFirmware(tenantId, VERSION);
createAndSaveFirmware(tenantId, VERSION);
}
@Test
public void testDeleteFirmwareWithReferenceByDevice() {
OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
OtaPackage savedFirmware = createAndSaveFirmware(tenantId, VERSION);
Device device = new Device();
device.setTenantId(tenantId);
@ -448,7 +448,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@Test
public void testUpdateDeviceProfileId() {
OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
OtaPackage savedFirmware = createAndSaveFirmware(tenantId, VERSION);
try {
thrown.expect(DataValidationException.class);
@ -493,7 +493,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@Test
public void testFindFirmwareById() {
OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
OtaPackage savedFirmware = createAndSaveFirmware(tenantId, VERSION);
OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
Assert.assertNotNull(foundFirmware);
@ -519,7 +519,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@Test
public void testDeleteFirmware() {
OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
OtaPackage savedFirmware = createAndSaveFirmware(tenantId, VERSION);
OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
Assert.assertNotNull(foundFirmware);
@ -532,7 +532,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
public void testFindTenantFirmwaresByTenantId() {
List<OtaPackageInfo> firmwares = new ArrayList<>();
for (int i = 0; i < 165; i++) {
OtaPackageInfo info = new OtaPackageInfo(createFirmware(tenantId, VERSION + i));
OtaPackageInfo info = new OtaPackageInfo(createAndSaveFirmware(tenantId, VERSION + i));
info.setHasData(true);
firmwares.add(info);
}
@ -579,7 +579,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
public void testFindTenantFirmwaresByTenantIdAndHasData() {
List<OtaPackageInfo> firmwares = new ArrayList<>();
for (int i = 0; i < 165; i++) {
firmwares.add(new OtaPackageInfo(otaPackageService.saveOtaPackage(createFirmware(tenantId, VERSION + i))));
firmwares.add(new OtaPackageInfo(otaPackageService.saveOtaPackage(createAndSaveFirmware(tenantId, VERSION + i))));
}
OtaPackageInfo firmwareWithUrl = new OtaPackageInfo();
@ -695,7 +695,15 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
}
private OtaPackage createFirmware(TenantId tenantId, String version) {
private OtaPackage createAndSaveFirmware(TenantId tenantId, String version) {
return otaPackageService.saveOtaPackage(createFirmware(tenantId, version, deviceProfileId));
}
public static OtaPackage createFirmware(
TenantId tenantId,
String version,
DeviceProfileId deviceProfileId
) {
OtaPackage firmware = new OtaPackage();
firmware.setTenantId(tenantId);
firmware.setDeviceProfileId(deviceProfileId);
@ -708,6 +716,6 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmware.setDataSize(DATA_SIZE);
return otaPackageService.saveOtaPackage(firmware);
return firmware;
}
}

16
dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java

@ -57,13 +57,13 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
Assert.assertTrue(saveRelation(relation));
Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON));
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON));
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, parentId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, parentId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON));
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, parentId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, parentId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON));
}
@Test
@ -80,9 +80,9 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
Assert.assertTrue(relationService.deleteRelationAsync(SYSTEM_TENANT_ID, relationA).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON));
Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON));
Assert.assertTrue(relationService.deleteRelationAsync(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
}
@ -118,9 +118,9 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
relationService.deleteEntityRelations(SYSTEM_TENANT_ID, childId);
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON));
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON));
}
@Test

434
dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java

@ -17,26 +17,77 @@ package org.thingsboard.server.dao.service;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cache.TbCacheValueWrapper;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.edge.Edge;
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.rpc.Rpc;
import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.tenant.TenantCacheKey;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Objects;
import java.util.Set;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.assertj.core.api.Assertions.assertThat;
public abstract class BaseTenantServiceTest extends AbstractServiceTest {
private IdComparator<Tenant> idComparator = new IdComparator<>();
@SpyBean
protected TenantDao tenantDao;
@Autowired
protected TbTransactionalCache<TenantCacheKey, Tenant> cache;
@Autowired
protected TbTransactionalCache<TenantCacheKey, Boolean> existsTenantCache;
@Test
public void testSaveTenant() {
Tenant tenant = new Tenant();
@ -103,7 +154,6 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest {
@Test
public void testFindTenants() {
List<Tenant> tenants = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<Tenant> pageData = tenantService.findTenants(pageLink);
@ -275,4 +325,382 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest {
tenant.setTenantProfileId(isolatedTenantProfile.getId());
tenantService.saveTenant(tenant);
}
@Test
public void testGettingTenantAddingItToCache() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
Mockito.reset(tenantDao);
verify(tenantDao, Mockito.times(0)).findById(any(), any());
tenantService.findTenantById(savedTenant.getId());
verify(tenantDao, Mockito.times(1)).findById(eq(savedTenant.getId()), eq(savedTenant.getId().getId()));
var cachedTenant = cache.get(TenantCacheKey.fromId(savedTenant.getId()));
Assert.assertNotNull("Getting an existing Tenant doesn't add it to the cache!", cachedTenant);
Assert.assertEquals(savedTenant, cachedTenant.get());
for (int i = 0; i < 100; i++) {
tenantService.findTenantById(savedTenant.getId());
}
verify(tenantDao, Mockito.times(1)).findById(eq(savedTenant.getId()), eq(savedTenant.getId().getId()));
tenantService.deleteTenant(savedTenant.getId());
}
@Test
public void testExistsTenantAddingResultToCache() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
Mockito.reset(tenantDao);
//fromIdExists invoked from device profile validator
existsTenantCache.evict(TenantCacheKey.fromIdExists(savedTenant.getTenantId()));
verify(tenantDao, Mockito.times(0)).existsById(any(), any());
tenantService.tenantExists(savedTenant.getId());
verify(tenantDao, Mockito.times(1)).existsById(eq(savedTenant.getId()), eq(savedTenant.getId().getId()));
var isExists = existsTenantCache.get(TenantCacheKey.fromIdExists(savedTenant.getId()));
Assert.assertNotNull("Getting an existing Tenant doesn't add it to the cache!", isExists);
for (int i = 0; i < 100; i++) {
tenantService.tenantExists(savedTenant.getId());
}
verify(tenantDao, Mockito.times(1)).existsById(eq(savedTenant.getId()), eq(savedTenant.getId().getId()));
tenantService.deleteTenant(savedTenant.getId());
}
@Test
public void testUpdatingExistingTenantEvictCache() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
tenantService.findTenantById(savedTenant.getId());
var cachedTenant = cache.get(TenantCacheKey.fromId(savedTenant.getId()));
Assert.assertNotNull("Saving a Tenant doesn't add it to the cache!", cachedTenant);
Assert.assertEquals(savedTenant, cachedTenant.get());
savedTenant.setTitle("My new tenant");
savedTenant = tenantService.saveTenant(savedTenant);
Mockito.reset(tenantDao);
cachedTenant = cache.get(TenantCacheKey.fromId(savedTenant.getId()));
Assert.assertNull("Updating a Tenant doesn't evict the cache!", cachedTenant);
verify(tenantDao, Mockito.times(0)).findById(any(), any());
tenantService.findTenantById(savedTenant.getId());
verify(tenantDao, Mockito.times(1)).findById(eq(savedTenant.getId()), eq(savedTenant.getId().getId()));
tenantService.deleteTenant(savedTenant.getId());
}
@Test
public void testRemovingTenantEvictCache() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
tenantService.findTenantById(savedTenant.getId());
tenantService.tenantExists(savedTenant.getId());
var cachedTenant =
cache.get(TenantCacheKey.fromId(savedTenant.getId()));
var cachedExists =
existsTenantCache.get(TenantCacheKey.fromIdExists(savedTenant.getId()));
Assert.assertNotNull("Saving a Tenant doesn't add it to the cache!", cachedTenant);
Assert.assertNotNull("Saving a Tenant doesn't add it to the cache!", cachedExists);
tenantService.deleteTenant(savedTenant.getId());
cachedTenant =
cache.get(TenantCacheKey.fromId(savedTenant.getId()));
cachedExists =
existsTenantCache.get(TenantCacheKey.fromIdExists(savedTenant.getId()));
Assert.assertNull("Removing a Tenant doesn't evict the cache!", cachedTenant);
Assert.assertNull("Removing a Tenant doesn't evict the cache!", cachedExists);
}
@Test
public void testDeleteTenantDeletingAllRelatedEntities() throws Exception {
TenantProfile profile = createAndSaveTenantProfile();
Tenant tenant = createAndSaveTenant(profile);
User user = createAndSaveUserFor(tenant);
Customer customer = createAndSaveCustomerFor(tenant);
WidgetsBundle widgetsBundle = createAndSaveWidgetBundleFor(tenant);
DeviceProfile deviceProfile = createAndSaveDeviceProfileWithProfileDataFor(tenant);
Device device = createAndSaveDeviceFor(tenant, customer, deviceProfile);
EntityView entityView = createAndSaveEntityViewFor(tenant, customer, device);
Asset asset = createAndSaveAssetFor(tenant, customer);
Dashboard dashboard = createAndSaveDashboardFor(tenant, customer);
RuleChain ruleChain = createAndSaveRuleChainFor(tenant);
Edge edge = createAndSaveEdgeFor(tenant);
OtaPackage otaPackage = createAndSaveOtaPackageFor(tenant, deviceProfile);
TbResource resource = createAndSaveResourceFor(tenant);
Rpc rpc = createAndSaveRpcFor(tenant, device);
tenantService.deleteTenant(tenant.getId());
Assert.assertNull(tenantService.findTenantById(tenant.getId()));
assertCustomerIsDeleted(tenant, customer);
assertWidgetsBundleIsDeleted(tenant, widgetsBundle);
assertEntityViewIsDeleted(tenant, device, entityView);
assertAssetIsDeleted(tenant, asset);
assertDeviceIsDeleted(tenant, device);
assertDeviceProfileIsDeleted(tenant, deviceProfile);
assertDashboardIsDeleted(tenant, dashboard);
assertEdgeIsDeleted(tenant, edge);
assertTenantAdminIsDeleted(tenant);
assertUserIsDeleted(tenant, user);
Assert.assertNull(ruleChainService.findRuleChainById(tenant.getId(), ruleChain.getId()));
Assert.assertNull(apiUsageStateService.findTenantApiUsageState(tenant.getId()));
assertResourceIsDeleted(tenant, resource);
assertOtaPackageIsDeleted(tenant, otaPackage);
Assert.assertNull(rpcService.findById(tenant.getId(), rpc.getId()));
tenantProfileService.deleteTenantProfile(TenantId.SYS_TENANT_ID, profile.getId());
}
private void assertOtaPackageIsDeleted(Tenant tenant, OtaPackage otaPackage) {
assertThat(otaPackageService.findOtaPackageById(tenant.getId(), otaPackage.getId()))
.as("otaPackage").isNull();
PageLink pageLinkOta = new PageLink(1);
PageData<OtaPackageInfo> pageDataOta = otaPackageService.findTenantOtaPackagesByTenantId(tenant.getId(), pageLinkOta);
Assert.assertEquals(0, pageDataOta.getTotalElements());
}
private void assertResourceIsDeleted(Tenant tenant, TbResource resource) {
assertThat(resourceService.findResourceById(tenant.getId(), resource.getId()))
.as("resource").isNull();
PageLink pageLinkResources = new PageLink(1);
PageData<TbResourceInfo> tenantResources =
resourceService.findAllTenantResourcesByTenantId(tenant.getId(), pageLinkResources);
Assert.assertEquals(0, tenantResources.getTotalElements());
}
private void assertUserIsDeleted(Tenant tenant, User user) {
assertThat(userService.findUserById(tenant.getId(), user.getId()))
.as("user").isNull();
PageLink pageLinkUsers = new PageLink(1);
PageData<User> users =
userService.findUsersByTenantId(tenant.getId(), pageLinkUsers);
Assert.assertEquals(0, users.getTotalElements());
}
private void assertTenantAdminIsDeleted(Tenant savedTenant) {
PageLink pageLinkTenantAdmins = new PageLink(1);
PageData<User> tenantAdmins =
userService.findTenantAdmins(savedTenant.getId(), pageLinkTenantAdmins);
Assert.assertEquals(0, tenantAdmins.getTotalElements());
}
private void assertEdgeIsDeleted(Tenant tenant, Edge edge) {
assertThat(edgeService.findEdgeById(tenant.getId(), edge.getId()))
.as("edge").isNull();
PageLink pageLinkEdges = new PageLink(1);
PageData<Edge> edges = edgeService.findEdgesByTenantId(tenant.getId(), pageLinkEdges);
Assert.assertEquals(0, edges.getTotalElements());
}
private void assertDashboardIsDeleted(Tenant tenant, Dashboard dashboard) {
assertThat(dashboardService.findDashboardById(tenant.getId(), dashboard.getId()))
.as("dashboard").isNull();
PageLink pageLinkDashboards = new PageLink(1);
PageData<DashboardInfo> dashboards =
dashboardService.findDashboardsByTenantId(tenant.getId(), pageLinkDashboards);
Assert.assertEquals(0, dashboards.getTotalElements());
}
private void assertDeviceProfileIsDeleted(Tenant tenant, DeviceProfile deviceProfile) {
assertThat(deviceProfileService.findDeviceProfileById(tenant.getId(), deviceProfile.getId()))
.as("deviceProfile").isNull();
PageLink pageLinkDeviceProfiles = new PageLink(1);
PageData<DeviceProfile> profiles =
deviceProfileService.findDeviceProfiles(tenant.getId(), pageLinkDeviceProfiles);
Assert.assertEquals(0, profiles.getTotalElements());
}
private void assertDeviceIsDeleted(Tenant tenant, Device device) {
assertThat(deviceService.findDeviceById(tenant.getId(), device.getId()))
.as("device").isNull();
PageLink pageLinkDevices = new PageLink(1);
PageData<Device> devices =
deviceService.findDevicesByTenantId(tenant.getId(), pageLinkDevices);
Assert.assertEquals(0, devices.getTotalElements());
}
private void assertAssetIsDeleted(Tenant tenant, Asset asset) {
assertThat(assetService.findAssetById(tenant.getId(), asset.getId()))
.as("asset").isNull();
PageLink pageLinkAssets = new PageLink(1);
PageData<Asset> assets =
assetService.findAssetsByTenantId(tenant.getId(), pageLinkAssets);
Assert.assertEquals(0, assets.getTotalElements());
}
private void assertEntityViewIsDeleted(Tenant tenant, Device device, EntityView entityView) {
assertThat(entityViewService.findEntityViewById(tenant.getId(), entityView.getId()))
.as("entityView").isNull();
List<EntityView> entityViews =
entityViewService.findEntityViewsByTenantIdAndEntityId(tenant.getId(), device.getId());
Assert.assertTrue(entityViews.isEmpty());
}
private void assertWidgetsBundleIsDeleted(Tenant tenant, WidgetsBundle widgetsBundle) {
assertThat(widgetsBundleService.findWidgetsBundleById(tenant.getId(), widgetsBundle.getId()))
.as("widgetBundle").isNull();
List<WidgetsBundle> widgetsBundlesByTenantId =
widgetsBundleService.findAllTenantWidgetsBundlesByTenantId(tenant.getId());
Assert.assertTrue(widgetsBundlesByTenantId.isEmpty());
}
private void assertCustomerIsDeleted(Tenant tenant, Customer customer) {
assertThat(customerService.findCustomerById(tenant.getId(), customer.getId()))
.as("customer").isNull();
PageLink pageLinkCustomer = new PageLink(1);
PageData<Customer> pageDataCustomer = customerService
.findCustomersByTenantId(tenant.getId(), pageLinkCustomer);
Assert.assertEquals(0, pageDataCustomer.getTotalElements());
}
private Rpc createAndSaveRpcFor(Tenant tenant, Device device) {
Rpc rpc = new Rpc();
rpc.setTenantId(tenant.getId());
rpc.setDeviceId(device.getId());
rpc.setStatus(RpcStatus.QUEUED);
rpc.setRequest(JacksonUtil.toJsonNode("{}"));
return rpcService.save(rpc);
}
private TbResource createAndSaveResourceFor(Tenant tenant) {
TbResource resource = new TbResource();
resource.setTenantId(tenant.getId());
resource.setTitle("Test resource");
resource.setResourceType(ResourceType.LWM2M_MODEL);
resource.setFileName("filename.txt");
resource.setResourceKey("Test resource key");
resource.setData("Some super test data");
return resourceService.saveResource(resource);
}
private OtaPackage createAndSaveOtaPackageFor(Tenant tenant, DeviceProfile deviceProfile) {
return otaPackageService.saveOtaPackage(
BaseOtaPackageServiceTest.createFirmware(
tenant.getId(), "2", deviceProfile.getId())
);
}
private Edge createAndSaveEdgeFor(Tenant tenant) {
Edge edge = constructEdge(tenant.getId(), "Test edge", "Simple");
return edgeService.saveEdge(edge);
}
private RuleChain createAndSaveRuleChainFor(Tenant tenant) {
RuleChain ruleChain = new RuleChain();
ruleChain.setTenantId(tenant.getId());
ruleChain.setName("Test rule chain");
ruleChain.setType(RuleChainType.CORE);
return ruleChainService.saveRuleChain(ruleChain);
}
private Dashboard createAndSaveDashboardFor(Tenant tenant, Customer customer) {
Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenant.getId());
dashboard.setTitle("Test dashboard");
dashboard.setAssignedCustomers(Set.of(customer.toShortCustomerInfo()));
return dashboardService.saveDashboard(dashboard);
}
private Asset createAndSaveAssetFor(Tenant tenant, Customer customer) {
Asset asset = new Asset();
asset.setTenantId(tenant.getId());
asset.setCustomerId(customer.getId());
asset.setType("Test asset type");
asset.setName("Test asset type");
asset.setLabel("Test asset type");
return assetService.saveAsset(asset);
}
private EntityView createAndSaveEntityViewFor(Tenant tenant, Customer customer, Device device) {
EntityView entityView = new EntityView();
entityView.setEntityId(device.getId());
entityView.setTenantId(tenant.getId());
entityView.setCustomerId(customer.getId());
entityView.setType("Test type");
entityView.setName("Test entity view");
entityView.setStartTimeMs(0);
entityView.setEndTimeMs(840000);
return entityViewService.saveEntityView(entityView);
}
private Device createAndSaveDeviceFor(Tenant tenant, Customer customer, DeviceProfile deviceProfile) {
Device device = new Device();
device.setCustomerId(customer.getId());
device.setTenantId(tenant.getId());
device.setType("Test type");
device.setName("TestType");
device.setLabel("Test type");
device.setDeviceProfileId(deviceProfile.getId());
return deviceService.saveDevice(device);
}
private DeviceProfile createAndSaveDeviceProfileWithProfileDataFor(Tenant tenant) {
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setTenantId(tenant.getId());
deviceProfile.setTransportType(DeviceTransportType.MQTT);
deviceProfile.setName("Test device profile");
deviceProfile.setType(DeviceProfileType.DEFAULT);
DeviceProfileData profileData = new DeviceProfileData();
profileData.setTransportConfiguration(new MqttDeviceProfileTransportConfiguration());
deviceProfile.setProfileData(profileData);
return deviceProfileService.saveDeviceProfile(deviceProfile);
}
private WidgetsBundle createAndSaveWidgetBundleFor(Tenant tenant) {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTenantId(tenant.getId());
widgetsBundle.setTitle("Test widgets bundle");
widgetsBundle.setAlias("TestWidgetsBundle");
widgetsBundle.setDescription("Just a simple widgets bundle");
return widgetsBundleService.saveWidgetsBundle(widgetsBundle);
}
private Customer createAndSaveCustomerFor(Tenant tenant) {
Customer customer = new Customer();
customer.setTitle("Test customer");
customer.setTenantId(tenant.getId());
customer.setEmail("testCustomer@test.com");
return customerService.saveCustomer(customer);
}
private User createAndSaveUserFor(Tenant tenant) {
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setEmail("tenantAdmin@test.com");
user.setFirstName("tenantAdmin");
user.setLastName("tenantAdmin");
user.setTenantId(tenant.getId());
return userService.saveUser(user);
}
private Tenant createAndSaveTenant(TenantProfile tenantProfile) {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
tenant.setTenantProfileId(tenantProfile.getId());
return tenantService.saveTenant(tenant);
}
private TenantProfile createAndSaveTenantProfile() {
TenantProfile tenantProfile = new TenantProfile();
tenantProfile.setName("Test tenant profile");
return tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile);
}
}

3
dao/src/test/resources/application-test.properties

@ -35,6 +35,9 @@ cache.specs.entityViews.maxSize=100000
cache.specs.claimDevices.timeToLiveInMinutes=1440
cache.specs.claimDevices.maxSize=100000
cache.specs.tenants.timeToLiveInMinutes=1440
cache.specs.tenants.maxSize=100000
cache.specs.securitySettings.timeToLiveInMinutes=1440
cache.specs.securitySettings.maxSize=100000

40
msa/js-executor/api/jsInvokeMessageProcessor.js

@ -32,15 +32,17 @@ const maxActiveScripts = Number(config.get('script.max_active_scripts'));
const slowQueryLogMs = Number(config.get('script.slow_query_log_ms'));
const slowQueryLogBody = config.get('script.slow_query_log_body') === 'true';
const {performance} = require('perf_hooks');
const { performance } = require('node:perf_hooks');
function JsInvokeMessageProcessor(producer) {
this.producer = producer;
this.executor = new JsExecutor(useSandbox);
this.scriptMap = new Map();
this.scriptIds = [];
this.executedScriptIdsCounter = [];
this.executedScriptsCounter = 0;
this.lastStatTime = performance.now();
this.compilationTime = 0;
}
JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function (message) {
@ -125,7 +127,8 @@ JsInvokeMessageProcessor.prototype.processInvokeRequest = function (requestId, r
const msSinceLastStat = nowMs - this.lastStatTime;
const requestsPerSec = msSinceLastStat == 0 ? statFrequency : statFrequency / msSinceLastStat * 1000;
this.lastStatTime = nowMs;
logger.info('STAT[%s]: requests [%s], took [%s]ms, request/s [%s]', this.executedScriptsCounter, statFrequency, msSinceLastStat, requestsPerSec);
logger.info('STAT[%s]: requests [%s], took [%s]ms, request/s [%s], compilation [%s]ms', this.executedScriptsCounter, statFrequency, msSinceLastStat, requestsPerSec, this.compilationTime);
this.compilationTime = 0;
}
if (this.executedScriptsCounter % scriptBodyTraceFrequency == 0) {
@ -167,6 +170,7 @@ JsInvokeMessageProcessor.prototype.processReleaseRequest = function (requestId,
var index = this.scriptIds.indexOf(scriptId);
if (index > -1) {
this.scriptIds.splice(index, 1);
this.executedScriptIdsCounter.splice(index, 1);
}
this.scriptMap.delete(scriptId);
}
@ -198,14 +202,18 @@ JsInvokeMessageProcessor.prototype.getOrCompileScript = function (scriptId, scri
return new Promise(function (resolve, reject) {
const script = self.scriptMap.get(scriptId);
if (script) {
incrementUseScriptId.call(self, scriptId);
resolve(script);
} else {
const startTime = performance.now();
self.executor.compileScript(scriptBody).then(
(compiledScript) => {
self.compilationTime += (performance.now() - startTime);
self.cacheScript(scriptId, compiledScript);
resolve(compiledScript);
},
(err) => {
self.compilationTime += (performance.now() - startTime);
reject(err);
}
);
@ -216,11 +224,10 @@ JsInvokeMessageProcessor.prototype.getOrCompileScript = function (scriptId, scri
JsInvokeMessageProcessor.prototype.cacheScript = function (scriptId, script) {
if (!this.scriptMap.has(scriptId)) {
this.scriptIds.push(scriptId);
this.executedScriptIdsCounter.push(0);
while (this.scriptIds.length > maxActiveScripts) {
logger.info('Active scripts count [%s] exceeds maximum limit [%s]', this.scriptIds.length, maxActiveScripts);
const prevScriptId = this.scriptIds.shift();
logger.info('Removing active script with id [%s]', prevScriptId);
this.scriptMap.delete(prevScriptId);
deleteMinUsedScript.apply(this);
}
}
this.scriptMap.set(scriptId, script);
@ -291,4 +298,27 @@ function getScriptId(request) {
return Utils.toUUIDString(request.scriptIdMSB, request.scriptIdLSB);
}
function incrementUseScriptId(scriptId) {
const index = this.scriptIds.indexOf(scriptId);
if (this.executedScriptIdsCounter[index] < Number.MAX_SAFE_INTEGER) {
this.executedScriptIdsCounter[index]++;
}
}
function deleteMinUsedScript() {
let min = Infinity;
let minIndex = 0;
const scriptIdsLength = this.executedScriptIdsCounter.length - 1; // ignored last added script
for (let i = 0; i < scriptIdsLength; i++) {
if (this.executedScriptIdsCounter[i] < min) {
min = this.executedScriptIdsCounter[i];
minIndex = i;
}
}
const prevScriptId = this.scriptIds.splice(minIndex, 1)[0];
this.executedScriptIdsCounter.splice(minIndex, 1)
logger.info('Removing active script with id [%s]', prevScriptId);
this.scriptMap.delete(prevScriptId);
}
module.exports = JsInvokeMessageProcessor;

20
pom.xml

@ -39,12 +39,12 @@
<javax-annotation.version>1.3.2</javax-annotation.version>
<jakarta.xml.bind-api.version>2.3.2</jakarta.xml.bind-api.version>
<jaxb-runtime.version>2.3.2</jaxb-runtime.version>
<spring-boot.version>2.5.12</spring-boot.version>
<spring-data.version>2.5.10</spring-data.version>
<spring.version>5.3.18</spring.version>
<spring-redis.version>5.5.10</spring-redis.version>
<spring-security.version>5.6.2</spring-security.version>
<spring-data-redis.version>2.5.10</spring-data-redis.version>
<spring-boot.version>2.7.0</spring-boot.version>
<spring-data.version>2.7.0</spring-data.version>
<spring.version>5.3.20</spring.version>
<spring-redis.version>5.5.12</spring-redis.version>
<spring-security.version>5.7.1</spring-security.version>
<spring-data-redis.version>2.7.0</spring-data-redis.version>
<jedis.version>3.7.1</jedis.version>
<jjwt.version>0.7.0</jjwt.version>
<slf4j.version>1.7.32</slf4j.version>
@ -70,7 +70,7 @@
<json-schema-validator.version>2.2.6</json-schema-validator.version>
<californium.version>3.0.0</californium.version>
<leshan.version>2.0.0-M5</leshan.version>
<gson.version>2.6.2</gson.version>
<gson.version>2.9.0</gson.version>
<freemarker.version>2.3.30</freemarker.version>
<mail.version>1.6.2</mail.version>
<curator.version>4.2.0</curator.version>
@ -112,21 +112,21 @@
<ua-parser.version>1.4.3</ua-parser.version>
<commons-beanutils.version>1.9.4</commons-beanutils.version>
<commons-collections.version>3.2.2</commons-collections.version>
<micrometer.version>1.8.3</micrometer.version>
<micrometer.version>1.9.0</micrometer.version>
<protobuf-dynamic.version>1.0.3TB</protobuf-dynamic.version>
<wire-schema.version>3.4.0</wire-schema.version>
<twilio.version>8.17.0</twilio.version>
<hibernate-validator.version>6.0.20.Final</hibernate-validator.version>
<javax.el.version>3.0.0</javax.el.version>
<javax.validation-api.version>2.0.1.Final</javax.validation-api.version>
<antisamy.version>1.6.4</antisamy.version>
<antisamy.version>1.6.8</antisamy.version>
<snmp4j.version>2.8.5</snmp4j.version>
<!-- TEST SCOPE -->
<awaitility.version>4.1.0</awaitility.version>
<cassandra-unit.version>4.3.1.0</cassandra-unit.version>
<dbunit.version>2.7.2</dbunit.version>
<java-websocket.version>1.5.2</java-websocket.version>
<jupiter.version>5.7.2</jupiter.version> <!-- keep the same version as spring-boot-starter-test depend on jupiter-->
<jupiter.version>5.8.2</jupiter.version> <!-- keep the same version as spring-boot-starter-test depend on jupiter-->
<json-path.version>2.6.0</json-path.version>
<spring-test-dbunit.version>1.3.0</spring-test-dbunit.version> <!-- 2016 -->
<takari-cpsuite.version>1.2.7</takari-cpsuite.version> <!-- 2015 -->

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java

@ -124,7 +124,7 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode<TbCreateR
}
private ListenableFuture<Boolean> checkRelation(TbContext ctx, SearchDirectionIds sdId, String relationType) {
return ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON);
return ctx.getRelationService().checkRelationAsync(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON);
}
private ListenableFuture<Boolean> processCreateRelation(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId, String relationType) {

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java

@ -98,7 +98,7 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode<TbDeleteR
private ListenableFuture<Boolean> processSingle(TbContext ctx, TbMsg msg, EntityContainer entityContainer, String relationType) {
SearchDirectionIds sdId = processSingleSearchDirection(msg, entityContainer);
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON),
return Futures.transformAsync(ctx.getRelationService().checkRelationAsync(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON),
result -> {
if (result) {
return processSingleDeleteRelation(ctx, sdId, relationType);

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java

@ -82,7 +82,7 @@ public class TbCheckRelationNode implements TbNode {
to = EntityIdFactory.getByTypeAndId(config.getEntityType(), config.getEntityId());
from = msg.getOriginator();
}
return ctx.getRelationService().checkRelation(ctx.getTenantId(), from, to, config.getRelationType(), RelationTypeGroup.COMMON);
return ctx.getRelationService().checkRelationAsync(ctx.getTenantId(), from, to, config.getRelationType(), RelationTypeGroup.COMMON);
}
private ListenableFuture<Boolean> processList(TbContext ctx, TbMsg msg) {

6
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java

@ -113,7 +113,7 @@ public class TbCreateRelationNodeTest {
metaData.putValue("type", "AssetType");
msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
when(ctx.getRelationService().checkRelation(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
.thenReturn(Futures.immediateFuture(false));
when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, RELATION_TYPE_CONTAINS, RelationTypeGroup.COMMON))))
.thenReturn(Futures.immediateFuture(true));
@ -144,7 +144,7 @@ public class TbCreateRelationNodeTest {
when(ctx.getRelationService().findByToAndTypeAsync(any(), eq(msg.getOriginator()), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
.thenReturn(Futures.immediateFuture(Collections.singletonList(relation)));
when(ctx.getRelationService().deleteRelationAsync(any(), eq(relation))).thenReturn(Futures.immediateFuture(true));
when(ctx.getRelationService().checkRelation(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
.thenReturn(Futures.immediateFuture(false));
when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, RELATION_TYPE_CONTAINS, RelationTypeGroup.COMMON))))
.thenReturn(Futures.immediateFuture(true));
@ -171,7 +171,7 @@ public class TbCreateRelationNodeTest {
metaData.putValue("type", "AssetType");
msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
when(ctx.getRelationService().checkRelation(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
.thenReturn(Futures.immediateFuture(false));
when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, RELATION_TYPE_CONTAINS, RelationTypeGroup.COMMON))))
.thenReturn(Futures.immediateFuture(true));

2
transport/coap/src/main/resources/tb-coap-transport.yml

@ -19,6 +19,8 @@ spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
spring.main.allow-circular-references: "true"
server:
# Server bind address (has no effect if web-environment is disabled).
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"

2
transport/http/src/main/resources/tb-http-transport.yml

@ -14,6 +14,8 @@
# limitations under the License.
#
spring.main.allow-circular-references: "true"
server:
# Server bind address
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"

2
transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

@ -19,6 +19,8 @@ spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
spring.main.allow-circular-references: "true"
server:
# Server bind address (has no effect if web-environment is disabled).
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"

2
transport/mqtt/src/main/resources/tb-mqtt-transport.yml

@ -19,6 +19,8 @@ spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
spring.main.allow-circular-references: "true"
server:
# Server bind address (has no effect if web-environment is disabled).
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"

2
transport/snmp/src/main/resources/tb-snmp-transport.yml

@ -19,6 +19,8 @@ spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
spring.main.allow-circular-references: "true"
server:
# Server bind address (has no effect if web-environment is disabled).
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"

2
ui-ngx/src/app/core/services/script/node-script-test.service.ts

@ -85,7 +85,7 @@ export class NodeScriptTestService {
return this.dialog.open<NodeScriptTestDialogComponent, NodeScriptTestDialogData, string>(NodeScriptTestDialogComponent,
{
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog', 'tb-fullscreen-dialog-gt-sm'],
panelClass: ['tb-dialog', 'tb-fullscreen-dialog', 'tb-fullscreen-dialog-gt-xs'],
data: {
msg,
metadata,

5
ui-ngx/src/app/core/services/utils.service.ts

@ -284,6 +284,11 @@ export class UtilsService {
if (!datasource.dataKeys) {
datasource.dataKeys = [];
}
datasource.dataKeys.forEach(dataKey => {
if (isUndefined(dataKey.label)) {
dataKey.label = dataKey.name;
}
});
});
return datasources;
}

2
ui-ngx/src/app/core/utils.ts

@ -100,7 +100,7 @@ export function isEmptyStr(value: any): boolean {
}
export function isNotEmptyStr(value: any): boolean {
return value !== null && typeof value === 'string' && value.trim().length > 0;
return typeof value === 'string' && value.trim().length > 0;
}
export function isFunction(value: any): boolean {

37
ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts

@ -35,7 +35,8 @@ import { DatePipe } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
export interface AlarmDetailsDialogData {
alarmId: string;
alarmId?: string;
alarm?: AlarmInfo;
allowAcknowledgment: boolean;
allowClear: boolean;
displayDetails: boolean;
@ -48,6 +49,7 @@ export interface AlarmDetailsDialogData {
})
export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDialogComponent, boolean> implements OnInit {
alarmId: string;
alarmFormGroup: FormGroup;
allowAcknowledgment: boolean;
@ -93,12 +95,17 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
}
);
this.loadAlarm();
if (!this.data.alarm) {
this.alarmId = this.data.alarmId;
this.loadAlarm();
} else {
this.alarmId = this.data.alarm?.id?.id;
this.loadAlarmSubject.next(this.data.alarm);
}
}
loadAlarm() {
this.alarmService.getAlarmInfo(this.data.alarmId).subscribe(
this.alarmService.getAlarmInfo(this.alarmId).subscribe(
alarm => this.loadAlarmSubject.next(alarm)
);
}
@ -140,15 +147,25 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
}
acknowledge(): void {
this.alarmService.ackAlarm(this.data.alarmId).subscribe(
() => { this.alarmUpdated = true; this.loadAlarm(); }
);
if (this.alarmId) {
this.alarmService.ackAlarm(this.alarmId).subscribe(
() => {
this.alarmUpdated = true;
this.loadAlarm();
}
);
}
}
clear(): void {
this.alarmService.clearAlarm(this.data.alarmId).subscribe(
() => { this.alarmUpdated = true; this.loadAlarm(); }
);
if (this.alarmId) {
this.alarmService.clearAlarm(this.alarmId).subscribe(
() => {
this.alarmUpdated = true;
this.loadAlarm();
}
);
}
}
}

15
ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts

@ -44,9 +44,15 @@ import {
AlarmDetailsDialogData
} from '@home/components/alarm/alarm-details-dialog.component';
import { DAY, historyInterval } from '@shared/models/time/time.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum';
export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> {
private authUser = getCurrentAuthUser(this.store);
searchStatus: AlarmSearchStatus;
constructor(private alarmService: AlarmService,
@ -55,7 +61,8 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
private datePipe: DatePipe,
private dialog: MatDialog,
public entityId: EntityId = null,
private defaultSearchStatus: AlarmSearchStatus = AlarmSearchStatus.ANY) {
private defaultSearchStatus: AlarmSearchStatus = AlarmSearchStatus.ANY,
private store: Store<AppState>) {
super();
this.loadDataOnInit = false;
this.tableTitle = '';
@ -114,6 +121,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
}
showAlarmDetails(entity: AlarmInfo) {
const isPermissionWrite = this.authUser.authority !== Authority.CUSTOMER_USER || entity.customerId.id === this.authUser.customerId;
this.dialog.open<AlarmDetailsDialogComponent, AlarmDetailsDialogData, boolean>
(AlarmDetailsDialogComponent,
{
@ -121,8 +129,9 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
alarmId: entity.id.id,
allowAcknowledgment: true,
allowClear: true,
alarm: entity,
allowAcknowledgment: isPermissionWrite,
allowClear: isPermissionWrite,
displayDetails: true
}
}).afterClosed().subscribe(

8
ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts

@ -24,6 +24,8 @@ import { DialogService } from '@core/services/dialog.service';
import { AlarmTableConfig } from './alarm-table-config';
import { AlarmSearchStatus } from '@shared/models/alarm.models';
import { AlarmService } from '@app/core/http/alarm.service';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@Component({
selector: 'tb-alarm-table',
@ -68,7 +70,8 @@ export class AlarmTableComponent implements OnInit {
private dialogService: DialogService,
private translate: TranslateService,
private datePipe: DatePipe,
private dialog: MatDialog) {
private dialog: MatDialog,
private store: Store<AppState>) {
}
ngOnInit() {
@ -80,7 +83,8 @@ export class AlarmTableComponent implements OnInit {
this.datePipe,
this.dialog,
this.entityIdValue,
AlarmSearchStatus.ANY
AlarmSearchStatus.ANY,
this.store
);
}

1
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -235,6 +235,7 @@
[opened]="isEditingWidget || isAddingWidget"
(openedStart)="detailsDrawerOpenedStart()"
(closed)="detailsDrawerClosed()"
disableClose
mode="over"
position="end">
<tb-details-panel *ngIf="!isEditingWidgetClosed" fxFlex

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

@ -76,6 +76,7 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig<EdgeEvent, TimePa
this.searchEnabled = false;
this.addEnabled = false;
this.entitiesDeleteEnabled = false;
this.pageMode = false;
this.headerComponent = EdgeDownlinkTableHeaderComponent;
this.entityTranslations = { noEntities: 'edge.no-downlinks-prompt' };

4
ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-header.component.ts

@ -18,8 +18,8 @@ import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntityTableHeaderComponent } from '../../components/entity/entity-table-header.component';
import { EdgeEvent } from "@shared/models/edge.models";
import { EdgeDownlinkTableConfig } from "@home/components/edge/edge-downlink-table-config";
import { EdgeEvent } from '@shared/models/edge.models';
import { EdgeDownlinkTableConfig } from '@home/components/edge/edge-downlink-table-config';
@Component({
selector: 'tb-edge-downlink-table-header',

2
ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html

@ -329,7 +329,7 @@
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.ws-limit-max-sessions-per-public-user</mat-label>
<mat-label translate>tenant-profile.ws-limit-max-sessions-per-regular-user</mat-label>
<input matInput type="number" formControlName="maxWsSessionsPerRegularUser">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxWsSessionsPerRegularUser').hasError('min')">
{{ 'tenant-profile.too-small-value-zero' | translate}}

8
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts

@ -27,7 +27,7 @@ import { WidgetContext } from '@home/models/widget-component.models';
import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { isDefinedAndNotNull, isEmptyStr, parseFunction } from '@core/utils';
import { isDefinedAndNotNull, isEmptyStr, isNotEmptyStr, parseFunction } from '@core/utils';
import { EntityDataPageLink } from '@shared/models/query/query.models';
const maxZoom = 4; // ?
@ -93,10 +93,12 @@ export class ImageMap extends LeafletMap {
type: widgetType.latest,
callbacks: {
onDataUpdated: (subscription) => {
if (subscription.data[0]?.data[0]?.length > 0) {
if (isNotEmptyStr(subscription.data[0]?.data[0]?.[1])) {
result.next([subscription.data[0].data, isUpdate]);
isUpdate = true;
} else {
result.next([[[0, options.mapImageUrl]], isUpdate]);
}
isUpdate = true;
}
}
};

2
ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<form (ngSubmit)="add()" style="min-width: 650px;">
<form (ngSubmit)="add()" class="dialog-container">
<mat-toolbar color="primary">
<h2 translate>rulenode.add</h2>
<span fxFlex>: {{ruleNode.component.name}}</span>

26
ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.scss

@ -0,0 +1,26 @@
/**
* 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 './../scss/constants';
:host {
.dialog-container {
min-width: 650px !important;
@media #{$mat-lt-md} {
min-width: 100% !important;
}
}
}

2
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts

@ -1733,7 +1733,7 @@ export interface AddRuleNodeDialogData {
selector: 'tb-add-rule-node-dialog',
templateUrl: './add-rule-node-dialog.component.html',
providers: [{provide: ErrorStateMatcher, useExisting: AddRuleNodeDialogComponent}],
styleUrls: []
styleUrls: ['./add-rule-node-dialog.component.scss']
})
export class AddRuleNodeDialogComponent extends DialogComponent<AddRuleNodeDialogComponent, FcRuleNode>
implements OnInit, ErrorStateMatcher {

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

@ -3162,6 +3162,7 @@
"cassandra-tenant-limits-configuration": "Cassandra query rate limit for tenant",
"ws-limit-max-sessions-per-tenant": "Maximum number of WS sessions per tenant",
"ws-limit-max-sessions-per-customer": "Maximum number of WS sessions per customer",
"ws-limit-max-sessions-per-regular-user": "Maximum number of WS sessions per regular user",
"ws-limit-max-sessions-per-public-user": "Maximum number of WS sessions per public user",
"ws-limit-queue-per-session": "Maximum size of WS message queue per session",
"ws-limit-max-subscriptions-per-tenant": "Maximum number of WS subscriptions per tenant",

143
ui-ngx/src/assets/locale/locale.constant-es_ES.json

@ -59,7 +59,9 @@
"read-more": "Leer más",
"hide": "Ocultar",
"done": "Terminado",
"print": "Imprimir"
"print": "Imprimir",
"restore": "Restaurar",
"confirm": "Confirmar"
},
"aggregation": {
"aggregation": "Agrupación",
@ -322,6 +324,30 @@
"queue-submit-strategy": "Estrategia de envíos",
"queue-processing-strategy": "Estrategia de procesamiento",
"queue-configuration": "Configuración Cola",
"repository-settings": "Ajustes del repositorio",
"repository-url": "URL del repositorio",
"repository-url-required": "Se requiere la URL del repositorio.",
"default-branch": "Nombre de rama por defecto",
"authentication-settings": "Ajustes de autenticación",
"auth-method": "Método de autenticación",
"auth-method-username-password": "Usuario / Contraseña",
"auth-method-private-key": "Clave privada",
"password-access-token": "Contraseña / Tóken de acceso",
"change-password-access-token": "Cambiar contraseña / Tóken de acceso",
"private-key": "Clave privada",
"drop-private-key-file-or": "Arrastrar y soltar un fichero de llave privada o",
"passphrase": "Frase de contraseña",
"enter-passphrase": "Entrar frase de contraseña",
"change-passphrase": "Cambiar frase de contraseña",
"check-access": "Verificar acceso",
"check-repository-access-success": "Acceso al repositorio verificado satisfactoriamente!",
"delete-repository-settings-title": "Estás seguro de borrar los ajustes del repositorio?",
"delete-repository-settings-text": "Atención, tras la confirmación los ajustes del repositorio serán eliminados y la característica de control de versiones no estará disponible.",
"auto-commit-settings": "Ajustes Auto-publicar",
"auto-commit-entities": "Entidades Auto-publicar",
"no-auto-commit-entities-prompt": "No hay entidades configuradas para auto-publicar",
"delete-auto-commit-settings-title": "Estás seguro de borrar los ajustes de auto-publicar?",
"delete-auto-commit-settings-text": "Atención, tras la confirmación los ajustes de auto-publicar serán borrados y la característica de auto-publicar se desactivará para todas las entidades.",
"2fa": {
"2fa": "Autenticación de dos factores (2FA)",
"available-providers": "Proveedores disponibles",
@ -485,7 +511,7 @@
"management": "Gestión de Activos",
"view-assets": "Ver Activos",
"add": "Añadir Activo",
"asset-type-max-length": "El tipo de activo debe ser menor de 256",
"assign-to-customer": "Asignar a cliente",
"assign-asset-to-customer": "Asignar Activo(s) A Cliente",
"assign-asset-to-customer-text": "Selecciona los activos a asignar al cliente",
@ -937,6 +963,8 @@
"assignedToCustomer": "Asignado al cliente",
"assignedToCustomers": "Asignado a los clientes",
"public": "Público",
"copyId": "Copiar ID del panel",
"idCopiedMessage": "El ID del panel ha sido copiado al portapapeles",
"public-link": "Link público",
"copy-public-link": "Copiar link público",
"public-link-copied-message": "El link público del panel se ha copiado al portapapeles",
@ -1817,6 +1845,9 @@
"type-current-tenant": "Propietario Actual",
"type-current-user": "Usuario Actual",
"type-current-user-owner": "Usuario Propietario Actual",
"type-widgets-bundle": "Paquete de widgets",
"type-widgets-bundles": "Paquetes de widgets",
"list-of-widgets-bundles": "{ count, plural, 1 {Un paquete de widget} other {Lista de # paquetes de widgets} }",
"search": "Buscar entidades",
"selected-entities": "{ count, plural, 1 {1 entidad} other {# entidades} } seleccionadas",
"entity-label": "Etiqueta de entidad",
@ -2246,6 +2277,7 @@
"current-device": "Dispositivo actual",
"default-value": "Valor por defecto",
"dynamic-source-type": "Tipo de origen dinámico",
"dynamic-value": "Valor dinámico",
"no-dynamic-value": "Sin valor dinámico",
"source-attribute": "Atributo de origen",
"switch-to-dynamic-value": "Cambiar a valor dinámico",
@ -2488,7 +2520,7 @@
"request-password-reset": "Solicitar restablecer contraseña",
"reset-password": "Restablecer contraseña",
"create-password": "Crear contraseña",
"two-factor-authentication": "Two factor authentication",
"two-factor-authentication": "Autenticado de dos factores",
"passwords-mismatch-error": "¡Las contraseñas introducidas deben ser iguales!",
"password-again": "Repita la contraseña de nuevo",
"sign-in": "Por favor, inicie sesión",
@ -2601,7 +2633,8 @@
"change-password": "Cambiar contraseña",
"current-password": "Contraseña actual",
"copy-jwt-token": "Copiar JWT",
"valid-till": "Válido hasta {{expirationData}}",
"jwt-token": "Tóken JWT",
"token-valid-till": "Válido hasta {{expirationData}}",
"tokenCopiedSuccessMessage": "JWT copiado al portapapeles",
"tokenCopiedWarnMessage": "JWT caducado, por favor actualiza la página."
},
@ -2660,6 +2693,20 @@
"backup-code-description": "Estos códigos de seguridad imprimibles son de un solo uso, te permiten identificarte cuando no tengas el teléfono a mano, útil cuando se viaja.",
"backup-code-hint": "{{ info }} códigos de un solo uso activos en este momento"
}
},
"password-requirement": {
"at-least": "Al menos:",
"character": "{ count, plural, 1 {1 caracter} other {# caracteres} }",
"digit": "{ count, plural, 1 {1 dígito} other {# dígitos} }",
"incorrect-password-try-again": "Contraseña incorrecta. Prueba otra vez",
"lowercase-letter": "{ count, plural, 1 {1 minúscula} other {# mínusculas} }",
"new-passwords-not-match": "Las contraseñas no coinciden",
"password-should-not-contain-spaces": "La contraseña no puede contener espacios",
"password-not-meet-requirements": "La contraseña no reune los requisitos necesarios",
"password-requirements": "Requisitos de contraseña",
"password-should-difference": "La nueva contraseña debe ser diferente a la actual",
"special-character": "{ count, plural, 1 {1 caracter especial} other {# caracteres especiales} }",
"uppercase-letter": "{ count, plural, 1 {1 mayúscula} other {# mayúsculas} }"
}
},
"relation": {
@ -3053,6 +3100,8 @@
"transport-device-msg-rate-limit": "Tasa de mensajes de dispositivo.",
"transport-device-telemetry-msg-rate-limit": "Tasa de mensajes de telemetría de dispositivo.",
"transport-device-telemetry-data-points-rate-limit": "Tasa de datapoints de telemetría de dispositivo.",
"tenant-entity-export-rate-limit": "Tasa de creación de versión de entidades",
"tenant-entity-import-rate-limit": "Tasa de carga de versión de entidades",
"max-transport-messages": "Nº Máximo de mensajes de transporte (0 - sin límite)",
"max-transport-messages-required": "Nº Máximo de mensajes de transporte requerido.",
"max-transport-messages-range": "Nº Máximo de mensajes de transporte no puede ser negativo",
@ -3091,7 +3140,22 @@
"max-created-alarms-range": "Nº Máximo de alarmas creadas no puede ser negativo",
"no-queue": "No Queue configured",
"add-queue": "Add Queue",
"queues-with-count": "Queues ({{count}})"
"queues-with-count": "Queues ({{count}})",
"tenant-rest-limits": "Rate limit for REST requests for tenant",
"customer-rest-limits": "Rate limit for REST requests for customer",
"incorrect-pattern-for-rate-limits": "The format is comma separated pairs of capacity and period (in seconds) with a colon between, e.g. 100:1,2000:60",
"too-small-value-zero": "The value must be bigger than 0",
"too-small-value-one": "The value must be bigger than 1",
"cassandra-tenant-limits-configuration": "Cassandra query rate limit for tenant",
"ws-limit-max-sessions-per-tenant": "Maximum number of WS sessions per tenant",
"ws-limit-max-sessions-per-customer": "Maximum number of WS sessions per customer",
"ws-limit-max-sessions-per-public-user": "Maximum number of WS sessions per public user",
"ws-limit-queue-per-session": "Maximum size of WS message queue per session",
"ws-limit-max-subscriptions-per-tenant": "Maximum number of WS subscriptions per tenant",
"ws-limit-max-subscriptions-per-customer": "Maximum number of WS subscriptions per customer",
"ws-limit-max-subscriptions-per-regular-user": "Maximum number of WS subscriptions per regular user",
"ws-limit-max-subscriptions-per-public-user": "Maximum number of WS subscriptions per public user",
"ws-limit-updates-per-session": "Rate limit for WS updates per session"
},
"timeinterval": {
"seconds-interval": "{ seconds, plural, 1 {1 segundo} other {# segundos} }",
@ -3220,6 +3284,68 @@
"json-value-invalid": "El valor JSON tiene un formato inválido",
"json-value-required": "Se requiere valor JSON"
},
"version-control": {
"version-control": "Control de Versión",
"management": "Administrador de versiones",
"branch": "Rama",
"default": "Por defecto",
"select-branch": "Seleccionar rama",
"branch-required": "Se requiere rama",
"create-entity-version": "Versión creación de entidad",
"version-name": "Nombre de versión",
"version-name-required": "Se requiere nombre de versión",
"author": "Autor",
"export-relations": "Exportar relaciones",
"export-attributes": "Exportar atributos",
"export-credentials": "Exportar credenciales",
"entity-versions": "Versiones de entidad",
"versions": "Versiones",
"created-time": "Hora de creación",
"version-id": "ID de versión",
"no-entity-versions-text": "No se han encontrado versiones de entidad",
"no-versions-text": "No se han encontrado versiones",
"copy-full-version-id": "Copiar el ID de versión",
"create-version": "Crear versión",
"nothing-to-commit": "No hay cambios a publicar",
"restore-version": "Restaurar versión",
"restore-entity-from-version": "Restaurar entidad desde versión '{{versionName}}'",
"load-relations": "Cargar relaciones",
"load-attributes": "Cargar atributos",
"load-credentials": "Cargar credencialess",
"show-version-diff": "Mostrar diff de versión",
"diff-entity-with-version": "Diff de la versión de entidad '{{versionName}}'",
"previous-difference": "Anterior diferencia",
"next-difference": "Siguiente diferencia",
"current": "Actual",
"differences": "{ count, plural, 1 {1 diferencia} other {# diferencias} }",
"create-entities-version": "Crear versión de entidades",
"default-sync-strategy": "Estrategia de sincronización por defecto",
"sync-strategy-merge": "Combinar (Merge)",
"sync-strategy-overwrite": "Sobreescribir",
"entities-to-export": "Entidades a exportar",
"entities-to-restore": "Entidades a restaurar",
"sync-strategy": "Estrategia de sincronización",
"all-entities": "Todas las entidades",
"no-entities-to-export-prompt": "Por favor, especifica las entidades a exportar",
"no-entities-to-restore-prompt": "Por favor, especifica las entidades a restaurar",
"add-entity-type": "Añadir tipo de entidad",
"remove-all": "Borrar todo",
"version-create-result": "{ added, plural, 0 {Ninguna entidad} 1 {1 entidad} other {# entidades} } añadidas.<br/>{ modified, plural, 0 {Ninguna entidad} 1 {1 entidad} other {# entidades} } modificadas.<br/>{ removed, plural, 0 {Ninguna entidad} 1 {1 entidad} other {# entidades} } borradas.",
"remove-other-entities": "Borrar otras entidades",
"find-existing-entity-by-name": "Buscar entidad existente por nombre",
"restore-entities-from-version": "Restaurar entidades desde la versión '{{versionName}}'",
"no-entities-restored": "No se restauraron entidades",
"created": "{{created}} creadas",
"updated": "{{updated}} actualizadas",
"deleted": "{{deleted}} borradas",
"remove-other-entities-confirm-text": "Atención! Esta acción <b>borrará</b> permanentemente <b>todas</b> las entidades actuales<br/>no presentes en la versión a restaurar.<br/><br/>Escribe <b>remove other entities</b> para confirmar.",
"auto-commit-to-branch": "auto-publicar a la rama <b>{{ branch }}</b>",
"default-create-entity-version-name": "{{entityName}} actualización",
"sync-strategy-merge-hint": "Crea o actualiza las entidades seleccionadas en el repositorio. Las demás entidades <b>no serán modificadas</b>.",
"sync-strategy-overwrite-hint": "Crea o actualiza las entidades seleccionadas en el repositorio. Las demás entidades <b>serán borradas</b>.",
"device-credentials-conflict": "Fallo al cargar el dispositivo con ID externo <b>{{entityId}}</b><br/>debido a que las mismas credenciales están ya presentes en la base de datos para otro dispositivo.<br/>Por favor, considera desactivar el ajuste <b>cargar credenciales</b> en el formulario de restauración.",
"missing-referenced-entity": "Fallo al cargar <b>{{sourceEntityTypeName}}</b> con ID externo <b>{{sourceEntityId}}</b><br/>porque hace referencia al tipo de entidad <b>{{targetEntityTypeName}}</b> con el ID <b>{{targetEntityId}}</b>."
},
"widget": {
"widget-library": "Bibloteca de Widgets",
"widget-bundle": "Paquetes de Widgets",
@ -4431,6 +4557,13 @@
"material-icons": "Iconos material-design",
"show-all": "Mostrar todos los iconos"
},
"phone-input": {
"phone-input-label": "Número de teléfono",
"phone-input-required": "Número de teléfono requerido",
"phone-input-validation": "El número es inválido o erróneo",
"phone-input-pattern": "Número inválido. Debe cumplir el formato E.164, ej. {{phoneNumber}}",
"phone-input-hint": "Número en el formato E.164, ej. {{phoneNumber}}"
},
"custom": {
"widget-action": {
"action-cell-button": "Acción botón de celda",

4
ui-ngx/src/styles.scss

@ -1263,8 +1263,8 @@ mat-label {
}
}
.tb-fullscreen-dialog-gt-sm {
@media #{$mat-gt-sm} {
.tb-fullscreen-dialog-gt-xs {
@media #{$mat-gt-xs} {
min-height: 100%;
min-width: 100%;
max-width: none !important;

Loading…
Cancel
Save