diff --git a/application/pom.xml b/application/pom.xml
index c2ae7eccc2..1b29ef3243 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -145,10 +145,6 @@
org.springframework.boot
spring-boot-starter-websocket
-
- org.springframework.cloud
- spring-cloud-starter-oauth2
-
org.springframework.security
spring-security-oauth2-client
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
index 250f9c75a5..10df66ece5 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
@@ -24,6 +24,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
private long maxDevices;
private long maxAssets;
+ private long maxCustomers;
+ private long maxUsers;
+ private long maxDashboards;
+ private long maxRuleChains;
private String transportTenantMsgRateLimit;
private String transportTenantTelemetryMsgRateLimit;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java b/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java
new file mode 100644
index 0000000000..e47dd1e301
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright © 2016-2020 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao;
+
+import org.thingsboard.server.common.data.id.TenantId;
+
+public interface TenantEntityDao {
+
+ Long countByTenantId(TenantId tenantId);
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
index 1366df3ac2..ae8bf5706c 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
@@ -23,6 +23,7 @@ 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.dao.Dao;
+import org.thingsboard.server.dao.TenantEntityDao;
import java.util.List;
import java.util.Optional;
@@ -32,7 +33,7 @@ import java.util.UUID;
* The Interface AssetDao.
*
*/
-public interface AssetDao extends Dao {
+public interface AssetDao extends Dao, TenantEntityDao {
/**
* Find asset info by id.
@@ -166,6 +167,4 @@ public interface AssetDao extends Dao {
*/
ListenableFuture> findTenantAssetTypesAsync(UUID tenantId);
- Long countAssetsByTenantId(TenantId tenantId);
-
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
index 923baa1df4..29eaad31fb 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
@@ -330,12 +330,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxAssets = profileConfiguration.getMaxAssets();
- if (maxAssets > 0) {
- long currentAssetsCount = assetDao.countAssetsByTenantId(tenantId);
- if (maxAssets >= currentAssetsCount) {
- throw new DataValidationException("Can't create assets more then " + maxAssets);
- }
- }
+ validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET);
}
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java
index 11fa5cb7cd..98622c8b83 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java
@@ -20,6 +20,7 @@ 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.dao.Dao;
+import org.thingsboard.server.dao.TenantEntityDao;
import java.util.Optional;
import java.util.UUID;
@@ -27,7 +28,7 @@ import java.util.UUID;
/**
* The Interface CustomerDao.
*/
-public interface CustomerDao extends Dao {
+public interface CustomerDao extends Dao, TenantEntityDao {
/**
* Save or update customer object
@@ -54,5 +55,5 @@ public interface CustomerDao extends Dao {
* @return the optional customer object
*/
Optional findCustomersByTenantIdAndTitle(UUID tenantId, String title);
-
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
index 14d591e7e8..d97b4e9c34 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
@@ -21,13 +21,16 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
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.CustomerId;
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.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
@@ -38,6 +41,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.user.UserService;
@@ -75,6 +79,10 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
@Autowired
private DashboardService dashboardService;
+ @Autowired
+ @Lazy
+ private TbTenantProfileCache tenantProfileCache;
+
@Override
public Customer findCustomerById(TenantId tenantId, CustomerId customerId) {
log.trace("Executing findCustomerById [{}]", customerId);
@@ -162,6 +170,11 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
@Override
protected void validateCreate(TenantId tenantId, Customer customer) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ long maxCustomers = profileConfiguration.getMaxCustomers();
+
+ validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER);
customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
c -> {
throw new DataValidationException("Customer with such title already exists!");
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java
index 0429c6d469..0beb89ef06 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java
@@ -18,11 +18,12 @@ package org.thingsboard.server.dao.dashboard;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.TenantEntityDao;
/**
* The Interface DashboardDao.
*/
-public interface DashboardDao extends Dao {
+public interface DashboardDao extends Dao, TenantEntityDao {
/**
* Save or update dashboard object
@@ -31,5 +32,4 @@ public interface DashboardDao extends Dao {
* @return saved dashboard object
*/
Dashboard save(TenantId tenantId, Dashboard dashboard);
-
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
index 112f998cd4..55bf31e321 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
@@ -19,10 +19,12 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
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.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
@@ -31,12 +33,14 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.concurrent.ExecutionException;
@@ -61,6 +65,10 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired
private CustomerDao customerDao;
+ @Autowired
+ @Lazy
+ private TbTenantProfileCache tenantProfileCache;
+
@Override
public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) {
log.trace("Executing findDashboardById [{}]", dashboardId);
@@ -214,6 +222,14 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
private DataValidator dashboardValidator =
new DataValidator() {
+ @Override
+ protected void validateCreate(TenantId tenantId, Dashboard data) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ long maxDashboards = profileConfiguration.getMaxDashboards();
+ validateNumberOfEntitiesPerTenant(tenantId, dashboardDao, maxDashboards, EntityType.DASHBOARD);
+ }
+
@Override
protected void validateDataImpl(TenantId tenantId, Dashboard dashboard) {
if (StringUtils.isEmpty(dashboard.getTitle())) {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
index 2f321ff499..3e8f6445a2 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
@@ -23,6 +23,7 @@ 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.dao.Dao;
+import org.thingsboard.server.dao.TenantEntityDao;
import java.util.List;
import java.util.Optional;
@@ -32,7 +33,7 @@ import java.util.UUID;
* The Interface DeviceDao.
*
*/
-public interface DeviceDao extends Dao {
+public interface DeviceDao extends Dao, TenantEntityDao {
/**
* Find device info by id.
@@ -203,8 +204,6 @@ public interface DeviceDao extends Dao {
*/
ListenableFuture findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id);
- Long countDevicesByTenantId(TenantId tenantId);
-
Long countDevicesByDeviceProfileId(TenantId tenantId, UUID deviceProfileId);
/**
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
index 24c597b4df..a919f86dfe 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
@@ -530,12 +530,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxDevices = profileConfiguration.getMaxDevices();
- if (maxDevices > 0) {
- long currentDevicesCount = deviceDao.countDevicesByTenantId(tenantId);
- if (maxDevices >= currentDevicesCount) {
- throw new DataValidationException("Can't create devices more then " + maxDevices);
- }
- }
+ validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE);
}
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
index a0f03b2a32..42d02addcd 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
@@ -37,12 +37,11 @@ public abstract class AbstractEntityService {
protected Optional extractConstraintViolationException(Exception t) {
if (t instanceof ConstraintViolationException) {
- return Optional.of ((ConstraintViolationException) t);
+ return Optional.of((ConstraintViolationException) t);
} else if (t.getCause() instanceof ConstraintViolationException) {
- return Optional.of ((ConstraintViolationException) (t.getCause()));
+ return Optional.of((ConstraintViolationException) (t.getCause()));
} else {
return Optional.empty();
}
}
-
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
index 5997839119..263f01570e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
@@ -24,6 +24,7 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType;
@@ -44,11 +45,13 @@ import org.thingsboard.server.common.data.rule.RuleChainData;
import org.thingsboard.server.common.data.rule.RuleChainImportResult;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.ArrayList;
@@ -81,6 +84,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
@Autowired
private TenantDao tenantDao;
+ @Autowired
+ @Lazy
+ private TbTenantProfileCache tenantProfileCache;
+
@Override
public RuleChain saveRuleChain(RuleChain ruleChain) {
ruleChainValidator.validate(ruleChain, RuleChain::getTenantId);
@@ -580,6 +587,14 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
private DataValidator ruleChainValidator =
new DataValidator() {
+ @Override
+ protected void validateCreate(TenantId tenantId, RuleChain data) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ long maxRuleChains = profileConfiguration.getMaxRuleChains();
+ validateNumberOfEntitiesPerTenant(tenantId, ruleChainDao, maxRuleChains, EntityType.RULE_CHAIN);
+ }
+
@Override
protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) {
if (StringUtils.isEmpty(ruleChain.getName())) {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
index c3214425fe..87ab9d26b6 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
@@ -19,13 +19,14 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.TenantEntityDao;
import java.util.UUID;
/**
* Created by igor on 3/12/18.
*/
-public interface RuleChainDao extends Dao {
+public interface RuleChainDao extends Dao, TenantEntityDao {
/**
* Find rule chains by tenantId and page link.
@@ -35,5 +36,4 @@ public interface RuleChainDao extends Dao {
* @return the list of rule chain objects
*/
PageData findRuleChainsByTenantId(UUID tenantId, PageLink pageLink);
-
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
index d78654164f..0bd0e7b030 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
@@ -18,7 +18,9 @@ package org.thingsboard.server.dao.service;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.BaseData;
+import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.HashSet;
@@ -79,6 +81,19 @@ public abstract class DataValidator> {
return emailMatcher.matches();
}
+ protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
+ TenantEntityDao tenantEntityDao,
+ long maxEntities,
+ EntityType entityType) {
+ if (maxEntities > 0) {
+ long currentEntitiesCount = tenantEntityDao.countByTenantId(tenantId);
+ if (currentEntitiesCount >= maxEntities) {
+ throw new DataValidationException(String.format("Can't create more then %d %ss!",
+ maxEntities, entityType.name().toLowerCase().replaceAll("_", " ")));
+ }
+ }
+ }
+
protected static void validateJsonStructure(JsonNode expectedNode, JsonNode actualNode) {
Set expectedFields = new HashSet<>();
Iterator fieldsIterator = expectedNode.fieldNames();
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
index 698183ea0a..d2a5167b43 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
@@ -178,8 +178,7 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im
}
@Override
- public Long countAssetsByTenantId(TenantId tenantId) {
+ public Long countByTenantId(TenantId tenantId) {
return assetRepository.countByTenantId(tenantId.getId());
-
}
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java
index bf2a845ec0..4b5a8e0791 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java
@@ -37,4 +37,5 @@ public interface CustomerRepository extends PagingAndSortingRepository {
+
+ Long countByTenantId(UUID tenantId);
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java
index 8d637f0cbe..0f083cf6b9 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java
@@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.dashboard.DashboardDao;
import org.thingsboard.server.dao.model.sql.DashboardEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
@@ -43,4 +44,9 @@ public class JpaDashboardDao extends JpaAbstractSearchTextDao getCrudRepository() {
return dashboardRepository;
}
+
+ @Override
+ public Long countByTenantId(TenantId tenantId) {
+ return dashboardRepository.countByTenantId(tenantId.getId());
+ }
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
index 9f3cc5b5a7..9519b169e2 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
@@ -50,9 +50,9 @@ public interface DeviceRepository extends PagingAndSortingRepository findByTenantIdAndProfileId(@Param("tenantId") UUID tenantId,
- @Param("profileId") UUID profileId,
- @Param("searchText") String searchText,
- Pageable pageable);
+ @Param("profileId") UUID profileId,
+ @Param("searchText") String searchText,
+ Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " +
"FROM DeviceEntity d " +
@@ -62,9 +62,9 @@ public interface DeviceRepository extends PagingAndSortingRepository findDeviceInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId,
- @Param("customerId") UUID customerId,
- @Param("searchText") String searchText,
- Pageable pageable);
+ @Param("customerId") UUID customerId,
+ @Param("searchText") String searchText,
+ Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId")
Page findByTenantId(@Param("tenantId") UUID tenantId,
@@ -102,9 +102,9 @@ public interface DeviceRepository extends PagingAndSortingRepository findDeviceInfosByTenantIdAndType(@Param("tenantId") UUID tenantId,
- @Param("type") String type,
- @Param("textSearch") String textSearch,
- Pageable pageable);
+ @Param("type") String type,
+ @Param("textSearch") String textSearch,
+ Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " +
"FROM DeviceEntity d " +
@@ -137,10 +137,10 @@ public interface DeviceRepository extends PagingAndSortingRepository findDeviceInfosByTenantIdAndCustomerIdAndType(@Param("tenantId") UUID tenantId,
- @Param("customerId") UUID customerId,
- @Param("type") String type,
- @Param("textSearch") String textSearch,
- Pageable pageable);
+ @Param("customerId") UUID customerId,
+ @Param("type") String type,
+ @Param("textSearch") String textSearch,
+ Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " +
"FROM DeviceEntity d " +
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
index fceefcadf9..7ce59bda0d 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
@@ -220,7 +220,7 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao
}
@Override
- public Long countDevicesByTenantId(TenantId tenantId) {
+ public Long countByTenantId(TenantId tenantId) {
return deviceRepository.countByTenantId(tenantId.getId());
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
index 57d1545a94..3331f57e00 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
@@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
+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.rule.RuleChain;
@@ -56,4 +57,8 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao imple
DaoUtil.toPageable(pageLink)));
}
+
+ @Override
+ public Long countByTenantId(TenantId tenantId) {
+ return userRepository.countByTenantId(tenantId.getId());
+ }
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java
index 1d512a7944..ceaf09ec59 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java
@@ -47,4 +47,5 @@ public interface UserRepository extends PagingAndSortingRepository {
+public interface UserDao extends Dao, TenantEntityDao {
/**
* Save or update user object
@@ -49,7 +50,7 @@ public interface UserDao extends Dao {
* @return the list of user entities
*/
PageData findByTenantId(UUID tenantId, PageLink pageLink);
-
+
/**
* Find tenant admin users by tenantId and page link.
*
@@ -58,7 +59,7 @@ public interface UserDao extends Dao {
* @return the list of user entities
*/
PageData findTenantAdmins(UUID tenantId, PageLink pageLink);
-
+
/**
* Find customer users by tenantId, customerId and page link.
*
@@ -68,5 +69,4 @@ public interface UserDao extends Dao {
* @return the list of user entities
*/
PageData findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink);
-
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
index 314684c3fe..a19ec57f48 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
@@ -24,8 +24,10 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
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;
@@ -36,6 +38,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
@@ -43,6 +46,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.HashMap;
@@ -84,6 +88,10 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
@Autowired
private CustomerDao customerDao;
+ @Autowired
+ @Lazy
+ private TbTenantProfileCache tenantProfileCache;
+
@Override
public User findUserByEmail(TenantId tenantId, String email) {
log.trace("Executing findUserByEmail [{}]", email);
@@ -364,6 +372,16 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
private DataValidator userValidator =
new DataValidator() {
+ @Override
+ protected void validateCreate(TenantId tenantId, User user) {
+ if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ long maxUsers = profileConfiguration.getMaxUsers();
+ validateNumberOfEntitiesPerTenant(tenantId, userDao, maxUsers, EntityType.USER);
+ }
+ }
+
@Override
protected void validateDataImpl(TenantId requestTenantId, User user) {
if (StringUtils.isEmpty(user.getEmail())) {
diff --git a/pom.xml b/pom.xml
index cc5bbce51b..9cf0438220 100755
--- a/pom.xml
+++ b/pom.xml
@@ -36,12 +36,11 @@
${project.name}
/var/log/${pkg.name}
/usr/share/${pkg.name}
- 2.2.6.RELEASE
- 2.1.2.RELEASE
- 5.2.6.RELEASE
- 5.2.3.RELEASE
- 2.2.4.RELEASE
- 3.1.0
+ 2.3.5.RELEASE
+ 5.2.10.RELEASE
+ 5.4.1
+ 2.4.1
+ 3.3.0
0.7.0
2.2.0
4.12
@@ -52,15 +51,16 @@
4.6.0
4.0.5
4.3.1.0
+ 3.11.9
1.2.7
28.2-jre
2.6.1
3.4
2.5
1.4
- 2.10.2
- 2.10.2
- 2.10.2
+ 2.11.3
+ 2.11.3
+ 2.11.3
2.2.6
1.0.2
2.6.2
@@ -72,7 +72,7 @@
1.22.1
1.16.18
1.2.4
- 4.1.49.Final
+ 4.1.53.Final
1.5.0
4.8.0
2.19.1
@@ -96,7 +96,7 @@
4.1.1
2.57
2.7.7
- 1.25
+ 1.27
1.11.747
1.105.0
3.2.0
@@ -872,11 +872,6 @@
spring-boot-starter-security
${spring-boot.version}
-
- org.springframework.cloud
- spring-cloud-starter-oauth2
- ${spring-oauth2.version}
-
org.springframework.security
spring-security-oauth2-client
@@ -1200,6 +1195,11 @@
${cassandra-unit.version}
test
+
+ org.apache.cassandra
+ cassandra-all
+ ${cassandra-all.version}
+
junit
junit
diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml
index c4d2b66d15..6253b39fc1 100644
--- a/rule-engine/rule-engine-components/pom.xml
+++ b/rule-engine/rule-engine-components/pom.xml
@@ -120,6 +120,11 @@
org.locationtech.jts
jts-core
+
+ com.sun.mail
+ javax.mail
+ provided
+
junit
junit
diff --git a/tools/pom.xml b/tools/pom.xml
index 8d596c8493..14fd8b01b7 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -54,7 +54,6 @@
org.apache.cassandra
cassandra-all
- 3.11.6
com.datastax.oss
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html
index 12258ffd5f..ae9fd8ea9c 100644
--- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html
+++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html
@@ -40,6 +40,54 @@
{{ 'tenant-profile.maximum-assets-range' | translate}}
+
+ tenant-profile.maximum-customers
+
+
+ {{ 'tenant-profile.maximum-customers-required' | translate}}
+
+
+ {{ 'tenant-profile.maximum-customers-range' | translate}}
+
+
+
+ tenant-profile.maximum-users
+
+
+ {{ 'tenant-profile.maximum-users-required' | translate}}
+
+
+ {{ 'tenant-profile.maximum-users-range' | translate}}
+
+
+
+ tenant-profile.maximum-dashboards
+
+
+ {{ 'tenant-profile.maximum-dashboards-required' | translate}}
+
+
+ {{ 'tenant-profile.maximum-dashboards-range' | translate}}
+
+
+
+ tenant-profile.maximum-rule-chains
+
+
+ {{ 'tenant-profile.maximum-rule-chains-required' | translate}}
+
+
+ {{ 'tenant-profile.maximum-rule-chains-range' | translate}}
+
+
tenant-profile.max-transport-messages
string;
export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
@@ -229,7 +230,6 @@ export const defaultSettings: any = {
strokeWeight: 2,
strokeOpacity: 1.0,
initCallback: () => { },
- defaultZoomLevel: 8,
disableScrollZooming: false,
minZoomLevel: 16,
credentials: '',
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts
index 94fa1a09ac..20d28afbd1 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts
@@ -17,7 +17,7 @@
import L from 'leaflet';
import LeafletMap from '../leaflet-map';
-import { UnitedMapSettings } from '../map-models';
+import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models';
import 'leaflet.gridlayer.googlemutant';
import { ResourcesService } from '@core/services/resources.service';
import { WidgetContext } from '@home/models/widget-component.models';
@@ -39,7 +39,7 @@ export class GoogleMap extends LeafletMap {
const map = L.map($container, {
attributionControl: false,
editable: !!options.editablePolygon
- }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
+ }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);
(L.gridLayer as any).googleMutant({
type: options?.gmDefaultMapType || 'roadmap'
}).addTo(map);
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts
index a6418ee557..7a0d12a03e 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts
@@ -16,7 +16,7 @@
import L from 'leaflet';
import LeafletMap from '../leaflet-map';
-import { UnitedMapSettings } from '../map-models';
+import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models';
import { WidgetContext } from '@home/models/widget-component.models';
export class HEREMap extends LeafletMap {
@@ -24,7 +24,7 @@ export class HEREMap extends LeafletMap {
super(ctx, $container, options);
const map = L.map($container, {
editable: !!options.editablePolygon
- }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
+ }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);
const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials);
tileLayer.addTo(map);
super.initSettings(options);
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts
index f4f10a0dac..7cfb77fd42 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts
@@ -16,7 +16,7 @@
import L from 'leaflet';
import LeafletMap from '../leaflet-map';
-import { UnitedMapSettings } from '../map-models';
+import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models';
import { WidgetContext } from '@home/models/widget-component.models';
export class OpenStreetMap extends LeafletMap {
@@ -24,7 +24,7 @@ export class OpenStreetMap extends LeafletMap {
super(ctx, $container, options);
const map = L.map($container, {
editable: !!options.editablePolygon
- }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
+ }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);
let tileLayer;
if (options.useCustomProvider) {
tileLayer = L.tileLayer(options.customProviderTileUrl);
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts
index 498a6053a1..29074501ab 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts
@@ -17,7 +17,7 @@
import L from 'leaflet';
import LeafletMap from '../leaflet-map';
-import { UnitedMapSettings } from '../map-models';
+import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models';
import { WidgetContext } from '@home/models/widget-component.models';
export class TencentMap extends LeafletMap {
@@ -26,7 +26,7 @@ export class TencentMap extends LeafletMap {
const txUrl = 'http://rt{s}.map.gtimg.com/realtimerender?z={z}&x={x}&y={y}&type=vector&style=0';
const map = L.map($container, {
editable: !!options.editablePolygon
- }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
+ }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);
const txLayer = L.tileLayer(txUrl, {
subdomains: '0123',
tms: true,
diff --git a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts
index f9b42f12f5..27a42680e5 100644
--- a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts
+++ b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts
@@ -56,6 +56,12 @@ export const customerHref = 'Attribute Data';
+export const timeseriesDataHref = 'Timeseries Data';
+
+export const aggregationTypeHref = 'Aggregation Type';
+
+export const dataSortOrderHref = 'Data Sort Order';
+
export const userHref = 'User';
export const entityDataHref = 'Entity data';
@@ -1080,6 +1086,23 @@ export const serviceCompletions: TbEditorCompletions = {
],
return: observableReturnTypeVariable('any')
},
+ getEntityTimeseries: {
+ description: 'Get entity timeseries',
+ meta: 'function',
+ args: [
+ {name: 'entityId', type: entityIdHref, description: 'Id of the entity'},
+ {name: 'keys', type: `Array<string>`, description: 'Array of the keys'},
+ {name: 'startTs', type: 'number', description: 'Start time in milliseconds'},
+ {name: 'endTs', type: 'number', description: 'End time in milliseconds'},
+ {name: 'limit', type: 'number', description: 'Limit of values to receive for each key'},
+ {name: 'agg', type: aggregationTypeHref, description: 'Aggregation type'},
+ {name: 'interval', type: 'number', description: 'Aggregation interval'},
+ {name: 'orderBy', type: dataSortOrderHref, description: 'Data order by time'},
+ {name: 'useStrictDataTypes', type: 'boolean', description: 'If "false" all values will be returned as strings'},
+ requestConfigArg
+ ],
+ return: observableReturnTypeVariable(timeseriesDataHref)
+ },
}
},
entityService: {
diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts
index ad946efdb7..ce22c27fac 100644
--- a/ui-ngx/src/app/shared/models/tenant.model.ts
+++ b/ui-ngx/src/app/shared/models/tenant.model.ts
@@ -26,6 +26,10 @@ export enum TenantProfileType {
export interface DefaultTenantProfileConfiguration {
maxDevices: number;
maxAssets: number;
+ maxCustomers: number;
+ maxUsers: number;
+ maxDashboards: number;
+ maxRuleChains: number;
transportTenantMsgRateLimit?: string;
transportTenantTelemetryMsgRateLimit?: string;
@@ -56,6 +60,10 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan
const defaultConfiguration: DefaultTenantProfileConfiguration = {
maxDevices: 0,
maxAssets: 0,
+ maxCustomers: 0,
+ maxUsers: 0,
+ maxDashboards: 0,
+ maxRuleChains: 0,
maxTransportMessages: 0,
maxTransportDataPoints: 0,
maxREExecutions: 0,
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json
index 9acd10ef95..0b6ee1deeb 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -1946,6 +1946,18 @@
"maximum-assets": "Maximum number of assets (0 - unlimited)",
"maximum-assets-required": "Maximum number of assets is required.",
"maximum-assets-range": "Maximum number of assets can't be negative",
+ "maximum-customers": "Maximum number of customers (0 - unlimited)",
+ "maximum-customers-required": "Maximum number of customers is required.",
+ "maximum-customers-range": "Maximum number of customers can't be negative",
+ "maximum-users": "Maximum number of users (0 - unlimited)",
+ "maximum-users-required": "Maximum number of users is required.",
+ "maximum-users-range": "Maximum number of users can't be negative",
+ "maximum-dashboards": "Maximum number of dashboards (0 - unlimited)",
+ "maximum-dashboards-required": "Maximum number of dashboards is required.",
+ "maximum-dashboards-range": "Maximum number of dashboards can't be negative",
+ "maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)",
+ "maximum-rule-chains-required": "Maximum number of rule chains is required.",
+ "maximum-rule-chains-range": "Maximum number of rule chains can't be negative",
"transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.",
"transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.",
"transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.",