Showing
16 changed files
with
100 additions
and
9 deletions
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
... | ... | @@ -62,7 +62,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { |
62 | 62 | @Value("${usage.stats.report.enabled:true}") |
63 | 63 | private boolean enabled; |
64 | 64 | |
65 | - @Value("${usage.stats.check.cycle:60000}") | |
65 | + @Value("${usage.stats.check.cycle:6000}") | |
66 | 66 | private long nextCycleCheckInterval; |
67 | 67 | |
68 | 68 | private final Lock updateLock = new ReentrantLock(); |
... | ... | @@ -107,7 +107,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { |
107 | 107 | } finally { |
108 | 108 | updateLock.unlock(); |
109 | 109 | } |
110 | - tsService.save(tenantId, tenantState.getEntityId(), updatedEntries, 0L); | |
110 | + tsService.save(tenantId, tenantState.getId(), updatedEntries, 0L); | |
111 | 111 | } |
112 | 112 | |
113 | 113 | @Override |
... | ... | @@ -150,7 +150,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { |
150 | 150 | dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); |
151 | 151 | } |
152 | 152 | } |
153 | - tenantState = new TenantApiUsageState(dbStateEntity.getEntityId()); | |
153 | + tenantState = new TenantApiUsageState(dbStateEntity.getId()); | |
154 | 154 | try { |
155 | 155 | List<TsKvEntry> dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getEntityId()).get(); |
156 | 156 | for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { | ... | ... |
... | ... | @@ -17,6 +17,7 @@ package org.thingsboard.server.service.apiusage; |
17 | 17 | |
18 | 18 | import lombok.Getter; |
19 | 19 | import org.thingsboard.server.common.data.ApiUsageRecordKey; |
20 | +import org.thingsboard.server.common.data.id.ApiUsageStateId; | |
20 | 21 | import org.thingsboard.server.common.data.id.EntityId; |
21 | 22 | import org.thingsboard.server.common.msg.tools.SchedulerUtils; |
22 | 23 | |
... | ... | @@ -29,7 +30,7 @@ public class TenantApiUsageState { |
29 | 30 | private final Map<ApiUsageRecordKey, Long> currentHourValues = new ConcurrentHashMap<>(); |
30 | 31 | |
31 | 32 | @Getter |
32 | - private final EntityId entityId; | |
33 | + private final ApiUsageStateId id; | |
33 | 34 | @Getter |
34 | 35 | private volatile long currentCycleTs; |
35 | 36 | @Getter |
... | ... | @@ -37,8 +38,8 @@ public class TenantApiUsageState { |
37 | 38 | @Getter |
38 | 39 | private volatile long currentHourTs; |
39 | 40 | |
40 | - public TenantApiUsageState(EntityId entityId) { | |
41 | - this.entityId = entityId; | |
41 | + public TenantApiUsageState(ApiUsageStateId id) { | |
42 | + this.id = id; | |
42 | 43 | this.currentCycleTs = SchedulerUtils.getStartOfCurrentMonth(); |
43 | 44 | this.nextCycleTs = SchedulerUtils.getStartOfNextMonth(); |
44 | 45 | this.currentHourTs = SchedulerUtils.getStartOfCurrentHour(); | ... | ... |
... | ... | @@ -396,6 +396,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore |
396 | 396 | if (mainConsumer != null) { |
397 | 397 | mainConsumer.unsubscribe(); |
398 | 398 | } |
399 | + if (usageStatsConsumer != null) { | |
400 | + usageStatsConsumer.unsubscribe(); | |
401 | + } | |
399 | 402 | } |
400 | 403 | |
401 | 404 | } | ... | ... |
... | ... | @@ -25,6 +25,7 @@ import org.springframework.http.ResponseEntity; |
25 | 25 | import org.springframework.stereotype.Component; |
26 | 26 | import org.springframework.web.context.request.async.DeferredResult; |
27 | 27 | import org.thingsboard.common.util.ThingsBoardThreadFactory; |
28 | +import org.thingsboard.server.common.data.ApiUsageState; | |
28 | 29 | import org.thingsboard.server.common.data.Customer; |
29 | 30 | import org.thingsboard.server.common.data.Device; |
30 | 31 | import org.thingsboard.server.common.data.DeviceProfile; |
... | ... | @@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.Tenant; |
33 | 34 | import org.thingsboard.server.common.data.User; |
34 | 35 | import org.thingsboard.server.common.data.asset.Asset; |
35 | 36 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
37 | +import org.thingsboard.server.common.data.id.ApiUsageStateId; | |
36 | 38 | import org.thingsboard.server.common.data.id.AssetId; |
37 | 39 | import org.thingsboard.server.common.data.id.CustomerId; |
38 | 40 | import org.thingsboard.server.common.data.id.DeviceId; |
... | ... | @@ -55,6 +57,7 @@ import org.thingsboard.server.dao.device.DeviceService; |
55 | 57 | import org.thingsboard.server.dao.entityview.EntityViewService; |
56 | 58 | import org.thingsboard.server.dao.rule.RuleChainService; |
57 | 59 | import org.thingsboard.server.dao.tenant.TenantService; |
60 | +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; | |
58 | 61 | import org.thingsboard.server.dao.user.UserService; |
59 | 62 | import org.thingsboard.server.service.security.model.SecurityUser; |
60 | 63 | import org.thingsboard.server.service.security.permission.AccessControlService; |
... | ... | @@ -111,6 +114,9 @@ public class AccessValidator { |
111 | 114 | @Autowired |
112 | 115 | protected AccessControlService accessControlService; |
113 | 116 | |
117 | + @Autowired | |
118 | + protected ApiUsageStateService apiUsageStateService; | |
119 | + | |
114 | 120 | private ExecutorService executor; |
115 | 121 | |
116 | 122 | @PostConstruct |
... | ... | @@ -193,6 +199,9 @@ public class AccessValidator { |
193 | 199 | case ENTITY_VIEW: |
194 | 200 | validateEntityView(currentUser, operation, entityId, callback); |
195 | 201 | return; |
202 | + case API_USAGE_STATE: | |
203 | + validateApiUsageState(currentUser, operation, entityId, callback); | |
204 | + return; | |
196 | 205 | default: |
197 | 206 | //TODO: add support of other entities |
198 | 207 | throw new IllegalStateException("Not Implemented!"); |
... | ... | @@ -237,6 +246,24 @@ public class AccessValidator { |
237 | 246 | } |
238 | 247 | } |
239 | 248 | |
249 | + private void validateApiUsageState(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) { | |
250 | + if (currentUser.isSystemAdmin()) { | |
251 | + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); | |
252 | + } else { | |
253 | + ApiUsageState apiUsageState = apiUsageStateService.findApiUsageStateById(currentUser.getTenantId(), new ApiUsageStateId(entityId.getId())); | |
254 | + if (apiUsageState == null) { | |
255 | + callback.onSuccess(ValidationResult.entityNotFound("Api Usage State with requested id wasn't found!")); | |
256 | + } else { | |
257 | + try { | |
258 | + accessControlService.checkPermission(currentUser, Resource.API_USAGE_STATE, operation, entityId, apiUsageState); | |
259 | + } catch (ThingsboardException e) { | |
260 | + callback.onSuccess(ValidationResult.accessDenied(e.getMessage())); | |
261 | + } | |
262 | + callback.onSuccess(ValidationResult.ok(apiUsageState)); | |
263 | + } | |
264 | + } | |
265 | + } | |
266 | + | |
240 | 267 | private void validateAsset(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) { |
241 | 268 | if (currentUser.isSystemAdmin()) { |
242 | 269 | callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); | ... | ... |
... | ... | @@ -35,7 +35,8 @@ public enum Resource { |
35 | 35 | OAUTH2_CONFIGURATION_INFO(), |
36 | 36 | OAUTH2_CONFIGURATION_TEMPLATE(), |
37 | 37 | TENANT_PROFILE(EntityType.TENANT_PROFILE), |
38 | - DEVICE_PROFILE(EntityType.DEVICE_PROFILE); | |
38 | + DEVICE_PROFILE(EntityType.DEVICE_PROFILE), | |
39 | + API_USAGE_STATE(EntityType.API_USAGE_STATE); | |
39 | 40 | |
40 | 41 | private final EntityType entityType; |
41 | 42 | ... | ... |
... | ... | @@ -40,6 +40,7 @@ public class TenantAdminPermissions extends AbstractPermissions { |
40 | 40 | put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker); |
41 | 41 | put(Resource.WIDGET_TYPE, widgetsPermissionChecker); |
42 | 42 | put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker); |
43 | + put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker); | |
43 | 44 | } |
44 | 45 | |
45 | 46 | public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { | ... | ... |
... | ... | @@ -16,6 +16,7 @@ |
16 | 16 | package org.thingsboard.server.dao.usagerecord; |
17 | 17 | |
18 | 18 | import org.thingsboard.server.common.data.ApiUsageState; |
19 | +import org.thingsboard.server.common.data.id.ApiUsageStateId; | |
19 | 20 | import org.thingsboard.server.common.data.id.TenantId; |
20 | 21 | |
21 | 22 | public interface ApiUsageStateService { |
... | ... | @@ -25,4 +26,6 @@ public interface ApiUsageStateService { |
25 | 26 | void deleteApiUsageStateByTenantId(TenantId tenantId); |
26 | 27 | |
27 | 28 | ApiUsageState createDefaultApiUsageState(TenantId id); |
29 | + | |
30 | + ApiUsageState findApiUsageStateById(TenantId tenantId, ApiUsageStateId id); | |
28 | 31 | } | ... | ... |
... | ... | @@ -66,6 +66,8 @@ public class EntityIdFactory { |
66 | 66 | return new DeviceProfileId(uuid); |
67 | 67 | case TENANT_PROFILE: |
68 | 68 | return new TenantProfileId(uuid); |
69 | + case API_USAGE_STATE: | |
70 | + return new ApiUsageStateId(uuid); | |
69 | 71 | } |
70 | 72 | throw new IllegalArgumentException("EntityType " + type + " is not supported!"); |
71 | 73 | } | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java
0 → 100644
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.common.data.query; | |
17 | + | |
18 | +import lombok.Data; | |
19 | +import org.thingsboard.server.common.data.id.EntityId; | |
20 | + | |
21 | +@Data | |
22 | +public class ApiUsageStateFilter implements EntityFilter { | |
23 | + @Override | |
24 | + public EntityFilterType getType() { | |
25 | + return EntityFilterType.API_USAGE_STATE; | |
26 | + } | |
27 | + | |
28 | +} | ... | ... |
... | ... | @@ -32,6 +32,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; |
32 | 32 | @JsonSubTypes.Type(value = AssetTypeFilter.class, name = "assetType"), |
33 | 33 | @JsonSubTypes.Type(value = DeviceTypeFilter.class, name = "deviceType"), |
34 | 34 | @JsonSubTypes.Type(value = EntityViewTypeFilter.class, name = "entityViewType"), |
35 | + @JsonSubTypes.Type(value = ApiUsageStateFilter.class, name = "apiUsageState"), | |
35 | 36 | @JsonSubTypes.Type(value = RelationsQueryFilter.class, name = "relationsQuery"), |
36 | 37 | @JsonSubTypes.Type(value = AssetSearchQueryFilter.class, name = "assetSearchQuery"), |
37 | 38 | @JsonSubTypes.Type(value = DeviceSearchQueryFilter.class, name = "deviceSearchQuery"), | ... | ... |
... | ... | @@ -25,7 +25,8 @@ public enum EntityFilterType { |
25 | 25 | RELATIONS_QUERY("relationsQuery"), |
26 | 26 | ASSET_SEARCH_QUERY("assetSearchQuery"), |
27 | 27 | DEVICE_SEARCH_QUERY("deviceSearchQuery"), |
28 | - ENTITY_VIEW_SEARCH_QUERY("entityViewSearchQuery"); | |
28 | + ENTITY_VIEW_SEARCH_QUERY("entityViewSearchQuery"), | |
29 | + API_USAGE_STATE("apiUsageState"); | |
29 | 30 | |
30 | 31 | private final String label; |
31 | 32 | ... | ... |
... | ... | @@ -210,6 +210,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
210 | 210 | entityTableMap.put(EntityType.CUSTOMER, "customer"); |
211 | 211 | entityTableMap.put(EntityType.USER, "tb_user"); |
212 | 212 | entityTableMap.put(EntityType.TENANT, "tenant"); |
213 | + entityTableMap.put(EntityType.API_USAGE_STATE, "(select aus.id, aus.created_time, aus.tenant_id, '' as name, '' as additional_info from api_usage_state as aus)"); | |
213 | 214 | } |
214 | 215 | |
215 | 216 | public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ |
... | ... | @@ -431,6 +432,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
431 | 432 | case DEVICE_SEARCH_QUERY: |
432 | 433 | case ASSET_SEARCH_QUERY: |
433 | 434 | case ENTITY_VIEW_SEARCH_QUERY: |
435 | + case API_USAGE_STATE: | |
434 | 436 | return ""; |
435 | 437 | default: |
436 | 438 | throw new RuntimeException("Not implemented!"); |
... | ... | @@ -682,6 +684,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
682 | 684 | return EntityType.ENTITY_VIEW; |
683 | 685 | case RELATIONS_QUERY: |
684 | 686 | return ((RelationsQueryFilter) entityFilter).getRootEntity().getEntityType(); |
687 | + case API_USAGE_STATE: | |
688 | + return EntityType.API_USAGE_STATE; | |
685 | 689 | default: |
686 | 690 | throw new RuntimeException("Not implemented!"); |
687 | 691 | } | ... | ... |
... | ... | @@ -20,6 +20,7 @@ import org.springframework.stereotype.Service; |
20 | 20 | import org.thingsboard.server.common.data.EntityType; |
21 | 21 | import org.thingsboard.server.common.data.Tenant; |
22 | 22 | import org.thingsboard.server.common.data.ApiUsageState; |
23 | +import org.thingsboard.server.common.data.id.ApiUsageStateId; | |
23 | 24 | import org.thingsboard.server.common.data.id.TenantId; |
24 | 25 | import org.thingsboard.server.dao.entity.AbstractEntityService; |
25 | 26 | import org.thingsboard.server.dao.exception.DataValidationException; |
... | ... | @@ -66,6 +67,13 @@ public class ApiApiUsageStateServiceImpl extends AbstractEntityService implement |
66 | 67 | return apiUsageStateDao.findTenantApiUsageState(tenantId.getId()); |
67 | 68 | } |
68 | 69 | |
70 | + @Override | |
71 | + public ApiUsageState findApiUsageStateById(TenantId tenantId, ApiUsageStateId id) { | |
72 | + log.trace("Executing findApiUsageStateById, tenantId [{}], apiUsageStateId [{}]", tenantId, id); | |
73 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
74 | + validateId(id, "Incorrect apiUsageStateId " + id); | |
75 | + return apiUsageStateDao.findById(tenantId, id.getId()); } | |
76 | + | |
69 | 77 | private DataValidator<ApiUsageState> apiUsageStateValidator = |
70 | 78 | new DataValidator<ApiUsageState>() { |
71 | 79 | @Override | ... | ... |
... | ... | @@ -753,6 +753,9 @@ export class EntityService { |
753 | 753 | case AliasFilterType.entityViewType: |
754 | 754 | result.entityFilter = deepClone(filter); |
755 | 755 | return of(result); |
756 | + case AliasFilterType.apiUsageState: | |
757 | + result.entityFilter = deepClone(filter); | |
758 | + return of(result); | |
756 | 759 | case AliasFilterType.relationsQuery: |
757 | 760 | result.stateEntity = filter.rootStateEntity; |
758 | 761 | let rootEntityType; | ... | ... |
... | ... | @@ -28,6 +28,7 @@ export enum AliasFilterType { |
28 | 28 | assetType = 'assetType', |
29 | 29 | deviceType = 'deviceType', |
30 | 30 | entityViewType = 'entityViewType', |
31 | + apiUsageState = 'apiUsageState', | |
31 | 32 | relationsQuery = 'relationsQuery', |
32 | 33 | assetSearchQuery = 'assetSearchQuery', |
33 | 34 | deviceSearchQuery = 'deviceSearchQuery', |
... | ... | @@ -43,6 +44,7 @@ export const aliasFilterTypeTranslationMap = new Map<AliasFilterType, string>( |
43 | 44 | [ AliasFilterType.assetType, 'alias.filter-type-asset-type' ], |
44 | 45 | [ AliasFilterType.deviceType, 'alias.filter-type-device-type' ], |
45 | 46 | [ AliasFilterType.entityViewType, 'alias.filter-type-entity-view-type' ], |
47 | + [ AliasFilterType.apiUsageState, 'alias.filter-type-apiUsageState' ], | |
46 | 48 | [ AliasFilterType.relationsQuery, 'alias.filter-type-relations-query' ], |
47 | 49 | [ AliasFilterType.assetSearchQuery, 'alias.filter-type-asset-search-query' ], |
48 | 50 | [ AliasFilterType.deviceSearchQuery, 'alias.filter-type-device-search-query' ], |
... | ... | @@ -106,6 +108,10 @@ export interface EntitySearchQueryFilter { |
106 | 108 | fetchLastLevelOnly?: boolean; |
107 | 109 | } |
108 | 110 | |
111 | +export interface ApiUsageStateFilter { | |
112 | + | |
113 | +} | |
114 | + | |
109 | 115 | export interface AssetSearchQueryFilter extends EntitySearchQueryFilter { |
110 | 116 | assetTypes?: string[]; |
111 | 117 | } |
... | ... | @@ -129,7 +135,8 @@ export type EntityFilters = |
129 | 135 | RelationsQueryFilter & |
130 | 136 | AssetSearchQueryFilter & |
131 | 137 | DeviceSearchQueryFilter & |
132 | - EntityViewSearchQueryFilter; | |
138 | + EntityViewSearchQueryFilter & | |
139 | + EntitySearchQueryFilter; | |
133 | 140 | |
134 | 141 | export interface EntityAliasFilter extends EntityFilters { |
135 | 142 | type?: AliasFilterType; | ... | ... |
... | ... | @@ -288,6 +288,7 @@ |
288 | 288 | "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", |
289 | 289 | "filter-type-entity-view-search-query": "Entity view search query", |
290 | 290 | "filter-type-entity-view-search-query-description": "Entity views with types {{entityViewTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", |
291 | + "filter-type-apiUsageState": "Api Usage State", | |
291 | 292 | "entity-filter": "Entity filter", |
292 | 293 | "resolve-multiple": "Resolve as multiple entities", |
293 | 294 | "filter-type": "Filter type", | ... | ... |