Commit 6b63d336dd58c3707ae7a704cc5ba86d8c056aee

Authored by Vladyslav_Prykhodko
2 parents ce1a3ea5 ecb2efda

Merge remote-tracking branch 'upstream/master' into improvement/ota-updates/select

Showing 80 changed files with 1450 additions and 432 deletions
... ... @@ -67,6 +67,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
67 67 type varchar(32) NOT NULL,
68 68 title varchar(255) NOT NULL,
69 69 version varchar(255) NOT NULL,
  70 + url varchar(255),
70 71 file_name varchar(255),
71 72 content_type varchar(255),
72 73 checksum_algorithm varchar(32),
... ...
... ... @@ -60,7 +60,9 @@ import org.thingsboard.server.dao.edge.EdgeService;
60 60 import org.thingsboard.server.dao.entityview.EntityViewService;
61 61 import org.thingsboard.server.dao.event.EventService;
62 62 import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
  63 +import org.thingsboard.server.dao.ota.OtaPackageService;
63 64 import org.thingsboard.server.dao.relation.RelationService;
  65 +import org.thingsboard.server.dao.resource.ResourceService;
64 66 import org.thingsboard.server.dao.rule.RuleChainService;
65 67 import org.thingsboard.server.dao.rule.RuleNodeStateService;
66 68 import org.thingsboard.server.dao.tenant.TenantProfileService;
... ... @@ -311,6 +313,14 @@ public class ActorSystemContext {
311 313 @Autowired(required = false)
312 314 @Getter private EdgeRpcService edgeRpcService;
313 315
  316 + @Lazy
  317 + @Autowired(required = false)
  318 + @Getter private ResourceService resourceService;
  319 +
  320 + @Lazy
  321 + @Autowired(required = false)
  322 + @Getter private OtaPackageService otaPackageService;
  323 +
314 324 @Value("${actors.session.max_concurrent_sessions_per_device:1}")
315 325 @Getter
316 326 private long maxConcurrentSessionsPerDevice;
... ...
... ... @@ -69,7 +69,9 @@ import org.thingsboard.server.dao.edge.EdgeService;
69 69 import org.thingsboard.server.dao.entityview.EntityViewService;
70 70 import org.thingsboard.server.dao.nosql.CassandraStatementTask;
71 71 import org.thingsboard.server.dao.nosql.TbResultSetFuture;
  72 +import org.thingsboard.server.dao.ota.OtaPackageService;
72 73 import org.thingsboard.server.dao.relation.RelationService;
  74 +import org.thingsboard.server.dao.resource.ResourceService;
73 75 import org.thingsboard.server.dao.rule.RuleChainService;
74 76 import org.thingsboard.server.dao.tenant.TenantService;
75 77 import org.thingsboard.server.dao.timeseries.TimeseriesService;
... ... @@ -487,6 +489,16 @@ class DefaultTbContext implements TbContext {
487 489 }
488 490
489 491 @Override
  492 + public ResourceService getResourceService() {
  493 + return mainCtx.getResourceService();
  494 + }
  495 +
  496 + @Override
  497 + public OtaPackageService getOtaPackageService() {
  498 + return mainCtx.getOtaPackageService();
  499 + }
  500 +
  501 + @Override
490 502 public RuleEngineDeviceProfileCache getDeviceProfileCache() {
491 503 return mainCtx.getDeviceProfileCache();
492 504 }
... ...
... ... @@ -110,8 +110,11 @@ public class AlarmController extends BaseController {
110 110 checkParameter(ALARM_ID, strAlarmId);
111 111 try {
112 112 AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
113   - checkAlarmId(alarmId, Operation.WRITE);
  113 + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
114 114
  115 + logEntityAction(alarm.getOriginator(), alarm,
  116 + getCurrentUser().getCustomerId(),
  117 + ActionType.ALARM_DELETE, null);
115 118 sendEntityNotificationMsg(getTenantId(), alarmId, EdgeEventActionType.DELETED);
116 119
117 120 return alarmService.deleteAlarm(getTenantId(), alarmId);
... ...
... ... @@ -17,7 +17,6 @@ package org.thingsboard.server.controller;
17 17
18 18 import com.fasterxml.jackson.core.JsonProcessingException;
19 19 import com.fasterxml.jackson.databind.ObjectMapper;
20   -import com.fasterxml.jackson.databind.node.ArrayNode;
21 20 import com.fasterxml.jackson.databind.node.ObjectNode;
22 21 import lombok.Getter;
23 22 import lombok.extern.slf4j.Slf4j;
... ... @@ -31,7 +30,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
31 30 import org.thingsboard.server.common.data.Customer;
32 31 import org.thingsboard.server.common.data.Dashboard;
33 32 import org.thingsboard.server.common.data.DashboardInfo;
34   -import org.thingsboard.server.common.data.DataConstants;
35 33 import org.thingsboard.server.common.data.Device;
36 34 import org.thingsboard.server.common.data.DeviceInfo;
37 35 import org.thingsboard.server.common.data.DeviceProfile;
... ... @@ -79,10 +77,6 @@ import org.thingsboard.server.common.data.id.TenantProfileId;
79 77 import org.thingsboard.server.common.data.id.UserId;
80 78 import org.thingsboard.server.common.data.id.WidgetTypeId;
81 79 import org.thingsboard.server.common.data.id.WidgetsBundleId;
82   -import org.thingsboard.server.common.data.kv.AttributeKvEntry;
83   -import org.thingsboard.server.common.data.kv.DataType;
84   -import org.thingsboard.server.common.data.kv.KvEntry;
85   -import org.thingsboard.server.common.data.kv.TsKvEntry;
86 80 import org.thingsboard.server.common.data.page.PageLink;
87 81 import org.thingsboard.server.common.data.page.SortOrder;
88 82 import org.thingsboard.server.common.data.page.TimePageLink;
... ... @@ -94,9 +88,6 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
94 88 import org.thingsboard.server.common.data.rule.RuleNode;
95 89 import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
96 90 import org.thingsboard.server.common.data.widget.WidgetsBundle;
97   -import org.thingsboard.server.common.msg.TbMsg;
98   -import org.thingsboard.server.common.msg.TbMsgDataType;
99   -import org.thingsboard.server.common.msg.TbMsgMetaData;
100 91 import org.thingsboard.server.dao.asset.AssetService;
101 92 import org.thingsboard.server.dao.attributes.AttributesService;
102 93 import org.thingsboard.server.dao.audit.AuditLogService;
... ... @@ -127,6 +118,7 @@ import org.thingsboard.server.gen.transport.TransportProtos;
127 118 import org.thingsboard.server.queue.discovery.PartitionService;
128 119 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
129 120 import org.thingsboard.server.queue.util.TbCoreComponent;
  121 +import org.thingsboard.server.service.action.RuleEngineEntityActionService;
130 122 import org.thingsboard.server.service.component.ComponentDiscoveryService;
131 123 import org.thingsboard.server.service.ota.OtaPackageStateService;
132 124 import org.thingsboard.server.service.edge.EdgeNotificationService;
... ... @@ -147,11 +139,9 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
147 139 import javax.mail.MessagingException;
148 140 import javax.servlet.http.HttpServletResponse;
149 141 import java.util.List;
150   -import java.util.Map;
151 142 import java.util.Optional;
152 143 import java.util.Set;
153 144 import java.util.UUID;
154   -import java.util.stream.Collectors;
155 145
156 146 import static org.thingsboard.server.dao.service.Validator.validateId;
157 147
... ... @@ -279,6 +269,9 @@ public abstract class BaseController {
279 269 @Autowired(required = false)
280 270 protected EdgeGrpcService edgeGrpcService;
281 271
  272 + @Autowired
  273 + protected RuleEngineEntityActionService ruleEngineEntityActionService;
  274 +
282 275 @Value("${server.log_controller_error_stack_trace}")
283 276 @Getter
284 277 private boolean logControllerErrorStackTrace;
... ... @@ -809,7 +802,7 @@ public abstract class BaseController {
809 802 customerId = user.getCustomerId();
810 803 }
811 804 if (e == null) {
812   - pushEntityActionToRuleEngine(entityId, entity, user, customerId, actionType, additionalInfo);
  805 + ruleEngineEntityActionService.pushEntityActionToRuleEngine(entityId, entity, user.getTenantId(), customerId, actionType, user, additionalInfo);
813 806 }
814 807 auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
815 808 }
... ... @@ -819,184 +812,6 @@ public abstract class BaseController {
819 812 return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null;
820 813 }
821 814
822   - private <E extends HasName, I extends EntityId> void pushEntityActionToRuleEngine(I entityId, E entity, User user, CustomerId customerId,
823   - ActionType actionType, Object... additionalInfo) {
824   - String msgType = null;
825   - switch (actionType) {
826   - case ADDED:
827   - msgType = DataConstants.ENTITY_CREATED;
828   - break;
829   - case DELETED:
830   - msgType = DataConstants.ENTITY_DELETED;
831   - break;
832   - case UPDATED:
833   - msgType = DataConstants.ENTITY_UPDATED;
834   - break;
835   - case ASSIGNED_TO_CUSTOMER:
836   - msgType = DataConstants.ENTITY_ASSIGNED;
837   - break;
838   - case UNASSIGNED_FROM_CUSTOMER:
839   - msgType = DataConstants.ENTITY_UNASSIGNED;
840   - break;
841   - case ATTRIBUTES_UPDATED:
842   - msgType = DataConstants.ATTRIBUTES_UPDATED;
843   - break;
844   - case ATTRIBUTES_DELETED:
845   - msgType = DataConstants.ATTRIBUTES_DELETED;
846   - break;
847   - case ALARM_ACK:
848   - msgType = DataConstants.ALARM_ACK;
849   - break;
850   - case ALARM_CLEAR:
851   - msgType = DataConstants.ALARM_CLEAR;
852   - break;
853   - case ASSIGNED_FROM_TENANT:
854   - msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT;
855   - break;
856   - case ASSIGNED_TO_TENANT:
857   - msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;
858   - break;
859   - case PROVISION_SUCCESS:
860   - msgType = DataConstants.PROVISION_SUCCESS;
861   - break;
862   - case PROVISION_FAILURE:
863   - msgType = DataConstants.PROVISION_FAILURE;
864   - break;
865   - case TIMESERIES_UPDATED:
866   - msgType = DataConstants.TIMESERIES_UPDATED;
867   - break;
868   - case TIMESERIES_DELETED:
869   - msgType = DataConstants.TIMESERIES_DELETED;
870   - break;
871   - case ASSIGNED_TO_EDGE:
872   - msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE;
873   - break;
874   - case UNASSIGNED_FROM_EDGE:
875   - msgType = DataConstants.ENTITY_UNASSIGNED_FROM_EDGE;
876   - break;
877   - }
878   - if (!StringUtils.isEmpty(msgType)) {
879   - try {
880   - TbMsgMetaData metaData = new TbMsgMetaData();
881   - metaData.putValue("userId", user.getId().toString());
882   - metaData.putValue("userName", user.getName());
883   - if (customerId != null && !customerId.isNullUid()) {
884   - metaData.putValue("customerId", customerId.toString());
885   - }
886   - if (actionType == ActionType.ASSIGNED_TO_CUSTOMER) {
887   - String strCustomerId = extractParameter(String.class, 1, additionalInfo);
888   - String strCustomerName = extractParameter(String.class, 2, additionalInfo);
889   - metaData.putValue("assignedCustomerId", strCustomerId);
890   - metaData.putValue("assignedCustomerName", strCustomerName);
891   - } else if (actionType == ActionType.UNASSIGNED_FROM_CUSTOMER) {
892   - String strCustomerId = extractParameter(String.class, 1, additionalInfo);
893   - String strCustomerName = extractParameter(String.class, 2, additionalInfo);
894   - metaData.putValue("unassignedCustomerId", strCustomerId);
895   - metaData.putValue("unassignedCustomerName", strCustomerName);
896   - } else if (actionType == ActionType.ASSIGNED_FROM_TENANT) {
897   - String strTenantId = extractParameter(String.class, 0, additionalInfo);
898   - String strTenantName = extractParameter(String.class, 1, additionalInfo);
899   - metaData.putValue("assignedFromTenantId", strTenantId);
900   - metaData.putValue("assignedFromTenantName", strTenantName);
901   - } else if (actionType == ActionType.ASSIGNED_TO_TENANT) {
902   - String strTenantId = extractParameter(String.class, 0, additionalInfo);
903   - String strTenantName = extractParameter(String.class, 1, additionalInfo);
904   - metaData.putValue("assignedToTenantId", strTenantId);
905   - metaData.putValue("assignedToTenantName", strTenantName);
906   - } else if (actionType == ActionType.ASSIGNED_TO_EDGE) {
907   - String strEdgeId = extractParameter(String.class, 1, additionalInfo);
908   - String strEdgeName = extractParameter(String.class, 2, additionalInfo);
909   - metaData.putValue("assignedEdgeId", strEdgeId);
910   - metaData.putValue("assignedEdgeName", strEdgeName);
911   - } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) {
912   - String strEdgeId = extractParameter(String.class, 1, additionalInfo);
913   - String strEdgeName = extractParameter(String.class, 2, additionalInfo);
914   - metaData.putValue("unassignedEdgeId", strEdgeId);
915   - metaData.putValue("unassignedEdgeName", strEdgeName);
916   - }
917   - ObjectNode entityNode;
918   - if (entity != null) {
919   - entityNode = json.valueToTree(entity);
920   - if (entityId.getEntityType() == EntityType.DASHBOARD) {
921   - entityNode.put("configuration", "");
922   - }
923   - } else {
924   - entityNode = json.createObjectNode();
925   - if (actionType == ActionType.ATTRIBUTES_UPDATED) {
926   - String scope = extractParameter(String.class, 0, additionalInfo);
927   - @SuppressWarnings("unchecked")
928   - List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo);
929   - metaData.putValue(DataConstants.SCOPE, scope);
930   - if (attributes != null) {
931   - for (AttributeKvEntry attr : attributes) {
932   - addKvEntry(entityNode, attr);
933   - }
934   - }
935   - } else if (actionType == ActionType.ATTRIBUTES_DELETED) {
936   - String scope = extractParameter(String.class, 0, additionalInfo);
937   - @SuppressWarnings("unchecked")
938   - List<String> keys = extractParameter(List.class, 1, additionalInfo);
939   - metaData.putValue(DataConstants.SCOPE, scope);
940   - ArrayNode attrsArrayNode = entityNode.putArray("attributes");
941   - if (keys != null) {
942   - keys.forEach(attrsArrayNode::add);
943   - }
944   - } else if (actionType == ActionType.TIMESERIES_UPDATED) {
945   - @SuppressWarnings("unchecked")
946   - List<TsKvEntry> timeseries = extractParameter(List.class, 0, additionalInfo);
947   - addTimeseries(entityNode, timeseries);
948   - } else if (actionType == ActionType.TIMESERIES_DELETED) {
949   - @SuppressWarnings("unchecked")
950   - List<String> keys = extractParameter(List.class, 0, additionalInfo);
951   - if (keys != null) {
952   - ArrayNode timeseriesArrayNode = entityNode.putArray("timeseries");
953   - keys.forEach(timeseriesArrayNode::add);
954   - }
955   - entityNode.put("startTs", extractParameter(Long.class, 1, additionalInfo));
956   - entityNode.put("endTs", extractParameter(Long.class, 2, additionalInfo));
957   - }
958   - }
959   - TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode));
960   - TenantId tenantId = user.getTenantId();
961   - if (tenantId.isNullUid()) {
962   - if (entity instanceof HasTenantId) {
963   - tenantId = ((HasTenantId) entity).getTenantId();
964   - }
965   - }
966   - tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null);
967   - } catch (Exception e) {
968   - log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e);
969   - }
970   - }
971   - }
972   -
973   - private void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) throws Exception {
974   - if (kvEntry.getDataType() == DataType.BOOLEAN) {
975   - kvEntry.getBooleanValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value));
976   - } else if (kvEntry.getDataType() == DataType.DOUBLE) {
977   - kvEntry.getDoubleValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value));
978   - } else if (kvEntry.getDataType() == DataType.LONG) {
979   - kvEntry.getLongValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value));
980   - } else if (kvEntry.getDataType() == DataType.JSON) {
981   - if (kvEntry.getJsonValue().isPresent()) {
982   - entityNode.set(kvEntry.getKey(), json.readTree(kvEntry.getJsonValue().get()));
983   - }
984   - } else {
985   - entityNode.put(kvEntry.getKey(), kvEntry.getValueAsString());
986   - }
987   - }
988   -
989   - private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) {
990   - T result = null;
991   - if (additionalInfo != null && additionalInfo.length > index) {
992   - Object paramObject = additionalInfo[index];
993   - if (clazz.isInstance(paramObject)) {
994   - result = clazz.cast(paramObject);
995   - }
996   - }
997   - return result;
998   - }
999   -
1000 815 protected <E extends HasName> String entityToStr(E entity) {
1001 816 try {
1002 817 return json.writeValueAsString(json.valueToTree(entity));
... ... @@ -1093,23 +908,6 @@ public abstract class BaseController {
1093 908 return result;
1094 909 }
1095 910
1096   - private void addTimeseries(ObjectNode entityNode, List<TsKvEntry> timeseries) throws Exception {
1097   - if (timeseries != null && !timeseries.isEmpty()) {
1098   - ArrayNode result = entityNode.putArray("timeseries");
1099   - Map<Long, List<TsKvEntry>> groupedTelemetry = timeseries.stream()
1100   - .collect(Collectors.groupingBy(TsKvEntry::getTs));
1101   - for (Map.Entry<Long, List<TsKvEntry>> entry : groupedTelemetry.entrySet()) {
1102   - ObjectNode element = json.createObjectNode();
1103   - element.put("ts", entry.getKey());
1104   - ObjectNode values = element.putObject("values");
1105   - for (TsKvEntry tsKvEntry : entry.getValue()) {
1106   - addKvEntry(values, tsKvEntry);
1107   - }
1108   - result.add(element);
1109   - }
1110   - }
1111   - }
1112   -
1113 911 protected void processDashboardIdFromAdditionalInfo(ObjectNode additionalInfo, String requiredFields) throws ThingsboardException {
1114 912 String dashboardId = additionalInfo.has(requiredFields) ? additionalInfo.get(requiredFields).asText() : null;
1115 913 if (dashboardId != null && !dashboardId.equals("null")) {
... ...
... ... @@ -64,6 +64,10 @@ public class OtaPackageController extends BaseController {
64 64 OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
65 65 OtaPackage otaPackage = checkOtaPackageId(otaPackageId, Operation.READ);
66 66
  67 + if (otaPackage.hasUrl()) {
  68 + return ResponseEntity.badRequest().build();
  69 + }
  70 +
67 71 ByteArrayResource resource = new ByteArrayResource(otaPackage.getData().array());
68 72 return ResponseEntity.ok()
69 73 .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + otaPackage.getFileName())
... ... @@ -182,11 +186,10 @@ public class OtaPackageController extends BaseController {
182 186 }
183 187
184 188 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
185   - @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}/{hasData}", method = RequestMethod.GET)
  189 + @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET)
186 190 @ResponseBody
187 191 public PageData<OtaPackageInfo> getOtaPackages(@PathVariable("deviceProfileId") String strDeviceProfileId,
188 192 @PathVariable("type") String strType,
189   - @PathVariable("hasData") boolean hasData,
190 193 @RequestParam int pageSize,
191 194 @RequestParam int page,
192 195 @RequestParam(required = false) String textSearch,
... ... @@ -197,7 +200,7 @@ public class OtaPackageController extends BaseController {
197 200 try {
198 201 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
199 202 return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(),
200   - new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), hasData, pageLink));
  203 + new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), pageLink));
201 204 } catch (Exception e) {
202 205 throw handleException(e);
203 206 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.service.action;
  17 +
  18 +import com.fasterxml.jackson.databind.ObjectMapper;
  19 +import com.fasterxml.jackson.databind.node.ArrayNode;
  20 +import com.fasterxml.jackson.databind.node.ObjectNode;
  21 +import lombok.RequiredArgsConstructor;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.apache.commons.lang3.StringUtils;
  24 +import org.springframework.stereotype.Service;
  25 +import org.thingsboard.server.common.data.DataConstants;
  26 +import org.thingsboard.server.common.data.EntityType;
  27 +import org.thingsboard.server.common.data.HasName;
  28 +import org.thingsboard.server.common.data.HasTenantId;
  29 +import org.thingsboard.server.common.data.User;
  30 +import org.thingsboard.server.common.data.audit.ActionType;
  31 +import org.thingsboard.server.common.data.id.CustomerId;
  32 +import org.thingsboard.server.common.data.id.EntityId;
  33 +import org.thingsboard.server.common.data.id.TenantId;
  34 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  35 +import org.thingsboard.server.common.data.kv.DataType;
  36 +import org.thingsboard.server.common.data.kv.KvEntry;
  37 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  38 +import org.thingsboard.server.common.msg.TbMsg;
  39 +import org.thingsboard.server.common.msg.TbMsgDataType;
  40 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  41 +import org.thingsboard.server.queue.util.TbCoreComponent;
  42 +import org.thingsboard.server.service.queue.TbClusterService;
  43 +
  44 +import java.util.List;
  45 +import java.util.Map;
  46 +import java.util.stream.Collectors;
  47 +
  48 +@TbCoreComponent
  49 +@Service
  50 +@RequiredArgsConstructor
  51 +@Slf4j
  52 +public class RuleEngineEntityActionService {
  53 + private final TbClusterService tbClusterService;
  54 +
  55 + private static final ObjectMapper json = new ObjectMapper();
  56 +
  57 + public void pushEntityActionToRuleEngine(EntityId entityId, HasName entity, TenantId tenantId, CustomerId customerId,
  58 + ActionType actionType, User user, Object... additionalInfo) {
  59 + String msgType = null;
  60 + switch (actionType) {
  61 + case ADDED:
  62 + msgType = DataConstants.ENTITY_CREATED;
  63 + break;
  64 + case DELETED:
  65 + msgType = DataConstants.ENTITY_DELETED;
  66 + break;
  67 + case UPDATED:
  68 + msgType = DataConstants.ENTITY_UPDATED;
  69 + break;
  70 + case ASSIGNED_TO_CUSTOMER:
  71 + msgType = DataConstants.ENTITY_ASSIGNED;
  72 + break;
  73 + case UNASSIGNED_FROM_CUSTOMER:
  74 + msgType = DataConstants.ENTITY_UNASSIGNED;
  75 + break;
  76 + case ATTRIBUTES_UPDATED:
  77 + msgType = DataConstants.ATTRIBUTES_UPDATED;
  78 + break;
  79 + case ATTRIBUTES_DELETED:
  80 + msgType = DataConstants.ATTRIBUTES_DELETED;
  81 + break;
  82 + case ALARM_ACK:
  83 + msgType = DataConstants.ALARM_ACK;
  84 + break;
  85 + case ALARM_CLEAR:
  86 + msgType = DataConstants.ALARM_CLEAR;
  87 + break;
  88 + case ALARM_DELETE:
  89 + msgType = DataConstants.ALARM_DELETE;
  90 + break;
  91 + case ASSIGNED_FROM_TENANT:
  92 + msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT;
  93 + break;
  94 + case ASSIGNED_TO_TENANT:
  95 + msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;
  96 + break;
  97 + case PROVISION_SUCCESS:
  98 + msgType = DataConstants.PROVISION_SUCCESS;
  99 + break;
  100 + case PROVISION_FAILURE:
  101 + msgType = DataConstants.PROVISION_FAILURE;
  102 + break;
  103 + case TIMESERIES_UPDATED:
  104 + msgType = DataConstants.TIMESERIES_UPDATED;
  105 + break;
  106 + case TIMESERIES_DELETED:
  107 + msgType = DataConstants.TIMESERIES_DELETED;
  108 + break;
  109 + case ASSIGNED_TO_EDGE:
  110 + msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE;
  111 + break;
  112 + case UNASSIGNED_FROM_EDGE:
  113 + msgType = DataConstants.ENTITY_UNASSIGNED_FROM_EDGE;
  114 + break;
  115 + }
  116 + if (!StringUtils.isEmpty(msgType)) {
  117 + try {
  118 + TbMsgMetaData metaData = new TbMsgMetaData();
  119 + if (user != null) {
  120 + metaData.putValue("userId", user.getId().toString());
  121 + metaData.putValue("userName", user.getName());
  122 + }
  123 + if (customerId != null && !customerId.isNullUid()) {
  124 + metaData.putValue("customerId", customerId.toString());
  125 + }
  126 + if (actionType == ActionType.ASSIGNED_TO_CUSTOMER) {
  127 + String strCustomerId = extractParameter(String.class, 1, additionalInfo);
  128 + String strCustomerName = extractParameter(String.class, 2, additionalInfo);
  129 + metaData.putValue("assignedCustomerId", strCustomerId);
  130 + metaData.putValue("assignedCustomerName", strCustomerName);
  131 + } else if (actionType == ActionType.UNASSIGNED_FROM_CUSTOMER) {
  132 + String strCustomerId = extractParameter(String.class, 1, additionalInfo);
  133 + String strCustomerName = extractParameter(String.class, 2, additionalInfo);
  134 + metaData.putValue("unassignedCustomerId", strCustomerId);
  135 + metaData.putValue("unassignedCustomerName", strCustomerName);
  136 + } else if (actionType == ActionType.ASSIGNED_FROM_TENANT) {
  137 + String strTenantId = extractParameter(String.class, 0, additionalInfo);
  138 + String strTenantName = extractParameter(String.class, 1, additionalInfo);
  139 + metaData.putValue("assignedFromTenantId", strTenantId);
  140 + metaData.putValue("assignedFromTenantName", strTenantName);
  141 + } else if (actionType == ActionType.ASSIGNED_TO_TENANT) {
  142 + String strTenantId = extractParameter(String.class, 0, additionalInfo);
  143 + String strTenantName = extractParameter(String.class, 1, additionalInfo);
  144 + metaData.putValue("assignedToTenantId", strTenantId);
  145 + metaData.putValue("assignedToTenantName", strTenantName);
  146 + } else if (actionType == ActionType.ASSIGNED_TO_EDGE) {
  147 + String strEdgeId = extractParameter(String.class, 1, additionalInfo);
  148 + String strEdgeName = extractParameter(String.class, 2, additionalInfo);
  149 + metaData.putValue("assignedEdgeId", strEdgeId);
  150 + metaData.putValue("assignedEdgeName", strEdgeName);
  151 + } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) {
  152 + String strEdgeId = extractParameter(String.class, 1, additionalInfo);
  153 + String strEdgeName = extractParameter(String.class, 2, additionalInfo);
  154 + metaData.putValue("unassignedEdgeId", strEdgeId);
  155 + metaData.putValue("unassignedEdgeName", strEdgeName);
  156 + }
  157 + ObjectNode entityNode;
  158 + if (entity != null) {
  159 + entityNode = json.valueToTree(entity);
  160 + if (entityId.getEntityType() == EntityType.DASHBOARD) {
  161 + entityNode.put("configuration", "");
  162 + }
  163 + } else {
  164 + entityNode = json.createObjectNode();
  165 + if (actionType == ActionType.ATTRIBUTES_UPDATED) {
  166 + String scope = extractParameter(String.class, 0, additionalInfo);
  167 + @SuppressWarnings("unchecked")
  168 + List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo);
  169 + metaData.putValue(DataConstants.SCOPE, scope);
  170 + if (attributes != null) {
  171 + for (AttributeKvEntry attr : attributes) {
  172 + addKvEntry(entityNode, attr);
  173 + }
  174 + }
  175 + } else if (actionType == ActionType.ATTRIBUTES_DELETED) {
  176 + String scope = extractParameter(String.class, 0, additionalInfo);
  177 + @SuppressWarnings("unchecked")
  178 + List<String> keys = extractParameter(List.class, 1, additionalInfo);
  179 + metaData.putValue(DataConstants.SCOPE, scope);
  180 + ArrayNode attrsArrayNode = entityNode.putArray("attributes");
  181 + if (keys != null) {
  182 + keys.forEach(attrsArrayNode::add);
  183 + }
  184 + } else if (actionType == ActionType.TIMESERIES_UPDATED) {
  185 + @SuppressWarnings("unchecked")
  186 + List<TsKvEntry> timeseries = extractParameter(List.class, 0, additionalInfo);
  187 + addTimeseries(entityNode, timeseries);
  188 + } else if (actionType == ActionType.TIMESERIES_DELETED) {
  189 + @SuppressWarnings("unchecked")
  190 + List<String> keys = extractParameter(List.class, 0, additionalInfo);
  191 + if (keys != null) {
  192 + ArrayNode timeseriesArrayNode = entityNode.putArray("timeseries");
  193 + keys.forEach(timeseriesArrayNode::add);
  194 + }
  195 + entityNode.put("startTs", extractParameter(Long.class, 1, additionalInfo));
  196 + entityNode.put("endTs", extractParameter(Long.class, 2, additionalInfo));
  197 + }
  198 + }
  199 + TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode));
  200 + if (tenantId.isNullUid()) {
  201 + if (entity instanceof HasTenantId) {
  202 + tenantId = ((HasTenantId) entity).getTenantId();
  203 + }
  204 + }
  205 + tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null);
  206 + } catch (Exception e) {
  207 + log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e);
  208 + }
  209 + }
  210 + }
  211 +
  212 +
  213 + private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) {
  214 + T result = null;
  215 + if (additionalInfo != null && additionalInfo.length > index) {
  216 + Object paramObject = additionalInfo[index];
  217 + if (clazz.isInstance(paramObject)) {
  218 + result = clazz.cast(paramObject);
  219 + }
  220 + }
  221 + return result;
  222 + }
  223 +
  224 + private void addTimeseries(ObjectNode entityNode, List<TsKvEntry> timeseries) throws Exception {
  225 + if (timeseries != null && !timeseries.isEmpty()) {
  226 + ArrayNode result = entityNode.putArray("timeseries");
  227 + Map<Long, List<TsKvEntry>> groupedTelemetry = timeseries.stream()
  228 + .collect(Collectors.groupingBy(TsKvEntry::getTs));
  229 + for (Map.Entry<Long, List<TsKvEntry>> entry : groupedTelemetry.entrySet()) {
  230 + ObjectNode element = json.createObjectNode();
  231 + element.put("ts", entry.getKey());
  232 + ObjectNode values = element.putObject("values");
  233 + for (TsKvEntry tsKvEntry : entry.getValue()) {
  234 + addKvEntry(values, tsKvEntry);
  235 + }
  236 + result.add(element);
  237 + }
  238 + }
  239 + }
  240 +
  241 + private void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) throws Exception {
  242 + if (kvEntry.getDataType() == DataType.BOOLEAN) {
  243 + kvEntry.getBooleanValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value));
  244 + } else if (kvEntry.getDataType() == DataType.DOUBLE) {
  245 + kvEntry.getDoubleValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value));
  246 + } else if (kvEntry.getDataType() == DataType.LONG) {
  247 + kvEntry.getLongValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value));
  248 + } else if (kvEntry.getDataType() == DataType.JSON) {
  249 + if (kvEntry.getJsonValue().isPresent()) {
  250 + entityNode.set(kvEntry.getKey(), json.readTree(kvEntry.getJsonValue().get()));
  251 + }
  252 + } else {
  253 + entityNode.put(kvEntry.getKey(), kvEntry.getValueAsString());
  254 + }
  255 + }
  256 +}
... ...
... ... @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.DataConstants;
24 24 import org.thingsboard.server.common.data.Device;
25 25 import org.thingsboard.server.common.data.DeviceProfile;
26 26 import org.thingsboard.server.common.data.OtaPackageInfo;
  27 +import org.thingsboard.server.common.data.StringUtils;
27 28 import org.thingsboard.server.common.data.id.DeviceId;
28 29 import org.thingsboard.server.common.data.id.OtaPackageId;
29 30 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -65,6 +66,7 @@ import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE;
65 66 import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE;
66 67 import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE;
67 68 import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS;
  69 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.URL;
68 70 import static org.thingsboard.server.common.data.ota.OtaPackageKey.VERSION;
69 71 import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
70 72 import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
... ... @@ -261,11 +263,12 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
261 263 }
262 264
263 265
264   - private void update(Device device, OtaPackageInfo firmware, long ts) {
  266 + private void update(Device device, OtaPackageInfo otaPackage, long ts) {
265 267 TenantId tenantId = device.getTenantId();
266 268 DeviceId deviceId = device.getId();
  269 + OtaPackageType otaPackageType = otaPackage.getType();
267 270
268   - BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.INITIATED.name()));
  271 + BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(otaPackageType, STATE), OtaPackageUpdateStatus.INITIATED.name()));
269 272
270 273 telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() {
271 274 @Override
... ... @@ -280,11 +283,37 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
280 283 });
281 284
282 285 List<AttributeKvEntry> attributes = new ArrayList<>();
283   - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), TITLE), firmware.getTitle())));
284   - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), VERSION), firmware.getVersion())));
285   - attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(firmware.getType(), SIZE), firmware.getDataSize())));
286   - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM_ALGORITHM), firmware.getChecksumAlgorithm().name())));
287   - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM), firmware.getChecksum())));
  286 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle())));
  287 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion())));
  288 + if (otaPackage.hasUrl()) {
  289 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl())));
  290 + List<String> attrToRemove = new ArrayList<>();
  291 +
  292 + if (otaPackage.getDataSize() == null) {
  293 + attrToRemove.add(getAttributeKey(otaPackageType, SIZE));
  294 + } else {
  295 + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize())));
  296 + }
  297 +
  298 + if (otaPackage.getChecksumAlgorithm() != null) {
  299 + attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM));
  300 + } else {
  301 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name())));
  302 + }
  303 +
  304 + if (StringUtils.isEmpty(otaPackage.getChecksum())) {
  305 + attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM));
  306 + } else {
  307 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum())));
  308 + }
  309 +
  310 + remove(device, otaPackageType, attrToRemove);
  311 + } else {
  312 + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize())));
  313 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name())));
  314 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum())));
  315 + remove(device, otaPackageType, Collections.singletonList(getAttributeKey(otaPackageType, URL)));
  316 + }
288 317
289 318 telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {
290 319 @Override
... ... @@ -299,20 +328,24 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
299 328 });
300 329 }
301 330
302   - private void remove(Device device, OtaPackageType firmwareType) {
303   - telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, OtaPackageUtil.getAttributeKeys(firmwareType),
  331 + private void remove(Device device, OtaPackageType otaPackageType) {
  332 + remove(device, otaPackageType, OtaPackageUtil.getAttributeKeys(otaPackageType));
  333 + }
  334 +
  335 + private void remove(Device device, OtaPackageType otaPackageType, List<String> attributesKeys) {
  336 + telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, attributesKeys,
304 337 new FutureCallback<>() {
305 338 @Override
306 339 public void onSuccess(@Nullable Void tmp) {
307   - log.trace("[{}] Success remove target firmware attributes!", device.getId());
  340 + log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType);
308 341 Set<AttributeKey> keysToNotify = new HashSet<>();
309   - OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key)));
  342 + attributesKeys.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key)));
310 343 tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null);
311 344 }
312 345
313 346 @Override
314 347 public void onFailure(Throwable t) {
315   - log.error("[{}] Failed to remove target firmware attributes!", device.getId(), t);
  348 + log.error("[{}] Failed to remove target {} attributes!", device.getId(), otaPackageType, t);
316 349 }
317 350 });
318 351 }
... ...
... ... @@ -157,6 +157,11 @@ public class DefaultTbResourceService implements TbResourceService {
157 157 resourceService.deleteResourcesByTenantId(tenantId);
158 158 }
159 159
  160 + @Override
  161 + public long sumDataSizeByTenantId(TenantId tenantId) {
  162 + return resourceService.sumDataSizeByTenantId(tenantId);
  163 + }
  164 +
160 165 private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) {
161 166 Comparator<LwM2mObject> comparator;
162 167 if ("name".equals(sortProperty)) {
... ...
... ... @@ -55,4 +55,5 @@ public interface TbResourceService {
55 55
56 56 void deleteResourcesByTenantId(TenantId tenantId);
57 57
  58 + long sumDataSizeByTenantId(TenantId tenantId);
58 59 }
... ...
... ... @@ -536,6 +536,9 @@ public class DefaultTransportApiService implements TransportApiService {
536 536
537 537 if (otaPackageInfo == null) {
538 538 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
  539 + } else if (otaPackageInfo.hasUrl()) {
  540 + builder.setResponseStatus(TransportProtos.ResponseStatus.FAILURE);
  541 + log.trace("[{}] Can`t send OtaPackage with URL data!", otaPackageInfo.getId());
539 542 } else {
540 543 builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS);
541 544 builder.setOtaPackageIdMSB(otaPackageId.getId().getMostSignificantBits());
... ...
... ... @@ -17,9 +17,9 @@ package org.thingsboard.server.service.ttl;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Value;
20   -import org.thingsboard.server.dao.util.PsqlDao;
21 20
22 21 import java.sql.Connection;
  22 +import java.sql.DriverManager;
23 23 import java.sql.ResultSet;
24 24 import java.sql.SQLException;
25 25 import java.sql.SQLWarning;
... ... @@ -62,4 +62,8 @@ public abstract class AbstractCleanUpService {
62 62
63 63 protected abstract void doCleanUp(Connection connection) throws SQLException;
64 64
  65 + protected Connection getConnection() throws SQLException {
  66 + return DriverManager.getConnection(dbUrl, dbUserName, dbPassword);
  67 + }
  68 +
65 69 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.service.ttl.alarms;
  17 +
  18 +import lombok.RequiredArgsConstructor;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Value;
  21 +import org.springframework.scheduling.annotation.Scheduled;
  22 +import org.springframework.stereotype.Service;
  23 +import org.thingsboard.server.common.data.alarm.Alarm;
  24 +import org.thingsboard.server.common.data.audit.ActionType;
  25 +import org.thingsboard.server.common.data.id.AlarmId;
  26 +import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.data.page.PageData;
  28 +import org.thingsboard.server.common.data.page.PageLink;
  29 +import org.thingsboard.server.common.data.page.SortOrder;
  30 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
  31 +import org.thingsboard.server.common.msg.queue.ServiceType;
  32 +import org.thingsboard.server.dao.alarm.AlarmDao;
  33 +import org.thingsboard.server.dao.alarm.AlarmService;
  34 +import org.thingsboard.server.dao.relation.RelationService;
  35 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
  36 +import org.thingsboard.server.dao.tenant.TenantDao;
  37 +import org.thingsboard.server.dao.util.PsqlDao;
  38 +import org.thingsboard.server.queue.discovery.PartitionService;
  39 +import org.thingsboard.server.queue.util.TbCoreComponent;
  40 +import org.thingsboard.server.service.action.RuleEngineEntityActionService;
  41 +import org.thingsboard.server.service.ttl.AbstractCleanUpService;
  42 +
  43 +import java.sql.Connection;
  44 +import java.sql.SQLException;
  45 +import java.util.Date;
  46 +import java.util.Optional;
  47 +import java.util.UUID;
  48 +import java.util.concurrent.TimeUnit;
  49 +
  50 +@TbCoreComponent
  51 +@Service
  52 +@Slf4j
  53 +@RequiredArgsConstructor
  54 +public class AlarmsCleanUpService {
  55 + @Value("${sql.ttl.alarms.removal_batch_size}")
  56 + private Integer removalBatchSize;
  57 +
  58 + private final TenantDao tenantDao;
  59 + private final AlarmDao alarmDao;
  60 + private final AlarmService alarmService;
  61 + private final RelationService relationService;
  62 + private final RuleEngineEntityActionService ruleEngineEntityActionService;
  63 + private final PartitionService partitionService;
  64 + private final TbTenantProfileCache tenantProfileCache;
  65 +
  66 + @Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.alarms.checking_interval})}", fixedDelayString = "${sql.ttl.alarms.checking_interval}")
  67 + public void cleanUp() {
  68 + PageLink tenantsBatchRequest = new PageLink(10_000, 0);
  69 + PageLink removalBatchRequest = new PageLink(removalBatchSize, 0 );
  70 + PageData<TenantId> tenantsIds;
  71 + do {
  72 + tenantsIds = tenantDao.findTenantsIds(tenantsBatchRequest);
  73 + for (TenantId tenantId : tenantsIds.getData()) {
  74 + if (!partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
  75 + continue;
  76 + }
  77 +
  78 + Optional<DefaultTenantProfileConfiguration> tenantProfileConfiguration = tenantProfileCache.get(tenantId).getProfileConfiguration();
  79 + if (tenantProfileConfiguration.isEmpty() || tenantProfileConfiguration.get().getAlarmsTtlDays() == 0) {
  80 + continue;
  81 + }
  82 +
  83 + long ttl = TimeUnit.DAYS.toMillis(tenantProfileConfiguration.get().getAlarmsTtlDays());
  84 + long expirationTime = System.currentTimeMillis() - ttl;
  85 +
  86 + long totalRemoved = 0;
  87 + while (true) {
  88 + PageData<AlarmId> toRemove = alarmDao.findAlarmsIdsByEndTsBeforeAndTenantId(expirationTime, tenantId, removalBatchRequest);
  89 + toRemove.getData().forEach(alarmId -> {
  90 + relationService.deleteEntityRelations(tenantId, alarmId);
  91 + Alarm alarm = alarmService.deleteAlarm(tenantId, alarmId).getAlarm();
  92 + ruleEngineEntityActionService.pushEntityActionToRuleEngine(alarm.getOriginator(), alarm, tenantId, null, ActionType.ALARM_DELETE, null);
  93 + });
  94 +
  95 + totalRemoved += toRemove.getTotalElements();
  96 + if (!toRemove.hasNext()) {
  97 + break;
  98 + }
  99 + }
  100 +
  101 + if (totalRemoved > 0) {
  102 + log.info("Removed {} outdated alarm(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime));
  103 + }
  104 + }
  105 +
  106 + tenantsBatchRequest = tenantsBatchRequest.nextPageLink();
  107 + } while (tenantsIds.hasNext());
  108 + }
  109 +
  110 +}
... ...
... ... @@ -40,7 +40,7 @@ public class EdgeEventsCleanUpService extends AbstractCleanUpService {
40 40 @Scheduled(initialDelayString = "${sql.ttl.edge_events.execution_interval_ms}", fixedDelayString = "${sql.ttl.edge_events.execution_interval_ms}")
41 41 public void cleanUp() {
42 42 if (ttlTaskExecutionEnabled) {
43   - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  43 + try (Connection conn = getConnection()) {
44 44 doCleanUp(conn);
45 45 } catch (SQLException e) {
46 46 log.error("SQLException occurred during TTL task execution ", e);
... ...
... ... @@ -43,7 +43,7 @@ public class EventsCleanUpService extends AbstractCleanUpService {
43 43 @Scheduled(initialDelayString = "${sql.ttl.events.execution_interval_ms}", fixedDelayString = "${sql.ttl.events.execution_interval_ms}")
44 44 public void cleanUp() {
45 45 if (ttlTaskExecutionEnabled) {
46   - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  46 + try (Connection conn = getConnection()) {
47 47 doCleanUp(conn);
48 48 } catch (SQLException e) {
49 49 log.error("SQLException occurred during TTL task execution ", e);
... ...
... ... @@ -36,7 +36,7 @@ public abstract class AbstractTimeseriesCleanUpService extends AbstractCleanUpSe
36 36 @Scheduled(initialDelayString = "${sql.ttl.ts.execution_interval_ms}", fixedDelayString = "${sql.ttl.ts.execution_interval_ms}")
37 37 public void cleanUp() {
38 38 if (ttlTaskExecutionEnabled) {
39   - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  39 + try (Connection conn = getConnection()) {
40 40 doCleanUp(conn);
41 41 } catch (SQLException e) {
42 42 log.error("SQLException occurred during TTL task execution ", e);
... ...
... ... @@ -273,6 +273,9 @@ sql:
273 273 enabled: "${SQL_TTL_EDGE_EVENTS_ENABLED:true}"
274 274 execution_interval_ms: "${SQL_TTL_EDGE_EVENTS_EXECUTION_INTERVAL:86400000}" # Number of milliseconds. The current value corresponds to one day
275 275 edge_events_ttl: "${SQL_TTL_EDGE_EVENTS_TTL:2628000}" # Number of seconds. The current value corresponds to one month
  276 + alarms:
  277 + checking_interval: "${SQL_ALARMS_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours
  278 + removal_batch_size: "${SQL_ALARMS_TTL_REMOVAL_BATCH_SIZE:3000}" # To delete outdated alarms not all at once but in batches
276 279
277 280 # Actor system parameters
278 281 actors:
... ...
... ... @@ -50,7 +50,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
50 50 private static final String FILE_NAME = "filename.txt";
51 51 private static final String VERSION = "v1.0";
52 52 private static final String CONTENT_TYPE = "text/plain";
53   - private static final String CHECKSUM_ALGORITHM = "sha256";
  53 + private static final String CHECKSUM_ALGORITHM = "SHA256";
54 54 private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
55 55 private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1});
56 56
... ... @@ -257,7 +257,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
257 257 @Test
258 258 public void testFindTenantFirmwaresByHasData() throws Exception {
259 259 List<OtaPackageInfo> otaPackagesWithData = new ArrayList<>();
260   - List<OtaPackageInfo> otaPackagesWithoutData = new ArrayList<>();
  260 + List<OtaPackageInfo> allOtaPackages = new ArrayList<>();
261 261
262 262 for (int i = 0; i < 165; i++) {
263 263 OtaPackageInfo firmwareInfo = new OtaPackageInfo();
... ... @@ -272,44 +272,45 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
272 272 MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
273 273
274 274 OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
275   - otaPackagesWithData.add(new OtaPackageInfo(savedFirmware));
276   - } else {
277   - otaPackagesWithoutData.add(savedFirmwareInfo);
  275 + savedFirmwareInfo = new OtaPackageInfo(savedFirmware);
  276 + otaPackagesWithData.add(savedFirmwareInfo);
278 277 }
  278 +
  279 + allOtaPackages.add(savedFirmwareInfo);
279 280 }
280 281
281   - List<OtaPackageInfo> loadedFirmwaresWithData = new ArrayList<>();
  282 + List<OtaPackageInfo> loadedOtaPackagesWithData = new ArrayList<>();
282 283 PageLink pageLink = new PageLink(24);
283 284 PageData<OtaPackageInfo> pageData;
284 285 do {
285   - pageData = doGetTypedWithPageLink("/api/otaPackages/" + deviceProfileId.toString() + "/FIRMWARE/true?",
  286 + pageData = doGetTypedWithPageLink("/api/otaPackages/" + deviceProfileId.toString() + "/FIRMWARE?",
286 287 new TypeReference<>() {
287 288 }, pageLink);
288   - loadedFirmwaresWithData.addAll(pageData.getData());
  289 + loadedOtaPackagesWithData.addAll(pageData.getData());
289 290 if (pageData.hasNext()) {
290 291 pageLink = pageLink.nextPageLink();
291 292 }
292 293 } while (pageData.hasNext());
293 294
294   - List<OtaPackageInfo> loadedFirmwaresWithoutData = new ArrayList<>();
  295 + List<OtaPackageInfo> allLoadedOtaPackages = new ArrayList<>();
295 296 pageLink = new PageLink(24);
296 297 do {
297   - pageData = doGetTypedWithPageLink("/api/otaPackages/" + deviceProfileId.toString() + "/FIRMWARE/false?",
  298 + pageData = doGetTypedWithPageLink("/api/otaPackages?",
298 299 new TypeReference<>() {
299 300 }, pageLink);
300   - loadedFirmwaresWithoutData.addAll(pageData.getData());
  301 + allLoadedOtaPackages.addAll(pageData.getData());
301 302 if (pageData.hasNext()) {
302 303 pageLink = pageLink.nextPageLink();
303 304 }
304 305 } while (pageData.hasNext());
305 306
306 307 Collections.sort(otaPackagesWithData, idComparator);
307   - Collections.sort(otaPackagesWithoutData, idComparator);
308   - Collections.sort(loadedFirmwaresWithData, idComparator);
309   - Collections.sort(loadedFirmwaresWithoutData, idComparator);
  308 + Collections.sort(allOtaPackages, idComparator);
  309 + Collections.sort(loadedOtaPackagesWithData, idComparator);
  310 + Collections.sort(allLoadedOtaPackages, idComparator);
310 311
311   - Assert.assertEquals(otaPackagesWithData, loadedFirmwaresWithData);
312   - Assert.assertEquals(otaPackagesWithoutData, loadedFirmwaresWithoutData);
  312 + Assert.assertEquals(otaPackagesWithData, loadedOtaPackagesWithData);
  313 + Assert.assertEquals(allOtaPackages, allLoadedOtaPackages);
313 314 }
314 315
315 316
... ...
... ... @@ -19,17 +19,24 @@ import com.datastax.oss.driver.api.core.uuid.Uuids;
19 19 import org.junit.After;
20 20 import org.junit.Assert;
21 21 import org.junit.Before;
  22 +import org.junit.Rule;
22 23 import org.junit.Test;
  24 +import org.junit.rules.ExpectedException;
23 25 import org.springframework.beans.factory.annotation.Autowired;
  26 +import org.thingsboard.server.common.data.EntityInfo;
  27 +import org.thingsboard.server.common.data.OtaPackage;
24 28 import org.thingsboard.server.common.data.ResourceType;
25 29 import org.thingsboard.server.common.data.TbResource;
26 30 import org.thingsboard.server.common.data.TbResourceInfo;
27 31 import org.thingsboard.server.common.data.Tenant;
  32 +import org.thingsboard.server.common.data.TenantProfile;
28 33 import org.thingsboard.server.common.data.User;
  34 +import org.thingsboard.server.common.data.exception.ThingsboardException;
29 35 import org.thingsboard.server.common.data.id.TenantId;
30 36 import org.thingsboard.server.common.data.page.PageData;
31 37 import org.thingsboard.server.common.data.page.PageLink;
32 38 import org.thingsboard.server.common.data.security.Authority;
  39 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
33 40 import org.thingsboard.server.controller.AbstractControllerTest;
34 41 import org.thingsboard.server.dao.exception.DataValidationException;
35 42 import org.thingsboard.server.dao.service.DaoSqlTest;
... ... @@ -109,6 +116,64 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
109 116 .andExpect(status().isOk());
110 117 }
111 118
  119 + @Rule
  120 + public ExpectedException thrown = ExpectedException.none();
  121 +
  122 + @Test
  123 + public void testSaveResourceWithMaxSumDataSizeOutOfLimit() throws Exception {
  124 + loginSysAdmin();
  125 + long limit = 1;
  126 + EntityInfo defaultTenantProfileInfo = doGet("/api/tenantProfileInfo/default", EntityInfo.class);
  127 + TenantProfile defaultTenantProfile = doGet("/api/tenantProfile/" + defaultTenantProfileInfo.getId().getId().toString(), TenantProfile.class);
  128 + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxResourcesInBytes(limit).build());
  129 + doPost("/api/tenantProfile", defaultTenantProfile, TenantProfile.class);
  130 +
  131 + loginTenantAdmin();
  132 +
  133 + Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
  134 +
  135 + createResource("test", DEFAULT_FILE_NAME);
  136 +
  137 + Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
  138 +
  139 + try {
  140 + thrown.expect(DataValidationException.class);
  141 + thrown.expectMessage(String.format("Failed to create the tb resource, files size limit is exhausted %d bytes!", limit));
  142 + createResource("test1", 1 + DEFAULT_FILE_NAME);
  143 + } finally {
  144 + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxResourcesInBytes(0).build());
  145 + loginSysAdmin();
  146 + doPost("/api/tenantProfile", defaultTenantProfile, TenantProfile.class);
  147 + }
  148 + }
  149 +
  150 + @Test
  151 + public void sumDataSizeByTenantId() throws ThingsboardException {
  152 + Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
  153 +
  154 + createResource("test", DEFAULT_FILE_NAME);
  155 + Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
  156 +
  157 + int maxSumDataSize = 8;
  158 +
  159 + for (int i = 2; i <= maxSumDataSize; i++) {
  160 + createResource("test" + i, i + DEFAULT_FILE_NAME);
  161 + Assert.assertEquals(i, resourceService.sumDataSizeByTenantId(tenantId));
  162 + }
  163 +
  164 + Assert.assertEquals(maxSumDataSize, resourceService.sumDataSizeByTenantId(tenantId));
  165 + }
  166 +
  167 + private TbResource createResource(String title, String filename) throws ThingsboardException {
  168 + TbResource resource = new TbResource();
  169 + resource.setTenantId(tenantId);
  170 + resource.setTitle(title);
  171 + resource.setResourceType(ResourceType.JKS);
  172 + resource.setFileName(filename);
  173 + resource.setData("1");
  174 + return resourceService.saveResource(resource);
  175 + }
  176 +
112 177 @Test
113 178 public void testSaveTbResource() throws Exception {
114 179 TbResource resource = new TbResource();
... ...
... ... @@ -44,9 +44,11 @@ public interface OtaPackageService {
44 44
45 45 PageData<OtaPackageInfo> findTenantOtaPackagesByTenantId(TenantId tenantId, PageLink pageLink);
46 46
47   - PageData<OtaPackageInfo> findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, boolean hasData, PageLink pageLink);
  47 + PageData<OtaPackageInfo> findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink);
48 48
49 49 void deleteOtaPackage(TenantId tenantId, OtaPackageId otaPackageId);
50 50
51 51 void deleteOtaPackagesByTenantId(TenantId tenantId);
  52 +
  53 + long sumDataSizeByTenantId(TenantId tenantId);
52 54 }
... ...
... ... @@ -49,5 +49,5 @@ public interface ResourceService {
49 49
50 50 void deleteResourcesByTenantId(TenantId tenantId);
51 51
52   -
  52 + long sumDataSizeByTenantId(TenantId tenantId);
53 53 }
... ...
... ... @@ -66,6 +66,7 @@ public class DataConstants {
66 66 public static final String TIMESERIES_DELETED = "TIMESERIES_DELETED";
67 67 public static final String ALARM_ACK = "ALARM_ACK";
68 68 public static final String ALARM_CLEAR = "ALARM_CLEAR";
  69 + public static final String ALARM_DELETE = "ALARM_DELETE";
69 70 public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT";
70 71 public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT";
71 72 public static final String PROVISION_SUCCESS = "PROVISION_SUCCESS";
... ...
... ... @@ -37,8 +37,8 @@ public class OtaPackage extends OtaPackageInfo {
37 37 super(id);
38 38 }
39 39
40   - public OtaPackage(OtaPackage firmware) {
41   - super(firmware);
42   - this.data = firmware.getData();
  40 + public OtaPackage(OtaPackage otaPackage) {
  41 + super(otaPackage);
  42 + this.data = otaPackage.getData();
43 43 }
44 44 }
... ...
... ... @@ -37,6 +37,7 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
37 37 private OtaPackageType type;
38 38 private String title;
39 39 private String version;
  40 + private String url;
40 41 private boolean hasData;
41 42 private String fileName;
42 43 private String contentType;
... ... @@ -60,6 +61,7 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
60 61 this.type = otaPackageInfo.getType();
61 62 this.title = otaPackageInfo.getTitle();
62 63 this.version = otaPackageInfo.getVersion();
  64 + this.url = otaPackageInfo.getUrl();
63 65 this.hasData = otaPackageInfo.isHasData();
64 66 this.fileName = otaPackageInfo.getFileName();
65 67 this.contentType = otaPackageInfo.getContentType();
... ... @@ -78,4 +80,9 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
78 80 public String getName() {
79 81 return title;
80 82 }
  83 +
  84 + @JsonIgnore
  85 + public boolean hasUrl() {
  86 + return StringUtils.isNotEmpty(url);
  87 + }
81 88 }
... ...
... ... @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
27 27
28 28 import java.io.ByteArrayInputStream;
29 29 import java.io.IOException;
  30 +import java.util.Optional;
30 31
31 32 import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo.mapper;
32 33
... ... @@ -92,6 +93,13 @@ public class TenantProfile extends SearchTextBased<TenantProfileId> implements H
92 93 }
93 94 }
94 95
  96 + @JsonIgnore
  97 + public Optional<DefaultTenantProfileConfiguration> getProfileConfiguration() {
  98 + return Optional.ofNullable(getProfileData().getConfiguration())
  99 + .filter(profileConfiguration -> profileConfiguration instanceof DefaultTenantProfileConfiguration)
  100 + .map(profileConfiguration -> (DefaultTenantProfileConfiguration) profileConfiguration);
  101 + }
  102 +
95 103 public TenantProfileData createDefaultTenantProfileData() {
96 104 TenantProfileData tpd = new TenantProfileData();
97 105 tpd.setConfiguration(new DefaultTenantProfileConfiguration());
... ...
... ... @@ -39,6 +39,7 @@ public enum ActionType {
39 39 RELATIONS_DELETED(false),
40 40 ALARM_ACK(false),
41 41 ALARM_CLEAR(false),
  42 + ALARM_DELETE(false),
42 43 LOGIN(false),
43 44 LOGOUT(false),
44 45 LOCKOUT(false),
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.exception;
  17 +
  18 +public class ApiUsageLimitsExceededException extends RuntimeException {
  19 + public ApiUsageLimitsExceededException(String message) {
  20 + super(message);
  21 + }
  22 +
  23 + public ApiUsageLimitsExceededException() {
  24 + }
  25 +}
... ...
... ... @@ -19,7 +19,7 @@ import lombok.Getter;
19 19
20 20 public enum OtaPackageKey {
21 21
22   - TITLE("title"), VERSION("version"), TS("ts"), STATE("state"), SIZE("size"), CHECKSUM("checksum"), CHECKSUM_ALGORITHM("checksum_algorithm");
  22 + TITLE("title"), VERSION("version"), TS("ts"), STATE("state"), SIZE("size"), CHECKSUM("checksum"), CHECKSUM_ALGORITHM("checksum_algorithm"), URL("url");
23 23
24 24 @Getter
25 25 private final String value;
... ...
... ... @@ -17,10 +17,11 @@ package org.thingsboard.server.common.data.page;
17 17
18 18 import com.fasterxml.jackson.annotation.JsonCreator;
19 19 import com.fasterxml.jackson.annotation.JsonProperty;
20   -import org.thingsboard.server.common.data.BaseData;
21 20
22 21 import java.util.Collections;
23 22 import java.util.List;
  23 +import java.util.function.Function;
  24 +import java.util.stream.Collectors;
24 25
25 26 public class PageData<T> {
26 27
... ... @@ -61,4 +62,8 @@ public class PageData<T> {
61 62 return hasNext;
62 63 }
63 64
  65 + public <D> PageData<D> mapData(Function<T, D> mapper) {
  66 + return new PageData<>(getData().stream().map(mapper).collect(Collectors.toList()), getTotalPages(), getTotalElements(), hasNext());
  67 + }
  68 +
64 69 }
... ...
... ... @@ -34,6 +34,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
34 34 private long maxUsers;
35 35 private long maxDashboards;
36 36 private long maxRuleChains;
  37 + private long maxResourcesInBytes;
  38 + private long maxOtaPackagesInBytes;
37 39
38 40 private String transportTenantMsgRateLimit;
39 41 private String transportTenantTelemetryMsgRateLimit;
... ... @@ -53,6 +55,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
53 55 private long maxCreatedAlarms;
54 56
55 57 private int defaultStorageTtlDays;
  58 + private int alarmsTtlDays;
56 59
57 60 private double warnThreshold;
58 61
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.dao;
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.id.TenantId;
20 20
  21 +import java.util.Collection;
21 22 import java.util.List;
22 23 import java.util.UUID;
23 24
... ... @@ -33,4 +34,6 @@ public interface Dao<T> {
33 34
34 35 boolean removeById(TenantId tenantId, UUID id);
35 36
  37 + void removeAllByIds(Collection<UUID> ids);
  38 +
36 39 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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 TenantEntityWithDataDao {
  21 +
  22 + Long sumDataSizeByTenantId(TenantId tenantId);
  23 +}
... ...
... ... @@ -21,10 +21,12 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
21 21 import org.thingsboard.server.common.data.alarm.AlarmQuery;
22 22 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
23 23 import org.thingsboard.server.common.data.alarm.AlarmStatus;
  24 +import org.thingsboard.server.common.data.id.AlarmId;
24 25 import org.thingsboard.server.common.data.id.CustomerId;
25 26 import org.thingsboard.server.common.data.id.EntityId;
26 27 import org.thingsboard.server.common.data.id.TenantId;
27 28 import org.thingsboard.server.common.data.page.PageData;
  29 +import org.thingsboard.server.common.data.page.PageLink;
28 30 import org.thingsboard.server.common.data.query.AlarmData;
29 31 import org.thingsboard.server.common.data.query.AlarmDataQuery;
30 32 import org.thingsboard.server.dao.Dao;
... ... @@ -54,4 +56,7 @@ public interface AlarmDao extends Dao<Alarm> {
54 56 AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
55 57
56 58 Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> status);
  59 +
  60 + PageData<AlarmId> findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink);
  61 +
57 62 }
... ...
... ... @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery;
34 34 import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
35 35 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
36 36 import org.thingsboard.server.common.data.alarm.AlarmStatus;
  37 +import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException;
37 38 import org.thingsboard.server.common.data.id.AlarmId;
38 39 import org.thingsboard.server.common.data.id.CustomerId;
39 40 import org.thingsboard.server.common.data.id.EntityId;
... ... @@ -119,7 +120,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
119 120 Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get();
120 121 if (existing == null || existing.getStatus().isCleared()) {
121 122 if (!alarmCreationEnabled) {
122   - throw new IllegalStateException("Alarm creation is disabled");
  123 + throw new ApiUsageLimitsExceededException("Alarms creation is disabled");
123 124 }
124 125 return createAlarm(alarm);
125 126 } else {
... ...
... ... @@ -487,6 +487,7 @@ public class ModelConstants {
487 487 public static final String OTA_PACKAGE_TYPE_COLUMN = "type";
488 488 public static final String OTA_PACKAGE_TILE_COLUMN = TITLE_PROPERTY;
489 489 public static final String OTA_PACKAGE_VERSION_COLUMN = "version";
  490 + public static final String OTA_PACKAGE_URL_COLUMN = "url";
490 491 public static final String OTA_PACKAGE_FILE_NAME_COLUMN = "file_name";
491 492 public static final String OTA_PACKAGE_CONTENT_TYPE_COLUMN = "content_type";
492 493 public static final String OTA_PACKAGE_CHECKSUM_ALGORITHM_COLUMN = "checksum_algorithm";
... ...
... ... @@ -51,6 +51,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_
51 51 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN;
52 52 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN;
53 53 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN;
  54 +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_URL_COLUMN;
54 55 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_VERSION_COLUMN;
55 56 import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
56 57
... ... @@ -77,6 +78,9 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
77 78 @Column(name = OTA_PACKAGE_VERSION_COLUMN)
78 79 private String version;
79 80
  81 + @Column(name = OTA_PACKAGE_URL_COLUMN)
  82 + private String url;
  83 +
80 84 @Column(name = OTA_PACKAGE_FILE_NAME_COLUMN)
81 85 private String fileName;
82 86
... ... @@ -118,6 +122,7 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
118 122 this.type = firmware.getType();
119 123 this.title = firmware.getTitle();
120 124 this.version = firmware.getVersion();
  125 + this.url = firmware.getUrl();
121 126 this.fileName = firmware.getFileName();
122 127 this.contentType = firmware.getContentType();
123 128 this.checksumAlgorithm = firmware.getChecksumAlgorithm();
... ... @@ -148,6 +153,7 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
148 153 firmware.setType(type);
149 154 firmware.setTitle(title);
150 155 firmware.setVersion(version);
  156 + firmware.setUrl(url);
151 157 firmware.setFileName(fileName);
152 158 firmware.setContentType(contentType);
153 159 firmware.setChecksumAlgorithm(checksumAlgorithm);
... ...
... ... @@ -22,6 +22,7 @@ import org.hibernate.annotations.Type;
22 22 import org.hibernate.annotations.TypeDef;
23 23 import org.thingsboard.common.util.JacksonUtil;
24 24 import org.thingsboard.server.common.data.OtaPackageInfo;
  25 +import org.thingsboard.server.common.data.StringUtils;
25 26 import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
26 27 import org.thingsboard.server.common.data.ota.OtaPackageType;
27 28 import org.thingsboard.server.common.data.id.DeviceProfileId;
... ... @@ -50,6 +51,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_
50 51 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN;
51 52 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN;
52 53 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN;
  54 +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_URL_COLUMN;
53 55 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_VERSION_COLUMN;
54 56 import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
55 57
... ... @@ -76,6 +78,9 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
76 78 @Column(name = OTA_PACKAGE_VERSION_COLUMN)
77 79 private String version;
78 80
  81 + @Column(name = OTA_PACKAGE_URL_COLUMN)
  82 + private String url;
  83 +
79 84 @Column(name = OTA_PACKAGE_FILE_NAME_COLUMN)
80 85 private String fileName;
81 86
... ... @@ -116,6 +121,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
116 121 }
117 122 this.title = firmware.getTitle();
118 123 this.version = firmware.getVersion();
  124 + this.url = firmware.getUrl();
119 125 this.fileName = firmware.getFileName();
120 126 this.contentType = firmware.getContentType();
121 127 this.checksumAlgorithm = firmware.getChecksumAlgorithm();
... ... @@ -125,7 +131,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
125 131 }
126 132
127 133 public OtaPackageInfoEntity(UUID id, long createdTime, UUID tenantId, UUID deviceProfileId, OtaPackageType type, String title, String version,
128   - String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize,
  134 + String url, String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize,
129 135 Object additionalInfo, boolean hasData) {
130 136 this.id = id;
131 137 this.createdTime = createdTime;
... ... @@ -134,6 +140,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
134 140 this.type = type;
135 141 this.title = title;
136 142 this.version = version;
  143 + this.url = url;
137 144 this.fileName = fileName;
138 145 this.contentType = contentType;
139 146 this.checksumAlgorithm = checksumAlgorithm;
... ... @@ -164,6 +171,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
164 171 firmware.setType(type);
165 172 firmware.setTitle(title);
166 173 firmware.setVersion(version);
  174 + firmware.setUrl(url);
167 175 firmware.setFileName(fileName);
168 176 firmware.setContentType(contentType);
169 177 firmware.setChecksumAlgorithm(checksumAlgorithm);
... ...
... ... @@ -20,28 +20,32 @@ import com.google.common.hash.Hashing;
20 20 import com.google.common.util.concurrent.ListenableFuture;
21 21 import lombok.RequiredArgsConstructor;
22 22 import lombok.extern.slf4j.Slf4j;
23   -import org.apache.commons.lang3.StringUtils;
24 23 import org.hibernate.exception.ConstraintViolationException;
  24 +import org.springframework.beans.factory.annotation.Autowired;
25 25 import org.springframework.cache.Cache;
26 26 import org.springframework.cache.CacheManager;
27 27 import org.springframework.cache.annotation.Cacheable;
  28 +import org.springframework.context.annotation.Lazy;
28 29 import org.springframework.stereotype.Service;
29 30 import org.thingsboard.server.cache.ota.OtaPackageDataCache;
30 31 import org.thingsboard.server.common.data.DeviceProfile;
31 32 import org.thingsboard.server.common.data.OtaPackage;
32 33 import org.thingsboard.server.common.data.OtaPackageInfo;
  34 +import org.thingsboard.server.common.data.StringUtils;
33 35 import org.thingsboard.server.common.data.Tenant;
34   -import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
35   -import org.thingsboard.server.common.data.ota.OtaPackageType;
36 36 import org.thingsboard.server.common.data.id.DeviceProfileId;
37 37 import org.thingsboard.server.common.data.id.OtaPackageId;
38 38 import org.thingsboard.server.common.data.id.TenantId;
  39 +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
  40 +import org.thingsboard.server.common.data.ota.OtaPackageType;
39 41 import org.thingsboard.server.common.data.page.PageData;
40 42 import org.thingsboard.server.common.data.page.PageLink;
  43 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
41 44 import org.thingsboard.server.dao.device.DeviceProfileDao;
42 45 import org.thingsboard.server.dao.exception.DataValidationException;
43 46 import org.thingsboard.server.dao.service.DataValidator;
44 47 import org.thingsboard.server.dao.service.PaginatedRemover;
  48 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
45 49 import org.thingsboard.server.dao.tenant.TenantDao;
46 50
47 51 import java.nio.ByteBuffer;
... ... @@ -50,6 +54,7 @@ import java.util.List;
50 54 import java.util.Optional;
51 55
52 56 import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_CACHE;
  57 +import static org.thingsboard.server.common.data.EntityType.OTA_PACKAGE;
53 58 import static org.thingsboard.server.dao.service.Validator.validateId;
54 59 import static org.thingsboard.server.dao.service.Validator.validatePageLink;
55 60
... ... @@ -67,6 +72,10 @@ public class BaseOtaPackageService implements OtaPackageService {
67 72 private final CacheManager cacheManager;
68 73 private final OtaPackageDataCache otaPackageDataCache;
69 74
  75 + @Autowired
  76 + @Lazy
  77 + private TbTenantProfileCache tenantProfileCache;
  78 +
70 79 @Override
71 80 public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo) {
72 81 log.trace("Executing saveOtaPackageInfo [{}]", otaPackageInfo);
... ... @@ -172,11 +181,11 @@ public class BaseOtaPackageService implements OtaPackageService {
172 181 }
173 182
174 183 @Override
175   - public PageData<OtaPackageInfo> findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, boolean hasData, PageLink pageLink) {
176   - log.trace("Executing findTenantOtaPackagesByTenantIdAndHasData, tenantId [{}], hasData [{}] pageLink [{}]", tenantId, hasData, pageLink);
  184 + public PageData<OtaPackageInfo> findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink) {
  185 + log.trace("Executing findTenantOtaPackagesByTenantIdAndHasData, tenantId [{}], pageLink [{}]", tenantId, pageLink);
177 186 validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
178 187 validatePageLink(pageLink);
179   - return otaPackageInfoDao.findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, otaPackageType, hasData, pageLink);
  188 + return otaPackageInfoDao.findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, otaPackageType, pageLink);
180 189 }
181 190
182 191 @Override
... ... @@ -205,6 +214,11 @@ public class BaseOtaPackageService implements OtaPackageService {
205 214 }
206 215
207 216 @Override
  217 + public long sumDataSizeByTenantId(TenantId tenantId) {
  218 + return otaPackageDao.sumDataSizeByTenantId(tenantId);
  219 + }
  220 +
  221 + @Override
208 222 public void deleteOtaPackagesByTenantId(TenantId tenantId) {
209 223 log.trace("Executing deleteOtaPackagesByTenantId, tenantId [{}]", tenantId);
210 224 validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
... ... @@ -228,30 +242,42 @@ public class BaseOtaPackageService implements OtaPackageService {
228 242 private DataValidator<OtaPackage> otaPackageValidator = new DataValidator<>() {
229 243
230 244 @Override
  245 + protected void validateCreate(TenantId tenantId, OtaPackage otaPackage) {
  246 + DefaultTenantProfileConfiguration profileConfiguration =
  247 + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  248 + long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes();
  249 + validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE);
  250 + }
  251 +
  252 + @Override
231 253 protected void validateDataImpl(TenantId tenantId, OtaPackage otaPackage) {
232 254 validateImpl(otaPackage);
233 255
234   - if (StringUtils.isEmpty(otaPackage.getFileName())) {
235   - throw new DataValidationException("OtaPackage file name should be specified!");
236   - }
  256 + if (!otaPackage.hasUrl()) {
  257 + if (StringUtils.isEmpty(otaPackage.getFileName())) {
  258 + throw new DataValidationException("OtaPackage file name should be specified!");
  259 + }
237 260
238   - if (StringUtils.isEmpty(otaPackage.getContentType())) {
239   - throw new DataValidationException("OtaPackage content type should be specified!");
240   - }
  261 + if (StringUtils.isEmpty(otaPackage.getContentType())) {
  262 + throw new DataValidationException("OtaPackage content type should be specified!");
  263 + }
241 264
242   - if (otaPackage.getChecksumAlgorithm() == null) {
243   - throw new DataValidationException("OtaPackage checksum algorithm should be specified!");
244   - }
245   - if (StringUtils.isEmpty(otaPackage.getChecksum())) {
246   - throw new DataValidationException("OtaPackage checksum should be specified!");
247   - }
  265 + if (otaPackage.getChecksumAlgorithm() == null) {
  266 + throw new DataValidationException("OtaPackage checksum algorithm should be specified!");
  267 + }
  268 + if (StringUtils.isEmpty(otaPackage.getChecksum())) {
  269 + throw new DataValidationException("OtaPackage checksum should be specified!");
  270 + }
248 271
249   - String currentChecksum;
  272 + String currentChecksum;
250 273
251   - currentChecksum = generateChecksum(otaPackage.getChecksumAlgorithm(), otaPackage.getData());
  274 + currentChecksum = generateChecksum(otaPackage.getChecksumAlgorithm(), otaPackage.getData());
252 275
253   - if (!currentChecksum.equals(otaPackage.getChecksum())) {
254   - throw new DataValidationException("Wrong otaPackage file!");
  276 + if (!currentChecksum.equals(otaPackage.getChecksum())) {
  277 + throw new DataValidationException("Wrong otaPackage file!");
  278 + }
  279 + } else {
  280 + //TODO: validate url
255 281 }
256 282 }
257 283
... ... @@ -264,6 +290,13 @@ public class BaseOtaPackageService implements OtaPackageService {
264 290 if (otaPackageOld.getData() != null && !otaPackageOld.getData().equals(otaPackage.getData())) {
265 291 throw new DataValidationException("Updating otaPackage data is prohibited!");
266 292 }
  293 +
  294 + if (otaPackageOld.getData() == null && otaPackage.getData() != null) {
  295 + DefaultTenantProfileConfiguration profileConfiguration =
  296 + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  297 + long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes();
  298 + validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE);
  299 + }
267 300 }
268 301 };
269 302
... ...
... ... @@ -16,8 +16,11 @@
16 16 package org.thingsboard.server.dao.ota;
17 17
18 18 import org.thingsboard.server.common.data.OtaPackage;
  19 +import org.thingsboard.server.common.data.id.TenantId;
19 20 import org.thingsboard.server.dao.Dao;
  21 +import org.thingsboard.server.dao.TenantEntityDao;
  22 +import org.thingsboard.server.dao.TenantEntityWithDataDao;
20 23
21   -public interface OtaPackageDao extends Dao<OtaPackage> {
22   -
  24 +public interface OtaPackageDao extends Dao<OtaPackage>, TenantEntityWithDataDao {
  25 + Long sumDataSizeByTenantId(TenantId tenantId);
23 26 }
... ...
... ... @@ -28,7 +28,7 @@ public interface OtaPackageInfoDao extends Dao<OtaPackageInfo> {
28 28
29 29 PageData<OtaPackageInfo> findOtaPackageInfoByTenantId(TenantId tenantId, PageLink pageLink);
30 30
31   - PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, boolean hasData, PageLink pageLink);
  31 + PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink);
32 32
33 33 boolean isOtaPackageUsed(OtaPackageId otaPackageId, OtaPackageType otaPackageType, DeviceProfileId deviceProfileId);
34 34
... ...
... ... @@ -19,6 +19,7 @@ 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.hibernate.exception.ConstraintViolationException;
  22 +import org.springframework.context.annotation.Lazy;
22 23 import org.springframework.stereotype.Service;
23 24 import org.thingsboard.server.common.data.ResourceType;
24 25 import org.thingsboard.server.common.data.TbResource;
... ... @@ -28,16 +29,19 @@ import org.thingsboard.server.common.data.id.TbResourceId;
28 29 import org.thingsboard.server.common.data.id.TenantId;
29 30 import org.thingsboard.server.common.data.page.PageData;
30 31 import org.thingsboard.server.common.data.page.PageLink;
  32 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
31 33 import org.thingsboard.server.dao.exception.DataValidationException;
32 34 import org.thingsboard.server.dao.model.ModelConstants;
33 35 import org.thingsboard.server.dao.service.DataValidator;
34 36 import org.thingsboard.server.dao.service.PaginatedRemover;
35 37 import org.thingsboard.server.dao.service.Validator;
  38 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
36 39 import org.thingsboard.server.dao.tenant.TenantDao;
37 40
38 41 import java.util.List;
39 42 import java.util.Optional;
40 43
  44 +import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE;
41 45 import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID;
42 46 import static org.thingsboard.server.dao.service.Validator.validateId;
43 47
... ... @@ -49,12 +53,13 @@ public class BaseResourceService implements ResourceService {
49 53 private final TbResourceDao resourceDao;
50 54 private final TbResourceInfoDao resourceInfoDao;
51 55 private final TenantDao tenantDao;
  56 + private final TbTenantProfileCache tenantProfileCache;
52 57
53   -
54   - public BaseResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao) {
  58 + public BaseResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao, @Lazy TbTenantProfileCache tenantProfileCache) {
55 59 this.resourceDao = resourceDao;
56 60 this.resourceInfoDao = resourceInfoDao;
57 61 this.tenantDao = tenantDao;
  62 + this.tenantProfileCache = tenantProfileCache;
58 63 }
59 64
60 65 @Override
... ... @@ -143,9 +148,24 @@ public class BaseResourceService implements ResourceService {
143 148 tenantResourcesRemover.removeEntities(tenantId, tenantId);
144 149 }
145 150
  151 + @Override
  152 + public long sumDataSizeByTenantId(TenantId tenantId) {
  153 + return resourceDao.sumDataSizeByTenantId(tenantId);
  154 + }
  155 +
146 156 private DataValidator<TbResource> resourceValidator = new DataValidator<>() {
147 157
148 158 @Override
  159 + protected void validateCreate(TenantId tenantId, TbResource resource) {
  160 + if (tenantId != null && !TenantId.SYS_TENANT_ID.equals(tenantId) ) {
  161 + DefaultTenantProfileConfiguration profileConfiguration =
  162 + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  163 + long maxSumResourcesDataInBytes = profileConfiguration.getMaxResourcesInBytes();
  164 + validateMaxSumDataSizePerTenant(tenantId, resourceDao, maxSumResourcesDataInBytes, resource.getData().length(), TB_RESOURCE);
  165 + }
  166 + }
  167 +
  168 + @Override
149 169 protected void validateDataImpl(TenantId tenantId, TbResource resource) {
150 170 if (StringUtils.isEmpty(resource.getTitle())) {
151 171 throw new DataValidationException("Resource title should be specified!");
... ...
... ... @@ -21,10 +21,11 @@ import org.thingsboard.server.common.data.id.TenantId;
21 21 import org.thingsboard.server.common.data.page.PageData;
22 22 import org.thingsboard.server.common.data.page.PageLink;
23 23 import org.thingsboard.server.dao.Dao;
  24 +import org.thingsboard.server.dao.TenantEntityWithDataDao;
24 25
25 26 import java.util.List;
26 27
27   -public interface TbResourceDao extends Dao<TbResource> {
  28 +public interface TbResourceDao extends Dao<TbResource>, TenantEntityWithDataDao {
28 29
29 30 TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
30 31
... ...
... ... @@ -23,8 +23,10 @@ import org.hibernate.validator.cfg.ConstraintMapping;
23 23 import org.thingsboard.server.common.data.BaseData;
24 24 import org.thingsboard.server.common.data.EntityType;
25 25 import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
26 27 import org.thingsboard.server.common.data.validation.NoXss;
27 28 import org.thingsboard.server.dao.TenantEntityDao;
  29 +import org.thingsboard.server.dao.TenantEntityWithDataDao;
28 30 import org.thingsboard.server.dao.exception.DataValidationException;
29 31
30 32 import javax.validation.ConstraintViolation;
... ... @@ -123,6 +125,19 @@ public abstract class DataValidator<D extends BaseData<?>> {
123 125 }
124 126 }
125 127
  128 + protected void validateMaxSumDataSizePerTenant(TenantId tenantId,
  129 + TenantEntityWithDataDao dataDao,
  130 + long maxSumDataSize,
  131 + long currentDataSize,
  132 + EntityType entityType) {
  133 + if (maxSumDataSize > 0) {
  134 + if (dataDao.sumDataSizeByTenantId(tenantId) + currentDataSize > maxSumDataSize) {
  135 + throw new DataValidationException(String.format("Failed to create the %s, files size limit is exhausted %d bytes!",
  136 + entityType.name().toLowerCase().replaceAll("_", " "), maxSumDataSize));
  137 + }
  138 + }
  139 + }
  140 +
126 141 protected static void validateJsonStructure(JsonNode expectedNode, JsonNode actualNode) {
127 142 Set<String> expectedFields = new HashSet<>();
128 143 Iterator<String> fieldsIterator = expectedNode.fieldNames();
... ...
... ... @@ -26,6 +26,7 @@ import org.thingsboard.server.dao.Dao;
26 26 import org.thingsboard.server.dao.DaoUtil;
27 27 import org.thingsboard.server.dao.model.BaseEntity;
28 28
  29 +import java.util.Collection;
29 30 import java.util.List;
30 31 import java.util.Optional;
31 32 import java.util.UUID;
... ... @@ -87,6 +88,12 @@ public abstract class JpaAbstractDao<E extends BaseEntity<D>, D>
87 88 return !getCrudRepository().existsById(id);
88 89 }
89 90
  91 + @Transactional
  92 + public void removeAllByIds(Collection<UUID> ids) {
  93 + CrudRepository<E, UUID> repository = getCrudRepository();
  94 + ids.forEach(repository::deleteById);
  95 + }
  96 +
90 97 @Override
91 98 public List<D> find(TenantId tenantId) {
92 99 List<E> entities = Lists.newArrayList(getCrudRepository().findAll());
... ...
... ... @@ -17,11 +17,13 @@ package org.thingsboard.server.dao.sql.alarm;
17 17
18 18 import org.springframework.data.domain.Page;
19 19 import org.springframework.data.domain.Pageable;
  20 +import org.springframework.data.jpa.repository.Modifying;
20 21 import org.springframework.data.jpa.repository.Query;
21 22 import org.springframework.data.repository.CrudRepository;
22 23 import org.springframework.data.repository.query.Param;
23 24 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
24 25 import org.thingsboard.server.common.data.alarm.AlarmStatus;
  26 +import org.thingsboard.server.common.data.id.TenantId;
25 27 import org.thingsboard.server.dao.model.sql.AlarmEntity;
26 28 import org.thingsboard.server.dao.model.sql.AlarmInfoEntity;
27 29
... ... @@ -159,4 +161,8 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, UUID> {
159 161 @Param("affectedEntityId") UUID affectedEntityId,
160 162 @Param("affectedEntityType") String affectedEntityType,
161 163 @Param("alarmStatuses") Set<AlarmStatus> alarmStatuses);
  164 +
  165 + @Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.createdTime < :time AND a.endTs < :time")
  166 + Page<UUID> findAlarmsIdsByEndTsBeforeAndTenantId(@Param("time") Long time, @Param("tenantId") UUID tenantId, Pageable pageable);
  167 +
162 168 }
... ...
... ... @@ -26,10 +26,12 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
26 26 import org.thingsboard.server.common.data.alarm.AlarmQuery;
27 27 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
28 28 import org.thingsboard.server.common.data.alarm.AlarmStatus;
  29 +import org.thingsboard.server.common.data.id.AlarmId;
29 30 import org.thingsboard.server.common.data.id.CustomerId;
30 31 import org.thingsboard.server.common.data.id.EntityId;
31 32 import org.thingsboard.server.common.data.id.TenantId;
32 33 import org.thingsboard.server.common.data.page.PageData;
  34 +import org.thingsboard.server.common.data.page.PageLink;
33 35 import org.thingsboard.server.common.data.query.AlarmData;
34 36 import org.thingsboard.server.common.data.query.AlarmDataQuery;
35 37 import org.thingsboard.server.dao.DaoUtil;
... ... @@ -161,4 +163,10 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
161 163 public Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> statuses) {
162 164 return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses);
163 165 }
  166 +
  167 + @Override
  168 + public PageData<AlarmId> findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink) {
  169 + return DaoUtil.pageToPageData(alarmRepository.findAlarmsIdsByEndTsBeforeAndTenantId(time, tenantId.getId(), DaoUtil.toPageable(pageLink)))
  170 + .mapData(AlarmId::new);
  171 + }
164 172 }
... ...
... ... @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.data.repository.CrudRepository;
21 21 import org.springframework.stereotype.Component;
22 22 import org.thingsboard.server.common.data.OtaPackage;
  23 +import org.thingsboard.server.common.data.id.TenantId;
23 24 import org.thingsboard.server.dao.ota.OtaPackageDao;
24 25 import org.thingsboard.server.dao.model.sql.OtaPackageEntity;
25 26 import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
... ... @@ -43,4 +44,8 @@ public class JpaOtaPackageDao extends JpaAbstractSearchTextDao<OtaPackageEntity,
43 44 return otaPackageRepository;
44 45 }
45 46
  47 + @Override
  48 + public Long sumDataSizeByTenantId(TenantId tenantId) {
  49 + return otaPackageRepository.sumDataSizeByTenantId(tenantId.getId());
  50 + }
46 51 }
... ...
... ... @@ -76,13 +76,12 @@ public class JpaOtaPackageInfoDao extends JpaAbstractSearchTextDao<OtaPackageInf
76 76 }
77 77
78 78 @Override
79   - public PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, boolean hasData, PageLink pageLink) {
  79 + public PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink) {
80 80 return DaoUtil.toPageData(otaPackageInfoRepository
81 81 .findAllByTenantIdAndTypeAndDeviceProfileIdAndHasData(
82 82 tenantId.getId(),
83 83 deviceProfileId.getId(),
84 84 otaPackageType,
85   - hasData,
86 85 Objects.toString(pageLink.getTextSearch(), ""),
87 86 DaoUtil.toPageable(pageLink)));
88 87 }
... ...
... ... @@ -26,27 +26,26 @@ import org.thingsboard.server.dao.model.sql.OtaPackageInfoEntity;
26 26 import java.util.UUID;
27 27
28 28 public interface OtaPackageInfoRepository extends CrudRepository<OtaPackageInfoEntity, UUID> {
29   - @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM OtaPackageEntity f WHERE " +
  29 + @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, CASE WHEN (f.data IS NOT NULL OR f.url IS NOT NULL) THEN true ELSE false END) FROM OtaPackageEntity f WHERE " +
30 30 "f.tenantId = :tenantId " +
31 31 "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
32 32 Page<OtaPackageInfoEntity> findAllByTenantId(@Param("tenantId") UUID tenantId,
33 33 @Param("searchText") String searchText,
34 34 Pageable pageable);
35 35
36   - @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM OtaPackageEntity f WHERE " +
  36 + @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, true) FROM OtaPackageEntity f WHERE " +
37 37 "f.tenantId = :tenantId " +
38 38 "AND f.deviceProfileId = :deviceProfileId " +
39 39 "AND f.type = :type " +
40   - "AND ((f.data IS NOT NULL AND :hasData = true) OR (f.data IS NULL AND :hasData = false ))" +
  40 + "AND (f.data IS NOT NULL OR f.url IS NOT NULL) " +
41 41 "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
42 42 Page<OtaPackageInfoEntity> findAllByTenantIdAndTypeAndDeviceProfileIdAndHasData(@Param("tenantId") UUID tenantId,
43 43 @Param("deviceProfileId") UUID deviceProfileId,
44 44 @Param("type") OtaPackageType type,
45   - @Param("hasData") boolean hasData,
46 45 @Param("searchText") String searchText,
47 46 Pageable pageable);
48 47
49   - @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM OtaPackageEntity f WHERE f.id = :id")
  48 + @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, CASE WHEN (f.data IS NOT NULL OR f.url IS NOT NULL) THEN true ELSE false END) FROM OtaPackageEntity f WHERE f.id = :id")
50 49 OtaPackageInfoEntity findOtaPackageInfoById(@Param("id") UUID id);
51 50
52 51 @Query(value = "SELECT exists(SELECT * " +
... ...
... ... @@ -15,10 +15,15 @@
15 15 */
16 16 package org.thingsboard.server.dao.sql.ota;
17 17
  18 +import org.springframework.data.jpa.repository.Query;
18 19 import org.springframework.data.repository.CrudRepository;
  20 +import org.springframework.data.repository.query.Param;
19 21 import org.thingsboard.server.dao.model.sql.OtaPackageEntity;
  22 +import org.thingsboard.server.dao.model.sql.OtaPackageInfoEntity;
20 23
21 24 import java.util.UUID;
22 25
23 26 public interface OtaPackageRepository extends CrudRepository<OtaPackageEntity, UUID> {
  27 + @Query(value = "SELECT COALESCE(SUM(ota.data_size), 0) FROM ota_package ota WHERE ota.tenant_id = :tenantId AND ota.data IS NOT NULL", nativeQuery = true)
  28 + Long sumDataSizeByTenantId(@Param("tenantId") UUID tenantId);
24 29 }
... ...
... ... @@ -92,4 +92,8 @@ public class JpaTbResourceDao extends JpaAbstractSearchTextDao<TbResourceEntity,
92 92 resourceType.name(), objectIds));
93 93 }
94 94
  95 + @Override
  96 + public Long sumDataSizeByTenantId(TenantId tenantId) {
  97 + return resourceRepository.sumDataSizeByTenantId(tenantId.getId());
  98 + }
95 99 }
... ...
... ... @@ -77,4 +77,7 @@ public interface TbResourceRepository extends CrudRepository<TbResourceEntity, U
77 77 @Param("systemAdminId") UUID sysAdminId,
78 78 @Param("resourceType") String resourceType,
79 79 @Param("resourceIds") String[] objectIds);
  80 +
  81 + @Query(value = "SELECT COALESCE(SUM(LENGTH(r.data)), 0) FROM resource r WHERE r.tenant_id = :tenantId", nativeQuery = true)
  82 + Long sumDataSizeByTenantId(@Param("tenantId") UUID tenantId);
80 83 }
... ...
... ... @@ -74,4 +74,10 @@ public class JpaTenantDao extends JpaAbstractSearchTextDao<TenantEntity, Tenant>
74 74 Objects.toString(pageLink.getTextSearch(), ""),
75 75 DaoUtil.toPageable(pageLink, TenantInfoEntity.tenantInfoColumnMap)));
76 76 }
  77 +
  78 + @Override
  79 + public PageData<TenantId> findTenantsIds(PageLink pageLink) {
  80 + return DaoUtil.pageToPageData(tenantRepository.findTenantsIds(DaoUtil.toPageable(pageLink))).mapData(TenantId::new);
  81 + }
  82 +
77 83 }
... ...
... ... @@ -50,4 +50,8 @@ public interface TenantRepository extends PagingAndSortingRepository<TenantEntit
50 50 Page<TenantInfoEntity> findTenantInfoByRegionNextPage(@Param("region") String region,
51 51 @Param("textSearch") String textSearch,
52 52 Pageable pageable);
  53 +
  54 + @Query("SELECT t.id FROM TenantEntity t")
  55 + Page<UUID> findTenantsIds(Pageable pageable);
  56 +
53 57 }
... ...
... ... @@ -46,5 +46,7 @@ public interface TenantDao extends Dao<Tenant> {
46 46 PageData<Tenant> findTenantsByRegion(TenantId tenantId, String region, PageLink pageLink);
47 47
48 48 PageData<TenantInfo> findTenantInfosByRegion(TenantId tenantId, String region, PageLink pageLink);
49   -
  49 +
  50 + PageData<TenantId> findTenantsIds(PageLink pageLink);
  51 +
50 52 }
... ...
... ... @@ -168,6 +168,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
168 168 type varchar(32) NOT NULL,
169 169 title varchar(255) NOT NULL,
170 170 version varchar(255) NOT NULL,
  171 + url varchar(255),
171 172 file_name varchar(255),
172 173 content_type varchar(255),
173 174 checksum_algorithm varchar(32),
... ...
... ... @@ -186,6 +186,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
186 186 type varchar(32) NOT NULL,
187 187 title varchar(255) NOT NULL,
188 188 version varchar(255) NOT NULL,
  189 + url varchar(255),
189 190 file_name varchar(255),
190 191 content_type varchar(255),
191 192 checksum_algorithm varchar(32),
... ...
... ... @@ -59,6 +59,7 @@ import org.thingsboard.server.dao.relation.RelationService;
59 59 import org.thingsboard.server.dao.resource.ResourceService;
60 60 import org.thingsboard.server.dao.rule.RuleChainService;
61 61 import org.thingsboard.server.dao.settings.AdminSettingsService;
  62 +import org.thingsboard.server.dao.tenant.DefaultTbTenantProfileCache;
62 63 import org.thingsboard.server.dao.tenant.TenantProfileService;
63 64 import org.thingsboard.server.dao.tenant.TenantService;
64 65 import org.thingsboard.server.dao.timeseries.TimeseriesService;
... ... @@ -158,10 +159,12 @@ public abstract class AbstractServiceTest {
158 159 @Autowired
159 160 protected ResourceService resourceService;
160 161
161   -
162 162 @Autowired
163 163 protected OtaPackageService otaPackageService;
164 164
  165 + @Autowired
  166 + protected DefaultTbTenantProfileCache tenantProfileCache;
  167 +
165 168 public class IdComparator<D extends HasId> implements Comparator<D> {
166 169 @Override
167 170 public int compare(D o1, D o2) {
... ...
... ... @@ -28,11 +28,13 @@ import org.thingsboard.server.common.data.DeviceProfile;
28 28 import org.thingsboard.server.common.data.OtaPackage;
29 29 import org.thingsboard.server.common.data.OtaPackageInfo;
30 30 import org.thingsboard.server.common.data.Tenant;
31   -import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
  31 +import org.thingsboard.server.common.data.TenantProfile;
32 32 import org.thingsboard.server.common.data.id.DeviceProfileId;
33 33 import org.thingsboard.server.common.data.id.TenantId;
  34 +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
34 35 import org.thingsboard.server.common.data.page.PageData;
35 36 import org.thingsboard.server.common.data.page.PageLink;
  37 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
36 38 import org.thingsboard.server.dao.exception.DataValidationException;
37 39
38 40 import java.nio.ByteBuffer;
... ... @@ -50,7 +52,9 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
50 52 private static final String CONTENT_TYPE = "text/plain";
51 53 private static final ChecksumAlgorithm CHECKSUM_ALGORITHM = ChecksumAlgorithm.SHA256;
52 54 private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
53   - private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1});
  55 + private static final long DATA_SIZE = 1L;
  56 + private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{(int) DATA_SIZE});
  57 + private static final String URL = "http://firmware.test.org";
54 58
55 59 private IdComparator<OtaPackageInfo> idComparator = new IdComparator<>();
56 60
... ... @@ -78,6 +82,41 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
78 82 @After
79 83 public void after() {
80 84 tenantService.deleteTenant(tenantId);
  85 + tenantProfileService.deleteTenantProfiles(tenantId);
  86 + }
  87 +
  88 + @Test
  89 + public void testSaveOtaPackageWithMaxSumDataSizeOutOfLimit() {
  90 + TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId);
  91 + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxOtaPackagesInBytes(DATA_SIZE).build());
  92 + tenantProfileService.saveTenantProfile(tenantId, defaultTenantProfile);
  93 +
  94 + Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId));
  95 +
  96 + createFirmware(tenantId, "1");
  97 + Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId));
  98 +
  99 + thrown.expect(DataValidationException.class);
  100 + thrown.expectMessage(String.format("Failed to create the ota package, files size limit is exhausted %d bytes!", DATA_SIZE));
  101 + createFirmware(tenantId, "2");
  102 + }
  103 +
  104 + @Test
  105 + public void sumDataSizeByTenantId() {
  106 + Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId));
  107 +
  108 + createFirmware(tenantId, "0.1");
  109 + Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId));
  110 +
  111 + int maxSumDataSize = 8;
  112 + List<OtaPackage> packages = new ArrayList<>(maxSumDataSize);
  113 +
  114 + for (int i = 2; i <= maxSumDataSize; i++) {
  115 + packages.add(createFirmware(tenantId, "0." + i));
  116 + Assert.assertEquals(i, otaPackageService.sumDataSizeByTenantId(tenantId));
  117 + }
  118 +
  119 + Assert.assertEquals(maxSumDataSize, otaPackageService.sumDataSizeByTenantId(tenantId));
81 120 }
82 121
83 122 @Test
... ... @@ -93,6 +132,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
93 132 firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
94 133 firmware.setChecksum(CHECKSUM);
95 134 firmware.setData(DATA);
  135 + firmware.setDataSize(DATA_SIZE);
96 136 OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
97 137
98 138 Assert.assertNotNull(savedFirmware);
... ... @@ -114,6 +154,35 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
114 154 }
115 155
116 156 @Test
  157 + public void testSaveFirmwareWithUrl() {
  158 + OtaPackageInfo firmware = new OtaPackageInfo();
  159 + firmware.setTenantId(tenantId);
  160 + firmware.setDeviceProfileId(deviceProfileId);
  161 + firmware.setType(FIRMWARE);
  162 + firmware.setTitle(TITLE);
  163 + firmware.setVersion(VERSION);
  164 + firmware.setUrl(URL);
  165 + firmware.setDataSize(0L);
  166 + OtaPackageInfo savedFirmware = otaPackageService.saveOtaPackageInfo(firmware);
  167 +
  168 + Assert.assertNotNull(savedFirmware);
  169 + Assert.assertNotNull(savedFirmware.getId());
  170 + Assert.assertTrue(savedFirmware.getCreatedTime() > 0);
  171 + Assert.assertEquals(firmware.getTenantId(), savedFirmware.getTenantId());
  172 + Assert.assertEquals(firmware.getTitle(), savedFirmware.getTitle());
  173 + Assert.assertEquals(firmware.getFileName(), savedFirmware.getFileName());
  174 + Assert.assertEquals(firmware.getContentType(), savedFirmware.getContentType());
  175 +
  176 + savedFirmware.setAdditionalInfo(JacksonUtil.newObjectNode());
  177 + otaPackageService.saveOtaPackageInfo(savedFirmware);
  178 +
  179 + OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
  180 + Assert.assertEquals(foundFirmware.getTitle(), savedFirmware.getTitle());
  181 +
  182 + otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId());
  183 + }
  184 +
  185 + @Test
117 186 public void testSaveFirmwareInfoAndUpdateWithData() {
118 187 OtaPackageInfo firmwareInfo = new OtaPackageInfo();
119 188 firmwareInfo.setTenantId(tenantId);
... ... @@ -141,6 +210,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
141 210 firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
142 211 firmware.setChecksum(CHECKSUM);
143 212 firmware.setData(DATA);
  213 + firmware.setDataSize(DATA_SIZE);
144 214
145 215 otaPackageService.saveOtaPackage(firmware);
146 216
... ... @@ -345,50 +415,15 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
345 415
346 416 @Test
347 417 public void testSaveFirmwareWithExistingTitleAndVersion() {
348   - OtaPackage firmware = new OtaPackage();
349   - firmware.setTenantId(tenantId);
350   - firmware.setDeviceProfileId(deviceProfileId);
351   - firmware.setType(FIRMWARE);
352   - firmware.setTitle(TITLE);
353   - firmware.setVersion(VERSION);
354   - firmware.setFileName(FILE_NAME);
355   - firmware.setContentType(CONTENT_TYPE);
356   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
357   - firmware.setChecksum(CHECKSUM);
358   - firmware.setData(DATA);
359   - otaPackageService.saveOtaPackage(firmware);
360   -
361   - OtaPackage newFirmware = new OtaPackage();
362   - newFirmware.setTenantId(tenantId);
363   - newFirmware.setDeviceProfileId(deviceProfileId);
364   - newFirmware.setType(FIRMWARE);
365   - newFirmware.setTitle(TITLE);
366   - newFirmware.setVersion(VERSION);
367   - newFirmware.setFileName(FILE_NAME);
368   - newFirmware.setContentType(CONTENT_TYPE);
369   - newFirmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
370   - newFirmware.setChecksum(CHECKSUM);
371   - newFirmware.setData(DATA);
372   -
  418 + createFirmware(tenantId, VERSION);
373 419 thrown.expect(DataValidationException.class);
374 420 thrown.expectMessage("OtaPackage with such title and version already exists!");
375   - otaPackageService.saveOtaPackage(newFirmware);
  421 + createFirmware(tenantId, VERSION);
376 422 }
377 423
378 424 @Test
379 425 public void testDeleteFirmwareWithReferenceByDevice() {
380   - OtaPackage firmware = new OtaPackage();
381   - firmware.setTenantId(tenantId);
382   - firmware.setDeviceProfileId(deviceProfileId);
383   - firmware.setType(FIRMWARE);
384   - firmware.setTitle(TITLE);
385   - firmware.setVersion(VERSION);
386   - firmware.setFileName(FILE_NAME);
387   - firmware.setContentType(CONTENT_TYPE);
388   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
389   - firmware.setChecksum(CHECKSUM);
390   - firmware.setData(DATA);
391   - OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
  426 + OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
392 427
393 428 Device device = new Device();
394 429 device.setTenantId(tenantId);
... ... @@ -409,18 +444,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
409 444
410 445 @Test
411 446 public void testUpdateDeviceProfileId() {
412   - OtaPackage firmware = new OtaPackage();
413   - firmware.setTenantId(tenantId);
414   - firmware.setDeviceProfileId(deviceProfileId);
415   - firmware.setType(FIRMWARE);
416   - firmware.setTitle(TITLE);
417   - firmware.setVersion(VERSION);
418   - firmware.setFileName(FILE_NAME);
419   - firmware.setContentType(CONTENT_TYPE);
420   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
421   - firmware.setChecksum(CHECKSUM);
422   - firmware.setData(DATA);
423   - OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
  447 + OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
424 448
425 449 try {
426 450 thrown.expect(DataValidationException.class);
... ... @@ -448,6 +472,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
448 472 firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
449 473 firmware.setChecksum(CHECKSUM);
450 474 firmware.setData(DATA);
  475 + firmware.setDataSize(DATA_SIZE);
451 476 OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
452 477
453 478 savedDeviceProfile.setFirmwareId(savedFirmware.getId());
... ... @@ -465,18 +490,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
465 490
466 491 @Test
467 492 public void testFindFirmwareById() {
468   - OtaPackage firmware = new OtaPackage();
469   - firmware.setTenantId(tenantId);
470   - firmware.setDeviceProfileId(deviceProfileId);
471   - firmware.setType(FIRMWARE);
472   - firmware.setTitle(TITLE);
473   - firmware.setVersion(VERSION);
474   - firmware.setFileName(FILE_NAME);
475   - firmware.setContentType(CONTENT_TYPE);
476   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
477   - firmware.setChecksum(CHECKSUM);
478   - firmware.setData(DATA);
479   - OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
  493 + OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
480 494
481 495 OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
482 496 Assert.assertNotNull(foundFirmware);
... ... @@ -502,18 +516,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
502 516
503 517 @Test
504 518 public void testDeleteFirmware() {
505   - OtaPackage firmware = new OtaPackage();
506   - firmware.setTenantId(tenantId);
507   - firmware.setDeviceProfileId(deviceProfileId);
508   - firmware.setType(FIRMWARE);
509   - firmware.setTitle(TITLE);
510   - firmware.setVersion(VERSION);
511   - firmware.setFileName(FILE_NAME);
512   - firmware.setContentType(CONTENT_TYPE);
513   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
514   - firmware.setChecksum(CHECKSUM);
515   - firmware.setData(DATA);
516   - OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
  519 + OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
517 520
518 521 OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
519 522 Assert.assertNotNull(foundFirmware);
... ... @@ -526,23 +529,25 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
526 529 public void testFindTenantFirmwaresByTenantId() {
527 530 List<OtaPackageInfo> firmwares = new ArrayList<>();
528 531 for (int i = 0; i < 165; i++) {
529   - OtaPackage firmware = new OtaPackage();
530   - firmware.setTenantId(tenantId);
531   - firmware.setDeviceProfileId(deviceProfileId);
532   - firmware.setType(FIRMWARE);
533   - firmware.setTitle(TITLE);
534   - firmware.setVersion(VERSION + i);
535   - firmware.setFileName(FILE_NAME);
536   - firmware.setContentType(CONTENT_TYPE);
537   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
538   - firmware.setChecksum(CHECKSUM);
539   - firmware.setData(DATA);
540   -
541   - OtaPackageInfo info = new OtaPackageInfo(otaPackageService.saveOtaPackage(firmware));
  532 + OtaPackageInfo info = new OtaPackageInfo(createFirmware(tenantId, VERSION + i));
542 533 info.setHasData(true);
543 534 firmwares.add(info);
544 535 }
545 536
  537 + OtaPackageInfo firmwareWithUrl = new OtaPackageInfo();
  538 + firmwareWithUrl.setTenantId(tenantId);
  539 + firmwareWithUrl.setDeviceProfileId(deviceProfileId);
  540 + firmwareWithUrl.setType(FIRMWARE);
  541 + firmwareWithUrl.setTitle(TITLE);
  542 + firmwareWithUrl.setVersion(VERSION);
  543 + firmwareWithUrl.setUrl(URL);
  544 + firmwareWithUrl.setDataSize(0L);
  545 +
  546 + OtaPackageInfo savedFwWithUrl = otaPackageService.saveOtaPackageInfo(firmwareWithUrl);
  547 + savedFwWithUrl.setHasData(true);
  548 +
  549 + firmwares.add(savedFwWithUrl);
  550 +
546 551 List<OtaPackageInfo> loadedFirmwares = new ArrayList<>();
547 552 PageLink pageLink = new PageLink(16);
548 553 PageData<OtaPackageInfo> pageData;
... ... @@ -571,58 +576,38 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
571 576 public void testFindTenantFirmwaresByTenantIdAndHasData() {
572 577 List<OtaPackageInfo> firmwares = new ArrayList<>();
573 578 for (int i = 0; i < 165; i++) {
574   - OtaPackageInfo firmwareInfo = new OtaPackageInfo();
575   - firmwareInfo.setTenantId(tenantId);
576   - firmwareInfo.setDeviceProfileId(deviceProfileId);
577   - firmwareInfo.setType(FIRMWARE);
578   - firmwareInfo.setTitle(TITLE);
579   - firmwareInfo.setVersion(VERSION + i);
580   - firmwareInfo.setFileName(FILE_NAME);
581   - firmwareInfo.setContentType(CONTENT_TYPE);
582   - firmwareInfo.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
583   - firmwareInfo.setChecksum(CHECKSUM);
584   - firmwareInfo.setDataSize((long) DATA.array().length);
585   - firmwares.add(otaPackageService.saveOtaPackageInfo(firmwareInfo));
  579 + firmwares.add(new OtaPackageInfo(otaPackageService.saveOtaPackage(createFirmware(tenantId, VERSION + i))));
586 580 }
587 581
  582 + OtaPackageInfo firmwareWithUrl = new OtaPackageInfo();
  583 + firmwareWithUrl.setTenantId(tenantId);
  584 + firmwareWithUrl.setDeviceProfileId(deviceProfileId);
  585 + firmwareWithUrl.setType(FIRMWARE);
  586 + firmwareWithUrl.setTitle(TITLE);
  587 + firmwareWithUrl.setVersion(VERSION);
  588 + firmwareWithUrl.setUrl(URL);
  589 + firmwareWithUrl.setDataSize(0L);
  590 +
  591 + OtaPackageInfo savedFwWithUrl = otaPackageService.saveOtaPackageInfo(firmwareWithUrl);
  592 + savedFwWithUrl.setHasData(true);
  593 +
  594 + firmwares.add(savedFwWithUrl);
  595 +
588 596 List<OtaPackageInfo> loadedFirmwares = new ArrayList<>();
589 597 PageLink pageLink = new PageLink(16);
590 598 PageData<OtaPackageInfo> pageData;
591 599 do {
592   - pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, false, pageLink);
  600 + pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, pageLink);
593 601 loadedFirmwares.addAll(pageData.getData());
594 602 if (pageData.hasNext()) {
595 603 pageLink = pageLink.nextPageLink();
596 604 }
597 605 } while (pageData.hasNext());
598 606
599   - Collections.sort(firmwares, idComparator);
600   - Collections.sort(loadedFirmwares, idComparator);
601   -
602   - Assert.assertEquals(firmwares, loadedFirmwares);
603   -
604   - firmwares.forEach(f -> {
605   - OtaPackage firmware = new OtaPackage(f.getId());
606   - firmware.setCreatedTime(f.getCreatedTime());
607   - firmware.setTenantId(f.getTenantId());
608   - firmware.setDeviceProfileId(deviceProfileId);
609   - firmware.setType(FIRMWARE);
610   - firmware.setTitle(f.getTitle());
611   - firmware.setVersion(f.getVersion());
612   - firmware.setFileName(FILE_NAME);
613   - firmware.setContentType(CONTENT_TYPE);
614   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
615   - firmware.setChecksum(CHECKSUM);
616   - firmware.setData(DATA);
617   - firmware.setDataSize((long) DATA.array().length);
618   - otaPackageService.saveOtaPackage(firmware);
619   - f.setHasData(true);
620   - });
621   -
622 607 loadedFirmwares = new ArrayList<>();
623 608 pageLink = new PageLink(16);
624 609 do {
625   - pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, true, pageLink);
  610 + pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, pageLink);
626 611 loadedFirmwares.addAll(pageData.getData());
627 612 if (pageData.hasNext()) {
628 613 pageLink = pageLink.nextPageLink();
... ... @@ -642,4 +627,20 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
642 627 Assert.assertTrue(pageData.getData().isEmpty());
643 628 }
644 629
  630 + private OtaPackage createFirmware(TenantId tenantId, String version) {
  631 + OtaPackage firmware = new OtaPackage();
  632 + firmware.setTenantId(tenantId);
  633 + firmware.setDeviceProfileId(deviceProfileId);
  634 + firmware.setType(FIRMWARE);
  635 + firmware.setTitle(TITLE);
  636 + firmware.setVersion(version);
  637 + firmware.setFileName(FILE_NAME);
  638 + firmware.setContentType(CONTENT_TYPE);
  639 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  640 + firmware.setChecksum(CHECKSUM);
  641 + firmware.setData(DATA);
  642 + firmware.setDataSize(DATA_SIZE);
  643 + return otaPackageService.saveOtaPackage(firmware);
  644 + }
  645 +
645 646 }
... ...
... ... @@ -24,7 +24,7 @@ const topicProperties = config.get('kafka.topic_properties');
24 24 const kafkaClientId = config.get('kafka.client_id');
25 25 const acks = Number(config.get('kafka.acks'));
26 26 const requestTimeout = Number(config.get('kafka.requestTimeout'));
27   -const compressionType = (config.get('kafka.requestTimeout') === "gzip") ? CompressionTypes.GZIP : CompressionTypes.None;
  27 +const compressionType = (config.get('kafka.compression') === "gzip") ? CompressionTypes.GZIP : CompressionTypes.None;
28 28
29 29 let kafkaClient;
30 30 let kafkaAdmin;
... ...
... ... @@ -19,18 +19,25 @@ import com.fasterxml.jackson.databind.JsonNode;
19 19 import com.fasterxml.jackson.databind.ObjectMapper;
20 20 import com.fasterxml.jackson.databind.node.ObjectNode;
21 21 import org.springframework.core.ParameterizedTypeReference;
  22 +import org.springframework.core.io.ByteArrayResource;
  23 +import org.springframework.core.io.Resource;
22 24 import org.springframework.http.HttpEntity;
  25 +import org.springframework.http.HttpHeaders;
23 26 import org.springframework.http.HttpMethod;
24 27 import org.springframework.http.HttpRequest;
25 28 import org.springframework.http.HttpStatus;
  29 +import org.springframework.http.MediaType;
26 30 import org.springframework.http.ResponseEntity;
27 31 import org.springframework.http.client.ClientHttpRequestExecution;
28 32 import org.springframework.http.client.ClientHttpRequestInterceptor;
29 33 import org.springframework.http.client.ClientHttpResponse;
30 34 import org.springframework.http.client.support.HttpRequestWrapper;
  35 +import org.springframework.util.LinkedMultiValueMap;
  36 +import org.springframework.util.MultiValueMap;
31 37 import org.springframework.util.StringUtils;
32 38 import org.springframework.web.client.HttpClientErrorException;
33 39 import org.springframework.web.client.RestTemplate;
  40 +import org.springframework.web.multipart.MultipartFile;
34 41 import org.thingsboard.common.util.ThingsBoardExecutors;
35 42 import org.thingsboard.rest.client.utils.RestJsonConverter;
36 43 import org.thingsboard.server.common.data.AdminSettings;
... ... @@ -48,6 +55,10 @@ import org.thingsboard.server.common.data.EntitySubtype;
48 55 import org.thingsboard.server.common.data.EntityView;
49 56 import org.thingsboard.server.common.data.EntityViewInfo;
50 57 import org.thingsboard.server.common.data.Event;
  58 +import org.thingsboard.server.common.data.OtaPackage;
  59 +import org.thingsboard.server.common.data.OtaPackageInfo;
  60 +import org.thingsboard.server.common.data.TbResource;
  61 +import org.thingsboard.server.common.data.TbResourceInfo;
51 62 import org.thingsboard.server.common.data.Tenant;
52 63 import org.thingsboard.server.common.data.TenantInfo;
53 64 import org.thingsboard.server.common.data.TenantProfile;
... ... @@ -78,8 +89,10 @@ import org.thingsboard.server.common.data.id.EdgeId;
78 89 import org.thingsboard.server.common.data.id.EntityId;
79 90 import org.thingsboard.server.common.data.id.EntityViewId;
80 91 import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
  92 +import org.thingsboard.server.common.data.id.OtaPackageId;
81 93 import org.thingsboard.server.common.data.id.RuleChainId;
82 94 import org.thingsboard.server.common.data.id.RuleNodeId;
  95 +import org.thingsboard.server.common.data.id.TbResourceId;
83 96 import org.thingsboard.server.common.data.id.TenantId;
84 97 import org.thingsboard.server.common.data.id.TenantProfileId;
85 98 import org.thingsboard.server.common.data.id.UserId;
... ... @@ -91,6 +104,8 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
91 104 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
92 105 import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
93 106 import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
  107 +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
  108 +import org.thingsboard.server.common.data.ota.OtaPackageType;
94 109 import org.thingsboard.server.common.data.page.PageData;
95 110 import org.thingsboard.server.common.data.page.PageLink;
96 111 import org.thingsboard.server.common.data.page.SortOrder;
... ... @@ -127,9 +142,9 @@ import java.util.HashMap;
127 142 import java.util.List;
128 143 import java.util.Map;
129 144 import java.util.Optional;
  145 +import java.util.UUID;
130 146 import java.util.concurrent.ConcurrentHashMap;
131 147 import java.util.concurrent.ExecutorService;
132   -import java.util.concurrent.Executors;
133 148 import java.util.concurrent.Future;
134 149 import java.util.stream.Collectors;
135 150
... ... @@ -147,7 +162,6 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
147 162 private final ObjectMapper objectMapper = new ObjectMapper();
148 163 private ExecutorService service = ThingsBoardExecutors.newWorkStealingPool(10, getClass());
149 164
150   -
151 165 protected static final String ACTIVATE_TOKEN_REGEX = "/api/noauth/activate?activateToken=";
152 166
153 167 public RestClient(String baseURL) {
... ... @@ -1238,6 +1252,21 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
1238 1252 HttpEntity.EMPTY, Device.class, tenantId, deviceId).getBody();
1239 1253 }
1240 1254
  1255 + public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(OtaPackageType otaPackageType, DeviceProfileId deviceProfileId) {
  1256 + Map<String, String> params = new HashMap<>();
  1257 + params.put("otaPackageType", otaPackageType.name());
  1258 + params.put("deviceProfileId", deviceProfileId.getId().toString());
  1259 +
  1260 + return restTemplate.exchange(
  1261 + baseURL + "/api/devices/count/{otaPackageType}?deviceProfileId={deviceProfileId}",
  1262 + HttpMethod.GET,
  1263 + HttpEntity.EMPTY,
  1264 + new ParameterizedTypeReference<Long>() {
  1265 + },
  1266 + params
  1267 + ).getBody();
  1268 + }
  1269 +
1241 1270 @Deprecated
1242 1271 public Device createDevice(String name, String type) {
1243 1272 Device device = new Device();
... ... @@ -2830,6 +2859,176 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
2830 2859 restTemplate.postForEntity(baseURL + "/api/edge/sync/{edgeId}", null, EdgeId.class, params);
2831 2860 }
2832 2861
  2862 + public ResponseEntity<Resource> downloadResource(TbResourceId resourceId) {
  2863 + Map<String, String> params = new HashMap<>();
  2864 + params.put("resourceId", resourceId.getId().toString());
  2865 +
  2866 + return restTemplate.exchange(
  2867 + baseURL + "/api/resource/{resourceId}/download",
  2868 + HttpMethod.GET,
  2869 + HttpEntity.EMPTY,
  2870 + new ParameterizedTypeReference<>() {},
  2871 + params
  2872 + );
  2873 + }
  2874 +
  2875 + public TbResourceInfo getResourceInfoById(TbResourceId resourceId) {
  2876 + Map<String, String> params = new HashMap<>();
  2877 + params.put("resourceId", resourceId.getId().toString());
  2878 +
  2879 + return restTemplate.exchange(
  2880 + baseURL + "/api/resource/info/{resourceId}",
  2881 + HttpMethod.GET,
  2882 + HttpEntity.EMPTY,
  2883 + new ParameterizedTypeReference<TbResourceInfo>() {},
  2884 + params
  2885 + ).getBody();
  2886 + }
  2887 +
  2888 + public TbResource getResourceId(TbResourceId resourceId) {
  2889 + Map<String, String> params = new HashMap<>();
  2890 + params.put("resourceId", resourceId.getId().toString());
  2891 +
  2892 + return restTemplate.exchange(
  2893 + baseURL + "/api/resource/{resourceId}",
  2894 + HttpMethod.GET,
  2895 + HttpEntity.EMPTY,
  2896 + new ParameterizedTypeReference<TbResource>() {},
  2897 + params
  2898 + ).getBody();
  2899 + }
  2900 +
  2901 + public TbResource saveResource(TbResource resource) {
  2902 + return restTemplate.postForEntity(
  2903 + baseURL + "/api/resource",
  2904 + resource,
  2905 + TbResource.class
  2906 + ).getBody();
  2907 + }
  2908 +
  2909 + public PageData<TbResourceInfo> getResources(PageLink pageLink) {
  2910 + Map<String, String> params = new HashMap<>();
  2911 + addPageLinkToParam(params, pageLink);
  2912 + return restTemplate.exchange(
  2913 + baseURL + "/api/resource?" + getUrlParams(pageLink),
  2914 + HttpMethod.GET,
  2915 + HttpEntity.EMPTY,
  2916 + new ParameterizedTypeReference<PageData<TbResourceInfo>>() {},
  2917 + params
  2918 + ).getBody();
  2919 + }
  2920 +
  2921 + public void deleteResource(TbResourceId resourceId) {
  2922 + restTemplate.delete("/api/resource/{resourceId}", resourceId.getId().toString());
  2923 + }
  2924 +
  2925 + public ResponseEntity<Resource> downloadOtaPackage(OtaPackageId otaPackageId) {
  2926 + Map<String, String> params = new HashMap<>();
  2927 + params.put("otaPackageId", otaPackageId.getId().toString());
  2928 +
  2929 + return restTemplate.exchange(
  2930 + baseURL + "/api/otaPackage/{otaPackageId}/download",
  2931 + HttpMethod.GET,
  2932 + HttpEntity.EMPTY,
  2933 + new ParameterizedTypeReference<>() {},
  2934 + params
  2935 + );
  2936 + }
  2937 +
  2938 + public OtaPackageInfo getOtaPackageInfoById(OtaPackageId otaPackageId) {
  2939 + Map<String, String> params = new HashMap<>();
  2940 + params.put("otaPackageId", otaPackageId.getId().toString());
  2941 +
  2942 + return restTemplate.exchange(
  2943 + baseURL + "/api/otaPackage/info/{otaPackageId}",
  2944 + HttpMethod.GET,
  2945 + HttpEntity.EMPTY,
  2946 + new ParameterizedTypeReference<OtaPackageInfo>() {},
  2947 + params
  2948 + ).getBody();
  2949 + }
  2950 +
  2951 + public OtaPackage getOtaPackageById(OtaPackageId otaPackageId) {
  2952 + Map<String, String> params = new HashMap<>();
  2953 + params.put("otaPackageId", otaPackageId.getId().toString());
  2954 +
  2955 + return restTemplate.exchange(
  2956 + baseURL + "/api/otaPackage/{otaPackageId}",
  2957 + HttpMethod.GET,
  2958 + HttpEntity.EMPTY,
  2959 + new ParameterizedTypeReference<OtaPackage>() {},
  2960 + params
  2961 + ).getBody();
  2962 + }
  2963 +
  2964 + public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo) {
  2965 + return restTemplate.postForEntity(baseURL + "/api/otaPackage", otaPackageInfo, OtaPackageInfo.class).getBody();
  2966 + }
  2967 +
  2968 + public OtaPackage saveOtaPackageData(OtaPackageId otaPackageId, String checkSum, ChecksumAlgorithm checksumAlgorithm, MultipartFile file) throws Exception {
  2969 + HttpHeaders header = new HttpHeaders();
  2970 + header.setContentType(MediaType.MULTIPART_FORM_DATA);
  2971 +
  2972 + MultiValueMap<String, String> fileMap = new LinkedMultiValueMap<>();
  2973 + fileMap.add(HttpHeaders.CONTENT_DISPOSITION, "form-data; name=file; filename=" + file.getName());
  2974 + HttpEntity<ByteArrayResource> fileEntity = new HttpEntity<>(new ByteArrayResource(file.getBytes()), fileMap);
  2975 +
  2976 + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
  2977 + body.add("file", fileEntity);
  2978 + HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, header);
  2979 +
  2980 + Map<String, String> params = new HashMap<>();
  2981 + params.put("otaPackageId", otaPackageId.getId().toString());
  2982 + params.put("checksumAlgorithm", checksumAlgorithm.name());
  2983 + String url = "/api/otaPackage/{otaPackageId}?checksumAlgorithm={checksumAlgorithm}";
  2984 +
  2985 + if(checkSum != null) {
  2986 + url += "&checkSum={checkSum}";
  2987 + }
  2988 +
  2989 + return restTemplate.postForEntity(
  2990 + baseURL + url, requestEntity, OtaPackage.class, params
  2991 + ).getBody();
  2992 + }
  2993 +
  2994 + public PageData<OtaPackageInfo> getOtaPackages(PageLink pageLink) {
  2995 + Map<String, String> params = new HashMap<>();
  2996 + addPageLinkToParam(params, pageLink);
  2997 +
  2998 + return restTemplate.exchange(
  2999 + baseURL + "/api/otaPackages?" + getUrlParams(pageLink),
  3000 + HttpMethod.GET,
  3001 + HttpEntity.EMPTY,
  3002 + new ParameterizedTypeReference<PageData<OtaPackageInfo>>() {
  3003 + },
  3004 + params
  3005 + ).getBody();
  3006 + }
  3007 +
  3008 + public PageData<OtaPackageInfo> getOtaPackages(DeviceProfileId deviceProfileId,
  3009 + OtaPackageType otaPackageType,
  3010 + boolean hasData,
  3011 + PageLink pageLink) {
  3012 + Map<String, String> params = new HashMap<>();
  3013 + params.put("hasData", String.valueOf(hasData));
  3014 + params.put("deviceProfileId", deviceProfileId.getId().toString());
  3015 + params.put("type", otaPackageType.name());
  3016 + addPageLinkToParam(params, pageLink);
  3017 +
  3018 + return restTemplate.exchange(
  3019 + baseURL + "/api/otaPackages/{deviceProfileId}/{type}/{hasData}?" + getUrlParams(pageLink),
  3020 + HttpMethod.GET,
  3021 + HttpEntity.EMPTY,
  3022 + new ParameterizedTypeReference<PageData<OtaPackageInfo>>() {
  3023 + },
  3024 + params
  3025 + ).getBody();
  3026 + }
  3027 +
  3028 + public void deleteOtaPackage(OtaPackageId otaPackageId) {
  3029 + restTemplate.delete(baseURL + "/api/otaPackage/{otaPackageId}", otaPackageId.getId().toString());
  3030 + }
  3031 +
2833 3032 @Deprecated
2834 3033 public Optional<JsonNode> getAttributes(String accessToken, String clientKeys, String sharedKeys) {
2835 3034 Map<String, String> params = new HashMap<>();
... ...
... ... @@ -47,7 +47,9 @@ import org.thingsboard.server.dao.edge.EdgeService;
47 47 import org.thingsboard.server.dao.entityview.EntityViewService;
48 48 import org.thingsboard.server.dao.nosql.CassandraStatementTask;
49 49 import org.thingsboard.server.dao.nosql.TbResultSetFuture;
  50 +import org.thingsboard.server.dao.ota.OtaPackageService;
50 51 import org.thingsboard.server.dao.relation.RelationService;
  52 +import org.thingsboard.server.dao.resource.ResourceService;
51 53 import org.thingsboard.server.dao.rule.RuleChainService;
52 54 import org.thingsboard.server.dao.tenant.TenantService;
53 55 import org.thingsboard.server.dao.timeseries.TimeseriesService;
... ... @@ -202,6 +204,10 @@ public interface TbContext {
202 204
203 205 EntityViewService getEntityViewService();
204 206
  207 + ResourceService getResourceService();
  208 +
  209 + OtaPackageService getOtaPackageService();
  210 +
205 211 RuleEngineDeviceProfileCache getDeviceProfileCache();
206 212
207 213 EdgeService getEdgeService();
... ...
... ... @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
29 29 import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
30 30 import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
31 31 import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
  32 +import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException;
32 33 import org.thingsboard.server.common.data.id.DeviceId;
33 34 import org.thingsboard.server.common.data.id.DeviceProfileId;
34 35 import org.thingsboard.server.common.data.id.EntityId;
... ... @@ -150,6 +151,8 @@ class DeviceState {
150 151 stateChanged = processAlarmClearNotification(ctx, msg);
151 152 } else if (msg.getType().equals(DataConstants.ALARM_ACK)) {
152 153 processAlarmAckNotification(ctx, msg);
  154 + } else if (msg.getType().equals(DataConstants.ALARM_DELETE)) {
  155 + processAlarmDeleteNotification(ctx, msg);
153 156 } else {
154 157 if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED) || msg.getType().equals(DataConstants.ENTITY_UNASSIGNED)) {
155 158 dynamicPredicateValueCtx.resetCustomer();
... ... @@ -193,6 +196,12 @@ class DeviceState {
193 196 ctx.tellSuccess(msg);
194 197 }
195 198
  199 + private void processAlarmDeleteNotification(TbContext ctx, TbMsg msg) {
  200 + Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class);
  201 + alarmStates.values().removeIf(alarmState -> alarmState.getCurrentAlarm().getId().equals(alarm.getId()));
  202 + ctx.tellSuccess(msg);
  203 + }
  204 +
196 205 private boolean processAttributesUpdateNotification(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
197 206 String scope = msg.getMetaData().getValue(DataConstants.SCOPE);
198 207 if (StringUtils.isEmpty(scope)) {
... ... @@ -253,7 +262,12 @@ class DeviceState {
253 262 for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {
254 263 AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(),
255 264 a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx));
256   - stateChanged |= alarmState.process(ctx, msg, latestValues, update);
  265 + try {
  266 + stateChanged |= alarmState.process(ctx, msg, latestValues, update);
  267 + } catch (ApiUsageLimitsExceededException e) {
  268 + alarmStates.remove(alarm.getId());
  269 + throw e;
  270 + }
257 271 }
258 272 }
259 273 }
... ...
... ... @@ -152,6 +152,7 @@ export interface IStateController {
152 152 getStateIndex(): number;
153 153 getStateIdAtIndex(index: number): string;
154 154 getEntityId(entityParamName: string): EntityId;
  155 + getCurrentStateName(): string;
155 156 }
156 157
157 158 export interface SubscriptionInfo {
... ...
... ... @@ -45,6 +45,7 @@ import { ActionNotificationShow } from '@core/notification/notification.actions'
45 45 import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
46 46 import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
47 47 import { OAuth2ClientInfo } from '@shared/models/oauth2.models';
  48 +import { isMobileApp } from '@core/utils';
48 49
49 50 @Injectable({
50 51 providedIn: 'root'
... ... @@ -194,11 +195,13 @@ export class AuthService {
194 195 }
195 196
196 197 public gotoDefaultPlace(isAuthenticated: boolean) {
197   - const authState = getCurrentAuthState(this.store);
198   - const url = this.defaultUrl(isAuthenticated, authState);
199   - this.zone.run(() => {
200   - this.router.navigateByUrl(url);
201   - });
  198 + if (!isMobileApp()) {
  199 + const authState = getCurrentAuthState(this.store);
  200 + const url = this.defaultUrl(isAuthenticated, authState);
  201 + this.zone.run(() => {
  202 + this.router.navigateByUrl(url);
  203 + });
  204 + }
202 205 }
203 206
204 207 public loadOAuth2Clients(): Observable<Array<OAuth2ClientInfo>> {
... ... @@ -516,12 +519,15 @@ export class AuthService {
516 519 return this.refreshTokenSubject !== null;
517 520 }
518 521
519   - public setUserFromJwtToken(jwtToken, refreshToken, notify) {
  522 + public setUserFromJwtToken(jwtToken, refreshToken, notify): Observable<boolean> {
  523 + const authenticatedSubject = new ReplaySubject<boolean>();
520 524 if (!jwtToken) {
521 525 AuthService.clearTokenData();
522 526 if (notify) {
523 527 this.notifyUnauthenticated();
524 528 }
  529 + authenticatedSubject.next(false);
  530 + authenticatedSubject.complete();
525 531 } else {
526 532 this.updateAndValidateTokens(jwtToken, refreshToken, true);
527 533 if (notify) {
... ... @@ -530,16 +536,30 @@ export class AuthService {
530 536 (authPayload) => {
531 537 this.notifyUserLoaded(true);
532 538 this.notifyAuthenticated(authPayload);
  539 + authenticatedSubject.next(true);
  540 + authenticatedSubject.complete();
533 541 },
534 542 () => {
535 543 this.notifyUserLoaded(true);
536 544 this.notifyUnauthenticated();
  545 + authenticatedSubject.next(false);
  546 + authenticatedSubject.complete();
537 547 }
538 548 );
539 549 } else {
540   - this.loadUser(false).subscribe();
  550 + this.loadUser(false).subscribe(
  551 + () => {
  552 + authenticatedSubject.next(true);
  553 + authenticatedSubject.complete();
  554 + },
  555 + () => {
  556 + authenticatedSubject.next(false);
  557 + authenticatedSubject.complete();
  558 + }
  559 + );
541 560 }
542 561 }
  562 + return authenticatedSubject;
543 563 }
544 564
545 565 private updateAndValidateTokens(jwtToken, refreshToken, notify: boolean) {
... ...
... ... @@ -29,6 +29,7 @@ import { DialogService } from '@core/services/dialog.service';
29 29 import { TranslateService } from '@ngx-translate/core';
30 30 import { UtilsService } from '@core/services/utils.service';
31 31 import { isObject } from '@core/utils';
  32 +import { MobileService } from '@core/services/mobile.service';
32 33
33 34 @Injectable({
34 35 providedIn: 'root'
... ... @@ -41,6 +42,7 @@ export class AuthGuard implements CanActivate, CanActivateChild {
41 42 private dialogService: DialogService,
42 43 private utils: UtilsService,
43 44 private translate: TranslateService,
  45 + private mobileService: MobileService,
44 46 private zone: NgZone) {}
45 47
46 48 getAuthState(): Observable<AuthState> {
... ... @@ -108,6 +110,10 @@ export class AuthGuard implements CanActivate, CanActivateChild {
108 110 return of(false);
109 111 }
110 112 }
  113 + if (this.mobileService.isMobileApp() && !path.startsWith('dashboard.')) {
  114 + this.mobileService.handleMobileNavigation(path, params);
  115 + return of(false);
  116 + }
111 117 const defaultUrl = this.authService.defaultUrl(true, authState, path, params);
112 118 if (defaultUrl) {
113 119 // this.authService.gotoDefaultPlace(true);
... ...
... ... @@ -40,7 +40,7 @@ export class OtaPackageService {
40 40
41 41 public getOtaPackagesInfoByDeviceProfileId(pageLink: PageLink, deviceProfileId: string, type: OtaUpdateType,
42 42 hasData = true, config?: RequestConfig): Observable<PageData<OtaPackageInfo>> {
43   - const url = `/api/otaPackages/${deviceProfileId}/${type}/${hasData}${pageLink.toQuery()}`;
  43 + const url = `/api/otaPackages/${deviceProfileId}/${type}${pageLink.toQuery()}`;
44 44 return this.http.get<PageData<OtaPackageInfo>>(url, defaultHttpOptionsFromConfig(config));
45 45 }
46 46
... ...
... ... @@ -20,9 +20,14 @@ import { isDefined } from '@core/utils';
20 20 import { MobileActionResult, WidgetMobileActionResult, WidgetMobileActionType } from '@shared/models/widget.models';
21 21 import { from, of } from 'rxjs';
22 22 import { Observable } from 'rxjs/internal/Observable';
23   -import { catchError } from 'rxjs/operators';
  23 +import { catchError, tap } from 'rxjs/operators';
  24 +import { OpenDashboardMessage, ReloadUserMessage, WindowMessage } from '@shared/models/window-message.model';
  25 +import { Params, Router } from '@angular/router';
  26 +import { AuthService } from '@core/auth/auth.service';
24 27
25 28 const dashboardStateNameHandler = 'tbMobileDashboardStateNameHandler';
  29 +const dashboardLoadedHandler = 'tbMobileDashboardLoadedHandler';
  30 +const navigationHandler = 'tbMobileNavigationHandler';
26 31 const mobileHandler = 'tbMobileHandler';
27 32
28 33 // @dynamic
... ... @@ -34,10 +39,20 @@ export class MobileService {
34 39 private readonly mobileApp;
35 40 private readonly mobileChannel;
36 41
37   - constructor(@Inject(WINDOW) private window: Window) {
  42 + private readonly onWindowMessageListener = this.onWindowMessage.bind(this);
  43 +
  44 + private reloadUserObservable: Observable<boolean>;
  45 + private lastDashboardId: string;
  46 +
  47 + constructor(@Inject(WINDOW) private window: Window,
  48 + private router: Router,
  49 + private authService: AuthService) {
38 50 const w = (this.window as any);
39 51 this.mobileChannel = w.flutter_inappwebview;
40 52 this.mobileApp = isDefined(this.mobileChannel);
  53 + if (this.mobileApp) {
  54 + window.addEventListener('message', this.onWindowMessageListener);
  55 + }
41 56 }
42 57
43 58 public isMobileApp(): boolean {
... ... @@ -50,6 +65,12 @@ export class MobileService {
50 65 }
51 66 }
52 67
  68 + public onDashboardLoaded() {
  69 + if (this.mobileApp) {
  70 + this.mobileChannel.callHandler(dashboardLoadedHandler);
  71 + }
  72 + }
  73 +
53 74 public handleWidgetMobileAction<T extends MobileActionResult>(type: WidgetMobileActionType, ...args: any[]):
54 75 Observable<WidgetMobileActionResult<T>> {
55 76 if (this.mobileApp) {
... ... @@ -67,4 +88,82 @@ export class MobileService {
67 88 }
68 89 }
69 90
  91 + public handleMobileNavigation(path?: string, params?: Params) {
  92 + if (this.mobileApp) {
  93 + this.mobileChannel.callHandler(navigationHandler, path, params);
  94 + }
  95 + }
  96 +
  97 + private onWindowMessage(event: MessageEvent) {
  98 + if (event.data) {
  99 + let message: WindowMessage;
  100 + try {
  101 + message = JSON.parse(event.data);
  102 + } catch (e) {}
  103 + if (message && message.type) {
  104 + switch (message.type) {
  105 + case 'openDashboardMessage':
  106 + const openDashboardMessage: OpenDashboardMessage = message.data;
  107 + this.openDashboard(openDashboardMessage);
  108 + break;
  109 + case 'reloadUserMessage':
  110 + const reloadUserMessage: ReloadUserMessage = message.data;
  111 + this.reloadUser(reloadUserMessage);
  112 + break;
  113 + }
  114 + }
  115 + }
  116 + }
  117 +
  118 + private openDashboard(openDashboardMessage: OpenDashboardMessage) {
  119 + if (openDashboardMessage && openDashboardMessage.dashboardId) {
  120 + if (this.reloadUserObservable) {
  121 + this.reloadUserObservable.subscribe(
  122 + (authenticated) => {
  123 + if (authenticated) {
  124 + this.doDashboardNavigation(openDashboardMessage);
  125 + }
  126 + }
  127 + );
  128 + } else {
  129 + this.doDashboardNavigation(openDashboardMessage);
  130 + }
  131 + }
  132 + }
  133 +
  134 + private doDashboardNavigation(openDashboardMessage: OpenDashboardMessage) {
  135 + let url = `/dashboard/${openDashboardMessage.dashboardId}`;
  136 + const params = [];
  137 + if (openDashboardMessage.state) {
  138 + params.push(`state=${openDashboardMessage.state}`);
  139 + }
  140 + if (openDashboardMessage.embedded) {
  141 + params.push(`embedded=true`);
  142 + }
  143 + if (openDashboardMessage.hideToolbar) {
  144 + params.push(`hideToolbar=true`);
  145 + }
  146 + if (this.lastDashboardId === openDashboardMessage.dashboardId) {
  147 + params.push(`reload=${new Date().getTime()}`);
  148 + }
  149 + if (params.length) {
  150 + url += `?${params.join('&')}`;
  151 + }
  152 + this.lastDashboardId = openDashboardMessage.dashboardId;
  153 + this.router.navigateByUrl(url, {replaceUrl: true});
  154 + }
  155 +
  156 + private reloadUser(reloadUserMessage: ReloadUserMessage) {
  157 + if (reloadUserMessage && reloadUserMessage.accessToken && reloadUserMessage.refreshToken) {
  158 + this.reloadUserObservable = this.authService.setUserFromJwtToken(reloadUserMessage.accessToken,
  159 + reloadUserMessage.refreshToken, true).pipe(
  160 + tap(
  161 + () => {
  162 + this.reloadUserObservable = null;
  163 + }
  164 + )
  165 + );
  166 + }
  167 + }
  168 +
70 169 }
... ...
... ... @@ -441,3 +441,7 @@ export function generateSecret(length?: number): string {
441 441 export function validateEntityId(entityId: EntityId | null): boolean {
442 442 return isDefinedAndNotNull(entityId?.id) && entityId.id !== NULL_UUID && isDefinedAndNotNull(entityId?.entityType);
443 443 }
  444 +
  445 +export function isMobileApp(): boolean {
  446 + return isDefined((window as any).flutter_inappwebview);
  447 +}
... ...
... ... @@ -87,7 +87,7 @@
87 87 (click)="updateDashboardImage($event)">
88 88 <mat-icon>wallpaper</mat-icon>
89 89 </button>
90   - <button [fxShow]="currentDashboardId && (isEdit || displayExport())" mat-icon-button
  90 + <button [fxShow]="currentDashboardId && !isMobileApp && (isEdit || displayExport())" mat-icon-button
91 91 matTooltip="{{'dashboard.export' | translate}}"
92 92 matTooltipPosition="below"
93 93 (click)="exportDashboard($event)">
... ...
... ... @@ -323,6 +323,17 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
323 323 this.runChangeDetection();
324 324 }
325 325 ));
  326 + this.rxSubscriptions.push(this.route.queryParamMap.subscribe(
  327 + (paramMap) => {
  328 + if (paramMap.has('reload')) {
  329 + this.dashboardCtx.aliasController.updateAliases();
  330 + setTimeout(() => {
  331 + this.mobileService.handleDashboardStateName(this.dashboardCtx.stateController.getCurrentStateName());
  332 + this.mobileService.onDashboardLoaded();
  333 + });
  334 + }
  335 + }
  336 + ));
326 337 this.rxSubscriptions.push(this.breakpointObserver
327 338 .observe(MediaBreakpoints['gt-sm'])
328 339 .subscribe((state: BreakpointState) => {
... ... @@ -770,6 +781,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
770 781 this.isRightLayoutOpened = openRightLayout ? true : false;
771 782 this.updateLayouts(layoutsData);
772 783 }
  784 + setTimeout(() => {
  785 + this.mobileService.onDashboardLoaded();
  786 + });
773 787 }
774 788
775 789 private updateLayouts(layoutsData?: DashboardLayoutsInfo) {
... ...
... ... @@ -185,6 +185,10 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
185 185 return this.utils.customTranslation(state.name, id);
186 186 }
187 187
  188 + public getCurrentStateName(): string {
  189 + return this.getStateName(this.stateObject[0].id, this.statesValue[this.stateObject[0].id]);
  190 + }
  191 +
188 192 public displayStateSelection(): boolean {
189 193 return this.states && Object.keys(this.states).length > 1;
190 194 }
... ...
... ... @@ -235,6 +235,10 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
235 235 return result;
236 236 }
237 237
  238 + public getCurrentStateName(): string {
  239 + return this.getStateName(this.stateObject.length - 1);
  240 + }
  241 +
238 242 public selectedStateIndexChanged() {
239 243 this.navigatePrevState(this.selectedStateIndex);
240 244 }
... ...
... ... @@ -198,4 +198,6 @@ export abstract class StateControllerComponent implements IStateControllerCompon
198 198
199 199 public abstract updateState(id?: string, params?: StateParams, openRightLayout?: boolean): void;
200 200
  201 + public abstract getCurrentStateName(): string;
  202 +
201 203 }
... ...
... ... @@ -89,6 +89,30 @@
89 89 </mat-error>
90 90 </mat-form-field>
91 91 <mat-form-field class="mat-block">
  92 + <mat-label translate>tenant-profile.maximum-resources-sum-data-size</mat-label>
  93 + <input matInput required min="0" step="1"
  94 + formControlName="maxResourcesInBytes"
  95 + type="number">
  96 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxResourcesInBytes').hasError('required')">
  97 + {{ 'tenant-profile.maximum-resources-sum-data-size-required' | translate}}
  98 + </mat-error>
  99 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxResourcesInBytes').hasError('min')">
  100 + {{ 'tenant-profile.maximum-resources-sum-data-size-range' | translate}}
  101 + </mat-error>
  102 + </mat-form-field>
  103 + <mat-form-field class="mat-block">
  104 + <mat-label translate>tenant-profile.maximum-ota-packages-sum-data-size</mat-label>
  105 + <input matInput required min="0" step="1"
  106 + formControlName="maxOtaPackagesInBytes"
  107 + type="number">
  108 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxOtaPackagesInBytes').hasError('required')">
  109 + {{ 'tenant-profile.maximum-ota-packages-sum-data-size-required' | translate}}
  110 + </mat-error>
  111 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxOtaPackagesInBytes').hasError('min')">
  112 + {{ 'tenant-profile.maximum-ota-packages-sum-data-size-range' | translate}}
  113 + </mat-error>
  114 + </mat-form-field>
  115 + <mat-form-field class="mat-block">
92 116 <mat-label translate>tenant-profile.max-transport-messages</mat-label>
93 117 <input matInput required min="0" step="1"
94 118 formControlName="maxTransportMessages"
... ... @@ -161,6 +185,18 @@
161 185 </mat-error>
162 186 </mat-form-field>
163 187 <mat-form-field class="mat-block">
  188 + <mat-label translate>tenant-profile.alarms-ttl-days</mat-label>
  189 + <input matInput required min="0" step="1"
  190 + formControlName="alarmsTtlDays"
  191 + type="number">
  192 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('alarmsTtlDays').hasError('required')">
  193 + {{ 'tenant-profile.alarms-ttl-days-required' | translate}}
  194 + </mat-error>
  195 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('alarmsTtlDays').hasError('min')">
  196 + {{ 'tenant-profile.alarms-ttl-days-days-range' | translate}}
  197 + </mat-error>
  198 + </mat-form-field>
  199 + <mat-form-field class="mat-block">
164 200 <mat-label translate>tenant-profile.max-rule-node-executions-per-message</mat-label>
165 201 <input matInput required min="0" step="1"
166 202 formControlName="maxRuleNodeExecutionsPerMessage"
... ...
... ... @@ -59,6 +59,8 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
59 59 maxUsers: [null, [Validators.required, Validators.min(0)]],
60 60 maxDashboards: [null, [Validators.required, Validators.min(0)]],
61 61 maxRuleChains: [null, [Validators.required, Validators.min(0)]],
  62 + maxResourcesInBytes: [null, [Validators.required, Validators.min(0)]],
  63 + maxOtaPackagesInBytes: [null, [Validators.required, Validators.min(0)]],
62 64 transportTenantMsgRateLimit: [null, []],
63 65 transportTenantTelemetryMsgRateLimit: [null, []],
64 66 transportTenantTelemetryDataPointsRateLimit: [null, []],
... ... @@ -74,7 +76,8 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
74 76 maxEmails: [null, [Validators.required, Validators.min(0)]],
75 77 maxSms: [null, [Validators.required, Validators.min(0)]],
76 78 maxCreatedAlarms: [null, [Validators.required, Validators.min(0)]],
77   - defaultStorageTtlDays: [null, [Validators.required, Validators.min(0)]]
  79 + defaultStorageTtlDays: [null, [Validators.required, Validators.min(0)]],
  80 + alarmsTtlDays: [null, [Validators.required, Validators.min(0)]]
78 81 });
79 82 this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => {
80 83 this.updateModel();
... ...
... ... @@ -18,6 +18,7 @@ import { ContactBased } from '@shared/models/contact-based.model';
18 18 import { TenantId } from './id/tenant-id';
19 19 import { TenantProfileId } from '@shared/models/id/tenant-profile-id';
20 20 import { BaseData } from '@shared/models/base-data';
  21 +import {Validators} from "@angular/forms";
21 22
22 23 export enum TenantProfileType {
23 24 DEFAULT = 'DEFAULT'
... ... @@ -30,6 +31,8 @@ export interface DefaultTenantProfileConfiguration {
30 31 maxUsers: number;
31 32 maxDashboards: number;
32 33 maxRuleChains: number;
  34 + maxResourcesInBytes: number;
  35 + maxOtaPackagesInBytes: number;
33 36
34 37 transportTenantMsgRateLimit?: string;
35 38 transportTenantTelemetryMsgRateLimit?: string;
... ... @@ -68,6 +71,8 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan
68 71 maxUsers: 0,
69 72 maxDashboards: 0,
70 73 maxRuleChains: 0,
  74 + maxResourcesInBytes: 0,
  75 + maxOtaPackagesInBytes: 0,
71 76 maxTransportMessages: 0,
72 77 maxTransportDataPoints: 0,
73 78 maxREExecutions: 0,
... ...
... ... @@ -14,9 +14,21 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -export type WindowMessageType = 'widgetException' | 'widgetEditModeInited' | 'widgetEditUpdated';
  17 +export type WindowMessageType = 'widgetException' | 'widgetEditModeInited' | 'widgetEditUpdated' | 'openDashboardMessage' | 'reloadUserMessage';
18 18
19 19 export interface WindowMessage {
20 20 type: WindowMessageType;
21 21 data?: any;
22 22 }
  23 +
  24 +export interface OpenDashboardMessage {
  25 + dashboardId: string;
  26 + state?: string;
  27 + hideToolbar?: boolean;
  28 + embedded?: boolean;
  29 +}
  30 +
  31 +export interface ReloadUserMessage {
  32 + accessToken: string;
  33 + refreshToken: string;
  34 +}
... ...
... ... @@ -2504,6 +2504,12 @@
2504 2504 "maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)",
2505 2505 "maximum-rule-chains-required": "Maximum number of rule chains is required.",
2506 2506 "maximum-rule-chains-range": "Maximum number of rule chains can't be negative",
  2507 + "maximum-resources-sum-data-size": "Maximum sum of resource files size in bytes (0 - unlimited)",
  2508 + "maximum-resources-sum-data-size-required": "Maximum sum of resource files size is required.",
  2509 + "maximum-resources-sum-data-size-range": "Maximum sum of resource files size can`t be negative",
  2510 + "maximum-ota-packages-sum-data-size": "Maximum sum of ota package files size in bytes (0 - unlimited)",
  2511 + "maximum-ota-package-sum-data-size-required": "Maximum sum of ota package files size is required.",
  2512 + "maximum-ota-package-sum-data-size-range": "Maximum sum of ota package files size can`t be negative",
2507 2513 "transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.",
2508 2514 "transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.",
2509 2515 "transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.",
... ... @@ -2528,6 +2534,9 @@
2528 2534 "default-storage-ttl-days": "Default storage TTL days (0 - unlimited)",
2529 2535 "default-storage-ttl-days-required": "Default storage TTL days is required.",
2530 2536 "default-storage-ttl-days-range": "Default storage TTL days can't be negative",
  2537 + "alarms-ttl-days": "Alarms TTL days (0 - unlimited)",
  2538 + "alarms-ttl-days-required": "Alarms TTL days required",
  2539 + "alarms-ttl-days-days-range": "Alarms TTL days can't be negative",
2531 2540 "max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)",
2532 2541 "max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.",
2533 2542 "max-rule-node-executions-per-message-range": "Maximum number of rule node executions per message can't be negative",
... ...
... ... @@ -174,6 +174,48 @@ $tb-dark-theme: get-tb-dark-theme(
174 174 }
175 175 }
176 176
  177 +@mixin _mat-toolbar-inverse-color($palette) {
  178 + background: mat-color($palette, default-contrast);
  179 + color: $dark-primary-text;
  180 +}
  181 +
  182 +@mixin mat-fab-toolbar-inverse-theme($theme) {
  183 + $primary: map-get($theme, primary);
  184 + $accent: map-get($theme, accent);
  185 + $warn: map-get($theme, warn);
  186 + $background: map-get($theme, foreground);
  187 + $foreground: map-get($theme, background);
  188 +
  189 + mat-fab-toolbar {
  190 + .mat-fab-toolbar-background {
  191 + background: mat-color($background, app-bar);
  192 + color: mat-color($foreground, text);
  193 + }
  194 + &.mat-primary {
  195 + .mat-fab-toolbar-background {
  196 + @include _mat-toolbar-inverse-color($primary);
  197 + }
  198 + }
  199 + mat-toolbar {
  200 + &.mat-primary {
  201 + @include _mat-toolbar-inverse-color($primary);
  202 + button.mat-icon-button {
  203 + mat-icon {
  204 + color: mat-color($primary);
  205 + }
  206 + }
  207 + }
  208 + }
  209 + .mat-fab {
  210 + &.mat-primary {
  211 + background: mat-color($primary, default-contrast);
  212 + color: mat-color($primary);
  213 + }
  214 + }
  215 + }
  216 +
  217 +}
  218 +
177 219 @mixin tb-components-theme($theme) {
178 220 $primary: map-get($theme, primary);
179 221
... ... @@ -184,6 +226,10 @@ $tb-dark-theme: get-tb-dark-theme(
184 226 }
185 227
186 228 @include mat-fab-toolbar-theme($tb-theme);
  229 +
  230 + div.tb-dashboard-page.mobile-app {
  231 + @include mat-fab-toolbar-inverse-theme($tb-theme);
  232 + }
187 233 }
188 234
189 235 .tb-default {
... ... @@ -195,3 +241,4 @@ $tb-dark-theme: get-tb-dark-theme(
195 241 .tb-dark {
196 242 @include angular-material-theme($tb-dark-theme);
197 243 }
  244 +
... ...