Browse Source

Merge branch 'master' of https://github.com/thingsboard/thingsboard into feature/notify-gateway-about-device-changed

pull/5740/head
zbeacon 5 years ago
parent
commit
5d6ac71e24
  1. 4
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 2
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  3. 7
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  4. 38
      application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
  5. 11
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  6. 34
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
  8. 22
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java
  10. 40
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  11. 8
      application/src/main/resources/thingsboard.yml
  12. 44
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  13. 2
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  14. 6
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  15. 10
      common/cluster-api/src/main/proto/queue.proto
  16. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
  17. 1
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java
  18. 36
      common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvLatestRemovingResult.java
  19. 8
      common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java
  20. 18
      common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java
  21. 14
      common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java
  22. 17
      common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java
  23. 7
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java
  24. 19
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java
  25. 20
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java
  26. 4
      common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java
  27. 21
      common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java
  28. 13
      common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java
  29. 8
      common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java
  30. 12
      common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java
  31. 12
      common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java
  32. 10
      common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java
  33. 4
      common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java
  34. 12
      common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java
  35. 12
      common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java
  36. 11
      common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java
  37. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  38. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DownlinkRequestCallback.java
  39. 92
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfig.java
  40. 29
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigService.java
  41. 235
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java
  42. 38
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbDummyLwM2MModelConfigStore.java
  43. 28
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MModelConfigStore.java
  44. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java
  45. 79
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbRedisLwM2MModelConfigStore.java
  46. 104
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java
  47. 37
      common/util/src/main/java/org/thingsboard/common/util/CollectionsUtil.java
  48. 8
      common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java
  49. 5
      dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
  50. 10
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  51. 52
      dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java
  52. 15
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
  53. 46
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java
  54. 3
      dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesLatestDao.java
  55. 7
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java
  56. 2
      pom.xml
  57. 3
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
  58. 1
      ui-ngx/src/app/core/api/widget-api.models.ts
  59. 4
      ui-ngx/src/app/core/utils.ts
  60. 37
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  61. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.models.ts
  62. 13
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html
  63. 29
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.scss
  64. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts
  65. 25
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-state.component.html
  66. 93
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-state.component.ts
  67. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts
  68. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/states/entity-state-controller.component.ts
  69. 13
      ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts
  70. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.models.ts
  71. 14
      ui-ngx/src/app/modules/home/components/dashboard-page/states/states-component.directive.ts
  72. 3
      ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html
  73. 9
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  74. 3
      ui-ngx/src/app/modules/home/components/tokens.ts
  75. 43
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
  76. 35
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
  77. 5
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
  78. 7
      ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts
  79. 9
      ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.scss
  80. 54
      ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts
  81. 110
      ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts
  82. 74
      ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts
  83. 3
      ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html
  84. 12
      ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts
  85. 21
      ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html
  86. 14
      ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss
  87. 114
      ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts
  88. 10
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  89. 4
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  90. 2
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss
  91. 2
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  92. 2
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
  93. 53
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts
  94. 32
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  95. 5
      ui-ngx/src/app/modules/home/models/datasource/attribute-datasource.ts
  96. 27
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  97. 41
      ui-ngx/src/app/shared/components/css.component.html
  98. 66
      ui-ngx/src/app/shared/components/css.component.scss
  99. 207
      ui-ngx/src/app/shared/components/css.component.ts
  100. 17
      ui-ngx/src/app/shared/components/json-form/react/json-form-base-component.tsx

4
application/src/main/data/json/system/widget_bundles/cards.json

@ -180,11 +180,11 @@
"resources": [],
"templateHtml": "<tb-markdown-widget \n [ctx]=\"ctx\">\n</tb-markdown-widget>",
"templateCss": "#container tb-markdown-widget {\n height: 100%;\n display: block;\n}\n\n#container tb-markdown-widget .tb-markdown-view {\n height: 100%;\n overflow: auto;\n}\n",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.markdownWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n datasourcesOptional: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.markdownWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n datasourcesOptional: true,\n hasDataPageLink: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Markdown/HTML card\",\n \"properties\": {\n \"markdownTextPattern\": {\n \"title\": \"Markdown/HTML pattern (markdown or HTML with variables, for ex. '${entityName} or ${keyName} - some text.')\",\n \"type\": \"string\",\n \"default\": \"# Markdown/HTML card \\n - **Current entity**: **${entityName}**. \\n - **Current value**: **${Random}**.\"\n },\n \"markdownCss\": {\n \"title\": \"Markdown/HTML CSS\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useMarkdownTextFunction\": {\n \"title\": \"Use markdown/HTML value function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"markdownTextFunction\": {\n \"title\": \"Markdown/HTML value function: f(data)\",\n \"type\": \"string\",\n \"default\": \"return '# Some title\\\\n - Entity name: ' + data[0]['entityName'];\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useMarkdownTextFunction\",\n {\n \"key\": \"markdownTextPattern\",\n \"type\": \"markdown\",\n \"condition\": \"model.useMarkdownTextFunction !== true\"\n },\n {\n \"key\": \"markdownTextFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/markdown/markdown_text_fn\",\n \"condition\": \"model.useMarkdownTextFunction === true\"\n },\n {\n \"key\": \"markdownCss\",\n \"type\": \"css\"\n }\n ]\n}\n",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"markdownTextPattern\":\"### Markdown/HTML card\\n - **Current entity**: ${entityName}.\\n - **Current value**: ${Random}.\",\"markdownTextFunction\":\"return '# Some title\\\\n - Entity name: ' + data[0]['entityName'];\",\"useMarkdownTextFunction\":false},\"title\":\"Markdown/HTML Card\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}"
}
}
]
}
}

2
application/src/main/data/json/system/widget_bundles/input_widgets.json

File diff suppressed because one or more lines are too long

7
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -573,10 +573,9 @@ public class TelemetryController extends BaseController {
for (String key : keys) {
deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted));
}
ListenableFuture<List<Void>> future = tsService.remove(user.getTenantId(), entityId, deleteTsKvQueries);
Futures.addCallback(future, new FutureCallback<>() {
tsSubService.deleteTimeseriesAndNotify(tenantId, entityId, keys, deleteTsKvQueries, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable List<Void> tmp) {
public void onSuccess(@Nullable Void tmp) {
logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null);
result.setResult(new ResponseEntity<>(HttpStatus.OK));
}
@ -586,7 +585,7 @@ public class TelemetryController extends BaseController {
logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, t);
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
}, executor);
});
});
}

38
application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java

@ -17,11 +17,11 @@ package org.thingsboard.server.service.ota;
import com.google.common.util.concurrent.FutureCallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
@ -51,8 +51,6 @@ import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.cluster.TbClusterService;
import javax.annotation.Nullable;
import java.util.ArrayList;
@ -122,17 +120,17 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
newFirmwareId = newDeviceProfile.getFirmwareId();
}
if (oldDevice != null) {
OtaPackageId oldFirmwareId = oldDevice.getFirmwareId();
if (oldFirmwareId == null) {
DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
oldFirmwareId = oldDeviceProfile.getFirmwareId();
}
if (newFirmwareId != null) {
OtaPackageId oldFirmwareId = oldDevice.getFirmwareId();
if (oldFirmwareId == null) {
DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
oldFirmwareId = oldDeviceProfile.getFirmwareId();
}
if (!newFirmwareId.equals(oldFirmwareId)) {
// Device was updated and new firmware is different from previous firmware.
send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis(), FIRMWARE);
}
} else {
} else if (oldFirmwareId != null){
// Device was updated and new firmware is not set.
remove(device, FIRMWARE);
}
@ -149,17 +147,17 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
newSoftwareId = newDeviceProfile.getSoftwareId();
}
if (oldDevice != null) {
OtaPackageId oldSoftwareId = oldDevice.getSoftwareId();
if (oldSoftwareId == null) {
DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
oldSoftwareId = oldDeviceProfile.getSoftwareId();
}
if (newSoftwareId != null) {
OtaPackageId oldSoftwareId = oldDevice.getSoftwareId();
if (oldSoftwareId == null) {
DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
oldSoftwareId = oldDeviceProfile.getSoftwareId();
}
if (!newSoftwareId.equals(oldSoftwareId)) {
// Device was updated and new firmware is different from previous firmware.
send(device.getTenantId(), device.getId(), newSoftwareId, System.currentTimeMillis(), SOFTWARE);
}
} else {
} else if (oldSoftwareId != null){
// Device was updated and new firmware is not set.
remove(device, SOFTWARE);
}
@ -302,14 +300,16 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
private void updateAttributes(Device device, OtaPackageInfo otaPackage, long ts, TenantId tenantId, DeviceId deviceId, OtaPackageType otaPackageType) {
List<AttributeKvEntry> attributes = new ArrayList<>();
List<String> attrToRemove = new ArrayList<>();
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion())));
if (StringUtils.isNotEmpty(otaPackage.getTag())) {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TAG), otaPackage.getTag())));
} else {
attrToRemove.add(getAttributeKey(otaPackageType, TAG));
}
if (otaPackage.hasUrl()) {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl())));
List<String> attrToRemove = new ArrayList<>();
if (otaPackage.getDataSize() == null) {
attrToRemove.add(getAttributeKey(otaPackageType, SIZE));
@ -328,15 +328,15 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
} else {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum())));
}
remove(device, otaPackageType, attrToRemove);
} else {
attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum())));
remove(device, otaPackageType, Collections.singletonList(getAttributeKey(otaPackageType, URL)));
attrToRemove.add(getAttributeKey(otaPackageType, URL));
}
remove(device, otaPackageType, attrToRemove);
telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {

11
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -26,14 +26,15 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
@ -47,6 +48,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
@ -64,7 +66,6 @@ import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.IdMsgPair;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.state.DeviceStateService;
@ -467,6 +468,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getScope(), proto.getKeysList(), callback);
} else if (msg.hasTsDelete()) {
TbTimeSeriesDeleteProto proto = msg.getTsDelete();
subscriptionManagerService.onTimeSeriesDelete(
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getKeysList(), callback);
} else if (msg.hasAlarmUpdate()) {
TbAlarmUpdateProto proto = msg.getAlarmUpdate();
subscriptionManagerService.onAlarmUpdate(

34
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -19,8 +19,10 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
@ -40,20 +42,20 @@ import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos.*;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmSubscriptionUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateTsValue;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateValueListProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
@ -337,6 +339,30 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
callback.onSuccess();
}
@Override
public void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List<String> keys, TbCallback callback) {
onLocalTelemetrySubUpdate(entityId,
s -> {
if (TbSubscriptionType.TIMESERIES.equals(s.getType())) {
return (TbTimeseriesSubscription) s;
} else {
return null;
}
}, s -> true, s -> {
List<TsKvEntry> subscriptionUpdate = null;
for (String key : keys) {
if (s.isAllKeys() || s.getKeyStates().containsKey(key)) {
if (subscriptionUpdate == null) {
subscriptionUpdate = new ArrayList<>();
}
subscriptionUpdate.add(new BasicTsKvEntry(0, new StringDataEntry(key, null)));
}
}
return subscriptionUpdate;
}, false);
callback.onSuccess();
}
private <T extends TbSubscription> void onLocalTelemetrySubUpdate(EntityId entityId,
Function<TbSubscription, T> castFunction,
Predicate<T> filterFunction,

2
application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java

@ -40,6 +40,8 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty);
void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List<String> keys, TbCallback callback);
void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);

22
application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.subscription;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -30,25 +31,25 @@ import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeSubscriptionProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionKetStateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateTsValue;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesSubscriptionProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
@ -207,6 +208,19 @@ public class TbSubscriptionUtils {
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
}
public static ToCoreMsg toTimeseriesDeleteProto(TenantId tenantId, EntityId entityId, List<String> keys) {
TbTimeSeriesDeleteProto.Builder builder = TbTimeSeriesDeleteProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name());
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits());
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.addAllKeys(keys);
SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder();
msgBuilder.setTsDelete(builder);
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
}
public static ToCoreMsg toAttributesUpdateProto(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
TbAttributeUpdateProto.Builder builder = TbAttributeUpdateProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name());

2
application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java

@ -90,7 +90,7 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList
Futures.addCallback(saveFuture, new FutureCallback<T>() {
@Override
public void onSuccess(@Nullable T result) {
callback.accept(null);
callback.accept(result);
}
@Override

40
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -33,10 +33,12 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
@ -59,6 +61,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -279,7 +282,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override
public void deleteLatestInternal(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback) {
ListenableFuture<List<Void>> deleteFuture = tsService.removeLatest(tenantId, entityId, keys);
ListenableFuture<List<TsKvLatestRemovingResult>> deleteFuture = tsService.removeLatest(tenantId, entityId, keys);
addVoidCallback(deleteFuture, callback);
}
@ -299,6 +302,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}, tsCallBackExecutor);
}
@Override
public void deleteTimeseriesAndNotify(TenantId tenantId, EntityId entityId, List<String> keys, List<DeleteTsKvQuery> deleteTsKvQueries, FutureCallback<Void> callback) {
ListenableFuture<List<TsKvLatestRemovingResult>> deleteFuture = tsService.remove(tenantId, entityId, deleteTsKvQueries);
addVoidCallback(deleteFuture, callback);
addWsCallback(deleteFuture, list -> onTimeSeriesDelete(tenantId, entityId, keys, list));
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value)
@ -365,6 +375,34 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}
}
private void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List<String> keys, List<TsKvLatestRemovingResult> ts) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
List<TsKvEntry> updated = new ArrayList<>();
List<String> deleted = new ArrayList<>();
ts.stream().filter(Objects::nonNull).forEach(res -> {
if (res.isRemoved()) {
if (res.getData() != null) {
updated.add(res.getData());
} else {
deleted.add(res.getKey());
}
}
});
subscriptionManagerService.get().onTimeSeriesUpdate(tenantId, entityId, updated, TbCallback.EMPTY);
subscriptionManagerService.get().onTimeSeriesDelete(tenantId, entityId, deleted, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toTimeseriesDeleteProto(tenantId, entityId, keys);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
}
private <S> void addVoidCallback(ListenableFuture<S> saveFuture, final FutureCallback<Void> callback) {
Futures.addCallback(saveFuture, new FutureCallback<S>() {
@Override

8
application/src/main/resources/thingsboard.yml

@ -265,17 +265,17 @@ sql:
batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}"
batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:4}"
batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
ts:
batch_size: "${SQL_TS_BATCH_SIZE:10000}"
batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_TS_BATCH_THREADS:4}"
batch_threads: "${SQL_TS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
ts_latest:
batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}"
batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:4}"
batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}"
# Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
batch_sort: "${SQL_BATCH_SORT:false}"
@ -290,7 +290,7 @@ sql:
timescale:
# Specify Interval size for new data chunks storage.
chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}"
batch_threads: "${SQL_TIMESCALE_BATCH_THREADS:4}"
batch_threads: "${SQL_TIMESCALE_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
ttl:
ts:
enabled: "${SQL_TTL_TS_ENABLED:true}"

44
application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java

@ -48,15 +48,17 @@ import org.thingsboard.server.gen.edge.v1.UserUpdateMsg;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.nullValue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
public static final String EDGE_HOST = "localhost";
public static final int EDGE_PORT = 7070;
private IdComparator<Edge> idComparator = new IdComparator<>();
private Tenant savedTenant;
@ -68,7 +70,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
tenant.setTitle("My tenant for Edge");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
tenantId = savedTenant.getId();
Assert.assertNotNull(savedTenant);
@ -667,45 +669,45 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
@Test
public void testSyncEdge() throws Exception {
Edge edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class);
Edge edge = doPost("/api/edge", constructEdge("Test Sync Edge", "test"), Edge.class);
Device device = new Device();
device.setName("Edge Device 1");
device.setName("Test Sync Edge Device 1");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class);
Asset asset = new Asset();
asset.setName("Edge Asset 1");
asset.setName("Test Sync Edge Asset 1");
asset.setType("test");
Asset savedAsset = doPost("/api/asset", asset, Asset.class);
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
EdgeImitator edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret());
EdgeImitator edgeImitator = new EdgeImitator(EDGE_HOST, EDGE_PORT, edge.getRoutingKey(), edge.getSecret());
edgeImitator.ignoreType(UserCredentialsUpdateMsg.class);
edgeImitator.expectMessageAmount(11);
edgeImitator.connect();
Assert.assertTrue(edgeImitator.waitForMessages());
assertThat(edgeImitator.waitForMessages()).as("await for messages on first connect").isTrue();
Assert.assertEquals(2, edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class).size()); // one msg during sync process, another from edge creation
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class).size()); // one msg during sync process for 'default' device profile
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class).size()); // one msg once device assigned to edge
Assert.assertEquals(2, edgeImitator.findAllMessagesByType(AssetUpdateMsg.class).size()); // two msgs - one during sync process, and one more once asset assigned to edge
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); // one msg during sync process for tenant admin user
Assert.assertEquals(4, edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class).size());
assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("one msg during sync process, another from edge creation").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("one msg during sync process for 'default' device profile").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("one msg once device assigned to edge").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("two msgs - one during sync process, and one more once asset assigned to edge").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("one msg during sync process for tenant admin user").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update").hasSize(4);
edgeImitator.expectMessageAmount(8);
doPost("/api/edge/sync/" + edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages());
assertThat(edgeImitator.waitForMessages()).as("await for messages after edge sync rest api call").isTrue();
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class).size());
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class).size());
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(AssetUpdateMsg.class).size());
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size());
Assert.assertEquals(4, edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class).size());
assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("rule chain msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("device profile msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("asset update msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("user update msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update msg").hasSize(4);
edgeImitator.allowIgnoredTypes();
try {
@ -720,4 +722,4 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
.andExpect(status().isOk());
}
}
}

2
application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java

@ -767,7 +767,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1);
doDelete("/api/alarm/" + savedAlarm.getId().getId().toString())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages(1));
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;

6
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -65,6 +65,8 @@ import java.util.stream.Collectors;
@Slf4j
public class EdgeImitator {
public static final int TIMEOUT_IN_SECONDS = 30;
private String routingKey;
private String routingSecret;
@ -293,7 +295,7 @@ public class EdgeImitator {
}
public boolean waitForMessages() throws InterruptedException {
return waitForMessages(5);
return waitForMessages(TIMEOUT_IN_SECONDS);
}
public boolean waitForMessages(int timeoutInSeconds) throws InterruptedException {
@ -308,7 +310,7 @@ public class EdgeImitator {
}
public boolean waitForResponses() throws InterruptedException {
return responsesLatch.await(5, TimeUnit.SECONDS);
return responsesLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
}
public void expectResponsesAmount(int messageAmount) {

10
common/cluster-api/src/main/proto/queue.proto

@ -561,6 +561,15 @@ message TbAttributeDeleteProto {
repeated string keys = 7;
}
message TbTimeSeriesDeleteProto {
string entityType = 1;
int64 entityIdMSB = 2;
int64 entityIdLSB = 3;
int64 tenantIdMSB = 4;
int64 tenantIdLSB = 5;
repeated string keys = 6;
}
message TbTimeSeriesUpdateProto {
string entityType = 1;
int64 entityIdMSB = 2;
@ -614,6 +623,7 @@ message SubscriptionMgrMsgProto {
TbAlarmSubscriptionProto alarmSub = 7;
TbAlarmUpdateProto alarmUpdate = 8;
TbAlarmDeleteProto alarmDelete = 9;
TbTimeSeriesDeleteProto tsDelete = 10;
}
message LocalSubscriptionServiceMsgProto {

5
common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import java.util.Collection;
@ -45,9 +46,9 @@ public interface TimeseriesService {
ListenableFuture<List<Void>> saveLatest(TenantId tenantId, EntityId entityId, List<TsKvEntry> tsKvEntry);
ListenableFuture<List<Void>> remove(TenantId tenantId, EntityId entityId, List<DeleteTsKvQuery> queries);
ListenableFuture<List<TsKvLatestRemovingResult>> remove(TenantId tenantId, EntityId entityId, List<DeleteTsKvQuery> queries);
ListenableFuture<List<Void>> removeLatest(TenantId tenantId, EntityId entityId, Collection<String> keys);
ListenableFuture<List<TsKvLatestRemovingResult>> removeLatest(TenantId tenantId, EntityId entityId, Collection<String> keys);
ListenableFuture<Collection<String>> removeAllLatest(TenantId tenantId, EntityId entityId);

1
common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java

@ -31,5 +31,4 @@ public class BaseDeleteTsKvQuery extends BaseTsKvQuery implements DeleteTsKvQuer
this(key, startTs, endTs, false);
}
}

36
common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvLatestRemovingResult.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.kv;
import lombok.Data;
@Data
public class TsKvLatestRemovingResult {
private String key;
private TsKvEntry data;
private boolean removed;
public TsKvLatestRemovingResult(TsKvEntry data) {
this.key = data.getKey();
this.data = data;
this.removed = true;
}
public TsKvLatestRemovingResult(String key, boolean removed) {
this.key = key;
this.removed = removed;
}
}

8
common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java

@ -102,7 +102,7 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(ruleEngineAdmin, sqsSettings, ruleEngineSettings.getTopic());
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, ruleEngineSettings.getTopic());
}
@Override
@ -112,7 +112,7 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic());
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, coreSettings.getTopic());
}
@Override
@ -182,13 +182,13 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getUsageStatsTopic(),
return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> createToOtaPackageStateServiceMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getOtaPackageTopic(),
return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, coreSettings.getOtaPackageTopic(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}

18
common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java

@ -43,6 +43,7 @@ import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin;
import org.thingsboard.server.queue.sqs.TbAwsSqsConsumerTemplate;
import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate;
@ -63,6 +64,7 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory {
private final PartitionService partitionService;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
@ -77,7 +79,8 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory {
PartitionService partitionService,
TbServiceInfoProvider serviceInfoProvider,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbAwsSqsQueueAttributes sqsQueueAttributes) {
TbAwsSqsQueueAttributes sqsQueueAttributes,
TbQueueTransportNotificationSettings transportNotificationSettings) {
this.sqsSettings = sqsSettings;
this.coreSettings = coreSettings;
this.transportApiSettings = transportApiSettings;
@ -85,6 +88,7 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory {
this.partitionService = partitionService;
this.serviceInfoProvider = serviceInfoProvider;
this.jsInvokeSettings = jsInvokeSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.coreAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getCoreAttributes());
this.ruleEngineAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getRuleEngineAttributes());
@ -95,7 +99,7 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic());
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, transportNotificationSettings.getNotificationsTopic());
}
@Override
@ -105,7 +109,7 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(ruleEngineAdmin, sqsSettings, ruleEngineSettings.getTopic());
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, ruleEngineSettings.getTopic());
}
@Override
@ -115,7 +119,7 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic());
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, coreSettings.getTopic());
}
@Override
@ -139,7 +143,7 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> createTransportApiResponseProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic());
return new TbAwsSqsProducerTemplate<>(transportApiAdmin, sqsSettings, transportApiSettings.getResponsesTopic());
}
@Override
@ -172,13 +176,13 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getUsageStatsTopic(),
return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> createToOtaPackageStateServiceMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getOtaPackageTopic(),
return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, coreSettings.getOtaPackageTopic(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}

14
common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java

@ -37,6 +37,7 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin;
import org.thingsboard.server.queue.sqs.TbAwsSqsConsumerTemplate;
@ -57,6 +58,7 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbAwsSqsSettings sqsSettings;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
@ -68,13 +70,15 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory
TbServiceInfoProvider serviceInfoProvider,
TbAwsSqsSettings sqsSettings,
TbAwsSqsQueueAttributes sqsQueueAttributes,
TbQueueRemoteJsInvokeSettings jsInvokeSettings) {
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbQueueTransportNotificationSettings transportNotificationSettings) {
this.partitionService = partitionService;
this.coreSettings = coreSettings;
this.serviceInfoProvider = serviceInfoProvider;
this.ruleEngineSettings = ruleEngineSettings;
this.sqsSettings = sqsSettings;
this.jsInvokeSettings = jsInvokeSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.coreAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getCoreAttributes());
this.ruleEngineAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getRuleEngineAttributes());
@ -84,17 +88,17 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic());
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, transportNotificationSettings.getNotificationsTopic());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic());
return new TbAwsSqsProducerTemplate<>(ruleEngineAdmin, sqsSettings, ruleEngineSettings.getTopic());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(ruleEngineAdmin, sqsSettings, ruleEngineSettings.getTopic());
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, ruleEngineSettings.getTopic());
}
@Override
@ -104,7 +108,7 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic());
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, coreSettings.getTopic());
}
@Override

17
common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java

@ -32,6 +32,7 @@ import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin;
@ -51,34 +52,39 @@ public class AwsSqsTransportQueueFactory implements TbTransportQueueFactory {
private final TbAwsSqsSettings sqsSettings;
private final TbQueueCoreSettings coreSettings;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin transportApiAdmin;
private final TbQueueAdmin notificationAdmin;
private final TbQueueAdmin ruleEngineAdmin;
public AwsSqsTransportQueueFactory(TbQueueTransportApiSettings transportApiSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbAwsSqsSettings sqsSettings,
TbServiceInfoProvider serviceInfoProvider,
TbQueueCoreSettings coreSettings,
TbAwsSqsQueueAttributes sqsQueueAttributes) {
TbAwsSqsQueueAttributes sqsQueueAttributes,
TbQueueRuleEngineSettings ruleEngineSettings) {
this.transportApiSettings = transportApiSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.sqsSettings = sqsSettings;
this.serviceInfoProvider = serviceInfoProvider;
this.coreSettings = coreSettings;
this.ruleEngineSettings = ruleEngineSettings;
this.coreAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getCoreAttributes());
this.transportApiAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getTransportApiAttributes());
this.notificationAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getNotificationsAttributes());
this.ruleEngineAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getRuleEngineAttributes());
}
@Override
public TbQueueRequestTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> createTransportApiRequestTemplate() {
TbAwsSqsProducerTemplate<TbProtoQueueMsg<TransportApiRequestMsg>> producerTemplate =
TbQueueProducer<TbProtoQueueMsg<TransportApiRequestMsg>> producerTemplate =
new TbAwsSqsProducerTemplate<>(transportApiAdmin, sqsSettings, transportApiSettings.getRequestsTopic());
TbAwsSqsConsumerTemplate<TbProtoQueueMsg<TransportApiResponseMsg>> consumerTemplate =
TbQueueConsumer<TbProtoQueueMsg<TransportApiResponseMsg>> consumerTemplate =
new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings,
transportApiSettings.getResponsesTopic() + "_" + serviceInfoProvider.getServiceId(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiResponseMsg.parseFrom(msg.getData()), msg.getHeaders()));
@ -96,7 +102,7 @@ public class AwsSqsTransportQueueFactory implements TbTransportQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbAwsSqsProducerTemplate<>(transportApiAdmin, sqsSettings, transportApiSettings.getRequestsTopic());
return new TbAwsSqsProducerTemplate<>(ruleEngineAdmin, sqsSettings, ruleEngineSettings.getTopic());
}
@Override
@ -126,5 +132,8 @@ public class AwsSqsTransportQueueFactory implements TbTransportQueueFactory {
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (ruleEngineAdmin != null) {
ruleEngineAdmin.destroy();
}
}
}

7
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java

@ -131,7 +131,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("monolith-rule-engine-notifications-" + serviceInfoProvider.getServiceId());
requestBuilder.defaultTopic(ruleEngineSettings.getTopic());
requestBuilder.admin(ruleEngineAdmin);
requestBuilder.admin(notificationAdmin);
return requestBuilder.build();
}
@ -151,7 +151,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("monolith-core-notifications-" + serviceInfoProvider.getServiceId());
requestBuilder.defaultTopic(coreSettings.getTopic());
requestBuilder.admin(coreAdmin);
requestBuilder.admin(notificationAdmin);
return requestBuilder.build();
}
@ -328,5 +328,8 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (fwUpdatesAdmin != null) {
fwUpdatesAdmin.destroy();
}
}
}

19
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java

@ -49,6 +49,7 @@ import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import javax.annotation.PreDestroy;
import java.nio.charset.StandardCharsets;
@ -65,6 +66,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
private final TbQueueTransportApiSettings transportApiSettings;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbKafkaConsumerStatsService consumerStatsService;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
@ -80,6 +82,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
TbQueueTransportApiSettings transportApiSettings,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbKafkaConsumerStatsService consumerStatsService,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbKafkaTopicConfigs kafkaTopicConfigs) {
this.partitionService = partitionService;
this.kafkaSettings = kafkaSettings;
@ -89,6 +92,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
this.transportApiSettings = transportApiSettings;
this.jsInvokeSettings = jsInvokeSettings;
this.consumerStatsService = consumerStatsService;
this.transportNotificationSettings = transportNotificationSettings;
this.coreAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getCoreConfigs());
this.ruleEngineAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getRuleEngineConfigs());
@ -103,8 +107,8 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToTransportMsg>> requestBuilder = TbKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("tb-core-transport-notifications-" + serviceInfoProvider.getServiceId());
requestBuilder.defaultTopic(coreSettings.getTopic());
requestBuilder.admin(coreAdmin);
requestBuilder.defaultTopic(transportNotificationSettings.getNotificationsTopic());
requestBuilder.admin(notificationAdmin);
return requestBuilder.build();
}
@ -124,7 +128,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("tb-core-rule-engine-notifications-" + serviceInfoProvider.getServiceId());
requestBuilder.defaultTopic(ruleEngineSettings.getTopic());
requestBuilder.admin(ruleEngineAdmin);
requestBuilder.admin(notificationAdmin);
return requestBuilder.build();
}
@ -144,7 +148,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("tb-core-to-core-notifications-" + serviceInfoProvider.getServiceId());
requestBuilder.defaultTopic(coreSettings.getTopic());
requestBuilder.admin(coreAdmin);
requestBuilder.admin(notificationAdmin);
return requestBuilder.build();
}
@ -192,8 +196,8 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<TransportApiResponseMsg>> requestBuilder = TbKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("tb-core-transport-api-producer-" + serviceInfoProvider.getServiceId());
requestBuilder.defaultTopic(coreSettings.getTopic());
requestBuilder.admin(coreAdmin);
requestBuilder.defaultTopic(transportApiSettings.getResponsesTopic());
requestBuilder.admin(transportApiAdmin);
return requestBuilder.build();
}
@ -294,5 +298,8 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (fwUpdatesAdmin != null) {
fwUpdatesAdmin.destroy();
}
}
}

20
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java

@ -46,6 +46,7 @@ import org.thingsboard.server.queue.kafka.TbKafkaTopicConfigs;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
import javax.annotation.PreDestroy;
@ -63,6 +64,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbKafkaConsumerStatsService consumerStatsService;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
@ -77,6 +79,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
TbQueueRuleEngineSettings ruleEngineSettings,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbKafkaConsumerStatsService consumerStatsService,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbKafkaTopicConfigs kafkaTopicConfigs) {
this.partitionService = partitionService;
this.kafkaSettings = kafkaSettings;
@ -85,6 +88,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
this.ruleEngineSettings = ruleEngineSettings;
this.jsInvokeSettings = jsInvokeSettings;
this.consumerStatsService = consumerStatsService;
this.transportNotificationSettings = transportNotificationSettings;
this.coreAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getCoreConfigs());
this.ruleEngineAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getRuleEngineConfigs());
@ -98,8 +102,8 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToTransportMsg>> requestBuilder = TbKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("tb-rule-engine-transport-notifications-" + serviceInfoProvider.getServiceId());
requestBuilder.defaultTopic(coreSettings.getTopic());
requestBuilder.admin(coreAdmin);
requestBuilder.defaultTopic(transportNotificationSettings.getNotificationsTopic());
requestBuilder.admin(notificationAdmin);
return requestBuilder.build();
}
@ -108,8 +112,8 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToRuleEngineMsg>> requestBuilder = TbKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("tb-rule-engine-to-rule-engine-" + serviceInfoProvider.getServiceId());
requestBuilder.defaultTopic(coreSettings.getTopic());
requestBuilder.admin(coreAdmin);
requestBuilder.defaultTopic(ruleEngineSettings.getTopic());
requestBuilder.admin(ruleEngineAdmin);
return requestBuilder.build();
}
@ -119,11 +123,10 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("tb-rule-engine-to-rule-engine-notifications-" + serviceInfoProvider.getServiceId());
requestBuilder.defaultTopic(ruleEngineSettings.getTopic());
requestBuilder.admin(ruleEngineAdmin);
requestBuilder.admin(notificationAdmin);
return requestBuilder.build();
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> createTbCoreMsgProducer() {
TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCoreMsg>> requestBuilder = TbKafkaProducerTemplate.builder();
@ -150,7 +153,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("tb-rule-engine-to-core-notifications-" + serviceInfoProvider.getServiceId());
requestBuilder.defaultTopic(coreSettings.getTopic());
requestBuilder.admin(coreAdmin);
requestBuilder.admin(notificationAdmin);
return requestBuilder.build();
}
@ -239,5 +242,8 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (fwUpdatesAdmin != null) {
fwUpdatesAdmin.destroy();
}
}
}

4
common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java

@ -112,7 +112,7 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(ruleEngineAdmin, pubSubSettings, ruleEngineSettings.getTopic());
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, ruleEngineSettings.getTopic());
}
@Override
@ -122,7 +122,7 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getTopic());
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, coreSettings.getTopic());
}
@Override

21
common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java

@ -46,7 +46,9 @@ import org.thingsboard.server.queue.pubsub.TbPubSubSettings;
import org.thingsboard.server.queue.pubsub.TbPubSubSubscriptionSettings;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import javax.annotation.PreDestroy;
import java.nio.charset.StandardCharsets;
@ -61,11 +63,14 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory {
private final PartitionService partitionService;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin jsExecutorAdmin;
private final TbQueueAdmin transportApiAdmin;
private final TbQueueAdmin notificationAdmin;
private final TbQueueAdmin ruleEngineAdmin;
public PubSubTbCoreQueueFactory(TbPubSubSettings pubSubSettings,
TbQueueCoreSettings coreSettings,
@ -73,6 +78,8 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory {
PartitionService partitionService,
TbServiceInfoProvider serviceInfoProvider,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbQueueRuleEngineSettings ruleEngineSettings,
TbPubSubSubscriptionSettings pubSubSubscriptionSettings) {
this.pubSubSettings = pubSubSettings;
this.coreSettings = coreSettings;
@ -80,16 +87,19 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory {
this.partitionService = partitionService;
this.serviceInfoProvider = serviceInfoProvider;
this.jsInvokeSettings = jsInvokeSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.ruleEngineSettings = ruleEngineSettings;
this.coreAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getCoreSettings());
this.jsExecutorAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getJsExecutorSettings());
this.transportApiAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getTransportApiSettings());
this.notificationAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getNotificationsSettings());
this.ruleEngineAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getRuleEngineSettings());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getTopic());
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, transportNotificationSettings.getNotificationsTopic());
}
@Override
@ -99,7 +109,7 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getTopic());
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, ruleEngineSettings.getTopic());
}
@Override
@ -109,7 +119,7 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getTopic());
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, coreSettings.getTopic());
}
@Override
@ -133,7 +143,7 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> createTransportApiResponseProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getTopic());
return new TbPubSubProducerTemplate<>(transportApiAdmin, pubSubSettings, transportApiSettings.getResponsesTopic());
}
@Override
@ -195,5 +205,8 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory {
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (ruleEngineAdmin != null) {
ruleEngineAdmin.destroy();
}
}
}

13
common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java

@ -45,6 +45,7 @@ import org.thingsboard.server.queue.pubsub.TbPubSubSubscriptionSettings;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
import javax.annotation.PreDestroy;
@ -60,6 +61,7 @@ public class PubSubTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory
private final PartitionService partitionService;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
@ -72,6 +74,7 @@ public class PubSubTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory
PartitionService partitionService,
TbServiceInfoProvider serviceInfoProvider,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbPubSubSubscriptionSettings pubSubSubscriptionSettings) {
this.pubSubSettings = pubSubSettings;
this.coreSettings = coreSettings;
@ -79,6 +82,7 @@ public class PubSubTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory
this.partitionService = partitionService;
this.serviceInfoProvider = serviceInfoProvider;
this.jsInvokeSettings = jsInvokeSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.coreAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getCoreSettings());
this.ruleEngineAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getRuleEngineSettings());
@ -88,28 +92,27 @@ public class PubSubTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getTopic());
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, transportNotificationSettings.getNotificationsTopic());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getTopic());
return new TbPubSubProducerTemplate<>(ruleEngineAdmin, pubSubSettings, ruleEngineSettings.getTopic());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(ruleEngineAdmin, pubSubSettings, ruleEngineSettings.getTopic());
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, ruleEngineSettings.getTopic());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> createTbCoreMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getTopic());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getTopic());
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, coreSettings.getTopic());
}
@Override

8
common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java

@ -66,13 +66,13 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
private final TbQueueTransportApiSettings transportApiSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbRabbitMqSettings rabbitMqSettings;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
private final TbQueueAdmin jsExecutorAdmin;
private final TbQueueAdmin transportApiAdmin;
private final TbQueueAdmin notificationAdmin;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
public RabbitMqMonolithQueueFactory(PartitionService partitionService, TbQueueCoreSettings coreSettings,
TbQueueRuleEngineSettings ruleEngineSettings,
@ -89,13 +89,13 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
this.transportApiSettings = transportApiSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.rabbitMqSettings = rabbitMqSettings;
this.jsInvokeSettings = jsInvokeSettings;
this.coreAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getCoreArgs());
this.ruleEngineAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getRuleEngineArgs());
this.jsExecutorAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getJsExecutorArgs());
this.transportApiAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getTransportApiArgs());
this.notificationAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getNotificationsArgs());
this.jsInvokeSettings = jsInvokeSettings;
}
@Override
@ -110,7 +110,7 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbRabbitMqProducerTemplate<>(ruleEngineAdmin, rabbitMqSettings, ruleEngineSettings.getTopic());
return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, ruleEngineSettings.getTopic());
}
@Override
@ -120,7 +120,7 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic());
return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, coreSettings.getTopic());
}
@Override

12
common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java

@ -48,6 +48,7 @@ import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import javax.annotation.PreDestroy;
import java.nio.charset.StandardCharsets;
@ -63,6 +64,7 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory {
private final PartitionService partitionService;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
@ -77,6 +79,7 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory {
PartitionService partitionService,
TbServiceInfoProvider serviceInfoProvider,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbRabbitMqQueueArguments queueArguments) {
this.rabbitMqSettings = rabbitMqSettings;
this.coreSettings = coreSettings;
@ -85,6 +88,7 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory {
this.partitionService = partitionService;
this.serviceInfoProvider = serviceInfoProvider;
this.jsInvokeSettings = jsInvokeSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.coreAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getCoreArgs());
this.ruleEngineAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getRuleEngineArgs());
@ -95,7 +99,7 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic());
return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, transportNotificationSettings.getNotificationsTopic());
}
@Override
@ -105,7 +109,7 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbRabbitMqProducerTemplate<>(ruleEngineAdmin, rabbitMqSettings, ruleEngineSettings.getTopic());
return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, ruleEngineSettings.getTopic());
}
@Override
@ -115,7 +119,7 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic());
return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, coreSettings.getTopic());
}
@Override
@ -139,7 +143,7 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> createTransportApiResponseProducer() {
return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic());
return new TbRabbitMqProducerTemplate<>(transportApiAdmin, rabbitMqSettings, transportApiSettings.getResponsesTopic());
}
@Override

12
common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java

@ -45,6 +45,7 @@ import org.thingsboard.server.queue.rabbitmq.TbRabbitMqSettings;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
import javax.annotation.PreDestroy;
@ -60,6 +61,7 @@ public class RabbitMqTbRuleEngineQueueFactory implements TbRuleEngineQueueFactor
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbRabbitMqSettings rabbitMqSettings;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
@ -71,6 +73,7 @@ public class RabbitMqTbRuleEngineQueueFactory implements TbRuleEngineQueueFactor
TbServiceInfoProvider serviceInfoProvider,
TbRabbitMqSettings rabbitMqSettings,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbRabbitMqQueueArguments queueArguments) {
this.partitionService = partitionService;
this.coreSettings = coreSettings;
@ -78,6 +81,7 @@ public class RabbitMqTbRuleEngineQueueFactory implements TbRuleEngineQueueFactor
this.ruleEngineSettings = ruleEngineSettings;
this.rabbitMqSettings = rabbitMqSettings;
this.jsInvokeSettings = jsInvokeSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.coreAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getCoreArgs());
this.ruleEngineAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getRuleEngineArgs());
@ -87,17 +91,17 @@ public class RabbitMqTbRuleEngineQueueFactory implements TbRuleEngineQueueFactor
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic());
return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, transportNotificationSettings.getNotificationsTopic());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic());
return new TbRabbitMqProducerTemplate<>(ruleEngineAdmin, rabbitMqSettings, ruleEngineSettings.getTopic());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbRabbitMqProducerTemplate<>(ruleEngineAdmin, rabbitMqSettings, ruleEngineSettings.getTopic());
return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, ruleEngineSettings.getTopic());
}
@Override
@ -107,7 +111,7 @@ public class RabbitMqTbRuleEngineQueueFactory implements TbRuleEngineQueueFactor
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic());
return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, coreSettings.getTopic());
}
@Override

10
common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java

@ -37,6 +37,7 @@ import org.thingsboard.server.queue.rabbitmq.TbRabbitMqProducerTemplate;
import org.thingsboard.server.queue.rabbitmq.TbRabbitMqQueueArguments;
import org.thingsboard.server.queue.rabbitmq.TbRabbitMqSettings;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
@ -51,6 +52,7 @@ public class RabbitMqTransportQueueFactory implements TbTransportQueueFactory {
private final TbRabbitMqSettings rabbitMqSettings;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueCoreSettings coreSettings;
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
@ -62,12 +64,14 @@ public class RabbitMqTransportQueueFactory implements TbTransportQueueFactory {
TbRabbitMqSettings rabbitMqSettings,
TbServiceInfoProvider serviceInfoProvider,
TbQueueCoreSettings coreSettings,
TbQueueRuleEngineSettings ruleEngineSettings,
TbRabbitMqQueueArguments queueArguments) {
this.transportApiSettings = transportApiSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.rabbitMqSettings = rabbitMqSettings;
this.serviceInfoProvider = serviceInfoProvider;
this.coreSettings = coreSettings;
this.ruleEngineSettings = ruleEngineSettings;
this.coreAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getCoreArgs());
this.ruleEngineAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getRuleEngineArgs());
@ -77,10 +81,10 @@ public class RabbitMqTransportQueueFactory implements TbTransportQueueFactory {
@Override
public TbQueueRequestTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> createTransportApiRequestTemplate() {
TbRabbitMqProducerTemplate<TbProtoQueueMsg<TransportApiRequestMsg>> producerTemplate =
TbQueueProducer<TbProtoQueueMsg<TransportApiRequestMsg>> producerTemplate =
new TbRabbitMqProducerTemplate<>(transportApiAdmin, rabbitMqSettings, transportApiSettings.getRequestsTopic());
TbRabbitMqConsumerTemplate<TbProtoQueueMsg<TransportApiResponseMsg>> consumerTemplate =
TbQueueConsumer<TbProtoQueueMsg<TransportApiResponseMsg>> consumerTemplate =
new TbRabbitMqConsumerTemplate<>(transportApiAdmin, rabbitMqSettings,
transportApiSettings.getResponsesTopic() + "." + serviceInfoProvider.getServiceId(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiResponseMsg.parseFrom(msg.getData()), msg.getHeaders()));
@ -98,7 +102,7 @@ public class RabbitMqTransportQueueFactory implements TbTransportQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbRabbitMqProducerTemplate<>(transportApiAdmin, rabbitMqSettings, transportApiSettings.getRequestsTopic());
return new TbRabbitMqProducerTemplate<>(ruleEngineAdmin, rabbitMqSettings, ruleEngineSettings.getTopic());
}
@Override

4
common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java

@ -109,7 +109,7 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbServiceBusProducerTemplate<>(ruleEngineAdmin, serviceBusSettings, ruleEngineSettings.getTopic());
return new TbServiceBusProducerTemplate<>(notificationAdmin, serviceBusSettings, ruleEngineSettings.getTopic());
}
@Override
@ -119,7 +119,7 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic());
return new TbServiceBusProducerTemplate<>(notificationAdmin, serviceBusSettings, coreSettings.getTopic());
}
@Override

12
common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java

@ -48,6 +48,7 @@ import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import javax.annotation.PreDestroy;
import java.nio.charset.StandardCharsets;
@ -63,6 +64,7 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory {
private final PartitionService partitionService;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
@ -77,6 +79,7 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory {
PartitionService partitionService,
TbServiceInfoProvider serviceInfoProvider,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbServiceBusQueueConfigs serviceBusQueueConfigs) {
this.serviceBusSettings = serviceBusSettings;
this.coreSettings = coreSettings;
@ -85,6 +88,7 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory {
this.partitionService = partitionService;
this.serviceInfoProvider = serviceInfoProvider;
this.jsInvokeSettings = jsInvokeSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.coreAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getCoreConfigs());
this.ruleEngineAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getRuleEngineConfigs());
@ -95,7 +99,7 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic());
return new TbServiceBusProducerTemplate<>(notificationAdmin, serviceBusSettings, transportNotificationSettings.getNotificationsTopic());
}
@Override
@ -105,7 +109,7 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbServiceBusProducerTemplate<>(ruleEngineAdmin, serviceBusSettings, ruleEngineSettings.getTopic());
return new TbServiceBusProducerTemplate<>(notificationAdmin, serviceBusSettings, ruleEngineSettings.getTopic());
}
@Override
@ -115,7 +119,7 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic());
return new TbServiceBusProducerTemplate<>(notificationAdmin, serviceBusSettings, coreSettings.getTopic());
}
@Override
@ -139,7 +143,7 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory {
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> createTransportApiResponseProducer() {
return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic());
return new TbServiceBusProducerTemplate<>(transportApiAdmin, serviceBusSettings, transportApiSettings.getResponsesTopic());
}
@Override

12
common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java

@ -45,6 +45,7 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
import javax.annotation.PreDestroy;
@ -60,6 +61,7 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbServiceBusSettings serviceBusSettings;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
@ -71,6 +73,7 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact
TbServiceInfoProvider serviceInfoProvider,
TbServiceBusSettings serviceBusSettings,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbServiceBusQueueConfigs serviceBusQueueConfigs) {
this.partitionService = partitionService;
this.coreSettings = coreSettings;
@ -78,6 +81,7 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact
this.ruleEngineSettings = ruleEngineSettings;
this.serviceBusSettings = serviceBusSettings;
this.jsInvokeSettings = jsInvokeSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.coreAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getCoreConfigs());
this.ruleEngineAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getRuleEngineConfigs());
@ -87,17 +91,17 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic());
return new TbServiceBusProducerTemplate<>(notificationAdmin, serviceBusSettings, transportNotificationSettings.getNotificationsTopic());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic());
return new TbServiceBusProducerTemplate<>(ruleEngineAdmin, serviceBusSettings, ruleEngineSettings.getTopic());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbServiceBusProducerTemplate<>(ruleEngineAdmin, serviceBusSettings, ruleEngineSettings.getTopic());
return new TbServiceBusProducerTemplate<>(notificationAdmin, serviceBusSettings, ruleEngineSettings.getTopic());
}
@Override
@ -107,7 +111,7 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic());
return new TbServiceBusProducerTemplate<>(notificationAdmin, serviceBusSettings, coreSettings.getTopic());
}
@Override

11
common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java

@ -37,6 +37,7 @@ import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
@ -51,14 +52,17 @@ public class ServiceBusTransportQueueFactory implements TbTransportQueueFactory
private final TbServiceBusSettings serviceBusSettings;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueCoreSettings coreSettings;
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin transportApiAdmin;
private final TbQueueAdmin notificationAdmin;
private final TbQueueAdmin ruleEngineAdmin;
public ServiceBusTransportQueueFactory(TbQueueTransportApiSettings transportApiSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbServiceBusSettings serviceBusSettings,
TbQueueRuleEngineSettings ruleEngineSettings,
TbServiceInfoProvider serviceInfoProvider,
TbQueueCoreSettings coreSettings,
TbServiceBusQueueConfigs serviceBusQueueConfigs) {
@ -67,10 +71,12 @@ public class ServiceBusTransportQueueFactory implements TbTransportQueueFactory
this.serviceBusSettings = serviceBusSettings;
this.serviceInfoProvider = serviceInfoProvider;
this.coreSettings = coreSettings;
this.ruleEngineSettings = ruleEngineSettings;
this.coreAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getCoreConfigs());
this.transportApiAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getTransportApiConfigs());
this.notificationAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getNotificationsConfigs());
this.ruleEngineAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getRuleEngineConfigs());
}
@Override
@ -96,7 +102,7 @@ public class ServiceBusTransportQueueFactory implements TbTransportQueueFactory
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbServiceBusProducerTemplate<>(transportApiAdmin, serviceBusSettings, transportApiSettings.getRequestsTopic());
return new TbServiceBusProducerTemplate<>(ruleEngineAdmin, serviceBusSettings, ruleEngineSettings.getTopic());
}
@Override
@ -127,5 +133,8 @@ public class ServiceBusTransportQueueFactory implements TbTransportQueueFactory
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (ruleEngineAdmin != null) {
ruleEngineAdmin.destroy();
}
}
}

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java

@ -41,6 +41,7 @@ import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider;
import org.thingsboard.server.transport.lwm2m.server.model.LwM2MModelConfigService;
import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService;
import org.thingsboard.server.transport.lwm2m.server.session.LwM2MSessionManager;
import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MClientStore;
@ -76,6 +77,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
private final LwM2MSessionManager sessionManager;
private final TransportDeviceProfileCache deviceProfileCache;
private final LwM2mVersionedModelProvider modelProvider;
private final LwM2MModelConfigService modelConfigService;
@Autowired
@Lazy
@ -259,6 +261,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
// TODO: change tests to use new certificate.
// this.securityStore.remove(client.getEndpoint(), registration.getId());
clientStore.remove(client.getEndpoint());
modelConfigService.removeUpdates(client.getEndpoint());
UUID profileId = client.getProfileId();
if (profileId != null) {
Optional<LwM2mClient> otherClients = lwM2mClientsByRegistrationId.values().stream().filter(e -> e.getProfileId().equals(profileId)).findFirst();
@ -333,6 +336,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
if (LwM2MClientState.REGISTERED.equals(lwM2MClient.getState())) {
PowerMode powerMode = getPowerMode(lwM2MClient);
if (PowerMode.PSM.equals(powerMode) || PowerMode.E_DRX.equals(powerMode)) {
modelConfigService.sendUpdates(lwM2MClient);
defaultLwM2MUplinkMsgHandler.initAttributes(lwM2MClient);
TransportProtos.TransportToDeviceActorMsg persistentRpcRequestMsg = TransportProtos.TransportToDeviceActorMsg
.newBuilder()

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DownlinkRequestCallback.java

@ -17,9 +17,9 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
public interface DownlinkRequestCallback<R, T> {
default boolean onSent(R request){
default boolean onSent(R request) {
return true;
};
}
void onSuccess(R request, T response);

92
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfig.java

@ -0,0 +1,92 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.common.util.CollectionsUtil.diffSets;
@Data
@NoArgsConstructor
@ToString(exclude = "toCancelRead")
@Slf4j
public class LwM2MModelConfig {
private String endpoint;
private Map<String, ObjectAttributes> attributesToAdd;
private Set<String> attributesToRemove;
private Set<String> toObserve;
private Set<String> toCancelObserve;
private Set<String> toRead;
@JsonIgnore
private Set<String> toCancelRead;
public LwM2MModelConfig(String endpoint) {
this.endpoint = endpoint;
this.attributesToAdd = new ConcurrentHashMap<>();
this.attributesToRemove = ConcurrentHashMap.newKeySet();
this.toObserve = ConcurrentHashMap.newKeySet();
this.toCancelObserve = ConcurrentHashMap.newKeySet();
this.toRead = ConcurrentHashMap.newKeySet();
this.toCancelRead = new HashSet<>();
}
public void merge(LwM2MModelConfig modelConfig) {
if (modelConfig.isEmpty() && modelConfig.getToCancelRead().isEmpty()) {
return;
}
modelConfig.getAttributesToAdd().forEach((k, v) -> {
if (this.attributesToRemove.contains(k)) {
this.attributesToRemove.remove(k);
} else {
this.attributesToAdd.put(k, v);
}
});
modelConfig.getAttributesToRemove().forEach(k -> {
if (this.attributesToAdd.containsKey(k)) {
this.attributesToRemove.remove(k);
} else {
this.attributesToRemove.add(k);
}
});
this.toObserve.addAll(diffSets(this.toCancelObserve, modelConfig.getToObserve()));
this.toCancelObserve.addAll(diffSets(this.toObserve, modelConfig.getToCancelObserve()));
this.toObserve.removeAll(modelConfig.getToCancelObserve());
this.toCancelObserve.removeAll(modelConfig.getToObserve());
this.toRead.removeAll(modelConfig.getToObserve());
this.toRead.removeAll(modelConfig.getToCancelRead());
this.toRead.addAll(modelConfig.getToRead());
}
@JsonIgnore
public boolean isEmpty() {
return attributesToAdd.isEmpty() && toObserve.isEmpty() && toCancelObserve.isEmpty() && toRead.isEmpty();
}
}

29
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigService.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.model;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
public interface LwM2MModelConfigService {
void sendUpdates(LwM2mClient lwM2mClient);
void sendUpdates(LwM2mClient lwM2mClient, LwM2MModelConfig modelConfig);
void persistUpdates(String endpoint);
void removeUpdates(String endpoint);
}

235
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java

@ -0,0 +1,235 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.model;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesRequest;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MModelConfigStore;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
@Slf4j
@Service
@TbLwM2mTransportComponent
public class LwM2MModelConfigServiceImpl implements LwM2MModelConfigService {
@Autowired
private TbLwM2MModelConfigStore modelStore;
@Autowired
@Lazy
private LwM2mDownlinkMsgHandler downlinkMsgHandler;
@Autowired
@Lazy
private LwM2mUplinkMsgHandler uplinkMsgHandler;
@Autowired
@Lazy
private LwM2mClientContext clientContext;
@Autowired
private LwM2MTelemetryLogService logService;
private ConcurrentMap<String, LwM2MModelConfig> currentModelConfigs;
@AfterStartUp(order = Integer.MAX_VALUE - 1)
private void init() {
List<LwM2MModelConfig> models = modelStore.getAll();
log.debug("Fetched model configs: {}", models);
currentModelConfigs = models.stream()
.collect(Collectors.toConcurrentMap(LwM2MModelConfig::getEndpoint, m -> m));
}
@Override
public void sendUpdates(LwM2mClient lwM2mClient) {
LwM2MModelConfig modelConfig = currentModelConfigs.get(lwM2mClient.getEndpoint());
if (modelConfig == null || modelConfig.isEmpty()) {
return;
}
doSend(lwM2mClient, modelConfig);
}
public void sendUpdates(LwM2mClient lwM2mClient, LwM2MModelConfig newModelConfig) {
String endpoint = lwM2mClient.getEndpoint();
LwM2MModelConfig modelConfig = currentModelConfigs.get(endpoint);
if (modelConfig == null || modelConfig.isEmpty()) {
modelConfig = newModelConfig;
currentModelConfigs.put(endpoint, modelConfig);
} else {
modelConfig.merge(newModelConfig);
}
if (lwM2mClient.isAsleep()) {
modelStore.put(modelConfig);
} else {
doSend(lwM2mClient, modelConfig);
}
}
private void doSend(LwM2mClient lwM2mClient, LwM2MModelConfig modelConfig) {
log.trace("Send LwM2M Model updates: [{}]", modelConfig);
String endpoint = lwM2mClient.getEndpoint();
Map<String, ObjectAttributes> attrToAdd = modelConfig.getAttributesToAdd();
attrToAdd.forEach((id, attributes) -> {
TbLwM2MWriteAttributesRequest request = TbLwM2MWriteAttributesRequest.builder().versionedId(id)
.attributes(attributes)
.timeout(clientContext.getRequestTimeout(lwM2mClient)).build();
downlinkMsgHandler.sendWriteAttributesRequest(lwM2mClient, request,
createDownlinkProxyCallback(() -> {
attrToAdd.remove(id);
if (modelConfig.isEmpty()) {
modelStore.remove(endpoint);
}
}, new TbLwM2MWriteAttributesCallback(logService, lwM2mClient, id))
);
});
Set<String> attrToRemove = modelConfig.getAttributesToRemove();
attrToRemove.forEach((id) -> {
TbLwM2MWriteAttributesRequest request = TbLwM2MWriteAttributesRequest.builder().versionedId(id)
.attributes(new ObjectAttributes())
.timeout(clientContext.getRequestTimeout(lwM2mClient)).build();
downlinkMsgHandler.sendWriteAttributesRequest(lwM2mClient, request,
createDownlinkProxyCallback(() -> {
attrToRemove.remove(id);
if (modelConfig.isEmpty()) {
modelStore.remove(endpoint);
}
}, new TbLwM2MWriteAttributesCallback(logService, lwM2mClient, id))
);
});
Set<String> toRead = modelConfig.getToRead();
toRead.forEach(id -> {
TbLwM2MReadRequest request = TbLwM2MReadRequest.builder().versionedId(id)
.timeout(clientContext.getRequestTimeout(lwM2mClient)).build();
downlinkMsgHandler.sendReadRequest(lwM2mClient, request,
createDownlinkProxyCallback(() -> {
toRead.remove(id);
if (modelConfig.isEmpty()) {
modelStore.remove(endpoint);
}
}, new TbLwM2MReadCallback(uplinkMsgHandler, logService, lwM2mClient, id))
);
});
Set<String> toObserve = modelConfig.getToObserve();
toObserve.forEach(id -> {
TbLwM2MObserveRequest request = TbLwM2MObserveRequest.builder().versionedId(id)
.timeout(clientContext.getRequestTimeout(lwM2mClient)).build();
downlinkMsgHandler.sendObserveRequest(lwM2mClient, request,
createDownlinkProxyCallback(() -> {
toObserve.remove(id);
if (modelConfig.isEmpty()) {
modelStore.remove(endpoint);
}
}, new TbLwM2MObserveCallback(uplinkMsgHandler, logService, lwM2mClient, id))
);
});
Set<String> toCancelObserve = modelConfig.getToCancelObserve();
toCancelObserve.forEach(id -> {
TbLwM2MCancelObserveRequest request = TbLwM2MCancelObserveRequest.builder().versionedId(id)
.timeout(clientContext.getRequestTimeout(lwM2mClient)).build();
downlinkMsgHandler.sendCancelObserveRequest(lwM2mClient, request,
createDownlinkProxyCallback(() -> {
toCancelObserve.remove(id);
if (modelConfig.isEmpty()) {
modelStore.remove(endpoint);
}
}, new TbLwM2MCancelObserveCallback(logService, lwM2mClient, id))
);
});
}
private <R, T> DownlinkRequestCallback<R, T> createDownlinkProxyCallback(Runnable processRemove, DownlinkRequestCallback<R, T> callback) {
return new DownlinkRequestCallback<>() {
@Override
public void onSuccess(R request, T response) {
processRemove.run();
callback.onSuccess(request, response);
}
@Override
public void onValidationError(String params, String msg) {
processRemove.run();
callback.onValidationError(params, msg);
}
@Override
public void onError(String params, Exception e) {
try {
if (e instanceof TimeoutException) {
return;
}
processRemove.run();
} finally {
callback.onError(params, e);
}
}
};
}
@Override
public void persistUpdates(String endpoint) {
LwM2MModelConfig modelConfig = currentModelConfigs.get(endpoint);
if (modelConfig != null && !modelConfig.isEmpty()) {
modelStore.put(modelConfig);
}
}
@Override
public void removeUpdates(String endpoint) {
currentModelConfigs.remove(endpoint);
}
@PreDestroy
private void destroy() {
currentModelConfigs.values().forEach(model -> {
if (model != null && !model.isEmpty()) {
modelStore.put(model);
}
});
}
}

38
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbDummyLwM2MModelConfigStore.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.store;
import org.thingsboard.server.transport.lwm2m.server.model.LwM2MModelConfig;
import java.util.Collections;
import java.util.List;
public class TbDummyLwM2MModelConfigStore implements TbLwM2MModelConfigStore {
@Override
public List<LwM2MModelConfig> getAll() {
return Collections.emptyList();
}
@Override
public void put(LwM2MModelConfig modelConfig) {
}
@Override
public void remove(String endpoint) {
}
}

28
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MModelConfigStore.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.store;
import org.thingsboard.server.transport.lwm2m.server.model.LwM2MModelConfig;
import java.util.List;
public interface TbLwM2MModelConfigStore {
List<LwM2MModelConfig> getAll();
void put(LwM2MModelConfig modelConfig);
void remove(String endpoint);
}

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java

@ -62,6 +62,11 @@ public class TbLwM2mStoreFactory {
return isRedis() ? new TbRedisLwM2MClientStore(getConnectionFactory()) : new TbDummyLwM2MClientStore();
}
@Bean
private TbLwM2MModelConfigStore modelConfigStore() {
return isRedis() ? new TbRedisLwM2MModelConfigStore(getConnectionFactory()) : new TbDummyLwM2MModelConfigStore();
}
@Bean
private TbLwM2MClientOtaInfoStore otaStore() {
return isRedis() ? new TbLwM2mRedisClientOtaInfoStore(getConnectionFactory()) : new TbDummyLwM2MClientOtaInfoStore();

79
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbRedisLwM2MModelConfigStore.java

@ -0,0 +1,79 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.store;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.transport.lwm2m.server.model.LwM2MModelConfig;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@AllArgsConstructor
public class TbRedisLwM2MModelConfigStore implements TbLwM2MModelConfigStore {
private static final String MODEL_EP = "MODEL#EP#";
private final RedisConnectionFactory connectionFactory;
@Override
public List<LwM2MModelConfig> getAll() {
try (var connection = connectionFactory.getConnection()) {
List<LwM2MModelConfig> configs = new ArrayList<>();
ScanOptions scanOptions = ScanOptions.scanOptions().count(100).match(MODEL_EP + "*").build();
List<Cursor<byte[]>> scans = new ArrayList<>();
if (connection instanceof RedisClusterConnection) {
((RedisClusterConnection) connection).clusterGetNodes().forEach(node -> {
scans.add(((RedisClusterConnection) connection).scan(node, scanOptions));
});
} else {
scans.add(connection.scan(scanOptions));
}
scans.forEach(scan -> {
scan.forEachRemaining(key -> {
byte[] element = connection.get(key);
configs.add(JacksonUtil.fromBytes(element, LwM2MModelConfig.class));
});
});
return configs;
}
}
@Override
public void put(LwM2MModelConfig modelConfig) {
byte[] clientSerialized = JacksonUtil.writeValueAsBytes(modelConfig);
try (var connection = connectionFactory.getConnection()) {
connection.getSet(getKey(modelConfig.getEndpoint()), clientSerialized);
}
}
@Override
public void remove(String endpoint) {
try (var connection = connectionFactory.getConnection()) {
connection.del(getKey(endpoint));
}
}
private byte[] getKey(String endpoint) {
return (MODEL_EP + endpoint).getBytes();
}
}

104
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java

@ -83,6 +83,8 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesRequest;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.model.LwM2MModelConfig;
import org.thingsboard.server.transport.lwm2m.server.model.LwM2MModelConfigService;
import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService;
import org.thingsboard.server.transport.lwm2m.server.session.LwM2MSessionManager;
import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
@ -106,6 +108,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.thingsboard.common.util.CollectionsUtil.diffSets;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
@ -147,6 +150,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
private final LwM2mVersionedModelProvider modelProvider;
private final RegistrationStore registrationStore;
private final TbLwM2mSecurityStore securityStore;
private final LwM2MModelConfigService modelConfigService;
public DefaultLwM2MUplinkMsgHandler(TransportService transportService,
LwM2MTransportServerConfig config,
@ -161,7 +165,8 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
TbLwM2MDtlsSessionStore sessionStore,
LwM2mVersionedModelProvider modelProvider,
RegistrationStore registrationStore,
TbLwM2mSecurityStore securityStore) {
TbLwM2mSecurityStore securityStore,
LwM2MModelConfigService modelConfigService) {
this.transportService = transportService;
this.sessionManager = sessionManager;
this.attributesService = attributesService;
@ -176,6 +181,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
this.modelProvider = modelProvider;
this.registrationStore = registrationStore;
this.securityStore = securityStore;
this.modelConfigService = modelConfigService;
}
@PostConstruct
@ -764,7 +770,6 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
//TODO: review and optimize the logic to minimize number of the requests to device.
private void onDeviceProfileUpdate(List<LwM2mClient> clients, Lwm2mDeviceProfileTransportConfiguration oldProfile, DeviceProfile deviceProfile) {
if (clientContext.profileUpdate(deviceProfile) != null) {
// #1
TelemetryMappingConfiguration oldTelemetryParams = oldProfile.getObserveAttr();
Set<String> attributeSetOld = oldTelemetryParams.getAttribute();
Set<String> telemetrySetOld = oldTelemetryParams.getTelemetry();
@ -784,46 +789,42 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
Set<String> observeToRemove = diffSets(observeNew, observeOld);
Set<String> newObjectsToRead = new HashSet<>();
Set<String> newObjectsToCancelRead = new HashSet<>();
// #3.1
if (!attributeSetOld.equals(attributeSetNew)) {
newObjectsToRead.addAll(diffSets(attributeSetOld, attributeSetNew));
newObjectsToCancelRead.addAll(diffSets(attributeSetNew, attributeSetOld));
}
// #3.2
if (!telemetrySetOld.equals(telemetrySetNew)) {
newObjectsToRead.addAll(diffSets(telemetrySetOld, telemetrySetNew));
newObjectsToCancelRead.addAll(diffSets(telemetrySetNew, telemetrySetOld));
}
// #3.3
if (!keyNameOld.equals(keyNameNew)) {
ParametersAnalyzeResult keyNameChange = this.getAnalyzerKeyName(keyNameOld, keyNameNew);
newObjectsToRead.addAll(keyNameChange.getPathPostParametersAdd());
}
// #3.4, #6
if (!attributeLwm2mOld.equals(attributeLwm2mNew)) {
this.compareAndSendWriteAttributes(clients, attributeLwm2mOld, attributeLwm2mNew);
}
ParametersAnalyzeResult analyzerParameters = getAttributesAnalyzer(attributeLwm2mOld, attributeLwm2mNew);
// #4.1 add
if (!newObjectsToRead.isEmpty()) {
Set<String> newObjectsToReadButNotNewInObserve = diffSets(observeToAdd, newObjectsToRead);
// update value in Resources
for (String versionedId : newObjectsToReadButNotNewInObserve) {
clients.forEach(client -> sendReadRequest(client, versionedId));
}
}
clients.forEach(client -> {
LwM2MModelConfig modelConfig = new LwM2MModelConfig(client.getEndpoint());
modelConfig.getToRead().addAll(diffSets(observeToAdd, newObjectsToRead));
modelConfig.getToCancelRead().addAll(newObjectsToCancelRead);
modelConfig.getToCancelObserve().addAll(observeToRemove);
modelConfig.getToObserve().addAll(observeToAdd);
// Calculating difference between old and new flags.
if (!observeToAdd.isEmpty()) {
for (String targetId : observeToAdd) {
clients.forEach(client -> sendObserveRequest(client, targetId));
}
}
if (!observeToRemove.isEmpty()) {
for (String targetId : observeToRemove) {
clients.forEach(client -> sendCancelObserveRequest(targetId, client));
}
}
Set<String> clientObjects = clientContext.getSupportedIdVerInClient(client);
Set<String> pathToAdd = analyzerParameters.getPathPostParametersAdd().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]))
.collect(Collectors.toUnmodifiableSet());
modelConfig.getAttributesToAdd().putAll(pathToAdd.stream().collect(Collectors.toMap(t -> t, attributeLwm2mNew::get)));
Set<String> pathToRemove = analyzerParameters.getPathPostParametersDel().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]))
.collect(Collectors.toUnmodifiableSet());
modelConfig.getAttributesToRemove().addAll(pathToRemove);
modelConfigService.sendUpdates(client, modelConfig);
});
// update value in fwInfo
OtherConfiguration newLwM2mSettings = newProfile.getClientLwM2mSettings();
@ -842,13 +843,6 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
}
}
/**
* Returns new set with elements that are present in set B(new) but absent in set A(old).
*/
private static <T> Set<T> diffSets(Set<T> a, Set<T> b) {
return b.stream().filter(p -> !a.contains(p)).collect(Collectors.toSet());
}
private ParametersAnalyzeResult getAnalyzerKeyName(Map<String, String> keyNameOld, Map<String, String> keyNameNew) {
ParametersAnalyzeResult analyzerParameters = new ParametersAnalyzeResult();
Set<String> paths = keyNameNew.entrySet()
@ -859,14 +853,10 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
return analyzerParameters;
}
/**
* #6.1 - send update WriteAttribute
* #6.2 - send empty WriteAttribute
*/
private void compareAndSendWriteAttributes(List<LwM2mClient> clients, Map<String, ObjectAttributes> lwm2mAttributesOld, Map<String, ObjectAttributes> lwm2mAttributesNew) {
private ParametersAnalyzeResult getAttributesAnalyzer(Map<String, ObjectAttributes> attributeLwm2mOld, Map<String, ObjectAttributes> attributeLwm2mNew) {
ParametersAnalyzeResult analyzerParameters = new ParametersAnalyzeResult();
Set<String> pathOld = lwm2mAttributesOld.keySet();
Set<String> pathNew = lwm2mAttributesNew.keySet();
Set<String> pathOld = attributeLwm2mOld.keySet();
Set<String> pathNew = attributeLwm2mNew.keySet();
analyzerParameters.setPathPostParametersAdd(pathNew
.stream().filter(p -> !pathOld.contains(p)).collect(Collectors.toSet()));
analyzerParameters.setPathPostParametersDel(pathOld
@ -874,31 +864,13 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
Set<String> pathCommon = pathNew
.stream().filter(pathOld::contains).collect(Collectors.toSet());
Set<String> pathCommonChange = pathCommon
.stream().filter(p -> !lwm2mAttributesOld.get(p).equals(lwm2mAttributesNew.get(p))).collect(Collectors.toSet());
.stream().filter(p -> !attributeLwm2mOld.get(p).equals(attributeLwm2mNew.get(p))).collect(Collectors.toSet());
analyzerParameters.getPathPostParametersAdd().addAll(pathCommonChange);
// #6
// #6.2
if (analyzerParameters.getPathPostParametersAdd().size() > 0) {
clients.forEach(client -> {
Set<String> clientObjects = clientContext.getSupportedIdVerInClient(client);
Set<String> pathSend = analyzerParameters.getPathPostParametersAdd().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]))
.collect(Collectors.toUnmodifiableSet());
if (!pathSend.isEmpty()) {
pathSend.forEach(target -> sendWriteAttributesRequest(client, target, lwm2mAttributesNew.get(target)));
}
});
}
// #6.2
if (analyzerParameters.getPathPostParametersDel().size() > 0) {
clients.forEach(client -> {
Set<String> clientObjects = clientContext.getSupportedIdVerInClient(client);
Set<String> pathSend = analyzerParameters.getPathPostParametersDel().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]))
.collect(Collectors.toUnmodifiableSet());
if (!pathSend.isEmpty()) {
pathSend.forEach(target -> sendWriteAttributesRequest(client, target, new ObjectAttributes()));
}
});
}
return analyzerParameters;
}
private void compareAndSetWriteAttributes(LwM2mClient client, ParametersAnalyzeResult analyzerParameters, Map<String, ObjectAttributes> lwm2mAttributesNew, LwM2MModelConfig modelConfig) {
}
/**

37
common/util/src/main/java/org/thingsboard/common/util/CollectionsUtil.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.common.util;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
public class CollectionsUtil {
public static boolean isEmpty(Collection<?> collection) {
return collection == null || collection.isEmpty();
}
public static boolean isNotEmpty(Collection<?> collection) {
return !isEmpty(collection);
}
/**
* Returns new set with elements that are present in set B(new) but absent in set A(old).
*/
public static <T> Set<T> diffSets(Set<T> a, Set<T> b) {
return b.stream().filter(p -> !a.contains(p)).collect(Collectors.toSet());
}
}

8
common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java

@ -123,4 +123,12 @@ public class JacksonUtil {
return OBJECT_MAPPER.treeToValue(tree, type);
}
public static <T> byte[] writeValueAsBytes(T value) {
try {
return OBJECT_MAPPER.writeValueAsBytes(value);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("The given Json object value: "
+ value + " cannot be transformed to a String", e);
}
}
}

5
dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java

@ -36,6 +36,9 @@ import java.util.stream.Collectors;
public abstract class DaoUtil {
public static final String DEFAULT_SORT_PROPERTY = "id";
public static final Sort DEFAULT_SORT = Sort.by(Sort.Direction.ASC, DEFAULT_SORT_PROPERTY);
private DaoUtil() {
}
@ -70,7 +73,7 @@ public abstract class DaoUtil {
public static Sort toSort(SortOrder sortOrder, Map<String,String> columnMap) {
if (sortOrder == null) {
return Sort.unsorted();
return DEFAULT_SORT;
} else {
String property = sortOrder.getProperty();
if (columnMap.containsKey(property)) {

10
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -336,12 +336,14 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
return savedDevice;
}
@Transactional
@Override
public void deleteDevice(TenantId tenantId, DeviceId deviceId) {
public void deleteDevice(final TenantId tenantId, final DeviceId deviceId) {
log.trace("Executing deleteDevice [{}]", deviceId);
validateId(deviceId, INCORRECT_DEVICE_ID + deviceId);
Device device = deviceDao.findById(tenantId, deviceId.getId());
final String deviceName = device.getName();
try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), deviceId).get();
if (entityViews != null && !entityViews.isEmpty()) {
@ -358,10 +360,10 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
}
deleteEntityRelations(tenantId, deviceId);
removeDeviceFromCacheByName(tenantId, device.getName());
removeDeviceFromCacheById(tenantId, device.getId());
deviceDao.removeById(tenantId, deviceId.getId());
removeDeviceFromCacheByName(tenantId, deviceName);
removeDeviceFromCacheById(tenantId, deviceId);
}
private void removeDeviceFromCacheByName(TenantId tenantId, String name) {

52
dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java

@ -16,7 +16,6 @@
package org.thingsboard.server.dao.sqlts;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
@ -34,6 +33,7 @@ import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity;
@ -45,11 +45,9 @@ import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository;
import org.thingsboard.server.dao.sqlts.latest.SearchTsKvLatestRepository;
import org.thingsboard.server.dao.sqlts.latest.TsKvLatestRepository;
import org.thingsboard.server.dao.timeseries.SimpleListenableFuture;
import org.thingsboard.server.dao.timeseries.TimeseriesLatestDao;
import org.thingsboard.server.dao.util.SqlTsLatestAnyDao;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
@ -59,7 +57,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -147,7 +144,7 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme
}
@Override
public ListenableFuture<Void> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
public ListenableFuture<TsKvLatestRemovingResult> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
return getRemoveLatestFuture(tenantId, entityId, query);
}
@ -175,15 +172,16 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme
return tsKvLatestRepository.findAllKeysByEntityIds(entityIds.stream().map(EntityId::getId).collect(Collectors.toList()));
}
private ListenableFuture<Void> getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
private ListenableFuture<TsKvLatestRemovingResult> getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
ListenableFuture<List<TsKvEntry>> future = findNewLatestEntryFuture(tenantId, entityId, query);
return Futures.transformAsync(future, entryList -> {
if (entryList.size() == 1) {
return getSaveLatestFuture(entityId, entryList.get(0));
TsKvEntry entry = entryList.get(0);
return Futures.transform(getSaveLatestFuture(entityId, entry), v -> new TsKvLatestRemovingResult(entry), MoreExecutors.directExecutor());
} else {
log.trace("Could not find new latest value for [{}], key - {}", entityId, query.getKey());
}
return Futures.immediateFuture(null);
return Futures.immediateFuture(new TsKvLatestRemovingResult(query.getKey(), true));
}, service);
}
@ -212,7 +210,7 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme
return Futures.immediateFuture(result);
}
protected ListenableFuture<Void> getRemoveLatestFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
protected ListenableFuture<TsKvLatestRemovingResult> getRemoveLatestFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
ListenableFuture<TsKvEntry> latestFuture = getFindLatestFuture(entityId, query.getKey());
ListenableFuture<Boolean> booleanFuture = Futures.transform(latestFuture, tsKvEntry -> {
@ -220,47 +218,25 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme
return ts > query.getStartTs() && ts <= query.getEndTs();
}, service);
ListenableFuture<Void> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
ListenableFuture<Boolean> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
if (isRemove) {
TsKvLatestEntity latestEntity = new TsKvLatestEntity();
latestEntity.setEntityId(entityId.getId());
latestEntity.setKey(getOrSaveKeyId(query.getKey()));
return service.submit(() -> {
tsKvLatestRepository.delete(latestEntity);
return null;
return true;
});
}
return Futures.immediateFuture(null);
return Futures.immediateFuture(false);
}, service);
final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>();
Futures.addCallback(removedLatestFuture, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void result) {
if (query.getRewriteLatestIfDeleted()) {
ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
if (isRemove) {
return getNewLatestEntryFuture(tenantId, entityId, query);
}
return Futures.immediateFuture(null);
}, service);
try {
resultFuture.set(savedLatestFuture.get());
} catch (InterruptedException | ExecutionException e) {
log.warn("Could not get latest saved value for [{}], {}", entityId, query.getKey(), e);
}
} else {
resultFuture.set(null);
}
}
@Override
public void onFailure(Throwable t) {
log.warn("[{}] Failed to process remove of the latest value", entityId, t);
return Futures.transformAsync(removedLatestFuture, isRemoved -> {
if (isRemoved && query.getRewriteLatestIfDeleted()) {
return getNewLatestEntryFuture(tenantId, entityId, query);
}
return Futures.immediateFuture(new TsKvLatestRemovingResult(query.getKey(), isRemoved));
}, MoreExecutors.directExecutor());
return resultFuture;
}
protected ListenableFuture<List<TsKvEntry>> getFindAllLatestFuture(EntityId entityId) {

15
dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java

@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -218,10 +219,10 @@ public class BaseTimeseriesService implements TimeseriesService {
}
@Override
public ListenableFuture<List<Void>> remove(TenantId tenantId, EntityId entityId, List<DeleteTsKvQuery> deleteTsKvQueries) {
public ListenableFuture<List<TsKvLatestRemovingResult>> remove(TenantId tenantId, EntityId entityId, List<DeleteTsKvQuery> deleteTsKvQueries) {
validate(entityId);
deleteTsKvQueries.forEach(BaseTimeseriesService::validate);
List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(deleteTsKvQueries.size() * DELETES_PER_ENTRY);
List<ListenableFuture<TsKvLatestRemovingResult>> futures = Lists.newArrayListWithExpectedSize(deleteTsKvQueries.size() * DELETES_PER_ENTRY);
for (DeleteTsKvQuery tsKvQuery : deleteTsKvQueries) {
deleteAndRegisterFutures(tenantId, futures, entityId, tsKvQuery);
}
@ -229,9 +230,9 @@ public class BaseTimeseriesService implements TimeseriesService {
}
@Override
public ListenableFuture<List<Void>> removeLatest(TenantId tenantId, EntityId entityId, Collection<String> keys) {
public ListenableFuture<List<TsKvLatestRemovingResult>> removeLatest(TenantId tenantId, EntityId entityId, Collection<String> keys) {
validate(entityId);
List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(keys.size());
List<ListenableFuture<TsKvLatestRemovingResult>> futures = Lists.newArrayListWithExpectedSize(keys.size());
for (String key : keys) {
DeleteTsKvQuery query = new BaseDeleteTsKvQuery(key, 0, System.currentTimeMillis(), false);
futures.add(timeseriesLatestDao.removeLatest(tenantId, entityId, query));
@ -252,10 +253,10 @@ public class BaseTimeseriesService implements TimeseriesService {
}, MoreExecutors.directExecutor());
}
private void deleteAndRegisterFutures(TenantId tenantId, List<ListenableFuture<Void>> futures, EntityId entityId, DeleteTsKvQuery query) {
futures.add(timeseriesDao.remove(tenantId, entityId, query));
private void deleteAndRegisterFutures(TenantId tenantId, List<ListenableFuture<TsKvLatestRemovingResult>> futures, EntityId entityId, DeleteTsKvQuery query) {
futures.add(Futures.transform(timeseriesDao.remove(tenantId, entityId, query), v -> null, MoreExecutors.directExecutor()));
futures.add(timeseriesLatestDao.removeLatest(tenantId, entityId, query));
futures.add(timeseriesDao.removePartition(tenantId, entityId, query));
futures.add(Futures.transform(timeseriesDao.removePartition(tenantId, entityId, query), v -> null, MoreExecutors.directExecutor()));
}
private static void validate(EntityId entityId) {

46
dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java

@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.nosql.TbResultSet;
@ -114,7 +115,7 @@ public class CassandraBaseTimeseriesLatestDao extends AbstractCassandraBaseTimes
}
@Override
public ListenableFuture<Void> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
public ListenableFuture<TsKvLatestRemovingResult> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
ListenableFuture<TsKvEntry> latestEntryFuture = findLatest(tenantId, entityId, query.getKey());
ListenableFuture<Boolean> booleanFuture = Futures.transform(latestEntryFuture, latestEntry -> {
@ -127,44 +128,22 @@ public class CassandraBaseTimeseriesLatestDao extends AbstractCassandraBaseTimes
return false;
}, readResultsProcessingExecutor);
ListenableFuture<Void> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
ListenableFuture<Boolean> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
if (isRemove) {
return deleteLatest(tenantId, entityId, query.getKey());
return Futures.transform(deleteLatest(tenantId, entityId, query.getKey()), res -> true, MoreExecutors.directExecutor());
}
return Futures.immediateFuture(null);
return Futures.immediateFuture(false);
}, readResultsProcessingExecutor);
final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>();
Futures.addCallback(removedLatestFuture, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void result) {
if (query.getRewriteLatestIfDeleted()) {
ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
if (isRemove) {
return getNewLatestEntryFuture(tenantId, entityId, query);
}
return Futures.immediateFuture(null);
}, readResultsProcessingExecutor);
try {
resultFuture.set(savedLatestFuture.get());
} catch (InterruptedException | ExecutionException e) {
log.warn("Could not get latest saved value for [{}], {}", entityId, query.getKey(), e);
}
} else {
resultFuture.set(null);
}
}
@Override
public void onFailure(Throwable t) {
log.warn("[{}] Failed to process remove of the latest value", entityId, t);
return Futures.transformAsync(removedLatestFuture, isRemoved -> {
if (isRemoved && query.getRewriteLatestIfDeleted()) {
return getNewLatestEntryFuture(tenantId, entityId, query);
}
return Futures.immediateFuture(new TsKvLatestRemovingResult(query.getKey(), isRemoved));
}, MoreExecutors.directExecutor());
return resultFuture;
}
private ListenableFuture<Void> getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
private ListenableFuture<TsKvLatestRemovingResult> getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
long startTs = 0;
long endTs = query.getStartTs() - 1;
ReadTsKvQuery findNewLatestQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, endTs - startTs, 1,
@ -173,11 +152,12 @@ public class CassandraBaseTimeseriesLatestDao extends AbstractCassandraBaseTimes
return Futures.transformAsync(future, entryList -> {
if (entryList.size() == 1) {
return saveLatest(tenantId, entityId, entryList.get(0));
TsKvEntry entry = entryList.get(0);
return Futures.transform(saveLatest(tenantId, entityId, entryList.get(0)), v -> new TsKvLatestRemovingResult(entry), MoreExecutors.directExecutor());
} else {
log.trace("Could not find new latest value for [{}], key - {}", entityId, query.getKey());
}
return Futures.immediateFuture(null);
return Futures.immediateFuture(new TsKvLatestRemovingResult(query.getKey(), true));
}, readResultsProcessingExecutor);
}

3
dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesLatestDao.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import java.util.List;
@ -32,7 +33,7 @@ public interface TimeseriesLatestDao {
ListenableFuture<Void> saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry);
ListenableFuture<Void> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query);
ListenableFuture<TsKvLatestRemovingResult> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query);
List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);

7
dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java

@ -44,6 +44,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@ -58,7 +59,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{(int) DATA_SIZE});
private static final String URL = "http://firmware.test.org";
private IdComparator<OtaPackageInfo> idComparator = new IdComparator<>();
private final IdComparator<OtaPackageInfo> idComparator = new IdComparator<>();
private TenantId tenantId;
@ -565,7 +566,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
Collections.sort(firmwares, idComparator);
Collections.sort(loadedFirmwares, idComparator);
Assert.assertEquals(firmwares, loadedFirmwares);
assertThat(firmwares).isEqualTo(loadedFirmwares);
otaPackageService.deleteOtaPackagesByTenantId(tenantId);
@ -620,7 +621,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
Collections.sort(firmwares, idComparator);
Collections.sort(loadedFirmwares, idComparator);
Assert.assertEquals(firmwares, loadedFirmwares);
assertThat(firmwares).isEqualTo(loadedFirmwares);
otaPackageService.deleteOtaPackagesByTenantId(tenantId);

2
pom.xml

@ -47,7 +47,7 @@
<jedis.version>3.3.0</jedis.version>
<jjwt.version>0.7.0</jjwt.version>
<slf4j.version>1.7.32</slf4j.version>
<log4j.version>2.16.0</log4j.version>
<log4j.version>2.17.0</log4j.version>
<logback.version>1.2.6</logback.version>
<rat.version>0.10</rat.version>
<cassandra.version>4.10.0</cassandra.version>

3
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import java.util.Collection;
@ -56,5 +57,5 @@ public interface RuleEngineTelemetryService {
void deleteAllLatest(TenantId tenantId, EntityId entityId, FutureCallback<Collection<String>> callback);
void deleteTimeseriesAndNotify(TenantId tenantId, EntityId entityId, List<String> keys, List<DeleteTsKvQuery> deleteTsKvQueries, FutureCallback<Void> callback);
}

1
ui-ngx/src/app/core/api/widget-api.models.ts

@ -150,6 +150,7 @@ export type StateControllerHolder = () => IStateController;
export interface IStateController {
dashboardCtrl: IDashboardController;
getStateParams(): StateParams;
stateChanged(): Observable<string>;
getStateParamsByStateId(stateId: string): StateParams;
openState(id: string, params?: StateParams, openRightLayout?: boolean): void;
updateState(id?: string, params?: StateParams, openRightLayout?: boolean): void;

4
ui-ngx/src/app/core/utils.ts

@ -114,6 +114,10 @@ export function isNumeric(value: any): boolean {
return (value - parseFloat(value) + 1) >= 0;
}
export function isBoolean(value: any): boolean {
return typeof value === 'boolean';
}
export function isString(value: any): boolean {
return typeof value === 'string';
}

37
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -17,7 +17,7 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, ElementRef,
Component, ElementRef, HostBinding,
Inject,
Injector,
Input,
@ -48,7 +48,7 @@ import {
} from '@app/shared/models/dashboard.models';
import { WINDOW } from '@core/services/window.service';
import { WindowMessage } from '@shared/models/window-message.model';
import { deepClone, isDefined, isDefinedAndNotNull } from '@app/core/utils';
import { deepClone, guid, hashCode, isDefined, isDefinedAndNotNull, isNotEmptyStr } from '@app/core/utils';
import {
DashboardContext,
DashboardPageLayout,
@ -132,6 +132,8 @@ import {
DashboardImageDialogData, DashboardImageDialogResult
} from '@home/components/dashboard-page/dashboard-image-dialog.component';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import cssjs from '@core/css/css';
import { DOCUMENT } from '@angular/common';
// @dynamic
@Component({
@ -147,6 +149,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
authUser: AuthUser = this.authState.authUser;
@HostBinding('class')
dashboardPageClass: string;
@Input()
embedded = false;
@ -228,6 +233,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
dashboardTimewindow: null,
state: null,
stateController: null,
stateChanged: null,
aliasController: null,
runChangeDetection: this.runChangeDetection.bind(this)
};
@ -302,6 +308,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
constructor(protected store: Store<AppState>,
@Inject(WINDOW) private window: Window,
@Inject(DOCUMENT) private document: Document,
private breakpointObserver: BreakpointObserver,
private route: ActivatedRoute,
private router: Router,
@ -419,6 +426,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.dashboardConfiguration.entityAliases,
this.dashboardConfiguration.filters);
this.updateDashboardCss();
if (this.widgetEditMode) {
const message: WindowMessage = {
type: 'widgetEditModeInited'
@ -427,6 +436,27 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
}
}
private updateDashboardCss() {
this.cleanupDashboardCss();
const cssString = this.dashboardConfiguration.settings.dashboardCss;
if (isNotEmptyStr(cssString)) {
const cssParser = new cssjs();
cssParser.testMode = false;
this.dashboardPageClass = 'tb-dashboard-page-css-' + guid();
cssParser.cssPreviewNamespace = this.dashboardPageClass;
cssParser.createStyleElement(this.dashboardPageClass, cssString);
}
}
private cleanupDashboardCss() {
if (this.dashboardPageClass) {
const el = this.document.getElementById(this.dashboardPageClass);
if (el) {
el.parentNode.removeChild(el);
}
}
}
private reset() {
this.dashboard = null;
this.translatedDashboardTitle = null;
@ -466,6 +496,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
}
ngOnDestroy(): void {
this.cleanupDashboardCss();
if (this.isMobileApp && this.syncStateWithQueryParam) {
this.mobileService.unregisterToggleLayoutFunction();
}
@ -729,6 +760,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
if (data) {
this.dashboard.configuration.settings = data.settings;
this.dashboardLogoCache = undefined;
this.updateDashboardCss();
const newGridSettings = data.gridSettings;
if (newGridSettings) {
const layout = this.dashboard.configuration.states[layoutKeys.state].layouts[layoutKeys.layout];
@ -893,6 +925,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.dashboardLogoCache = undefined;
this.dashboardConfiguration = this.dashboard.configuration;
this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow;
this.updateDashboardCss();
this.entityAliasesUpdated();
this.filtersUpdated();
this.updateLayouts();

2
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.models.ts

@ -20,6 +20,7 @@ import { Timewindow } from '@shared/models/time/time.models';
import { IAliasController, IStateController } from '@core/api/widget-api.models';
import { ILayoutController } from './layout/layout.models';
import { DashboardContextMenuItem, WidgetContextMenuItem } from '@home/models/dashboard-component.models';
import { Observable } from 'rxjs';
export declare type DashboardPageScope = 'tenant' | 'customer';
@ -30,6 +31,7 @@ export interface DashboardContext {
dashboardTimewindow: Timewindow;
aliasController: IAliasController;
stateController: IStateController;
stateChanged: Observable<string>;
runChangeDetection: () => void;
}

13
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html

@ -87,6 +87,19 @@
{{ 'dashboard.display-update-dashboard-image' | translate }}
</mat-slide-toggle>
</fieldset>
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header>
<mat-panel-description fxLayoutAlign="end" translate>
dashboard.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-css
label="{{ 'dashboard.dashboard-css' | translate }}"
formControlName="dashboardCss"
></tb-css>
</ng-template>
</mat-expansion-panel>
</div>
<div *ngIf="gridSettings" [formGroup]="gridSettingsFormGroup" fxLayout="column">
<fieldset class="fields-group" fxLayout="column" fxLayoutGap="8px">

29
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.scss

@ -30,4 +30,33 @@
.mat-slide-toggle-content {
white-space: normal;
}
.mat-expansion-panel {
&.tb-settings {
box-shadow: none;
.mat-content {
overflow: visible;
}
.mat-expansion-panel-header {
padding: 0;
&:hover {
background: none;
}
.mat-expansion-indicator {
padding: 2px;
}
}
.mat-expansion-panel-header-description {
align-items: center;
}
.mat-expansion-panel-body{
padding: 0;
}
.tb-css-content-panel {
margin: 0 0 8px;
}
}
.mat-expansion-panel-content {
font: inherit;
}
}
}

3
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts

@ -97,7 +97,8 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
disabled: hideToolbar}, []],
showUpdateDashboardImage: [
{value: isUndefined(this.settings.showUpdateDashboardImage) ? true : this.settings.showUpdateDashboardImage,
disabled: hideToolbar}, []]
disabled: hideToolbar}, []],
dashboardCss: [isUndefined(this.settings.dashboardCss) ? '' : this.settings.dashboardCss, []],
});
this.settingsFormGroup.get('stateControllerId').valueChanges.subscribe(
(stateControllerId: StateControllerId) => {

25
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-state.component.html

@ -0,0 +1,25 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<tb-dashboard-page
[embedded]="true"
[syncStateWithQueryParam]="false"
[hideToolbar]="true"
[currentState]="currentState"
[dashboard]="dashboard"
[parentDashboard]="parentDashboard">
</tb-dashboard-page>

93
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-state.component.ts

@ -0,0 +1,93 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Dashboard } from '@shared/models/dashboard.models';
import { StateObject } from '@core/api/widget-api.models';
import { updateEntityParams, WidgetContext } from '@home/models/widget-component.models';
import { deepClone, objToBase64 } from '@core/utils';
import { IDashboardComponent } from '@home/models/dashboard-component.models';
import { EntityId } from '@shared/models/id/entity-id';
import { Subscription } from 'rxjs';
@Component({
selector: 'tb-dashboard-state',
templateUrl: './dashboard-state.component.html',
styleUrls: []
})
export class DashboardStateComponent extends PageComponent implements OnInit, OnDestroy {
@Input()
ctx: WidgetContext;
@Input()
stateId: string;
@Input()
syncParentStateParams = false;
@Input()
entityParamName: string;
@Input()
entityId: EntityId;
currentState: string;
dashboard: Dashboard;
parentDashboard: IDashboardComponent;
private stateSubscription: Subscription;
constructor(protected store: Store<AppState>,
private cd: ChangeDetectorRef) {
super(store);
}
ngOnInit(): void {
this.dashboard = deepClone(this.ctx.stateController.dashboardCtrl.dashboardCtx.getDashboard());
this.updateCurrentState();
this.parentDashboard = this.ctx.parentDashboard ?
this.ctx.parentDashboard : this.ctx.dashboard;
if (this.syncParentStateParams) {
this.stateSubscription = this.ctx.stateController.dashboardCtrl.dashboardCtx.stateChanged.subscribe(() => {
this.updateCurrentState();
this.cd.markForCheck();
});
}
}
ngOnDestroy(): void {
if (this.stateSubscription) {
this.stateSubscription.unsubscribe();
}
}
private updateCurrentState(): void {
const stateObject: StateObject = {};
const params = deepClone(this.ctx.stateController.getStateParams());
updateEntityParams(params, this.entityParamName, this.entityId);
stateObject.params = params;
if (this.stateId) {
stateObject.id = this.stateId;
}
this.currentState = objToBase64([stateObject]);
}
}

2
ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts

@ -54,7 +54,7 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
super.ngOnDestroy();
}
protected init() {
public init() {
if (this.preservedState) {
this.stateObject = this.preservedState;
setTimeout(() => {

2
ui-ngx/src/app/modules/home/components/dashboard-page/states/entity-state-controller.component.ts

@ -58,7 +58,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
super.ngOnDestroy();
}
protected init() {
public init() {
if (this.preservedState) {
this.stateObject = this.preservedState;
this.selectedStateIndex = this.stateObject.length - 1;

13
ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts

@ -17,7 +17,7 @@
import { IStateControllerComponent, StateControllerState } from '@home/components/dashboard-page/states/state-controller.models';
import { IDashboardController } from '../dashboard-page.models';
import { DashboardState } from '@app/shared/models/dashboard.models';
import { Subscription } from 'rxjs';
import { Observable, Subject, Subscription } from 'rxjs';
import { NgZone, OnDestroy, OnInit, Directive } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { StatesControllerService } from '@home/components/dashboard-page/states/states-controller.service';
@ -27,6 +27,7 @@ import { StateObject, StateParams } from '@app/core/api/widget-api.models';
@Directive()
export abstract class StateControllerComponent implements IStateControllerComponent, OnInit, OnDestroy {
private stateChangedSubject = new Subject<string>();
stateObject: StateControllerState = [];
dashboardCtrl: IDashboardController;
preservedState: any;
@ -108,6 +109,7 @@ export abstract class StateControllerComponent implements IStateControllerCompon
const newState = this.decodeStateParam(paramMap.get('state'));
if (this.currentState !== newState) {
this.currentState = newState;
this.stateChangedSubject.next(this.currentState);
if (this.inited) {
this.onStateChanged();
}
@ -124,6 +126,7 @@ export abstract class StateControllerComponent implements IStateControllerCompon
subscription.unsubscribe();
});
this.rxSubscriptions.length = 0;
this.stateChangedSubject.complete();
}
protected updateStateParam(newState: string, replaceCurrentHistoryUrl = false) {
@ -142,6 +145,11 @@ export abstract class StateControllerComponent implements IStateControllerCompon
});
});
}
this.stateChangedSubject.next(this.currentState);
}
public stateChanged(): Observable<string> {
return this.stateChangedSubject.asObservable();
}
public openRightLayout(): void {
@ -159,6 +167,7 @@ export abstract class StateControllerComponent implements IStateControllerCompon
public reInit() {
this.preservedState = null;
this.currentState = this.decodeStateParam(this.route.snapshot.queryParamMap.get('state'));
this.stateChangedSubject.next(this.currentState);
this.init();
}
@ -166,7 +175,7 @@ export abstract class StateControllerComponent implements IStateControllerCompon
return stateURI !== null ? decodeURIComponent(stateURI) : null;
}
protected abstract init();
public abstract init();
protected abstract onMobileChanged();

1
ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.models.ts

@ -30,4 +30,5 @@ export interface IStateControllerComponent extends IStateController {
dashboardId: string;
preservedState: any;
reInit(): void;
init(): void;
}

14
ui-ngx/src/app/modules/home/components/dashboard-page/states/states-component.directive.ts

@ -28,6 +28,7 @@ import { DashboardState } from '@shared/models/dashboard.models';
import { IDashboardController } from '@home/components/dashboard-page/dashboard-page.models';
import { StatesControllerService } from '@home/components/dashboard-page/states/states-controller.service';
import { IStateControllerComponent } from '@home/components/dashboard-page/states/state-controller.models';
import { Subject } from 'rxjs';
@Directive({
// tslint:disable-next-line:directive-selector
@ -62,6 +63,8 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
stateControllerComponentRef: ComponentRef<IStateControllerComponent>;
stateControllerComponent: IStateControllerComponent;
private stateChangedSubject = new Subject<string>();
constructor(private viewContainerRef: ViewContainerRef,
private statesControllerService: StatesControllerService) {
}
@ -72,10 +75,12 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
ngOnDestroy(): void {
this.destroy();
this.stateChangedSubject.complete();
}
ngOnChanges(changes: SimpleChanges): void {
let reInitController = false;
let initController = false;
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
@ -92,12 +97,15 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
this.stateControllerComponent.state = this.state;
} else if (propName === 'currentState') {
this.stateControllerComponent.currentState = this.currentState;
initController = true;
} else if (propName === 'syncStateWithQueryParam') {
this.stateControllerComponent.syncStateWithQueryParam = this.syncStateWithQueryParam;
}
}
}
if (reInitController) {
if (initController) {
this.stateControllerComponent.init();
} else if (reInitController) {
this.stateControllerComponent.reInit();
}
}
@ -119,6 +127,10 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
this.stateControllerComponentRef = this.viewContainerRef.createComponent(stateControllerFactory);
this.stateControllerComponent = this.stateControllerComponentRef.instance;
this.dashboardCtrl.dashboardCtx.stateController = this.stateControllerComponent;
this.dashboardCtrl.dashboardCtx.stateChanged = this.stateChangedSubject.asObservable();
this.stateControllerComponent.stateChanged().subscribe((state) => {
this.stateChangedSubject.next(state);
});
this.stateControllerComponent.preservedState = preservedState;
this.stateControllerComponent.dashboardCtrl = this.dashboardCtrl;
this.stateControllerComponent.stateControllerInstanceId = stateControllerInstanceId;

3
ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html

@ -63,8 +63,9 @@
</mat-menu>
<div [ngClass]="dashboardClass" id="gridster-background" style="height: auto; min-height: 100%; display: inline;">
<gridster #gridster id="gridster-child" [options]="gridsterOpts">
<gridster-item [item]="widget" [ngClass]="{'tb-noselect': isEdit}" *ngFor="let widget of dashboardWidgets">
<gridster-item #gridsterItem [item]="widget" [ngClass]="{'tb-noselect': isEdit}" *ngFor="let widget of dashboardWidgets">
<tb-widget-container
[gridsterItem]="gridsterItem"
[widget]="widget"
[dashboardWidgets]="dashboardWidgets"
[dashboardStyle]="dashboardStyle"

9
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -143,8 +143,10 @@ import { DeviceCredentialsModule } from '@home/components/device/device-credenti
import { DeviceProfileCommonModule } from '@home/components/profile/device/common/device-profile-common.module';
import {
COMPLEX_FILTER_PREDICATE_DIALOG_COMPONENT_TOKEN,
DASHBOARD_PAGE_COMPONENT_TOKEN
DASHBOARD_PAGE_COMPONENT_TOKEN,
HOME_COMPONENTS_MODULE_TOKEN
} from '@home/components/tokens';
import { DashboardStateComponent } from '@home/components/dashboard-page/dashboard-state.component';
@NgModule({
declarations:
@ -252,6 +254,7 @@ import {
TwilioSmsProviderConfigurationComponent,
DashboardToolbarComponent,
DashboardPageComponent,
DashboardStateComponent,
DashboardLayoutComponent,
EditWidgetComponent,
DashboardWidgetSelectComponent,
@ -363,6 +366,7 @@ import {
TwilioSmsProviderConfigurationComponent,
DashboardToolbarComponent,
DashboardPageComponent,
DashboardStateComponent,
DashboardLayoutComponent,
EditWidgetComponent,
DashboardWidgetSelectComponent,
@ -381,7 +385,8 @@ import {
ImportExportService,
{provide: EMBED_DASHBOARD_DIALOG_TOKEN, useValue: EmbedDashboardDialogComponent},
{provide: COMPLEX_FILTER_PREDICATE_DIALOG_COMPONENT_TOKEN, useValue: ComplexFilterPredicateDialogComponent},
{provide: DASHBOARD_PAGE_COMPONENT_TOKEN, useValue: DashboardPageComponent}
{provide: DASHBOARD_PAGE_COMPONENT_TOKEN, useValue: DashboardPageComponent},
{provide: HOME_COMPONENTS_MODULE_TOKEN, useValue: HomeComponentsModule }
]
})
export class HomeComponentsModule { }

3
ui-ngx/src/app/modules/home/components/tokens.ts

@ -20,6 +20,9 @@ import { ComponentType } from '@angular/cdk/portal';
export const SHARED_HOME_COMPONENTS_MODULE_TOKEN: InjectionToken<Type<any>> =
new InjectionToken<Type<any>>('SHARED_HOME_COMPONENTS_MODULE_TOKEN');
export const HOME_COMPONENTS_MODULE_TOKEN: InjectionToken<Type<any>> =
new InjectionToken<Type<any>>('HOME_COMPONENTS_MODULE_TOKEN');
export const COMPLEX_FILTER_PREDICATE_DIALOG_COMPONENT_TOKEN: InjectionToken<ComponentType<any>> =
new InjectionToken<ComponentType<any>>('COMPLEX_FILTER_PREDICATE_DIALOG_COMPONENT_TOKEN');

43
ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import L, { FeatureGroup, Icon, LatLngBounds, LatLngTuple, Projection } from 'leaflet';
import L, { FeatureGroup, LatLngBounds, LatLngTuple, Projection } from 'leaflet';
import tinycolor from 'tinycolor2';
import 'leaflet-providers';
import { MarkerClusterGroup, MarkerClusterGroupOptions } from 'leaflet.markercluster/dist/leaflet.markercluster';
@ -24,7 +24,7 @@ import {
defaultSettings,
FormattedData,
MapSettings,
MarkerSettings,
MarkerSettings, MarkerIconInfo, MarkerImageInfo,
PolygonSettings,
PolylineSettings,
ReplaceInfo,
@ -64,11 +64,13 @@ export default abstract class LeafletMap {
points: FeatureGroup;
markersData: FormattedData[] = [];
polygonsData: FormattedData[] = [];
defaultMarkerIconInfo: { size: number[], icon: Icon };
defaultMarkerIconInfo: MarkerIconInfo;
loadingDiv: JQuery<HTMLElement>;
loading = false;
replaceInfoLabelMarker: Array<ReplaceInfo> = [];
markerLabelText: string;
polygonLabelText: string;
replaceInfoLabelPolygon: Array<ReplaceInfo> = [];
replaceInfoTooltipMarker: Array<ReplaceInfo> = [];
markerTooltipText: string;
drawRoutes: boolean;
@ -248,8 +250,16 @@ export default abstract class LeafletMap {
this.saveLocation(this.selectedEntity, this.convertToCustomFormat(e.layer.getLatLng())).subscribe(() => {
});
} else if (e.shape === 'tbRectangle' || e.shape === 'tbPolygon') {
// @ts-ignore
this.saveLocation(this.selectedEntity, this.convertPolygonToCustomFormat(e.layer.getLatLngs()[0])).subscribe(() => {
let coordinates;
if (e.shape === 'tbRectangle') {
// @ts-ignore
const bounds: L.LatLngBounds = e.layer.getBounds();
coordinates = [bounds.getNorthWest(), bounds.getSouthEast()];
} else {
// @ts-ignore
coordinates = e.layer.getLatLngs()[0];
}
this.saveLocation(this.selectedEntity, this.convertPolygonToCustomFormat(coordinates)).subscribe(() => {
});
}
// @ts-ignore
@ -281,7 +291,7 @@ export default abstract class LeafletMap {
result = iterator.next();
}
this.saveLocation(result.value.data, this.convertToCustomFormat(null)).subscribe(() => {});
} else if (e.shape === 'Polygon') {
} else if (e.shape === 'Polygon' || e.shape === 'Rectangle') {
const iterator = this.polygons.values();
let result = iterator.next();
while (!result.done && e.layer !== result.value.leafletPoly) {
@ -333,6 +343,7 @@ export default abstract class LeafletMap {
this.map.scrollWheelZoom.disable();
}
if (this.options.draggableMarker || this.editPolygons) {
map.pm.setGlobalOptions({ snappable: false } as L.PM.GlobalOptions);
this.addEditControl();
} else {
this.map.pm.disableDraw();
@ -525,16 +536,16 @@ export default abstract class LeafletMap {
let m: Marker;
rawMarkers.forEach(data => {
if (data.rotationAngle || data.rotationAngle === 0) {
const currentImage = this.options.useMarkerImageFunction ?
const currentImage: MarkerImageInfo = this.options.useMarkerImageFunction ?
safeExecute(this.options.markerImageFunction,
[data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage;
const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : '';
this.options.icon = L.divIcon({
this.options.icon = { icon: L.divIcon({
html: `<div class="arrow"
style="transform: translate(-10px, -10px)
rotate(${data.rotationAngle}deg);
${style}"><div>`
});
style="transform: translate(-10px, -10px)
rotate(${data.rotationAngle}deg);
${style}"><div>`
}), size: [30, 30]};
} else {
this.options.icon = null;
}
@ -764,6 +775,14 @@ export default abstract class LeafletMap {
if (coordinates.length === 1) {
coordinates = coordinates[0];
}
if (e.shape === 'Rectangle' && coordinates.length === 1) {
// @ts-ignore
const bounds: L.LatLngBounds = e.layer.getBounds();
const boundsArray = [bounds.getNorthWest(), bounds.getNorthEast(), bounds.getSouthWest(), bounds.getSouthEast()];
if (coordinates.every(point => boundsArray.find(boundPoint => boundPoint.equals(point)) !== undefined)) {
coordinates = [bounds.getNorthWest(), bounds.getSouthEast()];
}
}
this.saveLocation(data, this.convertPolygonToCustomFormat(coordinates)).subscribe(() => {});
}

35
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts

@ -17,13 +17,15 @@
import { Datasource } from '@app/shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models';
import tinycolor from 'tinycolor2';
import { BaseIconOptions, Icon } from 'leaflet';
export const DEFAULT_MAP_PAGE_SIZE = 16384;
export const DEFAULT_ZOOM_LEVEL = 8;
export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => MarkerImageInfo;
export type PosFuncton = (origXPos, origYPos) => { x, y };
export type MarkerIconReadyFunction = (icon: MarkerIconInfo) => void;
export type MapSettings = {
draggableMarker: boolean;
@ -51,7 +53,6 @@ export type MapSettings = {
useDefaultCenterPosition?: boolean;
gmDefaultMapType?: string;
useLabelFunction: boolean;
icon?: any;
zoomOnClick: boolean,
maxZoom: number,
showCoverageOnHover: boolean,
@ -73,10 +74,22 @@ export enum MapProviders {
tencent = 'tencent-map'
}
export type MarkerImageInfo = {
url: string;
size: number;
markerOffset?: [number, number];
tooltipOffset?: [number, number];
};
export type MarkerIconInfo = {
icon: Icon<BaseIconOptions>;
size: [number, number];
};
export type MarkerSettings = {
tooltipPattern?: any;
tooltipAction: { [name: string]: actionsHandler };
icon?: any;
icon?: MarkerIconInfo;
showLabel?: boolean;
label: string;
labelColor: string;
@ -91,7 +104,7 @@ export type MarkerSettings = {
autocloseTooltip: boolean;
showTooltipAction: string;
useClusterMarkers: boolean;
currentImage?: string;
currentImage?: MarkerImageInfo;
useMarkerImageFunction?: boolean;
markerImages?: string[];
markerImageSize: number;
@ -104,6 +117,8 @@ export type MarkerSettings = {
markerImageFunction?: MarkerImageFunction;
markerOffsetX: number;
markerOffsetY: number;
tooltipOffsetX: number;
tooltipOffsetY: number;
};
export interface FormattedData {
@ -131,6 +146,11 @@ export type PolygonSettings = {
polygonStrokeWeight: number;
polygonStrokeColor: string;
polygonColor: string;
showPolygonLabel?: boolean;
polygonLabel: string;
polygonLabelColor: string;
polygonLabelText: string;
usePolygonLabelFunction: boolean;
showPolygonTooltip: boolean;
autocloseTooltip: boolean;
showTooltipAction: string;
@ -139,8 +159,11 @@ export type PolygonSettings = {
usePolygonTooltipFunction: boolean;
polygonClick: { [name: string]: actionsHandler };
usePolygonColorFunction: boolean;
usePolygonStrokeColorFunction: boolean;
polygonTooltipFunction: GenericFunction;
polygonColorFunction?: GenericFunction;
polygonStrokeColorFunction?: GenericFunction;
polygonLabelFunction?: GenericFunction;
editablePolygon: boolean;
};
@ -215,6 +238,8 @@ export const defaultSettings: any = {
yPosKeyName: 'yPos',
markerOffsetX: 0.5,
markerOffsetY: 1,
tooltipOffsetX: 0,
tooltipOffsetY: -1,
latKeyName: 'latitude',
lngKeyName: 'longitude',
polygonKeyName: 'coordinates',
@ -227,8 +252,10 @@ export const defaultSettings: any = {
showPolygon: false,
labelColor: '#000000',
color: '#FE7569',
showPolygonLabel: false,
polygonColor: '#0000ff',
polygonStrokeColor: '#fe0001',
polygonLabelColor: '#000000',
polygonOpacity: 0.5,
polygonStrokeOpacity: 1,
polygonStrokeWeight: 1,

5
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts

@ -258,17 +258,20 @@ export class MapWidgetController implements MapWidgetInterface {
settings.mapProviderHere = hereProviders[0];
}
}
const customOptions = {
const customOptions: Partial<UnitedMapSettings> = {
provider: this.provider,
mapUrl: settings?.mapImageUrl,
labelFunction: parseFunction(settings.labelFunction, functionParams),
tooltipFunction: parseFunction(settings.tooltipFunction, functionParams),
colorFunction: parseFunction(settings.colorFunction, functionParams),
colorPointFunction: parseFunction(settings.colorPointFunction, functionParams),
polygonLabelFunction: parseFunction(settings.polygonLabelFunction, functionParams),
polygonColorFunction: parseFunction(settings.polygonColorFunction, functionParams),
polygonStrokeColorFunction: parseFunction(settings.polygonStrokeColorFunction, functionParams),
polygonTooltipFunction: parseFunction(settings.polygonTooltipFunction, functionParams),
markerImageFunction: parseFunction(settings.markerImageFunction, ['data', 'images', 'dsData', 'dsIndex']),
labelColor: this.ctx.widgetConfig.color,
polygonLabelColor: this.ctx.widgetConfig.color,
polygonKeyName: settings.polKeyName ? settings.polKeyName : settings.polygonKeyName,
tooltipPattern: settings.tooltipPattern ||
'<b>${entityName}</b><br/><br/><b>Latitude:</b> ${' +

7
ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts

@ -56,3 +56,10 @@ export function bindPopupActions(popup: L.Popup, settings: MarkerSettings | Poly
}
});
}
export function isCutPolygon(data): boolean {
if (Array.isArray(data[0]) && Array.isArray(data[0][0])) {
return true;
}
return false;
}

9
ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.scss

@ -29,6 +29,15 @@
box-shadow: none;
}
.tb-polygon-label {
border: none;
background: none;
box-shadow: none;
&:before {
content: none;
}
}
.leaflet-container {
background-color: white;
}

54
ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts

@ -15,7 +15,7 @@
///
import L, { Icon, LeafletMouseEvent } from 'leaflet';
import { FormattedData, MarkerSettings } from './map-models';
import { FormattedData, MarkerIconInfo, MarkerIconReadyFunction, MarkerImageInfo, MarkerSettings } from './map-models';
import {
bindPopupActions,
createTooltip,
@ -33,6 +33,7 @@ import LeafletMap from './leaflet-map';
export class Marker {
leafletMarker: L.Marker;
labelOffset: L.LatLngTuple;
tooltipOffset: L.LatLngTuple;
markerOffset: L.LatLngTuple;
tooltip: L.Popup;
@ -49,11 +50,12 @@ export class Marker {
isDefined(settings.markerOffsetY) ? settings.markerOffsetY : 1,
];
this.createMarkerIcon((iconInfo) => {
this.leafletMarker.setIcon(iconInfo.icon);
this.tooltipOffset = [0, -iconInfo.size[1] * this.markerOffset[1] + 10];
this.updateMarkerLabel(settings);
});
this.tooltipOffset = [
isDefined(settings.tooltipOffsetX) ? settings.tooltipOffsetX : 0,
isDefined(settings.tooltipOffsetY) ? settings.tooltipOffsetY : -1,
];
this.updateMarkerIcon(settings);
if (settings.showTooltip) {
this.tooltip = createTooltip(this.leafletMarker, settings, data.$datasource);
@ -111,7 +113,7 @@ export class Marker {
}
settings.labelText = fillPattern(this.map.markerLabelText, this.map.replaceInfoLabelMarker, this.data);
this.leafletMarker.bindTooltip(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`,
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset });
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.labelOffset });
}
}
@ -124,20 +126,22 @@ export class Marker {
updateMarkerIcon(settings: MarkerSettings) {
this.createMarkerIcon((iconInfo) => {
this.leafletMarker.setIcon(iconInfo.icon);
this.tooltipOffset = [0, -iconInfo.size[1] * this.markerOffset[1] + 10];
const anchor = iconInfo.icon.options.iconAnchor;
if (anchor && Array.isArray(anchor)) {
this.labelOffset = [iconInfo.size[0] / 2 - anchor[0], 10 - anchor[1]];
} else {
this.labelOffset = [0, -iconInfo.size[1] * this.markerOffset[1] + 10];
}
this.updateMarkerLabel(settings);
});
}
createMarkerIcon(onMarkerIconReady) {
private createMarkerIcon(onMarkerIconReady: MarkerIconReadyFunction) {
if (this.settings.icon) {
onMarkerIconReady({
size: [30, 30],
icon: this.settings.icon,
});
return;
onMarkerIconReady(this.settings.icon);
return;
}
const currentImage = this.settings.useMarkerImageFunction ?
const currentImage: MarkerImageInfo = this.settings.useMarkerImageFunction ?
safeExecute(this.settings.markerImageFunction,
[this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage;
let currentColor = this.settings.tinyColor;
@ -161,13 +165,21 @@ export class Marker {
width = currentImage.size * aspect;
height = currentImage.size;
}
let iconAnchor = currentImage.markerOffset;
let popupAnchor = currentImage.tooltipOffset;
if (!iconAnchor) {
iconAnchor = [width * this.markerOffset[0], height * this.markerOffset[1]];
}
if (!popupAnchor) {
popupAnchor = [width * this.tooltipOffset[0], height * this.tooltipOffset[1]];
}
const icon = L.icon({
iconUrl: currentImage.url,
iconSize: [width, height],
iconAnchor: [width * this.markerOffset[0], height * this.markerOffset[1]],
popupAnchor: [0, -height]
iconAnchor,
popupAnchor
});
const iconInfo = {
const iconInfo: MarkerIconInfo = {
size: [width, height],
icon
};
@ -182,8 +194,8 @@ export class Marker {
}
}
createDefaultMarkerIcon(color: tinycolor.Instance, onMarkerIconReady) {
let icon: { size: number[], icon: Icon };
createDefaultMarkerIcon(color: tinycolor.Instance, onMarkerIconReady: MarkerIconReadyFunction) {
let icon: MarkerIconInfo;
if (!tinycolor.equals(color, this.settings.tinyColor)) {
icon = this.createColoredMarkerIcon(color);
} else {
@ -195,7 +207,7 @@ export class Marker {
onMarkerIconReady(icon);
}
createColoredMarkerIcon(color: tinycolor.Instance): { size: number[], icon: Icon } {
createColoredMarkerIcon(color: tinycolor.Instance): MarkerIconInfo {
return {
size: [21, 34],
icon: L.icon({

110
ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts

@ -15,9 +15,15 @@
///
import L, { LatLngExpression, LeafletMouseEvent } from 'leaflet';
import { createTooltip } from './maps-utils';
import { functionValueCalculator, parseWithTranslation, safeExecute } from './common-maps-utils';
import { FormattedData, PolygonSettings } from './map-models';
import { createTooltip, isCutPolygon } from './maps-utils';
import {
fillPattern,
functionValueCalculator,
parseWithTranslation,
processPattern,
safeExecute
} from './common-maps-utils';
import { FormattedData, MarkerSettings, PolygonSettings } from './map-models';
export class Polygon {
@ -26,38 +32,47 @@ export class Polygon {
data: FormattedData;
dataSources: FormattedData[];
constructor(public map, polyData: FormattedData, dataSources: FormattedData[], private settings: PolygonSettings, onDragendListener?) {
constructor(public map, data: FormattedData, dataSources: FormattedData[], private settings: PolygonSettings,
private onDragendListener?) {
this.dataSources = dataSources;
this.data = polyData;
this.data = data;
const polygonColor = this.getPolygonColor(settings);
this.leafletPoly = L.polygon(polyData[this.settings.polygonKeyName], {
const polygonStrokeColor = this.getPolygonStrokeColor(settings);
const polyData = data[this.settings.polygonKeyName];
const polyConstructor = isCutPolygon(polyData) || polyData.length > 2 ? L.polygon : L.rectangle;
this.leafletPoly = polyConstructor(polyData, {
fill: true,
fillColor: polygonColor,
color: settings.polygonStrokeColor,
color: polygonStrokeColor,
weight: settings.polygonStrokeWeight,
fillOpacity: settings.polygonOpacity,
opacity: settings.polygonStrokeOpacity,
pmIgnore: !settings.editablePolygon
}).addTo(this.map);
if (settings.editablePolygon && onDragendListener) {
this.leafletPoly.on('pm:edit', (e) => onDragendListener(e, this.data));
}
this.updateLabel(settings);
if (settings.showPolygonTooltip) {
this.tooltip = createTooltip(this.leafletPoly, settings, polyData.$datasource);
this.updateTooltip(polyData);
}
if (settings.polygonClick) {
this.leafletPoly.on('click', (event: LeafletMouseEvent) => {
for (const action in this.settings.polygonClick) {
if (typeof (this.settings.polygonClick[action]) === 'function') {
this.settings.polygonClick[action](event.originalEvent, polyData.$datasource);
}
}
});
this.tooltip = createTooltip(this.leafletPoly, settings, data.$datasource);
this.updateTooltip(data);
}
this.createEventListeners();
}
private createEventListeners() {
if (this.settings.editablePolygon && this.onDragendListener) {
this.leafletPoly.on('pm:edit', (e) => this.onDragendListener(e, this.data));
}
if (this.settings.polygonClick) {
this.leafletPoly.on('click', (event: LeafletMouseEvent) => {
for (const action in this.settings.polygonClick) {
if (typeof (this.settings.polygonClick[action]) === 'function') {
this.settings.polygonClick[action](event.originalEvent, this.data.$datasource);
}
}
});
}
}
updateTooltip(data: FormattedData) {
@ -67,13 +82,54 @@ export class Polygon {
this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true));
}
updateLabel(settings: PolygonSettings) {
this.leafletPoly.unbindTooltip();
if (settings.showPolygonLabel) {
if (!this.map.polygonLabelText || settings.usePolygonLabelFunction) {
const pattern = settings.usePolygonLabelFunction ?
safeExecute(settings.polygonLabelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.polygonLabel;
this.map.polygonLabelText = parseWithTranslation.prepareProcessPattern(pattern, true);
this.map.replaceInfoLabelPolygon = processPattern(this.map.polygonLabelText, this.data);
}
settings.polygonLabelText = fillPattern(this.map.polygonLabelText, this.map.replaceInfoLabelPolygon, this.data);
this.leafletPoly.bindTooltip(`<div style="color: ${settings.polygonLabelColor};"><b>${settings.polygonLabelText}</b></div>`,
{ className: 'tb-polygon-label', permanent: true, sticky: true, direction: 'center' })
.openTooltip(this.leafletPoly.getBounds().getCenter());
}
}
updatePolygon(data: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) {
this.data = data;
this.dataSources = dataSources;
this.leafletPoly.setLatLngs(data[this.settings.polygonKeyName]);
const polyData = data[this.settings.polygonKeyName];
if (isCutPolygon(polyData) || polyData.length > 2) {
if (this.leafletPoly instanceof L.Rectangle) {
this.map.removeLayer(this.leafletPoly);
const polygonColor = this.getPolygonColor(settings);
const polygonStrokeColor = this.getPolygonStrokeColor(settings);
this.leafletPoly = L.polygon(polyData, {
fill: true,
fillColor: polygonColor,
color: polygonStrokeColor,
weight: settings.polygonStrokeWeight,
fillOpacity: settings.polygonOpacity,
opacity: settings.polygonStrokeOpacity,
pmIgnore: !settings.editablePolygon
}).addTo(this.map);
} else {
this.leafletPoly.setLatLngs(polyData);
}
} else if (polyData.length === 2) {
const bounds = new L.LatLngBounds(polyData);
// @ts-ignore
this.leafletPoly.setBounds(bounds);
}
if (settings.showPolygonTooltip) {
this.updateTooltip(this.data);
}
if (settings.showPolygonLabel) {
this.updateLabel(settings);
}
this.updatePolygonColor(settings);
}
@ -83,10 +139,11 @@ export class Polygon {
updatePolygonColor(settings: PolygonSettings) {
const polygonColor = this.getPolygonColor(settings);
const polygonStrokeColor = this.getPolygonStrokeColor(settings);
const style: L.PathOptions = {
fill: true,
fillColor: polygonColor,
color: settings.polygonStrokeColor,
color: polygonStrokeColor,
weight: settings.polygonStrokeWeight,
fillOpacity: settings.polygonOpacity,
opacity: settings.polygonStrokeOpacity
@ -107,4 +164,9 @@ export class Polygon {
return functionValueCalculator(settings.usePolygonColorFunction, settings.polygonColorFunction,
[this.data, this.dataSources, this.data.dsIndex], settings.polygonColor);
}
private getPolygonStrokeColor(settings: PolygonSettings): string | null {
return functionValueCalculator(settings.usePolygonStrokeColorFunction, settings.polygonStrokeColorFunction,
[this.data, this.dataSources, this.data.dsIndex], settings.polygonStrokeColor);
}
}

74
ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts

@ -343,15 +343,25 @@ export const commonMapSettingsSchema =
default: 'return {x: origXPos, y: origYPos};'
},
markerOffsetX: {
title: 'Marker X offset relative to position',
title: 'Marker X offset relative to position multiplied by marker width',
type: 'number',
default: 0.5
},
markerOffsetY: {
title: 'Marker Y offset relative to position',
title: 'Marker Y offset relative to position multiplied by marker height',
type: 'number',
default: 1
},
tooltipOffsetX: {
title: 'Tooltip X offset relative to marker anchor multiplied by marker width',
type: 'number',
default: 0
},
tooltipOffsetY: {
title: 'Tooltip Y offset relative to marker anchor multiplied by marker height',
type: 'number',
default: -1
},
color: {
title: 'Color',
type: 'string'
@ -482,13 +492,15 @@ export const commonMapSettingsSchema =
condition: 'model.showTooltip === true && model.useTooltipFunction === true'
},
{
key: 'markerOffsetX',
condition: 'model.provider === "image-map"'
key: 'tooltipOffsetX',
condition: 'model.showTooltip === true'
},
{
key: 'markerOffsetY',
condition: 'model.provider === "image-map"'
key: 'tooltipOffsetY',
condition: 'model.showTooltip === true'
},
'markerOffsetX',
'markerOffsetY',
{
key: 'posFunction',
type: 'javascript',
@ -556,6 +568,25 @@ export const mapPolygonSchema =
type: 'boolean',
default: false
},
showPolygonLabel: {
title: 'Show polygon label',
type: 'boolean',
default: false
},
polygonLabel: {
title: 'Polygon label (pattern examples: \'${entityName}\', \'${entityName}: (Text ${keyName} units.)\' )',
type: 'string',
default: '${entityName}'
},
usePolygonLabelFunction: {
title: 'Use polygon label function',
type: 'boolean',
default: false
},
polygonLabelFunction: {
title: 'Polygon label function: f(data, dsData, dsIndex)',
type: 'string'
},
polygonColor: {
title: 'Polygon color',
type: 'string'
@ -607,6 +638,15 @@ export const mapPolygonSchema =
title: 'Polygon Color function: f(data, dsData, dsIndex)',
type: 'string'
},
usePolygonStrokeColorFunction: {
title: 'Use polygon stroke color function',
type: 'boolean',
default: false
},
polygonStrokeColorFunction: {
title: 'Polygon Stroke Color function: f(data, dsData, dsIndex)',
type: 'string'
}
},
required: []
},
@ -614,6 +654,21 @@ export const mapPolygonSchema =
'showPolygon',
'polygonKeyName',
'editablePolygon',
'showPolygonLabel',
{
key: 'usePolygonLabelFunction',
condition: 'model.showPolygonLabel === true'
},
{
key: 'polygonLabel',
condition: 'model.showPolygonLabel === true && model.usePolygonLabelFunction !== true'
},
{
key: 'polygonLabelFunction',
type: 'javascript',
helpId: 'widget/lib/map/label_fn',
condition: 'model.showPolygonLabel === true && model.usePolygonLabelFunction === true'
},
{
key: 'polygonColor',
type: 'color'
@ -630,6 +685,13 @@ export const mapPolygonSchema =
key: 'polygonStrokeColor',
type: 'color'
},
'usePolygonStrokeColorFunction',
{
key: 'polygonStrokeColorFunction',
helpId: 'widget/lib/map/polygon_color_fn',
type: 'javascript',
condition: 'model.usePolygonStrokeColorFunction === true'
},
'polygonStrokeOpacity',
'polygonStrokeWeight',
'showPolygonTooltip',

3
ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html

@ -15,4 +15,5 @@
limitations under the License.
-->
<tb-markdown [data]="markdownText" lineNumbers fallbackToPlainMarkdown (click)="markdownClick($event)"></tb-markdown>
<tb-markdown [data]="markdownText" [additionalCompileModules]="[ homeComponentsModule ]"
[context]="{ ctx: ctx }" lineNumbers fallbackToPlainMarkdown (click)="markdownClick($event)"></tb-markdown>

12
ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { ChangeDetectorRef, Component, HostBinding, Input, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, HostBinding, Inject, Input, OnInit, Type } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { WidgetContext } from '@home/models/widget-component.models';
import { Store } from '@ngrx/store';
@ -32,6 +32,8 @@ import { FormattedData } from '@home/components/widget/lib/maps/map-models';
import { hashCode, isNotEmptyStr } from '@core/utils';
import cssjs from '@core/css/css';
import { UtilsService } from '@core/services/utils.service';
import { HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens';
import { EntityDataPageLink } from '@shared/models/query/query.models';
interface MarkdownWidgetSettings {
markdownTextPattern: string;
@ -62,6 +64,7 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit {
constructor(protected store: Store<AppState>,
private utils: UtilsService,
@Inject(HOME_COMPONENTS_MODULE_TOKEN) public homeComponentsModule: Type<any>,
private cd: ChangeDetectorRef) {
super(store);
}
@ -79,6 +82,13 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit {
cssParser.cssPreviewNamespace = this.markdownClass;
cssParser.createStyleElement(this.markdownClass, cssString);
}
const pageLink: EntityDataPageLink = {
page: 0,
pageSize: 16384,
textSearch: null,
dynamic: true
};
this.ctx.defaultSubscription.subscribeAllForPaginatedData(pageLink, null);
}
public onDataUpdated() {

21
ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html

@ -37,7 +37,12 @@
type="text"
(focus)="key.isFocused = true; focusInputElement($event)"
(blur)="key.isFocused = false; inputChanged(source, key)">
<mat-icon *ngIf="key.settings.icon" matPrefix>{{key.settings.icon}}</mat-icon>
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon" matPrefix>
<mat-icon *ngIf="!key.settings.safeCustomIcon; else customToggleIcon">{{key.settings.icon}}</mat-icon>
<ng-template #customToggleIcon>
<img class="mat-icon" [src]="key.settings.safeCustomIcon" alt="icon">
</ng-template>
</ng-container>
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')">
{{ getErrorMessageText(key.settings, 'required') }}
</mat-error>
@ -57,7 +62,12 @@
max="{{key.settings.maxValue}}"
(focus)="key.isFocused = true; focusInputElement($event)"
(blur)="key.isFocused = false; inputChanged(source, key)">
<mat-icon *ngIf="key.settings.icon" matPrefix>{{key.settings.icon}}</mat-icon>
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon" matPrefix>
<mat-icon *ngIf="!key.settings.safeCustomIcon; else customToggleIcon">{{key.settings.icon}}</mat-icon>
<ng-template #customToggleIcon>
<img class="mat-icon" [src]="key.settings.safeCustomIcon" alt="icon">
</ng-template>
</ng-container>
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')">
{{ getErrorMessageText(key.settings,'required') }}
</mat-error>
@ -77,7 +87,14 @@
</div>
<div class="input-field mat-block" *ngIf="key.settings.dataKeyValueType === 'booleanSwitch'">
<mat-slide-toggle formControlName="{{key.formId}}"
[labelPosition]="key.settings.slideToggleLabelPosition"
(change)="inputChanged(source, key)">
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon">
<mat-icon *ngIf="!key.settings.safeCustomIcon; else customToggleIcon">{{key.settings.icon}}</mat-icon>
<ng-template #customToggleIcon>
<img class="mat-icon" [src]="key.settings.safeCustomIcon" alt="icon">
</ng-template>
</ng-container>
<span class="label-wrapper">{{key.label}}</span>
</mat-slide-toggle>
</div>

14
ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss

@ -64,3 +64,17 @@
}
}
}
:host ::ng-deep {
.tb-multiple-input {
.mat-slide-toggle-content {
width: 100%;
display: flex;
align-items: center;
.mat-icon {
margin-right: 8px;
}
}
}
}

114
ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts

@ -24,7 +24,14 @@ import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { DataKey, Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models';
import { IWidgetSubscription } from '@core/api/widget-api.models';
import { createLabelFromDatasource, isDefinedAndNotNull, isEqual, isNotEmptyStr, isUndefined } from '@core/utils';
import {
createLabelFromDatasource,
isBoolean, isDefined,
isDefinedAndNotNull,
isEqual,
isNotEmptyStr,
isUndefined
} from '@core/utils';
import { EntityType } from '@shared/models/entity-type.models';
import * as _moment from 'moment';
import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
@ -35,6 +42,7 @@ import { forkJoin, Observable, Subject } from 'rxjs';
import { EntityId } from '@shared/models/id/entity-id';
import { ResizeObserver } from '@juggle/resize-observer';
import { takeUntil } from 'rxjs/operators';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
type FieldAlignment = 'row' | 'column';
@ -44,6 +52,9 @@ type MultipleInputWidgetDataKeyValueType = 'string' | 'double' | 'integer' |
'dateTime' | 'date' | 'time' | 'select';
type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly';
type ConvertGetValueFunction = (value: any, ctx: WidgetContext) => any;
type ConvertSetValueFunction = (value: any, originValue: any, ctx: WidgetContext) => any;
interface MultipleInputWidgetSettings {
widgetTitle: string;
showActionButtons: boolean;
@ -66,6 +77,7 @@ interface MultipleInputWidgetSelectOption {
interface MultipleInputWidgetDataKeySettings {
dataKeyType: MultipleInputWidgetDataKeyType;
dataKeyValueType: MultipleInputWidgetDataKeyValueType;
slideToggleLabelPosition?: 'after' | 'before';
selectOptions: MultipleInputWidgetSelectOption[];
required: boolean;
isEditable: MultipleInputWidgetDataKeyEditableType;
@ -78,10 +90,19 @@ interface MultipleInputWidgetDataKeySettings {
invalidDateErrorMessage?: string;
minValueErrorMessage?: string;
maxValueErrorMessage?: string;
useCustomIcon: boolean;
icon: string;
customIcon: string ;
safeCustomIcon?: SafeUrl;
inputTypeNumber?: boolean;
readOnly?: boolean;
disabledOnCondition?: boolean;
useGetValueFunction?: boolean;
getValueFunctionBody?: string;
getValueFunction?: ConvertGetValueFunction;
useSetValueFunction?: boolean;
setValueFunctionBody?: string;
setValueFunction?: ConvertSetValueFunction;
}
interface MultipleInputWidgetDataKey extends DataKey {
@ -139,7 +160,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
private utils: UtilsService,
private fb: FormBuilder,
private attributeService: AttributeService,
private translate: TranslateService) {
private translate: TranslateService,
private sanitizer: DomSanitizer) {
super(store);
}
@ -265,6 +287,34 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
});
}
if (dataKey.settings.dataKeyValueType === 'booleanSwitch' && isUndefined(dataKey.settings.slideToggleLabelPosition)) {
dataKey.settings.slideToggleLabelPosition = 'after';
}
if (dataKey.settings.useCustomIcon && isDefinedAndNotNull(dataKey.settings.customIcon)) {
dataKey.settings.safeCustomIcon = this.sanitizer.bypassSecurityTrustUrl(dataKey.settings.customIcon);
}
if (dataKey.settings.useGetValueFunction && dataKey.settings.getValueFunctionBody.length) {
try {
dataKey.settings.getValueFunction =
new Function('value, ctx', dataKey.settings.getValueFunctionBody) as ConvertGetValueFunction;
} catch (e) {
console.warn(`Parse getValue function in key ${dataKey.label}`, e);
dataKey.settings.getValueFunction = null;
}
}
if (dataKey.settings.useSetValueFunction && dataKey.settings.setValueFunctionBody.length) {
try {
dataKey.settings.setValueFunction =
new Function('value, originValue, ctx', dataKey.settings.setValueFunctionBody) as ConvertSetValueFunction;
} catch (e) {
console.warn(`Parse setValue function in key ${dataKey.label}`, e);
dataKey.settings.setValueFunction = null;
}
}
source.keys.push(dataKey);
});
} else {
@ -342,28 +392,41 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
source.keys.forEach((key) => {
const keyData = data[dataIndex].data;
if (keyData && keyData.length) {
let value;
let value = keyData[0][1];
const keyValue = this.getKeyValue(value, key.settings);
switch (key.settings.dataKeyValueType) {
case 'dateTime':
case 'date':
if (isDefinedAndNotNull(keyData[0][1]) && keyData[0][1] !== '') {
value = _moment(keyData[0][1]).toDate();
if (isDefinedAndNotNull(keyValue) && keyValue !== '') {
if (keyValue instanceof Date) {
value = keyValue;
} else {
value = _moment(keyValue).toDate();
}
} else {
value = null;
}
break;
case 'time':
value = _moment().startOf('day').add(keyData[0][1], 'ms').toDate();
if (keyValue instanceof Date) {
value = keyValue;
} else {
value = _moment().startOf('day').add(keyValue, 'ms').toDate();
}
break;
case 'booleanCheckbox':
case 'booleanSwitch':
value = (keyData[0][1] === 'true');
if (isBoolean(keyValue)) {
value = keyValue;
} else {
value = (keyValue === 'true');
}
break;
case 'select':
value = keyData[0][1].toString();
value = keyValue.toString();
break;
default:
value = keyData[0][1];
value = keyValue;
}
key.value = value;
}
@ -399,6 +462,18 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
});
}
private getKeyValue(data: any, keySetting: MultipleInputWidgetDataKeySettings) {
if (isDefined(keySetting.getValueFunction)) {
try {
return keySetting.getValueFunction(data, this.ctx);
} catch (e) {
console.warn(`Call function getValue`, e);
return data;
}
}
return data;
}
private updateWidgetDisplaying() {
this.changeAlignment = (this.ctx.$container && this.ctx.$container[0].offsetWidth < 620);
}
@ -493,7 +568,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
if (!this.settings.showActionButtons && !this.isSavingInProgress) {
this.isSavingInProgress = true;
const currentValue = this.multipleInputFormGroup.get(key.formId).value;
if (!key.settings.required || (key.settings.required && isDefinedAndNotNull(currentValue) && isNotEmptyStr(currentValue.toString()))) {
if (!key.settings.required ||
(key.settings.required && isDefinedAndNotNull(currentValue) && isNotEmptyStr(currentValue.toString()))) {
const dataToSave: MultipleInputWidgetSource = {
datasource: source.datasource,
keys: [key]
@ -523,8 +599,9 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
const sharedAttributes: AttributeData[] = [];
const telemetry: AttributeData[] = [];
for (const key of toSave.keys) {
const currentValue = key.settings.dataKeyHidden ? key.value : this.multipleInputFormGroup.get(key.formId).value;
let currentValue = key.settings.dataKeyHidden ? key.value : this.multipleInputFormGroup.get(key.formId).value;
if (!isEqual(currentValue, key.value) || this.settings.updateAllValues) {
currentValue = this.setKeyValue(currentValue, key, toSave);
const attribute: AttributeData = {
key: key.name,
value: null
@ -615,6 +692,21 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
}
}
private setKeyValue(value: any, key: MultipleInputWidgetDataKey, source: MultipleInputWidgetSource) {
if (isDefined(key.settings.setValueFunction)) {
const currentDatasourceData = this.subscription.data
.find(dsData => dsData.datasource === source.datasource && dsData.dataKey.name === key.name);
const originValue = currentDatasourceData.data[0][1];
try {
return key.settings.setValueFunction(value, originValue, this.ctx);
} catch (e) {
console.warn(`Call function setValue`, e);
return value;
}
}
return value;
}
public discardAll() {
this.multipleInputFormGroup.reset(undefined, {emitEvent: false});
this.sources.forEach((source) => {

10
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -45,6 +45,7 @@ import { MODULES_MAP } from '@shared/public-api';
import * as tinycolor_ from 'tinycolor2';
import moment from 'moment';
import { IModulesMap } from '@modules/common/modules-map.models';
import { HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens';
const tinycolor = tinycolor_;
@ -66,6 +67,7 @@ export class WidgetComponentService {
constructor(@Inject(WINDOW) private window: Window,
@Optional() @Inject(MODULES_MAP) private modulesMap: IModulesMap,
@Inject(HOME_COMPONENTS_MODULE_TOKEN) private homeComponentsModule: Type<any>,
private dynamicComponentFactoryService: DynamicComponentFactoryService,
private widgetService: WidgetService,
private utils: UtilsService,
@ -177,8 +179,10 @@ export class WidgetComponentService {
forkJoin(widgetModulesTasks).subscribe(
() => {
const loadDefaultWidgetInfoTasks = [
this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule, WidgetComponentsModule]),
this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule, WidgetComponentsModule]),
this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type',
[SharedModule, WidgetComponentsModule, this.homeComponentsModule]),
this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type',
[SharedModule, WidgetComponentsModule, this.homeComponentsModule]),
];
forkJoin(loadDefaultWidgetInfoTasks).subscribe(
() => {
@ -274,7 +278,7 @@ export class WidgetComponentService {
}
if (widgetControllerDescriptor) {
const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`;
this.loadWidgetResources(widgetInfo, widgetNamespace, [SharedModule, WidgetComponentsModule]).subscribe(
this.loadWidgetResources(widgetInfo, widgetNamespace, [SharedModule, WidgetComponentsModule, this.homeComponentsModule]).subscribe(
() => {
if (widgetControllerDescriptor.settingsSchema) {
widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema;

4
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -418,6 +418,10 @@
label="{{ 'widget-config.widget-style' | translate }}"
formControlName="widgetStyle"
></tb-json-object-edit>
<tb-css
label="{{ 'widget-config.widget-css' | translate }}"
formControlName="widgetCss"
></tb-css>
</ng-template>
</mat-expansion-panel>
</fieldset>

2
ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss

@ -133,7 +133,7 @@
.mat-expansion-panel-body{
padding: 0;
}
.tb-json-object-panel {
.tb-json-object-panel, .tb-css-content-panel {
margin: 0 0 8px;
}
.mat-checkbox-layout {

2
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -206,6 +206,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
padding: [null, []],
margin: [null, []],
widgetStyle: [null, []],
widgetCss: [null, []],
titleStyle: [null, []],
units: [null, []],
decimals: [null, [Validators.min(0), Validators.max(15), Validators.pattern(/^\d*$/)]],
@ -406,6 +407,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
padding: config.padding,
margin: config.margin,
widgetStyle: isDefined(config.widgetStyle) ? config.widgetStyle : {},
widgetCss: isDefined(config.widgetCss) ? config.widgetCss : '',
titleStyle: isDefined(config.titleStyle) ? config.titleStyle : {
fontSize: '16px',
fontWeight: 400

2
ui-ngx/src/app/modules/home/components/widget/widget-container.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<div tb-fullscreen [fullscreen]="widget.isFullscreen"
<div #tbWidgetElement tb-fullscreen [fullscreen]="widget.isFullscreen"
[fullscreenBackgroundStyle]="dashboardStyle"
[fullscreenBackgroundImage]="backgroundImage"
(fullscreenChanged)="onFullscreenChanged($event)"

53
ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts

@ -17,17 +17,21 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Input,
Component, ElementRef,
EventEmitter, HostBinding, Inject,
Input, OnDestroy,
OnInit,
Output
Output, Renderer2, ViewChild
} from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { DashboardWidget, DashboardWidgets } from '@home/models/dashboard-component.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { SafeStyle } from '@angular/platform-browser';
import { guid, hashCode, isNotEmptyStr } from '@core/utils';
import cssjs from '@core/css/css';
import { DOCUMENT } from '@angular/common';
import { GridsterItemComponent } from 'angular-gridster2';
export enum WidgetComponentActionType {
MOUSE_DOWN,
@ -43,13 +47,23 @@ export class WidgetComponentAction {
actionType: WidgetComponentActionType;
}
// @dynamic
@Component({
selector: 'tb-widget-container',
templateUrl: './widget-container.component.html',
styleUrls: ['./widget-container.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class WidgetContainerComponent extends PageComponent implements OnInit {
export class WidgetContainerComponent extends PageComponent implements OnInit, OnDestroy {
@HostBinding('class')
widgetContainerClass = 'tb-widget-container';
@ViewChild('tbWidgetElement', {static: true})
tbWidgetElement: ElementRef;
@Input()
gridsterItem: GridsterItemComponent;
@Input()
widget: DashboardWidget;
@ -87,13 +101,35 @@ export class WidgetContainerComponent extends PageComponent implements OnInit {
@Output()
widgetComponentAction: EventEmitter<WidgetComponentAction> = new EventEmitter<WidgetComponentAction>();
private cssClass: string;
constructor(protected store: Store<AppState>,
private cd: ChangeDetectorRef) {
private cd: ChangeDetectorRef,
private renderer: Renderer2,
@Inject(DOCUMENT) private document: Document) {
super(store);
}
ngOnInit(): void {
this.widget.widgetContext.containerChangeDetector = this.cd;
const cssString = this.widget.widget.config.widgetCss;
if (isNotEmptyStr(cssString)) {
const cssParser = new cssjs();
cssParser.testMode = false;
this.cssClass = 'tb-widget-css-' + guid();
this.renderer.addClass(this.gridsterItem.el, this.cssClass);
cssParser.cssPreviewNamespace = this.cssClass;
cssParser.createStyleElement(this.cssClass, cssString);
}
}
ngOnDestroy(): void {
if (this.cssClass) {
const el = this.document.getElementById(this.cssClass);
if (el) {
el.parentNode.removeChild(el);
}
}
}
isHighlighted(widget: DashboardWidget) {
@ -105,6 +141,11 @@ export class WidgetContainerComponent extends PageComponent implements OnInit {
}
onFullscreenChanged(expanded: boolean) {
if (expanded) {
this.renderer.addClass(this.tbWidgetElement.nativeElement, this.cssClass);
} else {
this.renderer.removeClass(this.tbWidgetElement.nativeElement, this.cssClass);
}
this.widgetFullscreenChanged.emit(expanded);
}

32
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -65,7 +65,7 @@ import {
validateEntityId
} from '@core/utils';
import {
IDynamicWidgetComponent, ShowWidgetHeaderActionFunction,
IDynamicWidgetComponent, ShowWidgetHeaderActionFunction, updateEntityParams,
WidgetContext,
WidgetHeaderAction,
WidgetInfo,
@ -1070,7 +1070,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
case WidgetActionType.updateDashboardState:
let targetDashboardStateId = descriptor.targetDashboardStateId;
const params = deepClone(this.widgetContext.stateController.getStateParams());
this.updateEntityParams(params, targetEntityParamName, targetEntityId, entityName, entityLabel);
updateEntityParams(params, targetEntityParamName, targetEntityId, entityName, entityLabel);
if (type === WidgetActionType.openDashboardState) {
if (descriptor.openInPopover) {
this.openDashboardStateInPopover($event, descriptor.targetDashboardStateId, params,
@ -1091,7 +1091,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
targetDashboardStateId = descriptor.targetDashboardStateId;
const stateObject: StateObject = {};
stateObject.params = {};
this.updateEntityParams(stateObject.params, targetEntityParamName, targetEntityId, entityName, entityLabel);
updateEntityParams(stateObject.params, targetEntityParamName, targetEntityId, entityName, entityLabel);
if (targetDashboardStateId) {
stateObject.id = targetDashboardStateId;
}
@ -1360,7 +1360,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.widgetContentContainer, this.dashboardPageComponent, preferredPlacement, hideOnClickOutside,
injector,
{
embed: true,
embedded: true,
syncStateWithQueryParam: false,
hideToolbar: hideDashboardToolbar,
currentState: objToBase64([stateObject]),
@ -1443,30 +1443,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
}
}
private updateEntityParams(params: StateParams, targetEntityParamName?: string, targetEntityId?: EntityId,
entityName?: string, entityLabel?: string) {
if (targetEntityId) {
let targetEntityParams: StateParams;
if (targetEntityParamName && targetEntityParamName.length) {
targetEntityParams = params[targetEntityParamName];
if (!targetEntityParams) {
targetEntityParams = {};
params[targetEntityParamName] = targetEntityParams;
params.targetEntityParamName = targetEntityParamName;
}
} else {
targetEntityParams = params;
}
targetEntityParams.entityId = targetEntityId;
if (entityName) {
targetEntityParams.entityName = entityName;
}
if (entityLabel) {
targetEntityParams.entityLabel = entityLabel;
}
}
}
private loadCustomActionResources(actionNamespace: string, customCss: string, customResources: Array<WidgetResource>): Observable<any> {
if (isDefined(customCss) && customCss.length > 0) {
this.cssParser.cssPreviewNamespace = actionNamespace;

5
ui-ngx/src/app/modules/home/models/datasource/attribute-datasource.ts

@ -88,7 +88,10 @@ export class AttributeDatasource implements DataSource<AttributeData> {
fetchAttributes(entityId: EntityId, attributesScope: TelemetryType,
pageLink: PageLink): Observable<PageData<AttributeData>> {
return this.getAllAttributes(entityId, attributesScope).pipe(
map((data) => pageLink.filterData(data))
map((data) => {
const filteredData = data.filter(attrData => attrData.lastUpdateTs !== 0 && attrData.value !== null);
return pageLink.filterData(filteredData);
})
);
}

27
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -37,7 +37,7 @@ import {
IStateController,
IWidgetSubscription,
IWidgetUtils,
RpcApi,
RpcApi, StateParams,
SubscriptionEntityInfo,
TimewindowFunctions,
WidgetActionsApi,
@ -81,6 +81,7 @@ import { Router } from '@angular/router';
import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
import { FormattedData } from '@home/components/widget/lib/maps/map-models';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { EntityId } from '@shared/models/id/entity-id';
export interface IWidgetAction {
name: string;
@ -545,3 +546,27 @@ export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId:
descriptor
};
}
export function updateEntityParams(params: StateParams, targetEntityParamName?: string, targetEntityId?: EntityId,
entityName?: string, entityLabel?: string) {
if (targetEntityId) {
let targetEntityParams: StateParams;
if (targetEntityParamName && targetEntityParamName.length) {
targetEntityParams = params[targetEntityParamName];
if (!targetEntityParams) {
targetEntityParams = {};
params[targetEntityParamName] = targetEntityParams;
params.targetEntityParamName = targetEntityParamName;
}
} else {
targetEntityParams = params;
}
targetEntityParams.entityId = targetEntityId;
if (entityName) {
targetEntityParams.entityName = entityName;
}
if (entityLabel) {
targetEntityParams.entityLabel = entityLabel;
}
}
}

41
ui-ngx/src/app/shared/components/css.component.html

@ -0,0 +1,41 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-css" style="background: #fff;" [ngClass]="{'tb-disabled': disabled, 'fill-height': fillHeight}"
tb-fullscreen
[fullscreen]="fullscreen" fxLayout="column">
<div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" class="tb-css-toolbar">
<label class="tb-title no-padding" [ngClass]="{'tb-error': hasErrors}">{{ label }}</label>
<span fxFlex></span>
<button type='button' *ngIf="!disabled" mat-button class="tidy" (click)="beautifyCss()">
{{'js-func.tidy' | translate }}
</button>
<fieldset style="width: initial">
<div matTooltip="{{(fullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
matTooltipPosition="above"
style="border-radius: 50%"
(click)="fullscreen = !fullscreen">
<button type='button' mat-button mat-icon-button class="tb-mat-32">
<mat-icon class="material-icons">{{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
</button>
</div>
</fieldset>
</div>
<div id="tb-css-panel" class="tb-css-content-panel" fxLayout="column">
<div #cssEditor id="tb-css-input" [ngClass]="{'fill-height': fillHeight}"></div>
</div>
</div>

66
ui-ngx/src/app/shared/components/css.component.scss

@ -0,0 +1,66 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-css {
position: relative;
&.tb-disabled {
color: rgba(0, 0, 0, .38);
}
&.fill-height {
height: 100%;
}
.tb-css-content-panel {
height: calc(100% - 80px);
margin-left: 15px;
border: 1px solid #c0c0c0;
#tb-css-input {
width: 100%;
min-width: 200px;
height: 100%;
&:not(.fill-height) {
min-height: 200px;
}
}
}
.tb-css-toolbar {
& > * {
&:not(:last-child) {
margin-right: 4px;
}
}
button.mat-button, button.mat-icon-button, button.mat-icon-button.tb-mat-32 {
background: rgba(220, 220, 220, .35);
align-items: center;
vertical-align: middle;
min-width: 32px;
min-height: 15px;
padding: 4px;
font-size: .8rem;
line-height: 15px;
&:not(.tb-help-popup-button) {
color: #7b7b7b;
}
}
.tb-help-popup-button-loading {
background: #f3f3f3;
}
}
}

207
ui-ngx/src/app/shared/components/css.component.ts

@ -0,0 +1,207 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
Component,
ElementRef,
forwardRef,
Input,
OnDestroy,
OnInit,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
import { Ace } from 'ace-builds';
import { getAce } from '@shared/models/ace/ace.models';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { ResizeObserver } from '@juggle/resize-observer';
import { beautifyCss } from '@shared/models/beautify.models';
@Component({
selector: 'tb-css',
templateUrl: './css.component.html',
styleUrls: ['./css.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CssComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CssComponent),
multi: true,
}
],
encapsulation: ViewEncapsulation.None
})
export class CssComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator {
@ViewChild('cssEditor', {static: true})
cssEditorElmRef: ElementRef;
private cssEditor: Ace.Editor;
private editorsResizeCaf: CancelAnimationFrame;
private editorResize$: ResizeObserver;
private ignoreChange = false;
@Input() label: string;
@Input() disabled: boolean;
@Input() fillHeight: boolean;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
fullscreen = false;
modelValue: string;
hasErrors = false;
private propagateChange = null;
constructor(public elementRef: ElementRef,
private utils: UtilsService,
private translate: TranslateService,
protected store: Store<AppState>,
private raf: RafService) {
}
ngOnInit(): void {
const editorElement = this.cssEditorElmRef.nativeElement;
let editorOptions: Partial<Ace.EditorOptions> = {
mode: 'ace/mode/css',
showGutter: true,
showPrintMargin: true,
readOnly: this.disabled
};
const advancedOptions = {
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
};
editorOptions = {...editorOptions, ...advancedOptions};
getAce().subscribe(
(ace) => {
this.cssEditor = ace.edit(editorElement, editorOptions);
this.cssEditor.session.setUseWrapMode(true);
this.cssEditor.setValue(this.modelValue ? this.modelValue : '', -1);
this.cssEditor.setReadOnly(this.disabled);
this.cssEditor.on('change', () => {
if (!this.ignoreChange) {
this.updateView();
}
});
// @ts-ignore
this.cssEditor.session.on('changeAnnotation', () => {
const annotations = this.cssEditor.session.getAnnotations();
const hasErrors = annotations.filter(annotation => annotation.type === 'error').length > 0;
if (this.hasErrors !== hasErrors) {
this.hasErrors = hasErrors;
this.propagateChange(this.modelValue);
}
});
this.editorResize$ = new ResizeObserver(() => {
this.onAceEditorResize();
});
this.editorResize$.observe(editorElement);
}
);
}
ngOnDestroy(): void {
if (this.editorResize$) {
this.editorResize$.disconnect();
}
}
private onAceEditorResize() {
if (this.editorsResizeCaf) {
this.editorsResizeCaf();
this.editorsResizeCaf = null;
}
this.editorsResizeCaf = this.raf.raf(() => {
this.cssEditor.resize();
this.cssEditor.renderer.updateFull();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.cssEditor) {
this.cssEditor.setReadOnly(this.disabled);
}
}
public validate(c: FormControl) {
return (!this.hasErrors) ? null : {
css: {
valid: false,
},
};
}
beautifyCss() {
beautifyCss(this.modelValue, {indent_size: 4}).subscribe(
(res) => {
if (this.modelValue !== res) {
this.cssEditor.setValue(res ? res : '', -1);
this.updateView();
}
}
);
}
writeValue(value: string): void {
this.modelValue = value;
if (this.cssEditor) {
this.ignoreChange = true;
this.cssEditor.setValue(this.modelValue ? this.modelValue : '', -1);
this.ignoreChange = false;
}
}
updateView() {
const editorValue = this.cssEditor.getValue();
if (this.modelValue !== editorValue) {
this.modelValue = editorValue;
this.propagateChange(this.modelValue);
}
}
}

17
ui-ngx/src/app/shared/components/json-form/react/json-form-base-component.tsx

@ -16,6 +16,7 @@
import * as React from 'react';
import JsonFormUtils from './json-form-utils';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { isDefinedAndNotNull } from '@core/utils';
export default ThingsboardBaseComponent => class<P extends JsonFormFieldProps>
extends React.Component<P, JsonFormFieldState> {
@ -67,10 +68,10 @@ export default ThingsboardBaseComponent => class<P extends JsonFormFieldProps>
defaultValue() {
let value = JsonFormUtils.selectOrSet(this.props.form.key, this.props.model);
if (this.props.form.schema.type === 'boolean') {
if (typeof value !== 'boolean' && this.props.form.default) {
if (typeof value !== 'boolean' && typeof this.props.form.default === 'boolean') {
value = this.props.form.default;
}
if (typeof value !== 'boolean' && this.props.form.schema && this.props.form.schema.default) {
if (typeof value !== 'boolean' && this.props.form.schema && typeof this.props.form.schema.default === 'boolean') {
value = this.props.form.schema.default;
}
if (typeof value !== 'boolean' &&
@ -79,13 +80,13 @@ export default ThingsboardBaseComponent => class<P extends JsonFormFieldProps>
value = false;
}
} else if (this.props.form.schema.type === 'integer' || this.props.form.schema.type === 'number') {
if (typeof value !== 'number' && this.props.form.default) {
if (typeof value !== 'number' && typeof this.props.form.default === 'number') {
value = this.props.form.default;
}
if (typeof value !== 'number' && this.props.form.schema && this.props.form.schema.default) {
if (typeof value !== 'number' && this.props.form.schema && typeof this.props.form.schema.default === 'number') {
value = this.props.form.schema.default;
}
if (typeof value !== 'number' && this.props.form.titleMap && this.props.form.titleMap[0].value) {
if (typeof value !== 'number' && this.props.form.titleMap && typeof this.props.form.titleMap[0].value === 'number') {
value = this.props.form.titleMap[0].value;
}
if (value && typeof value === 'string') {
@ -96,13 +97,13 @@ export default ThingsboardBaseComponent => class<P extends JsonFormFieldProps>
}
}
} else {
if (!value && this.props.form.default) {
if (!value && isDefinedAndNotNull(this.props.form.default)) {
value = this.props.form.default;
}
if (!value && this.props.form.schema && this.props.form.schema.default) {
if (!value && this.props.form.schema && isDefinedAndNotNull(this.props.form.schema.default)) {
value = this.props.form.schema.default;
}
if (!value && this.props.form.titleMap && this.props.form.titleMap[0].value) {
if (!value && this.props.form.titleMap && isDefinedAndNotNull(this.props.form.titleMap[0].value)) {
value = this.props.form.titleMap[0].value;
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save