Commit 861fd651096ac37a4d72a2ebdfe77fd59b4776fe

Authored by ShvaykaD
2 parents 4a187813 35a21079

merge with master

Showing 49 changed files with 360 additions and 98 deletions
... ... @@ -146,10 +146,6 @@
146 146 <artifactId>spring-boot-starter-websocket</artifactId>
147 147 </dependency>
148 148 <dependency>
149   - <groupId>org.springframework.cloud</groupId>
150   - <artifactId>spring-cloud-starter-oauth2</artifactId>
151   - </dependency>
152   - <dependency>
153 149 <groupId>org.springframework.security</groupId>
154 150 <artifactId>spring-security-oauth2-client</artifactId>
155 151 </dependency>
... ...
... ... @@ -35,14 +35,17 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
35 35 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
36 36 import org.thingsboard.server.common.data.page.PageData;
37 37 import org.thingsboard.server.common.data.page.PageLink;
  38 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
38 39 import org.thingsboard.server.common.data.security.Authority;
39 40 import org.thingsboard.server.common.data.security.UserCredentials;
40 41 import org.thingsboard.server.dao.customer.CustomerService;
41 42 import org.thingsboard.server.dao.dashboard.DashboardService;
42 43 import org.thingsboard.server.dao.oauth2.OAuth2User;
  44 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
43 45 import org.thingsboard.server.dao.tenant.TenantService;
44 46 import org.thingsboard.server.dao.user.UserService;
45 47 import org.thingsboard.server.service.install.InstallScripts;
  48 +import org.thingsboard.server.service.queue.TbClusterService;
46 49 import org.thingsboard.server.service.security.model.SecurityUser;
47 50 import org.thingsboard.server.service.security.model.UserPrincipal;
48 51
... ... @@ -76,6 +79,12 @@ public abstract class AbstractOAuth2ClientMapper {
76 79 @Autowired
77 80 private InstallScripts installScripts;
78 81
  82 + @Autowired
  83 + protected TbTenantProfileCache tenantProfileCache;
  84 +
  85 + @Autowired
  86 + protected TbClusterService tbClusterService;
  87 +
79 88 private final Lock userCreationLock = new ReentrantLock();
80 89
81 90 protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) {
... ... @@ -162,6 +171,10 @@ public abstract class AbstractOAuth2ClientMapper {
162 171 tenant.setTitle(tenantName);
163 172 tenant = tenantService.saveTenant(tenant);
164 173 installScripts.createDefaultRuleChains(tenant.getId());
  174 + tenantProfileCache.evict(tenant.getId());
  175 + tbClusterService.onTenantChange(tenant, null);
  176 + tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(),
  177 + ComponentLifecycleEvent.CREATED);
165 178 } else {
166 179 tenant = tenants.get(0);
167 180 }
... ...
... ... @@ -15,10 +15,15 @@
15 15 */
16 16 package org.thingsboard.server.service.security.auth.oauth2;
17 17
  18 +import org.springframework.beans.factory.annotation.Autowired;
18 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
19 20 import org.springframework.security.core.AuthenticationException;
20 21 import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
21 22 import org.springframework.stereotype.Component;
  23 +import org.thingsboard.server.common.data.id.CustomerId;
  24 +import org.thingsboard.server.common.data.id.EntityId;
  25 +import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.service.security.system.SystemSecurityService;
22 27 import org.thingsboard.server.utils.MiscUtils;
23 28
24 29 import javax.servlet.ServletException;
... ... @@ -32,11 +37,18 @@ import java.nio.charset.StandardCharsets;
32 37 @ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
33 38 public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
34 39
  40 + private final SystemSecurityService systemSecurityService;
  41 +
  42 + @Autowired
  43 + public Oauth2AuthenticationFailureHandler(final SystemSecurityService systemSecurityService) {
  44 + this.systemSecurityService = systemSecurityService;
  45 + }
  46 +
35 47 @Override
36 48 public void onAuthenticationFailure(HttpServletRequest request,
37 49 HttpServletResponse response, AuthenticationException exception)
38 50 throws IOException, ServletException {
39   - String baseUrl = MiscUtils.constructBaseUrl(request);
  51 + String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
40 52 getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
41 53 URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString()));
42 54 }
... ...
... ... @@ -22,12 +22,16 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
22 22 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
23 23 import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
24 24 import org.springframework.stereotype.Component;
  25 +import org.thingsboard.server.common.data.id.CustomerId;
  26 +import org.thingsboard.server.common.data.id.EntityId;
  27 +import org.thingsboard.server.common.data.id.TenantId;
25 28 import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
26 29 import org.thingsboard.server.dao.oauth2.OAuth2Service;
27 30 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
28 31 import org.thingsboard.server.service.security.model.SecurityUser;
29 32 import org.thingsboard.server.service.security.model.token.JwtToken;
30 33 import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
  34 +import org.thingsboard.server.service.security.system.SystemSecurityService;
31 35 import org.thingsboard.server.utils.MiscUtils;
32 36
33 37 import javax.servlet.http.HttpServletRequest;
... ... @@ -45,25 +49,27 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
45 49 private final OAuth2ClientMapperProvider oauth2ClientMapperProvider;
46 50 private final OAuth2Service oAuth2Service;
47 51 private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
  52 + private final SystemSecurityService systemSecurityService;
48 53
49 54 @Autowired
50 55 public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory,
51 56 final RefreshTokenRepository refreshTokenRepository,
52 57 final OAuth2ClientMapperProvider oauth2ClientMapperProvider,
53 58 final OAuth2Service oAuth2Service,
54   - final OAuth2AuthorizedClientService oAuth2AuthorizedClientService) {
  59 + final OAuth2AuthorizedClientService oAuth2AuthorizedClientService, final SystemSecurityService systemSecurityService) {
55 60 this.tokenFactory = tokenFactory;
56 61 this.refreshTokenRepository = refreshTokenRepository;
57 62 this.oauth2ClientMapperProvider = oauth2ClientMapperProvider;
58 63 this.oAuth2Service = oAuth2Service;
59 64 this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
  65 + this.systemSecurityService = systemSecurityService;
60 66 }
61 67
62 68 @Override
63 69 public void onAuthenticationSuccess(HttpServletRequest request,
64 70 HttpServletResponse response,
65 71 Authentication authentication) throws IOException {
66   - String baseUrl = MiscUtils.constructBaseUrl(request);
  72 + String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
67 73 try {
68 74 OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
69 75
... ...
... ... @@ -202,16 +202,19 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
202 202
203 203 @Override
204 204 public String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest) {
205   - String baseUrl;
  205 + String baseUrl = null;
206 206 AdminSettings generalSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "general");
207 207
208 208 JsonNode prohibitDifferentUrl = generalSettings.getJsonValue().get("prohibitDifferentUrl");
209 209
210 210 if (prohibitDifferentUrl != null && prohibitDifferentUrl.asBoolean()) {
211 211 baseUrl = generalSettings.getJsonValue().get("baseUrl").asText();
212   - } else {
  212 + }
  213 +
  214 + if (StringUtils.isEmpty(baseUrl)) {
213 215 baseUrl = MiscUtils.constructBaseUrl(httpServletRequest);
214 216 }
  217 +
215 218 return baseUrl;
216 219 }
217 220
... ...
... ... @@ -108,7 +108,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
108 108 * Since number of subscriptions is usually much less then number of devices that are pushing data.
109 109 */
110 110 subscriptionsBySessionId.values().forEach(map -> map.values()
111   - .forEach(sub -> pushSubscriptionToManagerService(sub, false)));
  111 + .forEach(sub -> pushSubscriptionToManagerService(sub, true)));
112 112 }
113 113 }
114 114
... ...
... ... @@ -165,7 +165,7 @@ public class DefaultTransportApiService implements TransportApiService {
165 165
166 166 private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) {
167 167 DeviceCredentials credentials = null;
168   - if (mqtt.getUserName() != null) {
  168 + if (!StringUtils.isEmpty(mqtt.getUserName())) {
169 169 credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName());
170 170 if (credentials != null) {
171 171 if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) {
... ...
... ... @@ -21,5 +21,5 @@ import java.io.Serializable;
21 21 * @author Andrew Shvayka
22 22 */
23 23 public enum ComponentLifecycleEvent implements Serializable {
24   - CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED, ADDED_TO_ALLOW_LIST, ADDED_TO_DENY_LIST
  24 + CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED
25 25 }
\ No newline at end of file
... ...
... ... @@ -24,6 +24,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
24 24
25 25 private long maxDevices;
26 26 private long maxAssets;
  27 + private long maxCustomers;
  28 + private long maxUsers;
  29 + private long maxDashboards;
  30 + private long maxRuleChains;
27 31
28 32 private String transportTenantMsgRateLimit;
29 33 private String transportTenantTelemetryMsgRateLimit;
... ...
... ... @@ -390,7 +390,8 @@ public class DefaultTransportService implements TransportService {
390 390 metaData.putValue("deviceName", sessionInfo.getDeviceName());
391 391 metaData.putValue("deviceType", sessionInfo.getDeviceType());
392 392 metaData.putValue("notifyDevice", "false");
393   - sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, new TransportTbQueueCallback(callback));
  393 + sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST,
  394 + new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, msg.getKvList().size(), callback)));
394 395 }
395 396 }
396 397
... ... @@ -399,7 +400,7 @@ public class DefaultTransportService implements TransportService {
399 400 if (checkLimits(sessionInfo, msg, callback)) {
400 401 reportActivityInternal(sessionInfo);
401 402 sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
402   - .setGetAttributes(msg).build(), callback);
  403 + .setGetAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback));
403 404 }
404 405 }
405 406
... ... @@ -409,7 +410,7 @@ public class DefaultTransportService implements TransportService {
409 410 SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo);
410 411 sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe());
411 412 sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
412   - .setSubscribeToAttributes(msg).build(), callback);
  413 + .setSubscribeToAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback));
413 414 }
414 415 }
415 416
... ... @@ -419,7 +420,7 @@ public class DefaultTransportService implements TransportService {
419 420 SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo);
420 421 sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe());
421 422 sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
422   - .setSubscribeToRPC(msg).build(), callback);
  423 + .setSubscribeToRPC(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback));
423 424 }
424 425 }
425 426
... ... @@ -428,7 +429,7 @@ public class DefaultTransportService implements TransportService {
428 429 if (checkLimits(sessionInfo, msg, callback)) {
429 430 reportActivityInternal(sessionInfo);
430 431 sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
431   - .setToDeviceRPCCallResponse(msg).build(), callback);
  432 + .setToDeviceRPCCallResponse(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback));
432 433 }
433 434 }
434 435
... ... @@ -805,7 +806,7 @@ public class DefaultTransportService implements TransportService {
805 806
806 807 @Override
807 808 public void onFailure(Throwable t) {
808   - callback.onError(t);
  809 + DefaultTransportService.this.transportCallbackExecutor.submit(() -> callback.onError(t));
809 810 }
810 811 }
811 812
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao;
  17 +
  18 +import org.thingsboard.server.common.data.id.TenantId;
  19 +
  20 +public interface TenantEntityDao {
  21 +
  22 + Long countByTenantId(TenantId tenantId);
  23 +}
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId;
23 23 import org.thingsboard.server.common.data.page.PageData;
24 24 import org.thingsboard.server.common.data.page.PageLink;
25 25 import org.thingsboard.server.dao.Dao;
  26 +import org.thingsboard.server.dao.TenantEntityDao;
26 27
27 28 import java.util.List;
28 29 import java.util.Optional;
... ... @@ -32,7 +33,7 @@ import java.util.UUID;
32 33 * The Interface AssetDao.
33 34 *
34 35 */
35   -public interface AssetDao extends Dao<Asset> {
  36 +public interface AssetDao extends Dao<Asset>, TenantEntityDao {
36 37
37 38 /**
38 39 * Find asset info by id.
... ... @@ -166,6 +167,4 @@ public interface AssetDao extends Dao<Asset> {
166 167 */
167 168 ListenableFuture<List<EntitySubtype>> findTenantAssetTypesAsync(UUID tenantId);
168 169
169   - Long countAssetsByTenantId(TenantId tenantId);
170   -
171 170 }
... ...
... ... @@ -330,12 +330,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
330 330 DefaultTenantProfileConfiguration profileConfiguration =
331 331 (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
332 332 long maxAssets = profileConfiguration.getMaxAssets();
333   - if (maxAssets > 0) {
334   - long currentAssetsCount = assetDao.countAssetsByTenantId(tenantId);
335   - if (maxAssets >= currentAssetsCount) {
336   - throw new DataValidationException("Can't create assets more then " + maxAssets);
337   - }
338   - }
  333 + validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET);
339 334 }
340 335
341 336 @Override
... ...
... ... @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.TenantId;
20 20 import org.thingsboard.server.common.data.page.PageData;
21 21 import org.thingsboard.server.common.data.page.PageLink;
22 22 import org.thingsboard.server.dao.Dao;
  23 +import org.thingsboard.server.dao.TenantEntityDao;
23 24
24 25 import java.util.Optional;
25 26 import java.util.UUID;
... ... @@ -27,7 +28,7 @@ import java.util.UUID;
27 28 /**
28 29 * The Interface CustomerDao.
29 30 */
30   -public interface CustomerDao extends Dao<Customer> {
  31 +public interface CustomerDao extends Dao<Customer>, TenantEntityDao {
31 32
32 33 /**
33 34 * Save or update customer object
... ... @@ -54,5 +55,5 @@ public interface CustomerDao extends Dao<Customer> {
54 55 * @return the optional customer object
55 56 */
56 57 Optional<Customer> findCustomersByTenantIdAndTitle(UUID tenantId, String title);
57   -
  58 +
58 59 }
... ...
... ... @@ -21,13 +21,16 @@ import com.google.common.util.concurrent.ListenableFuture;
21 21 import lombok.extern.slf4j.Slf4j;
22 22 import org.apache.commons.lang3.StringUtils;
23 23 import org.springframework.beans.factory.annotation.Autowired;
  24 +import org.springframework.context.annotation.Lazy;
24 25 import org.springframework.stereotype.Service;
25 26 import org.thingsboard.server.common.data.Customer;
  27 +import org.thingsboard.server.common.data.EntityType;
26 28 import org.thingsboard.server.common.data.Tenant;
27 29 import org.thingsboard.server.common.data.id.CustomerId;
28 30 import org.thingsboard.server.common.data.id.TenantId;
29 31 import org.thingsboard.server.common.data.page.PageData;
30 32 import org.thingsboard.server.common.data.page.PageLink;
  33 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
31 34 import org.thingsboard.server.dao.asset.AssetService;
32 35 import org.thingsboard.server.dao.dashboard.DashboardService;
33 36 import org.thingsboard.server.dao.device.DeviceService;
... ... @@ -38,6 +41,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
38 41 import org.thingsboard.server.dao.service.DataValidator;
39 42 import org.thingsboard.server.dao.service.PaginatedRemover;
40 43 import org.thingsboard.server.dao.service.Validator;
  44 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
41 45 import org.thingsboard.server.dao.tenant.TenantDao;
42 46 import org.thingsboard.server.dao.user.UserService;
43 47
... ... @@ -75,6 +79,10 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
75 79 @Autowired
76 80 private DashboardService dashboardService;
77 81
  82 + @Autowired
  83 + @Lazy
  84 + private TbTenantProfileCache tenantProfileCache;
  85 +
78 86 @Override
79 87 public Customer findCustomerById(TenantId tenantId, CustomerId customerId) {
80 88 log.trace("Executing findCustomerById [{}]", customerId);
... ... @@ -162,6 +170,11 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
162 170
163 171 @Override
164 172 protected void validateCreate(TenantId tenantId, Customer customer) {
  173 + DefaultTenantProfileConfiguration profileConfiguration =
  174 + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  175 + long maxCustomers = profileConfiguration.getMaxCustomers();
  176 +
  177 + validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER);
165 178 customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
166 179 c -> {
167 180 throw new DataValidationException("Customer with such title already exists!");
... ...
... ... @@ -18,11 +18,12 @@ package org.thingsboard.server.dao.dashboard;
18 18 import org.thingsboard.server.common.data.Dashboard;
19 19 import org.thingsboard.server.common.data.id.TenantId;
20 20 import org.thingsboard.server.dao.Dao;
  21 +import org.thingsboard.server.dao.TenantEntityDao;
21 22
22 23 /**
23 24 * The Interface DashboardDao.
24 25 */
25   -public interface DashboardDao extends Dao<Dashboard> {
  26 +public interface DashboardDao extends Dao<Dashboard>, TenantEntityDao {
26 27
27 28 /**
28 29 * Save or update dashboard object
... ... @@ -31,5 +32,4 @@ public interface DashboardDao extends Dao<Dashboard> {
31 32 * @return saved dashboard object
32 33 */
33 34 Dashboard save(TenantId tenantId, Dashboard dashboard);
34   -
35 35 }
... ...
... ... @@ -19,10 +19,12 @@ import com.google.common.util.concurrent.ListenableFuture;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.apache.commons.lang3.StringUtils;
21 21 import org.springframework.beans.factory.annotation.Autowired;
  22 +import org.springframework.context.annotation.Lazy;
22 23 import org.springframework.stereotype.Service;
23 24 import org.thingsboard.server.common.data.Customer;
24 25 import org.thingsboard.server.common.data.Dashboard;
25 26 import org.thingsboard.server.common.data.DashboardInfo;
  27 +import org.thingsboard.server.common.data.EntityType;
26 28 import org.thingsboard.server.common.data.Tenant;
27 29 import org.thingsboard.server.common.data.id.CustomerId;
28 30 import org.thingsboard.server.common.data.id.DashboardId;
... ... @@ -31,12 +33,14 @@ import org.thingsboard.server.common.data.page.PageData;
31 33 import org.thingsboard.server.common.data.page.PageLink;
32 34 import org.thingsboard.server.common.data.relation.EntityRelation;
33 35 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
  36 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
34 37 import org.thingsboard.server.dao.customer.CustomerDao;
35 38 import org.thingsboard.server.dao.entity.AbstractEntityService;
36 39 import org.thingsboard.server.dao.exception.DataValidationException;
37 40 import org.thingsboard.server.dao.service.DataValidator;
38 41 import org.thingsboard.server.dao.service.PaginatedRemover;
39 42 import org.thingsboard.server.dao.service.Validator;
  43 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
40 44 import org.thingsboard.server.dao.tenant.TenantDao;
41 45
42 46 import java.util.concurrent.ExecutionException;
... ... @@ -61,6 +65,10 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
61 65 @Autowired
62 66 private CustomerDao customerDao;
63 67
  68 + @Autowired
  69 + @Lazy
  70 + private TbTenantProfileCache tenantProfileCache;
  71 +
64 72 @Override
65 73 public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) {
66 74 log.trace("Executing findDashboardById [{}]", dashboardId);
... ... @@ -215,6 +223,14 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
215 223 private DataValidator<Dashboard> dashboardValidator =
216 224 new DataValidator<Dashboard>() {
217 225 @Override
  226 + protected void validateCreate(TenantId tenantId, Dashboard data) {
  227 + DefaultTenantProfileConfiguration profileConfiguration =
  228 + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  229 + long maxDashboards = profileConfiguration.getMaxDashboards();
  230 + validateNumberOfEntitiesPerTenant(tenantId, dashboardDao, maxDashboards, EntityType.DASHBOARD);
  231 + }
  232 +
  233 + @Override
218 234 protected void validateDataImpl(TenantId tenantId, Dashboard dashboard) {
219 235 if (StringUtils.isEmpty(dashboard.getTitle())) {
220 236 throw new DataValidationException("Dashboard title should be specified!");
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId;
23 23 import org.thingsboard.server.common.data.page.PageData;
24 24 import org.thingsboard.server.common.data.page.PageLink;
25 25 import org.thingsboard.server.dao.Dao;
  26 +import org.thingsboard.server.dao.TenantEntityDao;
26 27
27 28 import java.util.List;
28 29 import java.util.Optional;
... ... @@ -32,7 +33,7 @@ import java.util.UUID;
32 33 * The Interface DeviceDao.
33 34 *
34 35 */
35   -public interface DeviceDao extends Dao<Device> {
  36 +public interface DeviceDao extends Dao<Device>, TenantEntityDao {
36 37
37 38 /**
38 39 * Find device info by id.
... ... @@ -203,8 +204,6 @@ public interface DeviceDao extends Dao<Device> {
203 204 */
204 205 ListenableFuture<Device> findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id);
205 206
206   - Long countDevicesByTenantId(TenantId tenantId);
207   -
208 207 Long countDevicesByDeviceProfileId(TenantId tenantId, UUID deviceProfileId);
209 208
210 209 /**
... ...
... ... @@ -530,12 +530,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
530 530 DefaultTenantProfileConfiguration profileConfiguration =
531 531 (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
532 532 long maxDevices = profileConfiguration.getMaxDevices();
533   - if (maxDevices > 0) {
534   - long currentDevicesCount = deviceDao.countDevicesByTenantId(tenantId);
535   - if (maxDevices >= currentDevicesCount) {
536   - throw new DataValidationException("Can't create devices more then " + maxDevices);
537   - }
538   - }
  533 + validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE);
539 534 }
540 535
541 536 @Override
... ...
... ... @@ -37,12 +37,11 @@ public abstract class AbstractEntityService {
37 37
38 38 protected Optional<ConstraintViolationException> extractConstraintViolationException(Exception t) {
39 39 if (t instanceof ConstraintViolationException) {
40   - return Optional.of ((ConstraintViolationException) t);
  40 + return Optional.of((ConstraintViolationException) t);
41 41 } else if (t.getCause() instanceof ConstraintViolationException) {
42   - return Optional.of ((ConstraintViolationException) (t.getCause()));
  42 + return Optional.of((ConstraintViolationException) (t.getCause()));
43 43 } else {
44 44 return Optional.empty();
45 45 }
46 46 }
47   -
48 47 }
... ...
... ... @@ -24,6 +24,7 @@ import org.apache.commons.collections.CollectionUtils;
24 24 import org.apache.commons.lang3.StringUtils;
25 25 import org.hibernate.exception.ConstraintViolationException;
26 26 import org.springframework.beans.factory.annotation.Autowired;
  27 +import org.springframework.context.annotation.Lazy;
27 28 import org.springframework.stereotype.Service;
28 29 import org.thingsboard.server.common.data.BaseData;
29 30 import org.thingsboard.server.common.data.EntityType;
... ... @@ -44,11 +45,13 @@ import org.thingsboard.server.common.data.rule.RuleChainData;
44 45 import org.thingsboard.server.common.data.rule.RuleChainImportResult;
45 46 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
46 47 import org.thingsboard.server.common.data.rule.RuleNode;
  48 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
47 49 import org.thingsboard.server.dao.entity.AbstractEntityService;
48 50 import org.thingsboard.server.dao.exception.DataValidationException;
49 51 import org.thingsboard.server.dao.service.DataValidator;
50 52 import org.thingsboard.server.dao.service.PaginatedRemover;
51 53 import org.thingsboard.server.dao.service.Validator;
  54 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
52 55 import org.thingsboard.server.dao.tenant.TenantDao;
53 56
54 57 import java.util.ArrayList;
... ... @@ -81,6 +84,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
81 84 @Autowired
82 85 private TenantDao tenantDao;
83 86
  87 + @Autowired
  88 + @Lazy
  89 + private TbTenantProfileCache tenantProfileCache;
  90 +
84 91 @Override
85 92 public RuleChain saveRuleChain(RuleChain ruleChain) {
86 93 ruleChainValidator.validate(ruleChain, RuleChain::getTenantId);
... ... @@ -581,6 +588,14 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
581 588 private DataValidator<RuleChain> ruleChainValidator =
582 589 new DataValidator<RuleChain>() {
583 590 @Override
  591 + protected void validateCreate(TenantId tenantId, RuleChain data) {
  592 + DefaultTenantProfileConfiguration profileConfiguration =
  593 + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  594 + long maxRuleChains = profileConfiguration.getMaxRuleChains();
  595 + validateNumberOfEntitiesPerTenant(tenantId, ruleChainDao, maxRuleChains, EntityType.RULE_CHAIN);
  596 + }
  597 +
  598 + @Override
584 599 protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) {
585 600 if (StringUtils.isEmpty(ruleChain.getName())) {
586 601 throw new DataValidationException("Rule chain name should be specified!.");
... ...
... ... @@ -19,13 +19,14 @@ import org.thingsboard.server.common.data.page.PageData;
19 19 import org.thingsboard.server.common.data.page.PageLink;
20 20 import org.thingsboard.server.common.data.rule.RuleChain;
21 21 import org.thingsboard.server.dao.Dao;
  22 +import org.thingsboard.server.dao.TenantEntityDao;
22 23
23 24 import java.util.UUID;
24 25
25 26 /**
26 27 * Created by igor on 3/12/18.
27 28 */
28   -public interface RuleChainDao extends Dao<RuleChain> {
  29 +public interface RuleChainDao extends Dao<RuleChain>, TenantEntityDao {
29 30
30 31 /**
31 32 * Find rule chains by tenantId and page link.
... ... @@ -35,5 +36,4 @@ public interface RuleChainDao extends Dao<RuleChain> {
35 36 * @return the list of rule chain objects
36 37 */
37 38 PageData<RuleChain> findRuleChainsByTenantId(UUID tenantId, PageLink pageLink);
38   -
39 39 }
... ...
... ... @@ -18,7 +18,9 @@ package org.thingsboard.server.dao.service;
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.thingsboard.server.common.data.BaseData;
  21 +import org.thingsboard.server.common.data.EntityType;
21 22 import org.thingsboard.server.common.data.id.TenantId;
  23 +import org.thingsboard.server.dao.TenantEntityDao;
22 24 import org.thingsboard.server.dao.exception.DataValidationException;
23 25
24 26 import java.util.HashSet;
... ... @@ -79,6 +81,19 @@ public abstract class DataValidator<D extends BaseData<?>> {
79 81 return emailMatcher.matches();
80 82 }
81 83
  84 + protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
  85 + TenantEntityDao tenantEntityDao,
  86 + long maxEntities,
  87 + EntityType entityType) {
  88 + if (maxEntities > 0) {
  89 + long currentEntitiesCount = tenantEntityDao.countByTenantId(tenantId);
  90 + if (currentEntitiesCount >= maxEntities) {
  91 + throw new DataValidationException(String.format("Can't create more then %d %ss!",
  92 + maxEntities, entityType.name().toLowerCase().replaceAll("_", " ")));
  93 + }
  94 + }
  95 + }
  96 +
82 97 protected static void validateJsonStructure(JsonNode expectedNode, JsonNode actualNode) {
83 98 Set<String> expectedFields = new HashSet<>();
84 99 Iterator<String> fieldsIterator = expectedNode.fieldNames();
... ...
... ... @@ -178,8 +178,7 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
178 178 }
179 179
180 180 @Override
181   - public Long countAssetsByTenantId(TenantId tenantId) {
  181 + public Long countByTenantId(TenantId tenantId) {
182 182 return assetRepository.countByTenantId(tenantId.getId());
183   -
184 183 }
185 184 }
... ...
... ... @@ -37,4 +37,5 @@ public interface CustomerRepository extends PagingAndSortingRepository<CustomerE
37 37
38 38 CustomerEntity findByTenantIdAndTitle(UUID tenantId, String title);
39 39
  40 + Long countByTenantId(UUID tenantId);
40 41 }
... ...
... ... @@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired;
19 19 import org.springframework.data.repository.CrudRepository;
20 20 import org.springframework.stereotype.Component;
21 21 import org.thingsboard.server.common.data.Customer;
  22 +import org.thingsboard.server.common.data.id.TenantId;
22 23 import org.thingsboard.server.common.data.page.PageData;
23 24 import org.thingsboard.server.common.data.page.PageLink;
24 25 import org.thingsboard.server.dao.DaoUtil;
... ... @@ -62,4 +63,9 @@ public class JpaCustomerDao extends JpaAbstractSearchTextDao<CustomerEntity, Cus
62 63 Customer customer = DaoUtil.getData(customerRepository.findByTenantIdAndTitle(tenantId, title));
63 64 return Optional.ofNullable(customer);
64 65 }
  66 +
  67 + @Override
  68 + public Long countByTenantId(TenantId tenantId) {
  69 + return customerRepository.countByTenantId(tenantId.getId());
  70 + }
65 71 }
... ...
... ... @@ -24,4 +24,6 @@ import java.util.UUID;
24 24 * Created by Valerii Sosliuk on 5/6/2017.
25 25 */
26 26 public interface DashboardRepository extends CrudRepository<DashboardEntity, UUID> {
  27 +
  28 + Long countByTenantId(UUID tenantId);
27 29 }
... ...
... ... @@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired;
19 19 import org.springframework.data.repository.CrudRepository;
20 20 import org.springframework.stereotype.Component;
21 21 import org.thingsboard.server.common.data.Dashboard;
  22 +import org.thingsboard.server.common.data.id.TenantId;
22 23 import org.thingsboard.server.dao.dashboard.DashboardDao;
23 24 import org.thingsboard.server.dao.model.sql.DashboardEntity;
24 25 import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
... ... @@ -43,4 +44,9 @@ public class JpaDashboardDao extends JpaAbstractSearchTextDao<DashboardEntity, D
43 44 protected CrudRepository<DashboardEntity, UUID> getCrudRepository() {
44 45 return dashboardRepository;
45 46 }
  47 +
  48 + @Override
  49 + public Long countByTenantId(TenantId tenantId) {
  50 + return dashboardRepository.countByTenantId(tenantId.getId());
  51 + }
46 52 }
... ...
... ... @@ -50,9 +50,9 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
50 50 "AND d.deviceProfileId = :profileId " +
51 51 "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
52 52 Page<DeviceEntity> findByTenantIdAndProfileId(@Param("tenantId") UUID tenantId,
53   - @Param("profileId") UUID profileId,
54   - @Param("searchText") String searchText,
55   - Pageable pageable);
  53 + @Param("profileId") UUID profileId,
  54 + @Param("searchText") String searchText,
  55 + Pageable pageable);
56 56
57 57 @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " +
58 58 "FROM DeviceEntity d " +
... ... @@ -62,9 +62,9 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
62 62 "AND d.customerId = :customerId " +
63 63 "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
64 64 Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId,
65   - @Param("customerId") UUID customerId,
66   - @Param("searchText") String searchText,
67   - Pageable pageable);
  65 + @Param("customerId") UUID customerId,
  66 + @Param("searchText") String searchText,
  67 + Pageable pageable);
68 68
69 69 @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId")
70 70 Page<DeviceEntity> findByTenantId(@Param("tenantId") UUID tenantId,
... ... @@ -102,9 +102,9 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
102 102 "AND d.type = :type " +
103 103 "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))")
104 104 Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndType(@Param("tenantId") UUID tenantId,
105   - @Param("type") String type,
106   - @Param("textSearch") String textSearch,
107   - Pageable pageable);
  105 + @Param("type") String type,
  106 + @Param("textSearch") String textSearch,
  107 + Pageable pageable);
108 108
109 109 @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " +
110 110 "FROM DeviceEntity d " +
... ... @@ -137,10 +137,10 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
137 137 "AND d.type = :type " +
138 138 "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))")
139 139 Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndCustomerIdAndType(@Param("tenantId") UUID tenantId,
140   - @Param("customerId") UUID customerId,
141   - @Param("type") String type,
142   - @Param("textSearch") String textSearch,
143   - Pageable pageable);
  140 + @Param("customerId") UUID customerId,
  141 + @Param("type") String type,
  142 + @Param("textSearch") String textSearch,
  143 + Pageable pageable);
144 144
145 145 @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " +
146 146 "FROM DeviceEntity d " +
... ...
... ... @@ -220,7 +220,7 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
220 220 }
221 221
222 222 @Override
223   - public Long countDevicesByTenantId(TenantId tenantId) {
  223 + public Long countByTenantId(TenantId tenantId) {
224 224 return deviceRepository.countByTenantId(tenantId.getId());
225 225 }
226 226
... ...
... ... @@ -202,6 +202,9 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
202 202 " THEN (select additional_info from entity_view where id = entity_id)" +
203 203 " END as additional_info";
204 204
  205 + 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, " +
  206 + "(select title from tenant where id = aus.tenant_id) as name from api_usage_state as aus)";
  207 +
205 208 static {
206 209 entityTableMap.put(EntityType.ASSET, "asset");
207 210 entityTableMap.put(EntityType.DEVICE, "device");
... ... @@ -210,7 +213,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
210 213 entityTableMap.put(EntityType.CUSTOMER, "customer");
211 214 entityTableMap.put(EntityType.USER, "tb_user");
212 215 entityTableMap.put(EntityType.TENANT, "tenant");
213   - entityTableMap.put(EntityType.API_USAGE_STATE, "api_usage_state");
  216 + entityTableMap.put(EntityType.API_USAGE_STATE, SELECT_API_USAGE_STATE);
214 217 }
215 218
216 219 public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{
... ...
... ... @@ -80,7 +80,7 @@ public class EntityKeyMapping {
80 80 public static final List<String> labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO);
81 81 public static final List<String> contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO);
82 82
83   - public static final Set<String> apiUsageStateEntityFields = Collections.singleton(CREATED_TIME);
  83 + public static final Set<String> apiUsageStateEntityFields = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME));
84 84 public static final Set<String> commonEntityFieldsSet = new HashSet<>(commonEntityFields);
85 85 public static final Set<String> 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));
86 86
... ...
... ... @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.data.repository.CrudRepository;
21 21 import org.springframework.stereotype.Component;
  22 +import org.thingsboard.server.common.data.id.TenantId;
22 23 import org.thingsboard.server.common.data.page.PageData;
23 24 import org.thingsboard.server.common.data.page.PageLink;
24 25 import org.thingsboard.server.common.data.rule.RuleChain;
... ... @@ -56,4 +57,8 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao<RuleChainEntity, R
56 57 DaoUtil.toPageable(pageLink)));
57 58 }
58 59
  60 + @Override
  61 + public Long countByTenantId(TenantId tenantId) {
  62 + return ruleChainRepository.countByTenantId(tenantId.getId());
  63 + }
59 64 }
... ...
... ... @@ -32,4 +32,5 @@ public interface RuleChainRepository extends PagingAndSortingRepository<RuleChai
32 32 @Param("searchText") String searchText,
33 33 Pageable pageable);
34 34
  35 + Long countByTenantId(UUID tenantId);
35 36 }
... ...
... ... @@ -91,4 +91,9 @@ public class JpaUserDao extends JpaAbstractSearchTextDao<UserEntity, User> imple
91 91 DaoUtil.toPageable(pageLink)));
92 92
93 93 }
  94 +
  95 + @Override
  96 + public Long countByTenantId(TenantId tenantId) {
  97 + return userRepository.countByTenantId(tenantId.getId());
  98 + }
94 99 }
... ...
... ... @@ -47,4 +47,5 @@ public interface UserRepository extends PagingAndSortingRepository<UserEntity, U
47 47 @Param("searchText") String searchText,
48 48 Pageable pageable);
49 49
  50 + Long countByTenantId(UUID tenantId);
50 51 }
... ...
... ... @@ -20,10 +20,11 @@ import org.thingsboard.server.common.data.id.TenantId;
20 20 import org.thingsboard.server.common.data.page.PageData;
21 21 import org.thingsboard.server.common.data.page.PageLink;
22 22 import org.thingsboard.server.dao.Dao;
  23 +import org.thingsboard.server.dao.TenantEntityDao;
23 24
24 25 import java.util.UUID;
25 26
26   -public interface UserDao extends Dao<User> {
  27 +public interface UserDao extends Dao<User>, TenantEntityDao {
27 28
28 29 /**
29 30 * Save or update user object
... ... @@ -49,7 +50,7 @@ public interface UserDao extends Dao<User> {
49 50 * @return the list of user entities
50 51 */
51 52 PageData<User> findByTenantId(UUID tenantId, PageLink pageLink);
52   -
  53 +
53 54 /**
54 55 * Find tenant admin users by tenantId and page link.
55 56 *
... ... @@ -58,7 +59,7 @@ public interface UserDao extends Dao<User> {
58 59 * @return the list of user entities
59 60 */
60 61 PageData<User> findTenantAdmins(UUID tenantId, PageLink pageLink);
61   -
  62 +
62 63 /**
63 64 * Find customer users by tenantId, customerId and page link.
64 65 *
... ... @@ -68,5 +69,4 @@ public interface UserDao extends Dao<User> {
68 69 * @return the list of user entities
69 70 */
70 71 PageData<User> findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink);
71   -
72 72 }
... ...
... ... @@ -24,8 +24,10 @@ import org.apache.commons.lang3.RandomStringUtils;
24 24 import org.apache.commons.lang3.StringUtils;
25 25 import org.springframework.beans.factory.annotation.Autowired;
26 26 import org.springframework.beans.factory.annotation.Value;
  27 +import org.springframework.context.annotation.Lazy;
27 28 import org.springframework.stereotype.Service;
28 29 import org.thingsboard.server.common.data.Customer;
  30 +import org.thingsboard.server.common.data.EntityType;
29 31 import org.thingsboard.server.common.data.Tenant;
30 32 import org.thingsboard.server.common.data.User;
31 33 import org.thingsboard.server.common.data.id.CustomerId;
... ... @@ -36,6 +38,7 @@ import org.thingsboard.server.common.data.page.PageData;
36 38 import org.thingsboard.server.common.data.page.PageLink;
37 39 import org.thingsboard.server.common.data.security.Authority;
38 40 import org.thingsboard.server.common.data.security.UserCredentials;
  41 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
39 42 import org.thingsboard.server.dao.customer.CustomerDao;
40 43 import org.thingsboard.server.dao.entity.AbstractEntityService;
41 44 import org.thingsboard.server.dao.exception.DataValidationException;
... ... @@ -43,6 +46,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
43 46 import org.thingsboard.server.dao.model.ModelConstants;
44 47 import org.thingsboard.server.dao.service.DataValidator;
45 48 import org.thingsboard.server.dao.service.PaginatedRemover;
  49 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
46 50 import org.thingsboard.server.dao.tenant.TenantDao;
47 51
48 52 import java.util.HashMap;
... ... @@ -84,6 +88,10 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
84 88 @Autowired
85 89 private CustomerDao customerDao;
86 90
  91 + @Autowired
  92 + @Lazy
  93 + private TbTenantProfileCache tenantProfileCache;
  94 +
87 95 @Override
88 96 public User findUserByEmail(TenantId tenantId, String email) {
89 97 log.trace("Executing findUserByEmail [{}]", email);
... ... @@ -365,6 +373,16 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
365 373 private DataValidator<User> userValidator =
366 374 new DataValidator<User>() {
367 375 @Override
  376 + protected void validateCreate(TenantId tenantId, User user) {
  377 + if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
  378 + DefaultTenantProfileConfiguration profileConfiguration =
  379 + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  380 + long maxUsers = profileConfiguration.getMaxUsers();
  381 + validateNumberOfEntitiesPerTenant(tenantId, userDao, maxUsers, EntityType.USER);
  382 + }
  383 + }
  384 +
  385 + @Override
368 386 protected void validateDataImpl(TenantId requestTenantId, User user) {
369 387 if (StringUtils.isEmpty(user.getEmail())) {
370 388 throw new DataValidationException("User email should be specified!");
... ...
... ... @@ -36,12 +36,11 @@
36 36 <pkg.implementationTitle>${project.name}</pkg.implementationTitle>
37 37 <pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
38 38 <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
39   - <spring-boot.version>2.2.6.RELEASE</spring-boot.version>
40   - <spring-oauth2.version>2.1.2.RELEASE</spring-oauth2.version>
41   - <spring.version>5.2.6.RELEASE</spring.version>
42   - <spring-security.version>5.2.3.RELEASE</spring-security.version>
43   - <spring-data-redis.version>2.2.4.RELEASE</spring-data-redis.version>
44   - <jedis.version>3.1.0</jedis.version>
  39 + <spring-boot.version>2.3.5.RELEASE</spring-boot.version>
  40 + <spring.version>5.2.10.RELEASE</spring.version>
  41 + <spring-security.version>5.4.1</spring-security.version>
  42 + <spring-data-redis.version>2.4.1</spring-data-redis.version>
  43 + <jedis.version>3.3.0</jedis.version>
45 44 <jjwt.version>0.7.0</jjwt.version>
46 45 <json-path.version>2.2.0</json-path.version>
47 46 <junit.version>4.12</junit.version>
... ... @@ -52,15 +51,16 @@
52 51 <cassandra.version>4.6.0</cassandra.version>
53 52 <metrics.version>4.0.5</metrics.version>
54 53 <cassandra-unit.version>4.3.1.0</cassandra-unit.version>
  54 + <cassandra-all.version>3.11.9</cassandra-all.version>
55 55 <takari-cpsuite.version>1.2.7</takari-cpsuite.version>
56 56 <guava.version>28.2-jre</guava.version>
57 57 <caffeine.version>2.6.1</caffeine.version>
58 58 <commons-lang3.version>3.4</commons-lang3.version>
59 59 <commons-io.version>2.5</commons-io.version>
60 60 <commons-csv.version>1.4</commons-csv.version>
61   - <jackson.version>2.10.2</jackson.version>
62   - <jackson-annotations.version>2.10.2</jackson-annotations.version>
63   - <jackson-core.version>2.10.2</jackson-core.version>
  61 + <jackson.version>2.11.3</jackson.version>
  62 + <jackson-annotations.version>2.11.3</jackson-annotations.version>
  63 + <jackson-core.version>2.11.3</jackson-core.version>
64 64 <json-schema-validator.version>2.2.6</json-schema-validator.version>
65 65 <californium.version>1.0.2</californium.version>
66 66 <gson.version>2.6.2</gson.version>
... ... @@ -72,7 +72,7 @@
72 72 <grpc.version>1.22.1</grpc.version>
73 73 <lombok.version>1.16.18</lombok.version>
74 74 <paho.client.version>1.2.4</paho.client.version>
75   - <netty.version>4.1.49.Final</netty.version>
  75 + <netty.version>4.1.53.Final</netty.version>
76 76 <os-maven-plugin.version>1.5.0</os-maven-plugin.version>
77 77 <rabbitmq.version>4.8.0</rabbitmq.version>
78 78 <surfire.version>2.19.1</surfire.version>
... ... @@ -96,7 +96,7 @@
96 96 <bucket4j.version>4.1.1</bucket4j.version>
97 97 <fst.version>2.57</fst.version>
98 98 <antlr.version>2.7.7</antlr.version>
99   - <snakeyaml.version>1.25</snakeyaml.version>
  99 + <snakeyaml.version>1.27</snakeyaml.version>
100 100 <amazonaws.sqs.version>1.11.747</amazonaws.sqs.version>
101 101 <pubsub.client.version>1.105.0</pubsub.client.version>
102 102 <azure-servicebus.version>3.2.0</azure-servicebus.version>
... ... @@ -875,11 +875,6 @@
875 875 <version>${spring-boot.version}</version>
876 876 </dependency>
877 877 <dependency>
878   - <groupId>org.springframework.cloud</groupId>
879   - <artifactId>spring-cloud-starter-oauth2</artifactId>
880   - <version>${spring-oauth2.version}</version>
881   - </dependency>
882   - <dependency>
883 878 <groupId>org.springframework.security</groupId>
884 879 <artifactId>spring-security-oauth2-client</artifactId>
885 880 <version>${spring-security.version}</version>
... ... @@ -1203,6 +1198,11 @@
1203 1198 <scope>test</scope>
1204 1199 </dependency>
1205 1200 <dependency>
  1201 + <groupId>org.apache.cassandra</groupId>
  1202 + <artifactId>cassandra-all</artifactId>
  1203 + <version>${cassandra-all.version}</version>
  1204 + </dependency>
  1205 + <dependency>
1206 1206 <groupId>junit</groupId>
1207 1207 <artifactId>junit</artifactId>
1208 1208 <version>${junit.version}</version>
... ...
... ... @@ -121,6 +121,11 @@
121 121 <artifactId>jts-core</artifactId>
122 122 </dependency>
123 123 <dependency>
  124 + <groupId>com.sun.mail</groupId>
  125 + <artifactId>javax.mail</artifactId>
  126 + <scope>provided</scope>
  127 + </dependency>
  128 + <dependency>
124 129 <groupId>junit</groupId>
125 130 <artifactId>junit</artifactId>
126 131 <version>${junit.version}</version>
... ...
... ... @@ -67,6 +67,7 @@ class AlarmState {
67 67 initCurrentAlarm(ctx);
68 68 lastMsgMetaData = msg.getMetaData();
69 69 lastMsgQueueName = msg.getQueueName();
  70 + this.dataSnapshot = data;
70 71 return createOrClearAlarms(ctx, data, update, AlarmRuleState::eval);
71 72 }
72 73
... ... @@ -90,8 +91,7 @@ class AlarmState {
90 91 resultState = state;
91 92 break;
92 93 } else if (AlarmEvalResult.FALSE.equals(evalResult)) {
93   - state.clear();
94   - stateUpdate |= state.checkUpdate();
  94 + stateUpdate = clearAlarmState(stateUpdate, state);
95 95 }
96 96 }
97 97 if (resultState != null) {
... ... @@ -99,6 +99,7 @@ class AlarmState {
99 99 if (result != null) {
100 100 pushMsg(ctx, result);
101 101 }
  102 + stateUpdate = clearAlarmState(stateUpdate, clearState);
102 103 } else if (currentAlarm != null && clearState != null) {
103 104 if (!validateUpdate(update, clearState)) {
104 105 log.debug("[{}] Update is not valid for current clear state", alarmDefinition.getId());
... ... @@ -106,23 +107,26 @@ class AlarmState {
106 107 }
107 108 AlarmEvalResult evalResult = evalFunction.apply(clearState, data);
108 109 if (AlarmEvalResult.TRUE.equals(evalResult)) {
109   - clearState.clear();
110   - stateUpdate |= clearState.checkUpdate();
  110 + stateUpdate = clearAlarmState(stateUpdate, clearState);
111 111 for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
112   - state.clear();
113   - stateUpdate |= state.checkUpdate();
  112 + stateUpdate = clearAlarmState(stateUpdate, state);
114 113 }
115 114 ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis());
116 115 pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm));
117 116 currentAlarm = null;
118 117 } else if (AlarmEvalResult.FALSE.equals(evalResult)) {
119   - clearState.clear();
120   - stateUpdate |= clearState.checkUpdate();
  118 + stateUpdate = clearAlarmState(stateUpdate, clearState);
121 119 }
122 120 }
123 121 return stateUpdate;
124 122 }
125 123
  124 + public boolean clearAlarmState(boolean stateUpdate, AlarmRuleState state) {
  125 + state.clear();
  126 + stateUpdate |= state.checkUpdate();
  127 + return stateUpdate;
  128 + }
  129 +
126 130 public boolean validateUpdate(SnapshotUpdate update, AlarmRuleState state) {
127 131 if (update != null) {
128 132 //Check that the update type and that keys match.
... ... @@ -190,7 +194,7 @@ class AlarmState {
190 194 }
191 195 }
192 196
193   - private <T> TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) {
  197 + private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) {
194 198 AlarmSeverity severity = ruleState.getSeverity();
195 199 if (currentAlarm != null) {
196 200 // 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 {
230 234 }
231 235 }
232 236
233   - private <T> JsonNode createDetails(AlarmRuleState ruleState) {
  237 + private JsonNode createDetails(AlarmRuleState ruleState) {
234 238 ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode();
235 239 String alarmDetails = ruleState.getAlarmRule().getAlarmDetails();
236 240
... ... @@ -273,8 +277,7 @@ class AlarmState {
273 277 if (currentAlarm != null && currentAlarm.getId().equals(alarmNf.getId())) {
274 278 currentAlarm = null;
275 279 for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
276   - state.clear();
277   - updated |= state.checkUpdate();
  280 + updated = clearAlarmState(updated, state);
278 281 }
279 282 }
280 283 return updated;
... ...
... ... @@ -54,7 +54,6 @@
54 54 <dependency>
55 55 <groupId>org.apache.cassandra</groupId>
56 56 <artifactId>cassandra-all</artifactId>
57   - <version>3.11.6</version>
58 57 </dependency>
59 58 <dependency>
60 59 <groupId>com.datastax.oss</groupId>
... ...
... ... @@ -630,6 +630,9 @@ export class EntityService {
630 630 case EntityType.DASHBOARD:
631 631 entityFieldKeys.push(entityFields.title.keyName);
632 632 break;
  633 + case EntityType.API_USAGE_STATE:
  634 + entityFieldKeys.push(entityFields.name.keyName);
  635 + break;
633 636 }
634 637 return query ? entityFieldKeys.filter((entityField) => entityField.toLowerCase().indexOf(query) === 0) : entityFieldKeys;
635 638 }
... ...
... ... @@ -52,6 +52,9 @@
52 52 </mat-error>
53 53 </mat-form-field>
54 54 </div>
  55 + <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration').hasError('unique')">
  56 + {{ 'device-profile.mqtt-device-topic-filters-unique' | translate }}
  57 + </mat-error>
55 58 <div class="tb-hint" innerHTML="{{ 'device-profile.support-level-wildcards' | translate }}"></div>
56 59 <div class="tb-hint" innerHTML="{{ 'device-profile.single-level-wildcards-hint' | translate }}"></div>
57 60 <div class="tb-hint" innerHTML="{{ 'device-profile.multi-level-wildcards-hint' | translate }}"></div>
... ...
... ... @@ -52,7 +52,6 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
52 52
53 53 mqttTransportPayloadTypeTranslations = mqttTransportPayloadTypeTranslationMap;
54 54
55   -
56 55 mqttDeviceProfileTransportConfigurationFormGroup: FormGroup;
57 56
58 57 private requiredValue: boolean;
... ... @@ -90,7 +89,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
90 89 transportPayloadTypeConfiguration: this.fb.group({
91 90 transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required]
92 91 })
93   - })
  92 + }, {validator: this.uniqueDeviceTopicValidator})
94 93 });
95 94 this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').valueChanges.subscribe(payloadType => {
96 95 this.updateTransportPayloadBasedControls(payloadType);
... ... @@ -171,4 +170,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
171 170 return null;
172 171 };
173 172 }
  173 +
  174 + private uniqueDeviceTopicValidator(control: FormGroup): { [key: string]: boolean } | null {
  175 + if (control.value) {
  176 + const formValue = control.value as MqttDeviceProfileTransportConfiguration;
  177 + if (formValue.deviceAttributesTopic === formValue.deviceTelemetryTopic) {
  178 + return {unique: true};
  179 + }
  180 + }
  181 + return null;
  182 + }
174 183 }
... ...
... ... @@ -41,6 +41,54 @@
41 41 </mat-error>
42 42 </mat-form-field>
43 43 <mat-form-field class="mat-block">
  44 + <mat-label translate>tenant-profile.maximum-customers</mat-label>
  45 + <input matInput required min="0" step="1"
  46 + formControlName="maxCustomers"
  47 + type="number">
  48 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxCustomers').hasError('required')">
  49 + {{ 'tenant-profile.maximum-customers-required' | translate}}
  50 + </mat-error>
  51 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxCustomers').hasError('min')">
  52 + {{ 'tenant-profile.maximum-customers-range' | translate}}
  53 + </mat-error>
  54 + </mat-form-field>
  55 + <mat-form-field class="mat-block">
  56 + <mat-label translate>tenant-profile.maximum-users</mat-label>
  57 + <input matInput required min="0" step="1"
  58 + formControlName="maxUsers"
  59 + type="number">
  60 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxUsers').hasError('required')">
  61 + {{ 'tenant-profile.maximum-users-required' | translate}}
  62 + </mat-error>
  63 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxUsers').hasError('min')">
  64 + {{ 'tenant-profile.maximum-users-range' | translate}}
  65 + </mat-error>
  66 + </mat-form-field>
  67 + <mat-form-field class="mat-block">
  68 + <mat-label translate>tenant-profile.maximum-dashboards</mat-label>
  69 + <input matInput required min="0" step="1"
  70 + formControlName="maxDashboards"
  71 + type="number">
  72 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxDashboards').hasError('required')">
  73 + {{ 'tenant-profile.maximum-dashboards-required' | translate}}
  74 + </mat-error>
  75 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxDashboards').hasError('min')">
  76 + {{ 'tenant-profile.maximum-dashboards-range' | translate}}
  77 + </mat-error>
  78 + </mat-form-field>
  79 + <mat-form-field class="mat-block">
  80 + <mat-label translate>tenant-profile.maximum-rule-chains</mat-label>
  81 + <input matInput required min="0" step="1"
  82 + formControlName="maxRuleChains"
  83 + type="number">
  84 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxRuleChains').hasError('required')">
  85 + {{ 'tenant-profile.maximum-rule-chains-required' | translate}}
  86 + </mat-error>
  87 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxRuleChains').hasError('min')">
  88 + {{ 'tenant-profile.maximum-rule-chains-range' | translate}}
  89 + </mat-error>
  90 + </mat-form-field>
  91 + <mat-form-field class="mat-block">
44 92 <mat-label translate>tenant-profile.max-transport-messages</mat-label>
45 93 <input matInput required min="0" step="1"
46 94 formControlName="maxTransportMessages"
... ...
... ... @@ -55,6 +55,10 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
55 55 this.defaultTenantProfileConfigurationFormGroup = this.fb.group({
56 56 maxDevices: [null, [Validators.required, Validators.min(0)]],
57 57 maxAssets: [null, [Validators.required, Validators.min(0)]],
  58 + maxCustomers: [null, [Validators.required, Validators.min(0)]],
  59 + maxUsers: [null, [Validators.required, Validators.min(0)]],
  60 + maxDashboards: [null, [Validators.required, Validators.min(0)]],
  61 + maxRuleChains: [null, [Validators.required, Validators.min(0)]],
58 62 transportTenantMsgRateLimit: [null, []],
59 63 transportTenantTelemetryMsgRateLimit: [null, []],
60 64 transportTenantTelemetryDataPointsRateLimit: [null, []],
... ...
... ... @@ -56,6 +56,12 @@ export const customerHref = '<a href="https://github.com/thingsboard/thingsboard
56 56
57 57 export const attributeDataHref = '<a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts#L76">Attribute Data</a>';
58 58
  59 +export const timeseriesDataHref = '<a href="https://github.com/thingsboard/thingsboard/blob/627c0577b08452308f925cecb3860e35292c649e/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts#L91">Timeseries Data</a>';
  60 +
  61 +export const aggregationTypeHref = '<a href="https://github.com/thingsboard/thingsboard/blob/a8ea887eacf7729e603ace13ce2d7d89dae82931/ui-ngx/src/app/shared/models/time/time.models.ts#L54">Aggregation Type</a>';
  62 +
  63 +export const dataSortOrderHref = '<a href="https://github.com/thingsboard/thingsboard/blob/627c0577b08452308f925cecb3860e35292c649e/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts#L95">Data Sort Order</a>';
  64 +
59 65 export const userHref = '<a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/shared/models/user.model.ts#L23">User</a>';
60 66
61 67 export const entityDataHref = '<a href="https://github.com/thingsboard/thingsboard/blob/master/ui-ngx/src/app/shared/models/query/query.models.ts#L567">Entity data</a>';
... ... @@ -1080,6 +1086,23 @@ export const serviceCompletions: TbEditorCompletions = {
1080 1086 ],
1081 1087 return: observableReturnTypeVariable('any')
1082 1088 },
  1089 + getEntityTimeseries: {
  1090 + description: 'Get entity timeseries',
  1091 + meta: 'function',
  1092 + args: [
  1093 + {name: 'entityId', type: entityIdHref, description: 'Id of the entity'},
  1094 + {name: 'keys', type: `Array&lt;string&gt;`, description: 'Array of the keys'},
  1095 + {name: 'startTs', type: 'number', description: 'Start time in milliseconds'},
  1096 + {name: 'endTs', type: 'number', description: 'End time in milliseconds'},
  1097 + {name: 'limit', type: 'number', description: 'Limit of values to receive for each key'},
  1098 + {name: 'agg', type: aggregationTypeHref, description: 'Aggregation type'},
  1099 + {name: 'interval', type: 'number', description: 'Aggregation interval'},
  1100 + {name: 'orderBy', type: dataSortOrderHref, description: 'Data order by time'},
  1101 + {name: 'useStrictDataTypes', type: 'boolean', description: 'If "false" all values will be returned as strings'},
  1102 + requestConfigArg
  1103 + ],
  1104 + return: observableReturnTypeVariable(timeseriesDataHref)
  1105 + },
1083 1106 }
1084 1107 },
1085 1108 entityService: {
... ...
... ... @@ -885,6 +885,7 @@
885 885 "no-device-profiles-found": "No device profiles found.",
886 886 "create-new-device-profile": "Create a new one!",
887 887 "mqtt-device-topic-filters": "MQTT device topic filters",
  888 + "mqtt-device-topic-filters-unique": "MQTT device topic filters need to be unique.",
888 889 "mqtt-device-payload-type": "MQTT device payload",
889 890 "mqtt-device-payload-type-json": "JSON",
890 891 "mqtt-device-payload-type-proto": "Protobuf",
... ... @@ -1949,6 +1950,18 @@
1949 1950 "maximum-assets": "Maximum number of assets (0 - unlimited)",
1950 1951 "maximum-assets-required": "Maximum number of assets is required.",
1951 1952 "maximum-assets-range": "Maximum number of assets can't be negative",
  1953 + "maximum-customers": "Maximum number of customers (0 - unlimited)",
  1954 + "maximum-customers-required": "Maximum number of customers is required.",
  1955 + "maximum-customers-range": "Maximum number of customers can't be negative",
  1956 + "maximum-users": "Maximum number of users (0 - unlimited)",
  1957 + "maximum-users-required": "Maximum number of users is required.",
  1958 + "maximum-users-range": "Maximum number of users can't be negative",
  1959 + "maximum-dashboards": "Maximum number of dashboards (0 - unlimited)",
  1960 + "maximum-dashboards-required": "Maximum number of dashboards is required.",
  1961 + "maximum-dashboards-range": "Maximum number of dashboards can't be negative",
  1962 + "maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)",
  1963 + "maximum-rule-chains-required": "Maximum number of rule chains is required.",
  1964 + "maximum-rule-chains-range": "Maximum number of rule chains can't be negative",
1952 1965 "transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.",
1953 1966 "transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.",
1954 1967 "transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.",
... ...