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/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
index f76521ac48..ea6e7d4f19 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
@@ -35,14 +35,17 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
+import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.oauth2.OAuth2User;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.install.InstallScripts;
+import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
@@ -76,6 +79,12 @@ public abstract class AbstractOAuth2ClientMapper {
@Autowired
private InstallScripts installScripts;
+ @Autowired
+ protected TbTenantProfileCache tenantProfileCache;
+
+ @Autowired
+ protected TbClusterService tbClusterService;
+
private final Lock userCreationLock = new ReentrantLock();
protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) {
@@ -162,6 +171,10 @@ public abstract class AbstractOAuth2ClientMapper {
tenant.setTitle(tenantName);
tenant = tenantService.saveTenant(tenant);
installScripts.createDefaultRuleChains(tenant.getId());
+ tenantProfileCache.evict(tenant.getId());
+ tbClusterService.onTenantChange(tenant, null);
+ tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(),
+ ComponentLifecycleEvent.CREATED);
} else {
tenant = tenants.get(0);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java
index 984936874f..27a4962a0f 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java
@@ -15,10 +15,15 @@
*/
package org.thingsboard.server.service.security.auth.oauth2;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.service.security.system.SystemSecurityService;
import org.thingsboard.server.utils.MiscUtils;
import javax.servlet.ServletException;
@@ -32,11 +37,18 @@ import java.nio.charset.StandardCharsets;
@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
+ private final SystemSecurityService systemSecurityService;
+
+ @Autowired
+ public Oauth2AuthenticationFailureHandler(final SystemSecurityService systemSecurityService) {
+ this.systemSecurityService = systemSecurityService;
+ }
+
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
- String baseUrl = MiscUtils.constructBaseUrl(request);
+ String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString()));
}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
index 8a65eadedc..2e2eeeee61 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
@@ -22,12 +22,16 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
+import org.thingsboard.server.service.security.system.SystemSecurityService;
import org.thingsboard.server.utils.MiscUtils;
import javax.servlet.http.HttpServletRequest;
@@ -45,25 +49,27 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
private final OAuth2ClientMapperProvider oauth2ClientMapperProvider;
private final OAuth2Service oAuth2Service;
private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
+ private final SystemSecurityService systemSecurityService;
@Autowired
public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory,
final RefreshTokenRepository refreshTokenRepository,
final OAuth2ClientMapperProvider oauth2ClientMapperProvider,
final OAuth2Service oAuth2Service,
- final OAuth2AuthorizedClientService oAuth2AuthorizedClientService) {
+ final OAuth2AuthorizedClientService oAuth2AuthorizedClientService, final SystemSecurityService systemSecurityService) {
this.tokenFactory = tokenFactory;
this.refreshTokenRepository = refreshTokenRepository;
this.oauth2ClientMapperProvider = oauth2ClientMapperProvider;
this.oAuth2Service = oAuth2Service;
this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
+ this.systemSecurityService = systemSecurityService;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException {
- String baseUrl = MiscUtils.constructBaseUrl(request);
+ String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
try {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java
index 4219dbc609..9bd22c88f5 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java
@@ -202,16 +202,19 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
@Override
public String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest) {
- String baseUrl;
+ String baseUrl = null;
AdminSettings generalSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "general");
JsonNode prohibitDifferentUrl = generalSettings.getJsonValue().get("prohibitDifferentUrl");
if (prohibitDifferentUrl != null && prohibitDifferentUrl.asBoolean()) {
baseUrl = generalSettings.getJsonValue().get("baseUrl").asText();
- } else {
+ }
+
+ if (StringUtils.isEmpty(baseUrl)) {
baseUrl = MiscUtils.constructBaseUrl(httpServletRequest);
}
+
return baseUrl;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
index 89233d2f4f..4e62628851 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
@@ -108,7 +108,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
* Since number of subscriptions is usually much less then number of devices that are pushing data.
*/
subscriptionsBySessionId.values().forEach(map -> map.values()
- .forEach(sub -> pushSubscriptionToManagerService(sub, false)));
+ .forEach(sub -> pushSubscriptionToManagerService(sub, true)));
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
index c7cbea8200..5ceb9b3cd6 100644
--- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
+++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
@@ -165,7 +165,7 @@ public class DefaultTransportApiService implements TransportApiService {
private ListenableFuture validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) {
DeviceCredentials credentials = null;
- if (mqtt.getUserName() != null) {
+ if (!StringUtils.isEmpty(mqtt.getUserName())) {
credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName());
if (credentials != null) {
if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java
index 1f242a0f88..87adc31713 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java
@@ -21,5 +21,5 @@ import java.io.Serializable;
* @author Andrew Shvayka
*/
public enum ComponentLifecycleEvent implements Serializable {
- CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED, ADDED_TO_ALLOW_LIST, ADDED_TO_DENY_LIST
+ CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED
}
\ No newline at end of file
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/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
index 4af723f3a0..9b2e3022a9 100644
--- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
+++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
@@ -390,7 +390,8 @@ public class DefaultTransportService implements TransportService {
metaData.putValue("deviceName", sessionInfo.getDeviceName());
metaData.putValue("deviceType", sessionInfo.getDeviceType());
metaData.putValue("notifyDevice", "false");
- sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, new TransportTbQueueCallback(callback));
+ sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST,
+ new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, msg.getKvList().size(), callback)));
}
}
@@ -399,7 +400,7 @@ public class DefaultTransportService implements TransportService {
if (checkLimits(sessionInfo, msg, callback)) {
reportActivityInternal(sessionInfo);
sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
- .setGetAttributes(msg).build(), callback);
+ .setGetAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback));
}
}
@@ -409,7 +410,7 @@ public class DefaultTransportService implements TransportService {
SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo);
sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe());
sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
- .setSubscribeToAttributes(msg).build(), callback);
+ .setSubscribeToAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback));
}
}
@@ -419,7 +420,7 @@ public class DefaultTransportService implements TransportService {
SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo);
sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe());
sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
- .setSubscribeToRPC(msg).build(), callback);
+ .setSubscribeToRPC(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback));
}
}
@@ -428,7 +429,7 @@ public class DefaultTransportService implements TransportService {
if (checkLimits(sessionInfo, msg, callback)) {
reportActivityInternal(sessionInfo);
sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
- .setToDeviceRPCCallResponse(msg).build(), callback);
+ .setToDeviceRPCCallResponse(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback));
}
}
@@ -805,7 +806,7 @@ public class DefaultTransportService implements TransportService {
@Override
public void onFailure(Throwable t) {
- callback.onError(t);
+ DefaultTransportService.this.transportCallbackExecutor.submit(() -> callback.onError(t));
}
}
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/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
index db61e6ab05..8edb1917ba 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
@@ -202,6 +202,9 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
" THEN (select additional_info from entity_view where id = entity_id)" +
" END as additional_info";
+ private static final String SELECT_API_USAGE_STATE = "(select aus.id, aus.created_time, aus.tenant_id, '13814000-1dd2-11b2-8080-808080808080'::uuid as customer_id, " +
+ "(select title from tenant where id = aus.tenant_id) as name from api_usage_state as aus)";
+
static {
entityTableMap.put(EntityType.ASSET, "asset");
entityTableMap.put(EntityType.DEVICE, "device");
@@ -210,7 +213,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
entityTableMap.put(EntityType.CUSTOMER, "customer");
entityTableMap.put(EntityType.USER, "tb_user");
entityTableMap.put(EntityType.TENANT, "tenant");
- entityTableMap.put(EntityType.API_USAGE_STATE, "api_usage_state");
+ entityTableMap.put(EntityType.API_USAGE_STATE, SELECT_API_USAGE_STATE);
}
public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java
index 7332de4b49..017306c47c 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java
@@ -80,7 +80,7 @@ public class EntityKeyMapping {
public static final List labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO);
public static final List contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO);
- public static final Set apiUsageStateEntityFields = Collections.singleton(CREATED_TIME);
+ public static final Set apiUsageStateEntityFields = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME));
public static final Set commonEntityFieldsSet = new HashSet<>(commonEntityFields);
public static final Set relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO));
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 66000b3316..45dbb3be87 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
@@ -874,11 +874,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
@@ -1202,6 +1197,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/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
index c59dbb75ca..ce43ca260f 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
@@ -67,6 +67,7 @@ class AlarmState {
initCurrentAlarm(ctx);
lastMsgMetaData = msg.getMetaData();
lastMsgQueueName = msg.getQueueName();
+ this.dataSnapshot = data;
return createOrClearAlarms(ctx, data, update, AlarmRuleState::eval);
}
@@ -90,8 +91,7 @@ class AlarmState {
resultState = state;
break;
} else if (AlarmEvalResult.FALSE.equals(evalResult)) {
- state.clear();
- stateUpdate |= state.checkUpdate();
+ stateUpdate = clearAlarmState(stateUpdate, state);
}
}
if (resultState != null) {
@@ -99,6 +99,7 @@ class AlarmState {
if (result != null) {
pushMsg(ctx, result);
}
+ stateUpdate = clearAlarmState(stateUpdate, clearState);
} else if (currentAlarm != null && clearState != null) {
if (!validateUpdate(update, clearState)) {
log.debug("[{}] Update is not valid for current clear state", alarmDefinition.getId());
@@ -106,23 +107,26 @@ class AlarmState {
}
AlarmEvalResult evalResult = evalFunction.apply(clearState, data);
if (AlarmEvalResult.TRUE.equals(evalResult)) {
- clearState.clear();
- stateUpdate |= clearState.checkUpdate();
+ stateUpdate = clearAlarmState(stateUpdate, clearState);
for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
- state.clear();
- stateUpdate |= state.checkUpdate();
+ stateUpdate = clearAlarmState(stateUpdate, state);
}
ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis());
pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm));
currentAlarm = null;
} else if (AlarmEvalResult.FALSE.equals(evalResult)) {
- clearState.clear();
- stateUpdate |= clearState.checkUpdate();
+ stateUpdate = clearAlarmState(stateUpdate, clearState);
}
}
return stateUpdate;
}
+ public boolean clearAlarmState(boolean stateUpdate, AlarmRuleState state) {
+ state.clear();
+ stateUpdate |= state.checkUpdate();
+ return stateUpdate;
+ }
+
public boolean validateUpdate(SnapshotUpdate update, AlarmRuleState state) {
if (update != null) {
//Check that the update type and that keys match.
@@ -190,7 +194,7 @@ class AlarmState {
}
}
- private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) {
+ private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) {
AlarmSeverity severity = ruleState.getSeverity();
if (currentAlarm != null) {
// TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain).
@@ -230,7 +234,7 @@ class AlarmState {
}
}
- private JsonNode createDetails(AlarmRuleState ruleState) {
+ private JsonNode createDetails(AlarmRuleState ruleState) {
ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode();
String alarmDetails = ruleState.getAlarmRule().getAlarmDetails();
@@ -273,8 +277,7 @@ class AlarmState {
if (currentAlarm != null && currentAlarm.getId().equals(alarmNf.getId())) {
currentAlarm = null;
for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
- state.clear();
- updated |= state.checkUpdate();
+ updated = clearAlarmState(updated, state);
}
}
return updated;
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/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts
index acd89e3c1b..9263838ffa 100644
--- a/ui-ngx/src/app/core/http/entity.service.ts
+++ b/ui-ngx/src/app/core/http/entity.service.ts
@@ -630,6 +630,9 @@ export class EntityService {
case EntityType.DASHBOARD:
entityFieldKeys.push(entityFields.title.keyName);
break;
+ case EntityType.API_USAGE_STATE:
+ entityFieldKeys.push(entityFields.name.keyName);
+ break;
}
return query ? entityFieldKeys.filter((entityField) => entityField.toLowerCase().indexOf(query) === 0) : entityFieldKeys;
}
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html
index cedf147bc1..c855cfda97 100644
--- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html
+++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html
@@ -52,6 +52,9 @@
+
+ {{ 'device-profile.mqtt-device-topic-filters-unique' | translate }}
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts
index d16ad840f1..be54cff29a 100644
--- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts
+++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts
@@ -52,7 +52,6 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
mqttTransportPayloadTypeTranslations = mqttTransportPayloadTypeTranslationMap;
-
mqttDeviceProfileTransportConfigurationFormGroup: FormGroup;
private requiredValue: boolean;
@@ -90,7 +89,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
transportPayloadTypeConfiguration: this.fb.group({
transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required]
})
- })
+ }, {validator: this.uniqueDeviceTopicValidator})
});
this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').valueChanges.subscribe(payloadType => {
this.updateTransportPayloadBasedControls(payloadType);
@@ -171,4 +170,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
return null;
};
}
+
+ private uniqueDeviceTopicValidator(control: FormGroup): { [key: string]: boolean } | null {
+ if (control.value) {
+ const formValue = control.value as MqttDeviceProfileTransportConfiguration;
+ if (formValue.deviceAttributesTopic === formValue.deviceTelemetryTopic) {
+ return {unique: true};
+ }
+ }
+ return null;
+ }
}
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
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/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json
index d528992eb7..709ff5eb50 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -885,6 +885,7 @@
"no-device-profiles-found": "No device profiles found.",
"create-new-device-profile": "Create a new one!",
"mqtt-device-topic-filters": "MQTT device topic filters",
+ "mqtt-device-topic-filters-unique": "MQTT device topic filters need to be unique.",
"mqtt-device-payload-type": "MQTT device payload",
"mqtt-device-payload-type-json": "JSON",
"mqtt-device-payload-type-proto": "Protobuf",
@@ -1949,6 +1950,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.",