Browse Source

Merge remote-tracking branch 'origin/master' into feature/3.0-edge

pull/3811/head
Volodymyr Babak 6 years ago
parent
commit
dd780d05df
  1. 29
      .run/[CE Edge] Install.run.xml
  2. 31
      .run/[CE Edge] Server.run.xml
  3. 29
      .run/[CE Edge] Upgrade.run.xml
  4. 12
      .travis.yml
  5. 1
      README.md
  6. 15
      application/pom.xml
  7. 22
      application/src/main/data/certs/azure/BaltimoreCyberTrustRoot.crt.pem
  8. 104
      application/src/main/data/json/demo/dashboards/thermostats.json
  9. 3
      application/src/main/data/json/demo/rule_chains/root_rule_chain.json
  10. 6
      application/src/main/data/json/system/widget_bundles/alarm_widgets.json
  11. 56
      application/src/main/data/json/system/widget_bundles/analogue_gauges.json
  12. 86
      application/src/main/data/json/system/widget_bundles/cards.json
  13. 4
      application/src/main/data/json/system/widget_bundles/charts.json
  14. 146
      application/src/main/data/json/system/widget_bundles/digital_gauges.json
  15. 4
      application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json
  16. 4
      application/src/main/data/json/system/widget_bundles/gateway_widgets.json
  17. 362
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  18. 120
      application/src/main/data/json/system/widget_bundles/maps.json
  19. 135
      application/src/main/data/json/tenant/device_profile/rule_chain_template.json
  20. 24
      application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
  21. 1
      application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql
  22. 18
      application/src/main/data/upgrade/2.4.3/schema_update_ttl.sql
  23. 35
      application/src/main/data/upgrade/3.0.1/schema_ts_latest.sql
  24. 878
      application/src/main/data/upgrade/3.0.1/schema_update_to_uuid.sql
  25. 17
      application/src/main/data/upgrade/3.1.0/schema_update.sql
  26. 28
      application/src/main/data/upgrade/3.1.1/schema_update_after.sql
  27. 81
      application/src/main/data/upgrade/3.1.1/schema_update_before.sql
  28. 3
      application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java
  29. 45
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  30. 5
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  31. 21
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  32. 82
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  33. 2
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  34. 5
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  35. 11
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  36. 8
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  37. 8
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  38. 2
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  39. 88
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  40. 2
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  41. 68
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  42. 203
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  43. 79
      application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
  44. 177
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  45. 61
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  46. 13
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  47. 33
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  48. 162
      application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
  49. 22
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  50. 31
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  51. 201
      application/src/main/java/org/thingsboard/server/service/edge/rpc/init/DefaultSyncEdgeService.java
  52. 1
      application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java
  53. 30
      application/src/main/java/org/thingsboard/server/service/install/CassandraTsLatestDatabaseSchemaService.java
  54. 83
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  55. 2
      application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java
  56. 42
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  57. 2
      application/src/main/java/org/thingsboard/server/service/install/PsqlEntityDatabaseSchemaService.java
  58. 10
      application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java
  59. 198
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  60. 2
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  61. 4
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java
  62. 19
      application/src/main/java/org/thingsboard/server/service/install/TsLatestDatabaseSchemaService.java
  63. 2
      application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java
  64. 6
      application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlTable.java
  65. 233
      application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java
  66. 21
      application/src/main/java/org/thingsboard/server/service/install/migrate/TsLatestMigrateService.java
  67. 170
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  68. 99
      application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java
  69. 31
      application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java
  70. 103
      application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java
  71. 34
      application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java
  72. 58
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  73. 46
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  74. 81
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  75. 11
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java
  76. 5
      application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java
  77. 125
      application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java
  78. 32
      application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java
  79. 72
      application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java
  80. 85
      application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java
  81. 109
      application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java
  82. 75
      application/src/main/java/org/thingsboard/server/service/queue/TbRuleNodeProfilerInfo.java
  83. 35
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  84. 4
      application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java
  85. 8
      application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingStrategyFactory.java
  86. 60
      application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
  87. 4
      application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java
  88. 2
      application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java
  89. 4
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  90. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java
  91. 2
      application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
  92. 35
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  93. 84
      application/src/main/java/org/thingsboard/server/service/stats/DefaultJsInvokeStats.java
  94. 1
      application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
  95. 143
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  96. 484
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  97. 51
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
  98. 12
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
  99. 31
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionServiceStatistics.java
  100. 470
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java

29
.run/[CE Edge] Install.run.xml

@ -0,0 +1,29 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[CE Edge] Install" type="Application" factoryName="Application">
<envs>
<env name="SPRING_DATASOURCE_URL" value="jdbc:postgresql://localhost:5432/tb_ce_edge_3" />
</envs>
<option name="MAIN_CLASS_NAME" value="org.thingsboard.server.ThingsboardInstallApplication" />
<module name="application" />
<option name="VM_PARAMETERS" value="-Dinstall.data_dir=$PROJECT_DIR$/application/target/data -Dinstall.load_demo=true -Dspring.jpa.hibernate.ddl-auto=none -Dinstall.upgrade=false" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.thingsboard.server.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />
<option name="IS_SUBST" value="false" />
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
<option name="IS_IGNORE_MISSING_FILES" value="false" />
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
<ENTRIES>
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
</ENTRIES>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

31
.run/[CE Edge] Server.run.xml

@ -0,0 +1,31 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[CE Edge] Server" type="Application" factoryName="Application">
<envs>
<env name="SPRING_DATASOURCE_URL" value="jdbc:postgresql://localhost:5432/tb_ce_edge_3" />
<env name="EDGES_RPC_PORT" value="7070" />
<env name="COAP_ENABLED" value="true" />
<env name="EDGES_RPC_ENABLED" value="true" />
</envs>
<option name="MAIN_CLASS_NAME" value="org.thingsboard.server.ThingsboardServerApplication" />
<module name="application" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.thingsboard.server.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />
<option name="IS_SUBST" value="false" />
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
<option name="IS_IGNORE_MISSING_FILES" value="false" />
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
<ENTRIES>
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
</ENTRIES>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

29
.run/[CE Edge] Upgrade.run.xml

@ -0,0 +1,29 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[CE Edge] Upgrade" type="Application" factoryName="Application">
<envs>
<env name="SPRING_DATASOURCE_URL" value="jdbc:postgresql://localhost:5432/tb_ce_edge_3" />
</envs>
<option name="MAIN_CLASS_NAME" value="org.thingsboard.server.ThingsboardInstallApplication" />
<module name="application" />
<option name="VM_PARAMETERS" value="-Dinstall.data_dir=$PROJECT_DIR$/application/target/data -Dinstall.load_demo=true -Dspring.jpa.hibernate.ddl-auto=none -Dinstall.upgrade=true -Dinstall.upgrade.from_version=2.4.3" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.thingsboard.server.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />
<option name="IS_SUBST" value="false" />
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
<option name="IS_IGNORE_MISSING_FILES" value="false" />
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
<ENTRIES>
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
</ENTRIES>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

12
.travis.yml

@ -1,12 +0,0 @@
before_install:
- sudo rm -f /etc/mavenrc
- export M2_HOME=/usr/local/maven
- export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m"
- export HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false
jdk:
- openjdk8
language: java
sudo: required
services:
- docker
script: mvn clean verify -Ddockerfile.skip=false

1
README.md

@ -1,6 +1,5 @@
# ThingsBoard
[![Join the chat at https://gitter.im/thingsboard/chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/thingsboard/chat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://travis-ci.org/thingsboard/thingsboard.svg?branch=master)](https://travis-ci.org/thingsboard/thingsboard)
[![ThingsBoard Builds Server Status](https://img.shields.io/teamcity/build/e/ThingsBoard_Build?label=TB%20builds%20server&server=https%3A%2F%2Fbuilds.thingsboard.io&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAALzAAAC8wHS6QoqAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAB9FJREFUeJzVm3+MXUUVx7+zWwqEtnRLWisQ2lKVUisIQmsqYCohpUhpEGsFKSJJTS0qGiGIISJ/8CNGYzSaEKBQEZUiP7RgVbCVdpE0xYKBWgI2rFLZJZQWtFKobPfjH3Pfdu7s3Pvmzntv3/JNNr3bOXPO+Z6ZO3PumVmjFgEYJWmWpDmSZks6VtIESV3Zv29LWmGMubdVPgw7gEOBJcAaYC/18fd2+zyqngAwXdL7M9keSduMMXgyH5R0laRPSRpbwf62CrLDB8AAS4HnAqP2EvA1YBTwPuBnwP46I70H+DPwALAS+B5wBTCu3VyHIJvG98dMX+B/BW1vAvcAnwdmAp3t5hWFbORXR5AvwmPARcCYdnNJAnCBR+gd7HQ9HZgLfAt4PUB8AzCv3f43DGCTQ6o/RAo43gtCL2Da4W9TAUwEBhxiPymRvcabAR8eTl+biQ7neYokdyTXlvR7xPt9etM8GmZ0FDxL+WD42FdBdkTDJd0jyU1wzi7pd473e0+qA8AM4AbgkrK1BDgOWAc8ChyTaq+eM5ud93ofcHpAZiY2sanhZaDDaTfAZ7HJUmlWCJzm6bqLQM6QBanXkfthcxgPNbTEW9z2AT8AzgTmANdikxwXX/d0XOi0bQEmFNj6GPAfhuKnXkB98kNsNjsITwacKkI3MNrrf4UnswXoiiRfwyqgo4D8L2hVZglMw456DDYCRwR0jCH/KuWCgE2oysjX8KsA+V+2jHzm3CrP4PMBx/4JfAU4qETP+EAQ/gKcA/w7gnwNbl5yD7bG0DLyM7DZXw3d2f9PA+YD5wIzK+gLBSEFA/XIA2cAVwLvbSQAt3mGP5Gs7IDO8dg1ZYDGcAfOwujZuIwDn+ObUx09hHx+v7Eh5nndCyIIDgBbgd0lMiv9IABfIF+LeDnVyU97xj5XR/6bwI5sZEaXyH2UuHd+WSbfRXktYjAIAfL9wGdSA/Cgo+gtSio12IKJa3hNKAgZ+TciyL+AlwECKzI/ioLgTvsa+YtTyXeSz8ZW15E3wN88p3JBwCZNMeShIKkBTsRmmSG4a0o/sDSJfGboBE/5pRF9pgI9oSBUJP8mXpLk2bm6pO9Aw+QzI8s8xVFbXRaEf3h911cgD7Cyjg0/L/GxnoLdoUoA3O1vDxUyLWyO4AehCpYX6D2L/LpUhtsaCkIWxRoeT+g/DVsqT8EWYDowC5jh6FxUUc+tJJblOmSPqWp4JUFHl6TDUoxLOlnSdknPSnK3sA2S9lfQs0zS7SkzwQ/A61U6A6dKWufpSMVg5mmMeUPSXyv2v0zSN6oa7ZAdwRqiA5CRf0TS+KpGAxiQ1OFN4z8l6PErVXUxSvmp1hvTqUnk35adPWskPWSM6fPaq84ASXqscg/gi9gcvJuC6o0nfwrhw5EYvIpNn88HStcN4M6KulfTys/lzKlO0lb8P2Lrf6VbLDAF+DLweEX998aSx372bwP6gPlVA3BEAvm9FJwVYtPqjwDXA08n6AZbOYoeeeAWp++mSlPGGLMLeFjSuRW6Iektx4GDJc2TdJ6khZKOruKDh/skXWSM6a/Q5yjn+dDKFrE1vw0VR2m2039x4kj7uJ+SslyJ/+7rtaly4mCM+a+kBaq2TbnVpfWy216jmCzpkIR+7kK/MymHNsbslX0NYoMweMpsjNklaWuKXQ9zJf2eOocvAbzHee5N/ojIgvBVxY3madh3v4b1iWZ/o3zw5kpaS+SFDGCq8jPguUQ/CmsCZfi403dhwjv/AHAQMAl41mvbGBMEhq4/c1PJTwmQr1f7u97pfzj5EnwUead/KAg/ivD7Zkf+HSBpFwiRfwibI3SXkOj29PgEivAggdU+C8JWR+6+CN9dm1tSyHcBLwbIj87ax1Kcxe0DJmVyY4CdEeR/TXnVeRLwc+C3wHF1fP+Qp/uGlABc6Cl5mPziVi8IzwDfAZ6KIN9LyhQt9v1GT/+sFCXTOVBBXuOTd+TGkp+eqWjKSTBwMPAvR+9TjSibjK35l93mWIxdZFKOxPzFseEgAJd7Olt6v+AC8jdIqwRhLbZM758HRH3tYa/vnoqtKZ4JHIk99tvh6HqNVl3RLSB/JfBEBPnBwxXsJ2uf176qxO7hwE3ALq/PfuyVXhdXt4r8+QHyK7K2cXWCMLiTOPqODwTh2IDdD2CP12LwCnUKMankO8kfiAySd2SKgjCEfEEQ+nznsZc7eyLJA9zddPKZIx0c2NcHgMsL5MZhr83XULiTeCSXAEcG2m4PjPCXsEWWBdhbZ/4h6knN4u07Mxv4MbCojtxo7DW6RTRwopMFxt0xeoCJAblLvCDdlWpzRAG42CO2sET2UUfuVbetsYPF9mKq8zwg6Q8lsm7bRJxt8N0cAPdar5FUupYU9X03B2C782wknVUi+0nneacxZk9rXBpGABO8RXA72demJ7fcWyvubIe/TQN2y11MuJ6wA5v3z8HeMbjba+8n5StwJCDb9lYUEI/Fde3mEQ1svnBKRvp32K/LEPYQd1z3XQJfsG3/Sw/gKElLZev8tb8rnizpBEmF1SDZ06ZbJN0saa+kayQtV77qi6QnJF1njFnXdOebAcIXssvQB3yfcGrcCZwEnAfMC8mMKGArNUVT28VubF4/nyZflx8Jr8BVkr4tm83tzn5ek/S8pM2SnpT0gv8H283C/wGTFfhGtexQwQAAAABJRU5ErkJggg==&labelColor=305680)](https://builds.thingsboard.io/viewType.html?buildTypeId=ThingsBoard_Build&guest=1)
ThingsBoard is an open-source IoT platform for data collection, processing, visualization, and device management.

15
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.0.1-SNAPSHOT</version>
<version>3.2.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>
@ -46,10 +46,6 @@
</properties>
<dependencies>
<dependency>
<groupId>de.ruedigermoeller</groupId>
<artifactId>fst</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
@ -97,6 +93,10 @@
<groupId>org.thingsboard.common</groupId>
<artifactId>queue</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>stats</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>edge-api</artifactId>
@ -308,6 +308,11 @@
<groupId>com.github.ua-parser</groupId>
<artifactId>uap-java</artifactId>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

22
application/src/main/data/certs/azure/BaltimoreCyberTrustRoot.crt.pem

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----

104
application/src/main/data/json/demo/dashboards/thermostats.json

@ -50,6 +50,8 @@
"datasources": [
{
"type": "entity",
"name": null,
"entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e",
"dataKeys": [
{
"name": "active",
@ -92,9 +94,36 @@
"_hash": 0.5726727868178358,
"units": "%",
"decimals": 0
},
{
"name": "latitude",
"type": "attribute",
"label": "latitude",
"color": "#ffc107",
"settings": {
"columnWidth": "0px",
"useCellStyleFunction": false,
"cellStyleFunction": "",
"useCellContentFunction": false,
"cellContentFunction": ""
},
"_hash": 0.16055765877264894
},
{
"name": "longitude",
"type": "attribute",
"label": "longitude",
"color": "#607d8b",
"settings": {
"columnWidth": "0px",
"useCellStyleFunction": false,
"cellStyleFunction": "",
"useCellContentFunction": false,
"cellContentFunction": ""
},
"_hash": 0.10969512220289346
}
],
"entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e"
]
}
],
"showTitleIcon": false,
@ -882,11 +911,11 @@
"actions": {
"tooltipAction": [
{
"id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66",
"name": "delete",
"icon": "more_horiz",
"type": "custom",
"customFunction": "var $rootScope = widgetContext.$scope.$injector.get('$rootScope');\nvar entityDatasource = widgetContext.map.subscription.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.map.saveMarkerLocation(entityDatasource[0],\n widgetContext.map.locations[0], {\n \"lat\": null,\n \"lng\": null\n }).then(function succes() {\n $rootScope.$broadcast('widgetForceReInit');\n });"
"customFunction": "var entityDatasource = widgetContext.mapInstance.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.mapInstance.saveMarkerLocation(entityDatasource[0], null, null).subscribe(function success() {\n widgetContext.updateAliases();\n});",
"id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66"
}
]
},
@ -1017,11 +1046,11 @@
"actions": {
"tooltipAction": [
{
"id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66",
"name": "delete",
"icon": "more_horiz",
"type": "custom",
"customFunction": "var $rootScope = widgetContext.$scope.$injector.get('$rootScope');\nvar entityDatasource = widgetContext.map.subscription.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.map.saveMarkerLocation(entityDatasource[0],\n widgetContext.map.locations[0], {\n \"lat\": null,\n \"lng\": null\n }).then(function succes() {\n $rootScope.$broadcast('widgetForceReInit');\n });"
"customFunction": "var entityDatasource = widgetContext.mapInstance.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.mapInstance.saveMarkerLocation(entityDatasource[0], null, null).subscribe(function success() {\n widgetContext.updateAliases();\n});",
"id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66"
}
]
},
@ -1033,6 +1062,61 @@
"displayTimewindow": true
},
"id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf"
},
"f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": {
"isSystemType": true,
"bundleAlias": "input_widgets",
"typeAlias": "update_double_timeseries",
"type": "latest",
"title": "New widget",
"sizeX": 7.5,
"sizeY": 3,
"config": {
"datasources": [
{
"type": "entity",
"name": null,
"entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547",
"dataKeys": [
{
"name": "temperature",
"type": "timeseries",
"label": "temperature",
"color": "#2196f3",
"settings": {},
"_hash": 0.4164505192982848
}
]
}
],
"timewindow": {
"realtime": {
"timewindowMs": 60000
}
},
"showTitle": true,
"backgroundColor": "#fff",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "8px",
"settings": {
"showResultMessage": true,
"showLabel": true
},
"title": "New Update double timeseries",
"dropShadow": true,
"enableFullscreen": false,
"widgetStyle": {},
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400
},
"useDashboardTimewindow": true,
"showLegend": false,
"actions": {}
},
"row": 0,
"col": 0,
"id": "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3"
}
},
"states": {
@ -1131,6 +1215,12 @@
"sizeY": 6,
"row": 6,
"col": 0
},
"f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": {
"sizeX": 7.5,
"sizeY": 3,
"row": 12,
"col": 0
}
},
"gridSettings": {
@ -1214,4 +1304,4 @@
}
},
"name": "Thermostats"
}
}

3
application/src/main/data/json/demo/rule_chains/root_rule_chain.json

@ -43,7 +43,8 @@
"name": "Save Client Attributes",
"debugMode": false,
"configuration": {
"scope": "CLIENT_SCOPE"
"scope": "CLIENT_SCOPE",
"notifyDevice": "false"
}
},
{

6
application/src/main/data/json/system/widget_bundles/alarm_widgets.json

@ -16,10 +16,10 @@
"templateHtml": "<tb-alarms-table-widget \n [ctx]=\"ctx\">\n</tb-alarms-table-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.alarmsTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStatusFilter\": {\n \"title\": \"Enable alarm status filter\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStatusFilter\",\n \"enableStickyAction\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableFilter\": {\n \"title\": \"Enable alarm filter\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableFilter\",\n \"enableStickyAction\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}",
"dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
"defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStatusFilter\":true,\"enableStickyAction\":false},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{}}"
"defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStickyAction\":false,\"enableFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{},\"alarmStatusList\":[],\"alarmSeverityList\":[],\"alarmTypeList\":[],\"searchPropagatedAlarms\":false}"
}
}
]
}
}

56
application/src/main/data/json/system/widget_bundles/analogue_gauges.json

@ -6,35 +6,19 @@
},
"widgetTypes": [
{
"alias": "radial_gauge_canvas_gauges",
"name": "Radial gauge - Canvas Gauges",
"alias": "analogue_compass",
"name": "Analogue Compass",
"descriptor": {
"type": "latest",
"sizeX": 6,
"sizeY": 5,
"resources": [],
"templateHtml": "<canvas id=\"radialGauge\"></canvas>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueRadialGauge(self.ctx, 'radialGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueRadialGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < -100) {\\n\\tvalue = -100;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":10,\"highlights\":[],\"showUnitTitle\":true,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":10,\"valueInt\":3,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"numbersFont\":{\"family\":\"Roboto\",\"size\":18,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":36,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"minValue\":-100,\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Radial gauge - Canvas Gauges\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "speed_gauge_canvas_gauges",
"name": "Speed gauge - Canvas Gauges",
"descriptor": {
"type": "latest",
"sizeX": 7,
"sizeY": 5,
"resources": [],
"templateHtml": "<canvas id=\"radialGauge\"></canvas>\n",
"templateHtml": "<canvas id=\"compass\"></canvas>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueRadialGauge(self.ctx, 'radialGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueRadialGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onDestroy = function() {\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueCompass(self.ctx, 'compass');\n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueCompass.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 220) {\\n\\tvalue = 220;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":180,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":false,\"defaultColor\":\"#e65100\",\"needleCircleSize\":7,\"highlights\":[{\"from\":80,\"to\":120,\"color\":\"#fdd835\"},{\"color\":\"#e57373\",\"from\":120,\"to\":180}],\"showUnitTitle\":false,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":2,\"valueInt\":3,\"minValue\":0,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":1500,\"animationRule\":\"linear\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"colorNeedleShadowDown\":\"rgba(188, 143, 143, 0.78)\",\"units\":\"MPH\",\"majorTicksCount\":9,\"numbersFont\":{\"family\":\"Roboto\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":28,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"size\":32,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\",\"family\":\"Segment7Standard\"},\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Speed gauge - Canvas Gauges\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"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\":\"8px\",\"settings\":{\"minorTicks\":22,\"needleCircleSize\":15,\"showBorder\":true,\"borderOuterWidth\":10,\"colorPlate\":\"#222\",\"colorMajorTicks\":\"#f5f5f5\",\"colorMinorTicks\":\"#ddd\",\"colorNeedle\":\"#f08080\",\"colorNeedleCircle\":\"#e8e8e8\",\"colorBorder\":\"#ccc\",\"majorTickFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ccc\"},\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"animationTarget\":\"needle\",\"majorTicks\":[\"N\",\"NE\",\"E\",\"SE\",\"S\",\"SW\",\"W\",\"NW\"]},\"title\":\"Analogue Compass\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
},
{
@ -47,7 +31,7 @@
"resources": [],
"templateHtml": "<canvas id=\"linearGauge\"></canvas>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueLinearGauge(self.ctx, 'linearGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueLinearGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onDestroy = function() {\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueLinearGauge(self.ctx, 'linearGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueLinearGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 30 - 15;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"defaultColor\":\"#e64a19\",\"barStrokeWidth\":2.5,\"colorBar\":\"rgba(255, 255, 255, 0.4)\",\"colorBarEnd\":\"rgba(221, 221, 221, 0.38)\",\"showUnitTitle\":true,\"minorTicks\":2,\"valueBox\":true,\"valueInt\":3,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"colorNeedleShadowUp\":\"rgba(2,255,255,0.2)\",\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\",\"highlightsWidth\":10,\"animation\":true,\"animationDuration\":1500,\"animationRule\":\"linear\",\"showBorder\":false,\"majorTicksCount\":8,\"numbersFont\":{\"family\":\"Arial\",\"size\":18,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#78909c\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":26,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#37474f\"},\"valueFont\":{\"family\":\"Roboto\",\"size\":40,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#444\",\"shadowColor\":\"rgba(0,0,0,0.3)\"},\"minValue\":-60,\"highlights\":[{\"from\":-60,\"to\":-40,\"color\":\"#90caf9\"},{\"from\":-40,\"to\":-20,\"color\":\"rgba(144, 202, 249, 0.66)\"},{\"from\":-20,\"to\":0,\"color\":\"rgba(144, 202, 249, 0.33)\"},{\"from\":0,\"to\":20,\"color\":\"rgba(244, 67, 54, 0.2)\"},{\"from\":20,\"to\":40,\"color\":\"rgba(244, 67, 54, 0.4)\"},{\"from\":40,\"to\":60,\"color\":\"rgba(244, 67, 54, 0.6)\"},{\"from\":60,\"to\":80,\"color\":\"rgba(244, 67, 54, 0.8)\"},{\"from\":80,\"to\":100,\"color\":\"#f44336\"}],\"unitTitle\":\"Temperature\",\"units\":\"°C\",\"colorBarProgress\":\"#90caf9\",\"colorBarProgressEnd\":\"#f44336\",\"colorBarStroke\":\"#b0bec5\",\"valueDec\":1},\"title\":\"Temperature gauge - Canvas Gauges\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
@ -63,26 +47,42 @@
"resources": [],
"templateHtml": "<canvas id=\"radialGauge\"></canvas>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueRadialGauge(self.ctx, 'radialGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueRadialGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onDestroy = function() {\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueRadialGauge(self.ctx, 'radialGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueRadialGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":60,\"startAngle\":67.5,\"ticksAngle\":225,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":7,\"highlights\":[{\"from\":-60,\"to\":-50,\"color\":\"#42a5f5\"},{\"from\":-50,\"to\":-40,\"color\":\"rgba(66, 165, 245, 0.83)\"},{\"from\":-40,\"to\":-30,\"color\":\"rgba(66, 165, 245, 0.66)\"},{\"from\":-30,\"to\":-20,\"color\":\"rgba(66, 165, 245, 0.5)\"},{\"from\":-20,\"to\":-10,\"color\":\"rgba(66, 165, 245, 0.33)\"},{\"from\":-10,\"to\":0,\"color\":\"rgba(66, 165, 245, 0.16)\"},{\"from\":0,\"to\":10,\"color\":\"rgba(229, 115, 115, 0.16)\"},{\"from\":10,\"to\":20,\"color\":\"rgba(229, 115, 115, 0.33)\"},{\"from\":20,\"to\":30,\"color\":\"rgba(229, 115, 115, 0.5)\"},{\"from\":30,\"to\":40,\"color\":\"rgba(229, 115, 115, 0.66)\"},{\"from\":40,\"to\":50,\"color\":\"rgba(229, 115, 115, 0.83)\"},{\"from\":50,\"to\":60,\"color\":\"#e57373\"}],\"showUnitTitle\":true,\"colorPlate\":\"#cfd8dc\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":2,\"valueInt\":3,\"valueDec\":1,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":1000,\"animationRule\":\"bounce\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"colorNeedleShadowDown\":\"rgba(188, 143, 143, 0.78)\",\"units\":\"°C\",\"majorTicksCount\":12,\"numbersFont\":{\"family\":\"Roboto\",\"size\":20,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":28,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":30,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\",\"unitTitle\":\"Temperature\",\"minValue\":-60},\"title\":\"Temperature radial gauge - Canvas Gauges\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "analogue_compass",
"name": "Analogue Compass",
"alias": "speed_gauge_canvas_gauges",
"name": "Speed gauge - Canvas Gauges",
"descriptor": {
"type": "latest",
"sizeX": 7,
"sizeY": 5,
"resources": [],
"templateHtml": "<canvas id=\"radialGauge\"></canvas>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueRadialGauge(self.ctx, 'radialGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueRadialGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 220) {\\n\\tvalue = 220;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":180,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":false,\"defaultColor\":\"#e65100\",\"needleCircleSize\":7,\"highlights\":[{\"from\":80,\"to\":120,\"color\":\"#fdd835\"},{\"color\":\"#e57373\",\"from\":120,\"to\":180}],\"showUnitTitle\":false,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":2,\"valueInt\":3,\"minValue\":0,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":1500,\"animationRule\":\"linear\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"colorNeedleShadowDown\":\"rgba(188, 143, 143, 0.78)\",\"units\":\"MPH\",\"majorTicksCount\":9,\"numbersFont\":{\"family\":\"Roboto\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":28,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"size\":32,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\",\"family\":\"Segment7Standard\"},\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Speed gauge - Canvas Gauges\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "radial_gauge_canvas_gauges",
"name": "Radial gauge - Canvas Gauges",
"descriptor": {
"type": "latest",
"sizeX": 6,
"sizeY": 5,
"resources": [],
"templateHtml": "<canvas id=\"compass\"></canvas>",
"templateHtml": "<canvas id=\"radialGauge\"></canvas>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueCompass(self.ctx, 'compass');\n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueCompass.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onDestroy = function() {\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueRadialGauge(self.ctx, 'radialGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueRadialGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"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\":\"8px\",\"settings\":{\"minorTicks\":22,\"needleCircleSize\":15,\"showBorder\":true,\"borderOuterWidth\":10,\"colorPlate\":\"#222\",\"colorMajorTicks\":\"#f5f5f5\",\"colorMinorTicks\":\"#ddd\",\"colorNeedle\":\"#f08080\",\"colorNeedleCircle\":\"#e8e8e8\",\"colorBorder\":\"#ccc\",\"majorTickFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ccc\"},\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"animationTarget\":\"needle\",\"majorTicks\":[\"N\",\"NE\",\"E\",\"SE\",\"S\",\"SW\",\"W\",\"NW\"]},\"title\":\"Analogue Compass\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < -100) {\\n\\tvalue = -100;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":10,\"highlights\":[],\"showUnitTitle\":true,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":10,\"valueInt\":3,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"numbersFont\":{\"family\":\"Roboto\",\"size\":18,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":36,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"minValue\":-100,\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Radial gauge - Canvas Gauges\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
}
]

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

File diff suppressed because one or more lines are too long

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

@ -71,7 +71,7 @@
"resources": [],
"templateHtml": "",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.pie-label {\n font-size: 12px;\n font-family: 'Roboto';\n font-weight: bold;\n text-align: center;\n padding: 2px;\n color: white;\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'pie'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.pieSettingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.pieDatakeySettingsSchema;\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\nself.actionSources = function() {\n return {\n 'sliceClick': {\n name: 'widget-action.pie-slice-click',\n multiple: false\n }\n };\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'pie'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.pieSettingsSchema();\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.pieDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\nself.actionSources = function() {\n return {\n 'sliceClick': {\n name: 'widget-action.pie-slice-click',\n multiple: false\n }\n };\n}\n",
"settingsSchema": "{}\n",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.6114638304362894,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.9955906536344441,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.9430835931647599,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"radius\":1,\"fontColor\":\"#545454\",\"fontSize\":10,\"decimals\":1,\"legend\":{\"show\":true,\"position\":\"nw\",\"labelBoxBorderColor\":\"#CCCCCC\",\"backgroundColor\":\"#F0F0F0\",\"backgroundOpacity\":0.85},\"innerRadius\":0,\"showLabels\":true,\"showPercentages\":true,\"stroke\":{\"width\":5},\"tilt\":1,\"animatedPie\":false},\"title\":\"Pie - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
@ -166,7 +166,7 @@
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"
}
}
]

146
application/src/main/data/json/system/widget_bundles/digital_gauges.json

@ -6,35 +6,19 @@
},
"widgetTypes": [
{
"alias": "digital_bar",
"name": "Digital horizontal bar",
"descriptor": {
"type": "latest",
"sizeX": 6,
"sizeY": 2.5,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 80) {\\n\\tvalue = 80;\\n} else if (value > 160) {\\n\\tvalue = 160;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"horizontalBar\",\"showTitle\":false,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
},
{
"alias": "digital_speedometer",
"name": "Digital speedometer",
"alias": "gauge_justgage",
"name": "Gauge - justGage",
"descriptor": {
"type": "latest",
"sizeX": 5,
"sizeX": 4,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 45) {\\n\\tvalue = 45;\\n} else if (value > 130) {\\n\\tvalue = 130;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital speedometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":36,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"arc\"},\"title\":\"Gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
@ -47,58 +31,58 @@
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < -60) {\\n\\tvalue = 60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[\"#304ffe\",\"#7e57c2\",\"#ff4081\",\"#d32f2f\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"dashThickness\":1.5,\"minValue\":-60,\"gaugeColor\":\"#333333\",\"neonGlowBrightness\":35,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital thermometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
},
{
"alias": "digital_vertical_bar",
"name": "Digital vertical bar",
"alias": "digital_speedometer",
"name": "Digital speedometer",
"descriptor": {
"type": "latest",
"sizeX": 2.5,
"sizeY": 4.5,
"sizeX": 5,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#3d5afe\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":14},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":8,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#cccccc\"},\"neonGlowBrightness\":20,\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"verticalBar\",\"showTitle\":false,\"minValue\":-60,\"dashThickness\":1.2,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 45) {\\n\\tvalue = 45;\\n} else if (value > 130) {\\n\\tvalue = 130;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital speedometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
},
{
"alias": "gauge_justgage",
"name": "Gauge - justGage",
"alias": "neon_gauge_justgage",
"name": "Neon gauge - justGage",
"descriptor": {
"type": "latest",
"sizeX": 4,
"sizeX": 5,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":36,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"arc\"},\"title\":\"Gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":70,\"dashThickness\":1,\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Neon gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
},
{
"alias": "horizontal_bar_justgage",
"name": "Horizontal bar - justGage",
"alias": "lcd_gauge",
"name": "LCD gauge",
"descriptor": {
"type": "latest",
"sizeX": 7,
"sizeX": 5,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>\n",
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"horizontalBar\"},\"title\":\"Horizontal bar - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 180) {\\n\\tvalue = 180;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"arc\"},\"title\":\"LCD gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
@ -111,106 +95,122 @@
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"400\",\"size\":16},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"verticalBar\",\"units\":\"%\"},\"title\":\"LCD bar gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "lcd_gauge",
"name": "LCD gauge",
"alias": "simple_neon_gauge_justgage",
"name": "Simple neon gauge - justGage",
"descriptor": {
"type": "latest",
"sizeX": 5,
"sizeX": 3,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 180) {\\n\\tvalue = 180;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"arc\"},\"title\":\"LCD gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#388e3c\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Simple neon gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
},
{
"alias": "mini_gauge_justgage",
"name": "Mini gauge - justGage",
"alias": "vertical_bar_justgage",
"name": "Vertical bar - justGage",
"descriptor": {
"type": "latest",
"sizeX": 2,
"sizeY": 2,
"sizeY": 3.5,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#7cb342\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"roundedLineCap\":true,\"gaugeType\":\"donut\"},\"title\":\"Mini gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":12,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":1.5,\"gaugeColor\":\"#eeeeee\",\"showTitle\":false,\"gaugeType\":\"verticalBar\"},\"title\":\"Vertical bar - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "neon_gauge_justgage",
"name": "Neon gauge - justGage",
"alias": "simple_gauge_justgage",
"name": "Simple gauge - justGage",
"descriptor": {
"type": "latest",
"sizeX": 5,
"sizeY": 3,
"sizeX": 2,
"sizeY": 2,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>\n",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "\nself.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#ef6c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"gaugeColor\":\"#eeeeee\",\"gaugeType\":\"donut\"},\"title\":\"Simple gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "digital_bar",
"name": "Digital horizontal bar",
"descriptor": {
"type": "latest",
"sizeX": 6,
"sizeY": 2.5,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":70,\"dashThickness\":1,\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Neon gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 80) {\\n\\tvalue = 80;\\n} else if (value > 160) {\\n\\tvalue = 160;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"horizontalBar\",\"showTitle\":false,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
},
{
"alias": "simple_gauge_justgage",
"name": "Simple gauge - justGage",
"alias": "mini_gauge_justgage",
"name": "Mini gauge - justGage",
"descriptor": {
"type": "latest",
"sizeX": 2,
"sizeY": 2,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>\n",
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "\nself.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#ef6c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"gaugeColor\":\"#eeeeee\",\"gaugeType\":\"donut\"},\"title\":\"Simple gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#7cb342\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"roundedLineCap\":true,\"gaugeType\":\"donut\"},\"title\":\"Mini gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "simple_neon_gauge_justgage",
"name": "Simple neon gauge - justGage",
"alias": "horizontal_bar_justgage",
"name": "Horizontal bar - justGage",
"descriptor": {
"type": "latest",
"sizeX": 3,
"sizeX": 7,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>\n",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#388e3c\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Simple neon gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"horizontalBar\"},\"title\":\"Horizontal bar - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "vertical_bar_justgage",
"name": "Vertical bar - justGage",
"alias": "digital_vertical_bar",
"name": "Digital vertical bar",
"descriptor": {
"type": "latest",
"sizeX": 2,
"sizeY": 3.5,
"sizeX": 2.5,
"sizeY": 4.5,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n };\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":12,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":1.5,\"gaugeColor\":\"#eeeeee\",\"showTitle\":false,\"gaugeType\":\"verticalBar\"},\"title\":\"Vertical bar - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#3d5afe\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":14},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":8,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#cccccc\"},\"neonGlowBrightness\":20,\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"verticalBar\",\"showTitle\":false,\"minValue\":-60,\"dashThickness\":1.2,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
}
]

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

120
application/src/main/data/json/system/widget_bundles/maps.json

File diff suppressed because one or more lines are too long

135
application/src/main/data/json/tenant/device_profile/rule_chain_template.json

@ -0,0 +1,135 @@
{
"ruleChain": {
"additionalInfo": {
"description": ""
},
"name": "Device Profile Rule Chain Template",
"firstRuleNodeId": null,
"root": false,
"debugMode": false,
"configuration": null
},
"metadata": {
"firstNodeIndex": 6,
"nodes": [
{
"additionalInfo": {
"layoutX": 822,
"layoutY": 294
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries",
"debugMode": false,
"configuration": {
"defaultTTL": 0
}
},
{
"additionalInfo": {
"layoutX": 824,
"layoutY": 221
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
"debugMode": false,
"configuration": {
"scope": "CLIENT_SCOPE"
}
},
{
"additionalInfo": {
"layoutX": 494,
"layoutY": 309
},
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch",
"debugMode": false,
"configuration": {
"version": 0
}
},
{
"additionalInfo": {
"layoutX": 824,
"layoutY": 383
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device",
"debugMode": false,
"configuration": {
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
"additionalInfo": {
"layoutX": 823,
"layoutY": 444
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other",
"debugMode": false,
"configuration": {
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
"additionalInfo": {
"layoutX": 822,
"layoutY": 507
},
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "RPC Call Request",
"debugMode": false,
"configuration": {
"timeoutInSeconds": 60
}
},
{
"additionalInfo": {
"description": "",
"layoutX": 209,
"layoutY": 307
},
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node",
"debugMode": false,
"configuration": {
"persistAlarmRulesState": false
}
}
],
"connections": [
{
"fromIndex": 2,
"toIndex": 4,
"type": "Other"
},
{
"fromIndex": 2,
"toIndex": 1,
"type": "Post attributes"
},
{
"fromIndex": 2,
"toIndex": 0,
"type": "Post telemetry"
},
{
"fromIndex": 2,
"toIndex": 3,
"type": "RPC Request from Device"
},
{
"fromIndex": 2,
"toIndex": 5,
"type": "RPC Request to Device"
},
{
"fromIndex": 6,
"toIndex": 2,
"type": "Success"
}
],
"ruleChainConnections": null
}
}

24
application/src/main/data/json/tenant/rule_chains/root_rule_chain.json

@ -9,7 +9,7 @@
"configuration": null
},
"metadata": {
"firstNodeIndex": 2,
"firstNodeIndex": 6,
"nodes": [
{
"additionalInfo": {
@ -32,7 +32,8 @@
"name": "Save Client Attributes",
"debugMode": false,
"configuration": {
"scope": "CLIENT_SCOPE"
"scope": "CLIENT_SCOPE",
"notifyDevice": "false"
}
},
{
@ -82,9 +83,28 @@
"configuration": {
"timeoutInSeconds": 60
}
},
{
"additionalInfo": {
"description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.",
"layoutX": 204,
"layoutY": 240
},
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node",
"debugMode": false,
"configuration": {
"persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false
}
}
],
"connections": [
{
"fromIndex": 6,
"toIndex": 2,
"type": "Success"
},
{
"fromIndex": 2,
"toIndex": 4,

1
application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql

@ -64,6 +64,7 @@ BEGIN
AND tablename like 'ts_kv_' || '%'
AND tablename != 'ts_kv_latest'
AND tablename != 'ts_kv_dictionary'
AND tablename != 'ts_kv_indefinite'
LOOP
IF partition != partition_by_max_ttl_date THEN
IF partition_year IS NOT NULL THEN

18
application/src/main/data/upgrade/2.4.3/schema_update_ttl.sql

@ -22,45 +22,45 @@ BEGIN
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION delete_device_records_from_ts_kv(tenant_id varchar, customer_id varchar, ttl bigint,
CREATE OR REPLACE FUNCTION delete_device_records_from_ts_kv(tenant_id uuid, customer_id uuid, ttl bigint,
OUT deleted bigint) AS
$$
BEGIN
EXECUTE format(
'WITH deleted AS (DELETE FROM ts_kv WHERE entity_id IN (SELECT to_uuid(device.id) as entity_id FROM device WHERE tenant_id = %L and customer_id = %L) AND ts < %L::bigint RETURNING *) SELECT count(*) FROM deleted',
'WITH deleted AS (DELETE FROM ts_kv WHERE entity_id IN (SELECT device.id as entity_id FROM device WHERE tenant_id = %L and customer_id = %L) AND ts < %L::bigint RETURNING *) SELECT count(*) FROM deleted',
tenant_id, customer_id, ttl) into deleted;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION delete_asset_records_from_ts_kv(tenant_id varchar, customer_id varchar, ttl bigint,
CREATE OR REPLACE FUNCTION delete_asset_records_from_ts_kv(tenant_id uuid, customer_id uuid, ttl bigint,
OUT deleted bigint) AS
$$
BEGIN
EXECUTE format(
'WITH deleted AS (DELETE FROM ts_kv WHERE entity_id IN (SELECT to_uuid(asset.id) as entity_id FROM asset WHERE tenant_id = %L and customer_id = %L) AND ts < %L::bigint RETURNING *) SELECT count(*) FROM deleted',
'WITH deleted AS (DELETE FROM ts_kv WHERE entity_id IN (SELECT asset.id as entity_id FROM asset WHERE tenant_id = %L and customer_id = %L) AND ts < %L::bigint RETURNING *) SELECT count(*) FROM deleted',
tenant_id, customer_id, ttl) into deleted;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION delete_customer_records_from_ts_kv(tenant_id varchar, customer_id varchar, ttl bigint,
CREATE OR REPLACE FUNCTION delete_customer_records_from_ts_kv(tenant_id uuid, customer_id uuid, ttl bigint,
OUT deleted bigint) AS
$$
BEGIN
EXECUTE format(
'WITH deleted AS (DELETE FROM ts_kv WHERE entity_id IN (SELECT to_uuid(customer.id) as entity_id FROM customer WHERE tenant_id = %L and id = %L) AND ts < %L::bigint RETURNING *) SELECT count(*) FROM deleted',
'WITH deleted AS (DELETE FROM ts_kv WHERE entity_id IN (SELECT customer.id as entity_id FROM customer WHERE tenant_id = %L and id = %L) AND ts < %L::bigint RETURNING *) SELECT count(*) FROM deleted',
tenant_id, customer_id, ttl) into deleted;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid varchar(31),
CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid,
IN system_ttl bigint, INOUT deleted bigint)
LANGUAGE plpgsql AS
$$
DECLARE
tenant_cursor CURSOR FOR select tenant.id as tenant_id
from tenant;
tenant_id_record varchar;
customer_id_record varchar;
tenant_id_record uuid;
customer_id_record uuid;
tenant_ttl bigint;
customer_ttl bigint;
deleted_for_entities bigint;

35
application/src/main/data/upgrade/3.0.1/schema_ts_latest.sql

@ -0,0 +1,35 @@
--
-- Copyright © 2016-2020 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
CREATE TABLE IF NOT EXISTS ts_kv_latest
(
entity_id uuid NOT NULL,
key int NOT NULL,
ts bigint NOT NULL,
bool_v boolean,
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
json_v json,
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key)
);
CREATE TABLE IF NOT EXISTS ts_kv_dictionary
(
key varchar(255) NOT NULL,
key_id serial UNIQUE,
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
);

878
application/src/main/data/upgrade/3.0.1/schema_update_to_uuid.sql

@ -0,0 +1,878 @@
--
-- Copyright © 2016-2020 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS
$$
BEGIN
uuid_id := substring(entity_id, 8, 8) || '-' || substring(entity_id, 4, 4) || '-1' || substring(entity_id, 1, 3) ||
'-' || substring(entity_id, 16, 4) || '-' || substring(entity_id, 20, 12);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION extract_ts(uuid UUID) RETURNS BIGINT AS
$$
DECLARE
bytes bytea;
BEGIN
bytes := uuid_send(uuid);
RETURN
(
(
(get_byte(bytes, 0)::bigint << 24) |
(get_byte(bytes, 1)::bigint << 16) |
(get_byte(bytes, 2)::bigint << 8) |
(get_byte(bytes, 3)::bigint << 0)
) + (
((get_byte(bytes, 4)::bigint << 8 |
get_byte(bytes, 5)::bigint)) << 32
) + (
(((get_byte(bytes, 6)::bigint & 15) << 8 | get_byte(bytes, 7)::bigint) & 4095) << 48
) - 122192928000000000
) / 10000::double precision;
END
$$ LANGUAGE plpgsql
IMMUTABLE
PARALLEL SAFE
RETURNS NULL ON NULL INPUT;
CREATE OR REPLACE FUNCTION column_type_to_uuid(table_name varchar, column_name varchar) RETURNS VOID
LANGUAGE plpgsql AS
$$
BEGIN
execute format('ALTER TABLE %s RENAME COLUMN %s TO old_%s;', table_name, column_name, column_name);
execute format('ALTER TABLE %s ADD COLUMN %s UUID;', table_name, column_name);
execute format('UPDATE %s SET %s = to_uuid(old_%s) WHERE old_%s is not null;', table_name, column_name, column_name, column_name);
execute format('ALTER TABLE %s DROP COLUMN old_%s;', table_name, column_name);
END;
$$;
CREATE OR REPLACE FUNCTION get_column_type(table_name varchar, column_name varchar, OUT data_type varchar) RETURNS varchar
LANGUAGE plpgsql AS
$$
BEGIN
execute (format('SELECT data_type from information_schema.columns where table_name = %L and column_name = %L',
table_name, column_name)) INTO data_type;
END;
$$;
CREATE OR REPLACE PROCEDURE drop_all_idx()
LANGUAGE plpgsql AS
$$
BEGIN
DROP INDEX IF EXISTS idx_alarm_originator_alarm_type;
DROP INDEX IF EXISTS idx_alarm_originator_created_time;
DROP INDEX IF EXISTS idx_alarm_tenant_created_time;
DROP INDEX IF EXISTS idx_event_type_entity_id;
DROP INDEX IF EXISTS idx_relation_to_id;
DROP INDEX IF EXISTS idx_relation_from_id;
DROP INDEX IF EXISTS idx_device_customer_id;
DROP INDEX IF EXISTS idx_device_customer_id_and_type;
DROP INDEX IF EXISTS idx_device_type;
DROP INDEX IF EXISTS idx_asset_customer_id;
DROP INDEX IF EXISTS idx_asset_customer_id_and_type;
DROP INDEX IF EXISTS idx_asset_type;
DROP INDEX IF EXISTS idx_attribute_kv_by_key_and_last_update_ts;
END;
$$;
CREATE OR REPLACE PROCEDURE create_all_idx()
LANGUAGE plpgsql AS
$$
BEGIN
CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC);
CREATE INDEX IF NOT EXISTS idx_alarm_originator_created_time ON alarm(originator_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_created_time ON alarm(tenant_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id);
CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id);
CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id);
CREATE INDEX IF NOT EXISTS idx_device_customer_id ON device(tenant_id, customer_id);
CREATE INDEX IF NOT EXISTS idx_device_customer_id_and_type ON device(tenant_id, customer_id, type);
CREATE INDEX IF NOT EXISTS idx_device_type ON device(tenant_id, type);
CREATE INDEX IF NOT EXISTS idx_asset_customer_id ON asset(tenant_id, customer_id);
CREATE INDEX IF NOT EXISTS idx_asset_customer_id_and_type ON asset(tenant_id, customer_id, type);
CREATE INDEX IF NOT EXISTS idx_asset_type ON asset(tenant_id, type);
CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribute_kv(entity_id, attribute_key, last_update_ts desc);
END;
$$;
-- admin_settings
CREATE OR REPLACE PROCEDURE update_admin_settings()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'admin_settings';
column_id varchar := 'id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE admin_settings DROP CONSTRAINT admin_settings_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE admin_settings ADD CONSTRAINT admin_settings_pkey PRIMARY KEY (id);
ALTER TABLE admin_settings ADD COLUMN created_time BIGINT;
UPDATE admin_settings SET created_time = extract_ts(id) WHERE id is not null;
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
END;
$$;
-- alarm
CREATE OR REPLACE PROCEDURE update_alarm()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'alarm';
column_id varchar := 'id';
column_originator_id varchar := 'originator_id';
column_tenant_id varchar := 'tenant_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE alarm DROP CONSTRAINT alarm_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE alarm ADD COLUMN created_time BIGINT;
UPDATE alarm SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE alarm ADD CONSTRAINT alarm_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_originator_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_originator_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_originator_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_originator_id;
END IF;
data_type := get_column_type(table_name, column_tenant_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_tenant_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id;
END IF;
END;
$$;
-- asset
CREATE OR REPLACE PROCEDURE update_asset()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'asset';
column_id varchar := 'id';
column_customer_id varchar := 'customer_id';
column_tenant_id varchar := 'tenant_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE asset DROP CONSTRAINT asset_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE asset ADD COLUMN created_time BIGINT;
UPDATE asset SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE asset ADD CONSTRAINT asset_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_customer_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_customer_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id;
END IF;
data_type := get_column_type(table_name, column_tenant_id);
IF data_type = 'character varying' THEN
ALTER TABLE asset DROP CONSTRAINT asset_name_unq_key;
PERFORM column_type_to_uuid(table_name, column_tenant_id);
ALTER TABLE asset ADD CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name);
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id;
END IF;
END;
$$;
-- attribute_kv
CREATE OR REPLACE PROCEDURE update_attribute_kv()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'attribute_kv';
column_entity_id varchar := 'entity_id';
BEGIN
data_type := get_column_type(table_name, column_entity_id);
IF data_type = 'character varying' THEN
ALTER TABLE attribute_kv DROP CONSTRAINT attribute_kv_pkey;
PERFORM column_type_to_uuid(table_name, column_entity_id);
ALTER TABLE attribute_kv ADD CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key);
RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id;
END IF;
END;
$$;
-- audit_log
CREATE OR REPLACE PROCEDURE update_audit_log()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'audit_log';
column_id varchar := 'id';
column_customer_id varchar := 'customer_id';
column_tenant_id varchar := 'tenant_id';
column_entity_id varchar := 'entity_id';
column_user_id varchar := 'user_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE audit_log DROP CONSTRAINT audit_log_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE audit_log ADD COLUMN created_time BIGINT;
UPDATE audit_log SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE audit_log ADD CONSTRAINT audit_log_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_customer_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_customer_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id;
END IF;
data_type := get_column_type(table_name, column_tenant_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_tenant_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id;
END IF;
data_type := get_column_type(table_name, column_entity_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_entity_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id;
END IF;
data_type := get_column_type(table_name, column_user_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_user_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_user_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_user_id;
END IF;
END;
$$;
-- component_descriptor
CREATE OR REPLACE PROCEDURE update_component_descriptor()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'component_descriptor';
column_id varchar := 'id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE component_descriptor DROP CONSTRAINT component_descriptor_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE component_descriptor ADD CONSTRAINT component_descriptor_pkey PRIMARY KEY (id);
ALTER TABLE component_descriptor ADD COLUMN created_time BIGINT;
UPDATE component_descriptor SET created_time = extract_ts(id) WHERE id is not null;
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
END;
$$;
-- customer
CREATE OR REPLACE PROCEDURE update_customer()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'customer';
column_id varchar := 'id';
column_tenant_id varchar := 'tenant_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE customer DROP CONSTRAINT customer_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE customer ADD CONSTRAINT customer_pkey PRIMARY KEY (id);
ALTER TABLE customer ADD COLUMN created_time BIGINT;
UPDATE customer SET created_time = extract_ts(id) WHERE id is not null;
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_tenant_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_tenant_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id;
END IF;
END;
$$;
-- dashboard
CREATE OR REPLACE PROCEDURE update_dashboard()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'dashboard';
column_id varchar := 'id';
column_tenant_id varchar := 'tenant_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE dashboard DROP CONSTRAINT dashboard_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE dashboard ADD CONSTRAINT dashboard_pkey PRIMARY KEY (id);
ALTER TABLE dashboard ADD COLUMN created_time BIGINT;
UPDATE dashboard SET created_time = extract_ts(id) WHERE id is not null;
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_tenant_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_tenant_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id;
END IF;
END;
$$;
-- device
CREATE OR REPLACE PROCEDURE update_device()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'device';
column_id varchar := 'id';
column_customer_id varchar := 'customer_id';
column_tenant_id varchar := 'tenant_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE device DROP CONSTRAINT device_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE device ADD COLUMN created_time BIGINT;
UPDATE device SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE device ADD CONSTRAINT device_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_customer_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_customer_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id;
END IF;
data_type := get_column_type(table_name, column_tenant_id);
IF data_type = 'character varying' THEN
ALTER TABLE device DROP CONSTRAINT device_name_unq_key;
PERFORM column_type_to_uuid(table_name, column_tenant_id);
ALTER TABLE device ADD CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name);
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id;
END IF;
END;
$$;
-- device_credentials
CREATE OR REPLACE PROCEDURE update_device_credentials()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'device_credentials';
column_id varchar := 'id';
column_device_id varchar := 'device_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE device_credentials DROP CONSTRAINT device_credentials_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE device_credentials ADD COLUMN created_time BIGINT;
UPDATE device_credentials SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE device_credentials ADD CONSTRAINT device_credentials_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_device_id);
IF data_type = 'character varying' THEN
ALTER TABLE device_credentials DROP CONSTRAINT IF EXISTS device_credentials_device_id_unq_key;
PERFORM column_type_to_uuid(table_name, column_device_id);
-- remove duplicate credentials with same device_id
DELETE from device_credentials where id in (
select dc.id
from (
SELECT id, device_id,
ROW_NUMBER() OVER (
PARTITION BY
device_id
ORDER BY
created_time DESC
) row_num
FROM
device_credentials
) as dc
WHERE dc.row_num > 1
);
ALTER TABLE device_credentials ADD CONSTRAINT device_credentials_device_id_unq_key UNIQUE (device_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_device_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_device_id;
END IF;
END;
$$;
-- event
CREATE OR REPLACE PROCEDURE update_event()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'event';
column_id varchar := 'id';
column_entity_id varchar := 'entity_id';
column_tenant_id varchar := 'tenant_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE event DROP CONSTRAINT event_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE event ADD COLUMN created_time BIGINT;
UPDATE event SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE event ADD CONSTRAINT event_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
ALTER TABLE event DROP CONSTRAINT event_unq_key;
data_type := get_column_type(table_name, column_entity_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_entity_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id;
END IF;
data_type := get_column_type(table_name, column_tenant_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_tenant_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id;
END IF;
ALTER TABLE event ADD CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid);
END;
$$;
-- relation
CREATE OR REPLACE PROCEDURE update_relation()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'relation';
column_from_id varchar := 'from_id';
column_to_id varchar := 'to_id';
BEGIN
ALTER TABLE relation DROP CONSTRAINT relation_pkey;
data_type := get_column_type(table_name, column_from_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_from_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_from_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_from_id;
END IF;
data_type := get_column_type(table_name, column_to_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_to_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_to_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_to_id;
END IF;
ALTER TABLE relation ADD CONSTRAINT relation_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type);
END;
$$;
-- tb_user
CREATE OR REPLACE PROCEDURE update_tb_user()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'tb_user';
column_id varchar := 'id';
column_customer_id varchar := 'customer_id';
column_tenant_id varchar := 'tenant_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE tb_user DROP CONSTRAINT tb_user_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE tb_user ADD COLUMN created_time BIGINT;
UPDATE tb_user SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE tb_user ADD CONSTRAINT tb_user_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_customer_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_customer_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id;
END IF;
data_type := get_column_type(table_name, column_tenant_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_tenant_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id;
END IF;
END;
$$;
-- tenant
CREATE OR REPLACE PROCEDURE update_tenant()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'tenant';
column_id varchar := 'id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE tenant DROP CONSTRAINT tenant_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE tenant ADD COLUMN created_time BIGINT;
UPDATE tenant SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE tenant ADD CONSTRAINT tenant_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
END;
$$;
-- user_credentials
CREATE OR REPLACE PROCEDURE update_user_credentials()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'user_credentials';
column_id varchar := 'id';
column_user_id varchar := 'user_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE user_credentials DROP CONSTRAINT user_credentials_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE user_credentials ADD COLUMN created_time BIGINT;
UPDATE user_credentials SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE user_credentials ADD CONSTRAINT user_credentials_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_user_id);
IF data_type = 'character varying' THEN
ALTER TABLE user_credentials DROP CONSTRAINT user_credentials_user_id_key;
ALTER TABLE user_credentials RENAME COLUMN user_id TO old_user_id;
ALTER TABLE user_credentials ADD COLUMN user_id UUID UNIQUE;
UPDATE user_credentials SET user_id = to_uuid(old_user_id) WHERE old_user_id is not null;
ALTER TABLE user_credentials DROP COLUMN old_user_id;
RAISE NOTICE 'Table % column % updated!', table_name, column_user_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_user_id;
END IF;
END;
$$;
-- widget_type
CREATE OR REPLACE PROCEDURE update_widget_type()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'widget_type';
column_id varchar := 'id';
column_tenant_id varchar := 'tenant_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE widget_type DROP CONSTRAINT widget_type_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE widget_type ADD COLUMN created_time BIGINT;
UPDATE widget_type SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE widget_type ADD CONSTRAINT widget_type_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_tenant_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_tenant_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id;
END IF;
END;
$$;
-- widgets_bundle
CREATE OR REPLACE PROCEDURE update_widgets_bundle()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'widgets_bundle';
column_id varchar := 'id';
column_tenant_id varchar := 'tenant_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE widgets_bundle DROP CONSTRAINT widgets_bundle_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE widgets_bundle ADD COLUMN created_time BIGINT;
UPDATE widgets_bundle SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE widgets_bundle ADD CONSTRAINT widgets_bundle_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_tenant_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_tenant_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id;
END IF;
END;
$$;
-- rule_chain
CREATE OR REPLACE PROCEDURE update_rule_chain()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'rule_chain';
column_id varchar := 'id';
column_first_rule_node_id varchar := 'first_rule_node_id';
column_tenant_id varchar := 'tenant_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE rule_chain DROP CONSTRAINT rule_chain_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE rule_chain ADD COLUMN created_time BIGINT;
UPDATE rule_chain SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE rule_chain ADD CONSTRAINT rule_chain_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_first_rule_node_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_first_rule_node_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_first_rule_node_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_first_rule_node_id;
END IF;
data_type := get_column_type(table_name, column_tenant_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_tenant_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id;
END IF;
END;
$$;
-- rule_node
CREATE OR REPLACE PROCEDURE update_rule_node()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'rule_node';
column_id varchar := 'id';
column_rule_chain_id varchar := 'rule_chain_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE rule_node DROP CONSTRAINT rule_node_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE rule_node ADD COLUMN created_time BIGINT;
UPDATE rule_node SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE rule_node ADD CONSTRAINT rule_node_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_rule_chain_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_rule_chain_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_rule_chain_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_rule_chain_id;
END IF;
END;
$$;
-- entity_view
CREATE OR REPLACE PROCEDURE update_entity_view()
LANGUAGE plpgsql AS
$$
DECLARE
data_type varchar;
table_name varchar := 'entity_view';
column_id varchar := 'id';
column_entity_id varchar := 'entity_id';
column_tenant_id varchar := 'tenant_id';
column_customer_id varchar := 'customer_id';
BEGIN
data_type := get_column_type(table_name, column_id);
IF data_type = 'character varying' THEN
ALTER TABLE entity_view DROP CONSTRAINT entity_view_pkey;
PERFORM column_type_to_uuid(table_name, column_id);
ALTER TABLE entity_view ADD COLUMN created_time BIGINT;
UPDATE entity_view SET created_time = extract_ts(id) WHERE id is not null;
ALTER TABLE entity_view ADD CONSTRAINT entity_view_pkey PRIMARY KEY (id);
RAISE NOTICE 'Table % column % updated!', table_name, column_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_id;
END IF;
data_type := get_column_type(table_name, column_entity_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_entity_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id;
END IF;
data_type := get_column_type(table_name, column_tenant_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_tenant_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id;
END IF;
data_type := get_column_type(table_name, column_customer_id);
IF data_type = 'character varying' THEN
PERFORM column_type_to_uuid(table_name, column_customer_id);
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id;
ELSE
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id;
END IF;
END;
$$;
CREATE TABLE IF NOT EXISTS ts_kv_latest
(
entity_id uuid NOT NULL,
key int NOT NULL,
ts bigint NOT NULL,
bool_v boolean,
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
json_v json,
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key)
);
CREATE TABLE IF NOT EXISTS ts_kv_dictionary
(
key varchar(255) NOT NULL,
key_id serial UNIQUE,
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
);

17
application/src/main/data/upgrade/3.1.0/schema_update.sql

@ -0,0 +1,17 @@
--
-- Copyright © 2016-2020 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_alarm_type_created_time ON alarm(tenant_id, type, created_time DESC);

28
application/src/main/data/upgrade/3.1.1/schema_update_after.sql

@ -0,0 +1,28 @@
--
-- Copyright © 2016-2020 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
DROP PROCEDURE IF EXISTS update_tenant_profiles;
DROP PROCEDURE IF EXISTS update_device_profiles;
ALTER TABLE tenant ALTER COLUMN tenant_profile_id SET NOT NULL;
ALTER TABLE tenant DROP CONSTRAINT IF EXISTS fk_tenant_profile;
ALTER TABLE tenant ADD CONSTRAINT fk_tenant_profile FOREIGN KEY (tenant_profile_id) REFERENCES tenant_profile(id);
ALTER TABLE tenant DROP COLUMN IF EXISTS isolated_tb_core;
ALTER TABLE tenant DROP COLUMN IF EXISTS isolated_tb_rule_engine;
ALTER TABLE device ALTER COLUMN device_profile_id SET NOT NULL;
ALTER TABLE device DROP CONSTRAINT IF EXISTS fk_device_profile;
ALTER TABLE device ADD CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id);

81
application/src/main/data/upgrade/3.1.1/schema_update_before.sql

@ -0,0 +1,81 @@
--
-- Copyright © 2016-2020 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
CREATE TABLE IF NOT EXISTS device_profile (
id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
created_time bigint NOT NULL,
name varchar(255),
type varchar(255),
transport_type varchar(255),
profile_data jsonb,
description varchar,
search_text varchar(255),
is_default boolean,
tenant_id uuid,
default_rule_chain_id uuid,
CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
);
CREATE TABLE IF NOT EXISTS tenant_profile (
id uuid NOT NULL CONSTRAINT tenant_profile_pkey PRIMARY KEY,
created_time bigint NOT NULL,
name varchar(255),
profile_data jsonb,
description varchar,
search_text varchar(255),
is_default boolean,
isolated_tb_core boolean,
isolated_tb_rule_engine boolean,
CONSTRAINT tenant_profile_name_unq_key UNIQUE (name)
);
CREATE OR REPLACE PROCEDURE update_tenant_profiles()
LANGUAGE plpgsql AS
$$
BEGIN
UPDATE tenant as t SET tenant_profile_id = p.id
FROM
(SELECT id from tenant_profile WHERE isolated_tb_core = false AND isolated_tb_rule_engine = false) as p
WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = false AND t.isolated_tb_rule_engine = false;
UPDATE tenant as t SET tenant_profile_id = p.id
FROM
(SELECT id from tenant_profile WHERE isolated_tb_core = true AND isolated_tb_rule_engine = false) as p
WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = true AND t.isolated_tb_rule_engine = false;
UPDATE tenant as t SET tenant_profile_id = p.id
FROM
(SELECT id from tenant_profile WHERE isolated_tb_core = false AND isolated_tb_rule_engine = true) as p
WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = false AND t.isolated_tb_rule_engine = true;
UPDATE tenant as t SET tenant_profile_id = p.id
FROM
(SELECT id from tenant_profile WHERE isolated_tb_core = true AND isolated_tb_rule_engine = true) as p
WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = true AND t.isolated_tb_rule_engine = true;
END;
$$;
CREATE OR REPLACE PROCEDURE update_device_profiles()
LANGUAGE plpgsql AS
$$
BEGIN
UPDATE device as d SET device_profile_id = p.id, device_data = '{"configuration":{"type":"DEFAULT"}, "transportConfiguration":{"type":"DEFAULT"}}'
FROM
(SELECT id, tenant_id, name from device_profile) as p
WHERE d.device_profile_id IS NULL AND p.tenant_id = d.tenant_id AND d.type = p.name;
END;
$$;

3
application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java

@ -29,7 +29,8 @@ import java.util.Arrays;
@ComponentScan({"org.thingsboard.server.install",
"org.thingsboard.server.service.component",
"org.thingsboard.server.service.install",
"org.thingsboard.server.dao"})
"org.thingsboard.server.dao",
"org.thingsboard.server.common.stats"})
public class ThingsboardInstallApplication {
private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";

45
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -32,6 +32,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.common.data.DataConstants;
@ -44,7 +45,6 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
@ -60,17 +60,20 @@ import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleNodeStateService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
@ -78,6 +81,7 @@ import org.thingsboard.server.service.script.JsExecutorService;
import org.thingsboard.server.service.script.JsInvokeService;
import org.thingsboard.server.service.session.DeviceSessionCacheService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.transport.TbCoreToTransportService;
@ -90,7 +94,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Component
@ -126,6 +129,10 @@ public class ActorSystemContext {
@Getter
private DeviceService deviceService;
@Autowired
@Getter
private TbDeviceProfileCache deviceProfileCache;
@Autowired
@Getter
private AssetService assetService;
@ -138,6 +145,10 @@ public class ActorSystemContext {
@Getter
private TenantService tenantService;
@Autowired
@Getter
private TenantProfileService tenantProfileService;
@Autowired
@Getter
private CustomerService customerService;
@ -150,6 +161,10 @@ public class ActorSystemContext {
@Getter
private RuleChainService ruleChainService;
@Autowired
@Getter
private RuleNodeStateService ruleNodeStateService;
@Autowired
private PartitionService partitionService;
@ -169,10 +184,6 @@ public class ActorSystemContext {
@Getter
private EventService eventService;
@Autowired
@Getter
private AlarmService alarmService;
@Autowired
@Getter
private RelationService relationService;
@ -193,6 +204,10 @@ public class ActorSystemContext {
@Getter
private TelemetrySubscriptionService tsSubService;
@Autowired
@Getter
private AlarmSubscriptionService alarmService;
@Autowired
@Getter
private JsInvokeService jsSandbox;
@ -225,6 +240,10 @@ public class ActorSystemContext {
@Getter
private ClaimDevicesService claimDevicesService;
@Autowired
@Getter
private JsInvokeStats jsInvokeStats;
//TODO: separate context for TbCore and TbRuleEngine
@Autowired(required = false)
@Getter
@ -283,19 +302,14 @@ public class ActorSystemContext {
@Getter
private long statisticsPersistFrequency;
@Getter
private final AtomicInteger jsInvokeRequestsCount = new AtomicInteger(0);
@Getter
private final AtomicInteger jsInvokeResponsesCount = new AtomicInteger(0);
@Getter
private final AtomicInteger jsInvokeFailuresCount = new AtomicInteger(0);
@Scheduled(fixedDelayString = "${actors.statistics.js_print_interval_ms}")
public void printStats() {
if (statisticsEnabled) {
if (jsInvokeRequestsCount.get() > 0 || jsInvokeResponsesCount.get() > 0 || jsInvokeFailuresCount.get() > 0) {
if (jsInvokeStats.getRequests() > 0 || jsInvokeStats.getResponses() > 0 || jsInvokeStats.getFailures() > 0) {
log.info("Rule Engine JS Invoke Stats: requests [{}] responses [{}] failures [{}]",
jsInvokeRequestsCount.getAndSet(0), jsInvokeResponsesCount.getAndSet(0), jsInvokeFailuresCount.getAndSet(0));
jsInvokeStats.getRequests(), jsInvokeStats.getResponses(), jsInvokeStats.getFailures());
jsInvokeStats.reset();
}
}
}
@ -538,4 +552,5 @@ public class ActorSystemContext {
log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs);
getScheduler().schedule(() -> ctx.tell(msg), delayInMs, TimeUnit.MILLISECONDS);
}
}

5
application/src/main/java/org/thingsboard/server/actors/app/AppActor.java

@ -27,6 +27,7 @@ import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.tenant.TenantActor;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
@ -116,7 +117,9 @@ public class AppActor extends ContextAwareActor {
boolean isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
boolean isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE);
for (Tenant tenant : tenantIterator) {
if (isCore || (isRuleEngine && !tenant.isIsolatedTbRuleEngine())) {
// TODO: Tenant Profile from cache
TenantProfile tenantProfile = systemContext.getTenantProfileService().findTenantProfileById(TenantId.SYS_TENANT_ID, tenant.getTenantProfileId());
if (isCore || (isRuleEngine && !tenantProfile.isIsolatedTbRuleEngine())) {
log.debug("[{}] Creating tenant actor", tenant.getId());
getOrCreateTenantActor(tenant.getId());
log.debug("[{}] Tenant actor created.", tenant.getId());

21
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -28,6 +28,7 @@ import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@ -79,8 +80,6 @@ import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE;
import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
/**
* @author Andrew Shvayka
@ -279,17 +278,17 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture;
ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture;
if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
clientAttributesFuture = findAllAttributesByScope(CLIENT_SCOPE);
sharedAttributesFuture = findAllAttributesByScope(SHARED_SCOPE);
clientAttributesFuture = findAllAttributesByScope(DataConstants.CLIENT_SCOPE);
sharedAttributesFuture = findAllAttributesByScope(DataConstants.SHARED_SCOPE);
} else if (!CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE);
sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE);
clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE);
sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE);
} else if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
clientAttributesFuture = Futures.immediateFuture(Collections.emptyList());
sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE);
sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE);
} else {
sharedAttributesFuture = Futures.immediateFuture(Collections.emptyList());
clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE);
clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE);
}
return Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture));
}
@ -316,7 +315,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder();
if (msg.isDeleted()) {
List<String> sharedKeys = msg.getDeletedKeys().stream()
.filter(key -> SHARED_SCOPE.equals(key.getScope()))
.filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
.map(AttributeKey::getAttributeKey)
.collect(Collectors.toList());
if (!sharedKeys.isEmpty()) {
@ -324,7 +323,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
hasNotificationData = true;
}
} else {
if (SHARED_SCOPE.equals(msg.getScope())) {
if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) {
List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues());
if (attributes.size() > 0) {
List<TsKvProto> sharedUpdated = msg.getValues().stream().map(this::toTsKvProto)
@ -334,7 +333,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
hasNotificationData = true;
}
} else {
log.debug("[{}] No public server side attributes changed!", deviceId);
log.debug("[{}] No public shared side attributes changed!", deviceId);
}
}
}

82
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -22,6 +22,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
import org.thingsboard.rule.engine.api.RuleEngineRpcService;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.ScriptEngine;
@ -38,20 +40,21 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeState;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
@ -69,7 +72,6 @@ import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
@ -107,6 +109,7 @@ class DefaultTbContext implements TbContext {
if (nodeCtx.getSelf().isDebugMode()) {
relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th));
}
msg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId());
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null));
}
@ -124,7 +127,7 @@ class DefaultTbContext implements TbContext {
@Override
public void enqueue(TbMsg tbMsg, String queueName, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName);
enqueue(tpi, tbMsg, onFailure, onSuccess);
}
@ -133,55 +136,66 @@ class DefaultTbContext implements TbContext {
.setTenantIdMSB(getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(getTenantId().getId().getLeastSignificantBits())
.setTbMsg(TbMsg.toByteString(tbMsg)).build();
if (nodeCtx.getSelf().isDebugMode()) {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, "To Root Rule Chain");
}
mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg, new SimpleTbQueueCallback(onSuccess, onFailure));
}
@Override
public void enqueueForTellFailure(TbMsg tbMsg, String failureMessage) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg);
enqueueForTellNext(tpi, tbMsg, Collections.singleton(TbRelationTypes.FAILURE), failureMessage, null, null);
}
@Override
public void enqueueForTellNext(TbMsg tbMsg, String relationType) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg);
enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, null, null);
}
@Override
public void enqueueForTellNext(TbMsg tbMsg, Set<String> relationTypes) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg);
enqueueForTellNext(tpi, tbMsg, relationTypes, null, null, null);
}
@Override
public void enqueueForTellNext(TbMsg tbMsg, String relationType, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg);
enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure);
}
@Override
public void enqueueForTellNext(TbMsg tbMsg, Set<String> relationTypes, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg);
enqueueForTellNext(tpi, tbMsg, relationTypes, null, onSuccess, onFailure);
}
@Override
public void enqueueForTellNext(TbMsg tbMsg, String queueName, String relationType, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName);
enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure);
}
@Override
public void enqueueForTellNext(TbMsg tbMsg, String queueName, Set<String> relationTypes, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName);
enqueueForTellNext(tpi, tbMsg, relationTypes, null, onSuccess, onFailure);
}
private void enqueueForTellNext(TopicPartitionInfo tpi, TbMsg tbMsg, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) {
private TopicPartitionInfo resolvePartition(TbMsg tbMsg, String queueName) {
return mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator());
}
private TopicPartitionInfo resolvePartition(TbMsg tbMsg) {
return resolvePartition(tbMsg, tbMsg.getQueueName());
}
private void enqueueForTellNext(TopicPartitionInfo tpi, TbMsg source, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) {
RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId();
RuleNodeId ruleNodeId = nodeCtx.getSelf().getId();
tbMsg = TbMsg.newMsg(tbMsg, ruleChainId, ruleNodeId);
TbMsg tbMsg = TbMsg.newMsg(source, ruleChainId, ruleNodeId);
TransportProtos.ToRuleEngineMsg.Builder msg = TransportProtos.ToRuleEngineMsg.newBuilder()
.setTenantIdMSB(getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(getTenantId().getId().getLeastSignificantBits())
@ -190,6 +204,10 @@ class DefaultTbContext implements TbContext {
if (failureMessage != null) {
msg.setFailureMessage(failureMessage);
}
if (nodeCtx.getSelf().isDebugMode()) {
relationTypes.forEach(relationType ->
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, relationType));
}
mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg.build(), new SimpleTbQueueCallback(onSuccess, onFailure));
}
@ -198,6 +216,7 @@ class DefaultTbContext implements TbContext {
if (nodeCtx.getSelf().isDebugMode()) {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, "ACK", null);
}
tbMsg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId());
tbMsg.getCallback().onSuccess();
}
@ -295,21 +314,21 @@ class DefaultTbContext implements TbContext {
@Override
public void logJsEvalRequest() {
if (mainCtx.isStatisticsEnabled()) {
mainCtx.getJsInvokeRequestsCount().incrementAndGet();
mainCtx.getJsInvokeStats().incrementRequests();
}
}
@Override
public void logJsEvalResponse() {
if (mainCtx.isStatisticsEnabled()) {
mainCtx.getJsInvokeResponsesCount().incrementAndGet();
mainCtx.getJsInvokeStats().incrementResponses();
}
}
@Override
public void logJsEvalFailure() {
if (mainCtx.isStatisticsEnabled()) {
mainCtx.getJsInvokeFailuresCount().incrementAndGet();
mainCtx.getJsInvokeStats().incrementFailures();
}
}
@ -354,7 +373,7 @@ class DefaultTbContext implements TbContext {
}
@Override
public AlarmService getAlarmService() {
public RuleEngineAlarmService getAlarmService() {
return mainCtx.getAlarmService();
}
@ -383,6 +402,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getEntityViewService();
}
@Override
public RuleEngineDeviceProfileCache getDeviceProfileCache() {
return mainCtx.getDeviceProfileCache();
}
@Override
public EdgeService getEdgeService() {
return mainCtx.getEdgeService();
@ -427,6 +451,30 @@ class DefaultTbContext implements TbContext {
return mainCtx.getRedisTemplate();
}
@Override
public PageData<RuleNodeState> findRuleNodeStates(PageLink pageLink) {
if (log.isDebugEnabled()) {
log.debug("[{}][{}] Fetch Rule Node States.", getTenantId(), getSelfId());
}
return mainCtx.getRuleNodeStateService().findByRuleNodeId(getTenantId(), getSelfId(), pageLink);
}
@Override
public RuleNodeState findRuleNodeStateForEntity(EntityId entityId) {
if (log.isDebugEnabled()) {
log.debug("[{}][{}][{}] Fetch Rule Node State for entity.", getTenantId(), getSelfId(), entityId);
}
return mainCtx.getRuleNodeStateService().findByRuleNodeIdAndEntityId(getTenantId(), getSelfId(), entityId);
}
@Override
public RuleNodeState saveRuleNodeState(RuleNodeState state) {
if (log.isDebugEnabled()) {
log.debug("[{}][{}][{}] Persist Rule Node State for entity: {}", getTenantId(), getSelfId(), state.getEntityId(), state.getStateData());
}
state.setRuleNodeId(getSelfId());
return mainCtx.getRuleNodeStateService().save(getTenantId(), state);
}
private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
TbMsgMetaData metaData = new TbMsgMetaData();

2
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java

@ -168,7 +168,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
private TbActorRef createRuleNodeActor(TbActorCtx ctx, RuleNode ruleNode) {
return ctx.getOrCreateChildActor(new TbEntityActorId(ruleNode.getId()),
() -> DefaultActorService.RULE_DISPATCHER_NAME,
() -> new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleNode.getName(), ruleNode.getId()));
() -> new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleChainName, ruleNode.getId()));
}
private void initRoutes(RuleChain ruleChain, List<RuleNode> ruleNodeList) {

5
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.RuleNodeException;
import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
/**
* @author Andrew Shvayka
@ -38,6 +39,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
private RuleNode ruleNode;
private TbNode tbNode;
private DefaultTbContext defaultCtx;
private RuleNodeInfo info;
RuleNodeActorMessageProcessor(TenantId tenantId, String ruleChainName, RuleNodeId ruleNodeId, ActorSystemContext systemContext
, TbActorRef parent, TbActorRef self) {
@ -46,6 +48,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
this.self = self;
this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
this.defaultCtx = new DefaultTbContext(systemContext, new RuleNodeCtx(tenantId, parent, self, ruleNode));
this.info = new RuleNodeInfo(ruleNodeId, ruleChainName, ruleNode != null ? ruleNode.getName() : "Unknown");
}
@Override
@ -59,6 +62,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
@Override
public void onUpdate(TbActorCtx context) throws Exception {
RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
this.info = new RuleNodeInfo(entityId, ruleChainName, newRuleNode != null ? newRuleNode.getName() : "Unknown");
boolean restartRequired = state != ComponentLifecycleState.ACTIVE ||
!(ruleNode.getType().equals(newRuleNode.getType()) && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
this.ruleNode = newRuleNode;
@ -99,6 +103,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
msg.getMsg().getCallback().onProcessingStart(info);
checkActive(msg.getMsg());
if (ruleNode.isDebugMode()) {
systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType());

11
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -31,6 +31,7 @@ import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
@ -47,6 +48,7 @@ import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
import java.util.List;
import java.util.Optional;
@ -76,12 +78,16 @@ public class TenantActor extends RuleChainManagerActor {
// This Service may be started for specific tenant only.
Optional<TenantId> isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant();
// TODO: Tenant Profile from cache
TenantProfile tenantProfile = systemContext.getTenantProfileService().findTenantProfileById(tenantId, tenant.getTenantProfileId());
isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE);
if (isRuleEngineForCurrentTenant) {
try {
if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenant.isIsolatedTbRuleEngine())) {
if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) {
log.info("[{}] Going to init rule chains", tenantId);
initRuleChains();
} else {
@ -112,6 +118,9 @@ public class TenantActor extends RuleChainManagerActor {
if (msg.getMsgType().equals(MsgType.QUEUE_TO_RULE_ENGINE_MSG)) {
QueueToRuleEngineMsg queueMsg = (QueueToRuleEngineMsg) msg;
queueMsg.getTbMsg().getCallback().onSuccess();
} else if (msg.getMsgType().equals(MsgType.TRANSPORT_TO_DEVICE_ACTOR_MSG)){
TransportToDeviceActorMsgWrapper transportMsg = (TransportToDeviceActorMsgWrapper) msg;
transportMsg.getCallback().onSuccess();
}
return true;
}

8
application/src/main/java/org/thingsboard/server/controller/AdminController.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@ -59,7 +60,11 @@ public class AdminController extends BaseController {
public AdminSettings getAdminSettings(@PathVariable("key") String key) throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
return checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key));
AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key));
if (adminSettings.getKey().equals("mail")) {
((ObjectNode) adminSettings.getJsonValue()).put("password", "");
}
return adminSettings;
} catch (Exception e) {
throw handleException(e);
}
@ -74,6 +79,7 @@ public class AdminController extends BaseController {
adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings));
if (adminSettings.getKey().equals("mail")) {
mailService.updateMailConfiguration();
((ObjectNode) adminSettings.getJsonValue()).put("password", "");
}
return adminSettings;
} catch (Exception e) {

8
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -87,10 +87,10 @@ public class AlarmController extends BaseController {
try {
alarm.setTenantId(getCurrentUser().getTenantId());
checkEntity(alarm.getId(), alarm, Resource.ALARM);
checkEntity(alarm.getId(), alarm, Resource.ALARM);
Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm));
logEntityAction(savedAlarm.getId(), savedAlarm,
logEntityAction(savedAlarm.getOriginator(), savedAlarm,
getCurrentUser().getCustomerId(),
alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
@ -132,7 +132,7 @@ public class AlarmController extends BaseController {
long ackTs = System.currentTimeMillis();
alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, ackTs).get();
alarm.setAckTs(ackTs);
logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null);
logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null);
sendNotificationMsgToEdgeService(getTenantId(), alarmId, ActionType.ALARM_ACK);
} catch (Exception e) {
@ -151,7 +151,7 @@ public class AlarmController extends BaseController {
long clearTs = System.currentTimeMillis();
alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, clearTs).get();
alarm.setClearTs(clearTs);
logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null);
logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null);
sendNotificationMsgToEdgeService(getTenantId(), alarmId, ActionType.ALARM_CLEAR);
} catch (Exception e) {

2
application/src/main/java/org/thingsboard/server/controller/AssetController.java

@ -490,7 +490,7 @@ public class AssetController extends BaseController {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(assetService.findAssetsByTenantIdAndEdgeId(tenantId, edgeId, pageLink).get());
return checkNotNull(assetService.findAssetsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
} catch (Exception e) {
throw handleException(e);
}

88
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
@ -32,12 +33,15 @@ import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@ -53,6 +57,7 @@ import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -60,6 +65,7 @@ import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
@ -79,7 +85,6 @@ import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
@ -87,6 +92,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
@ -96,6 +102,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
@ -107,12 +114,14 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.mail.MessagingException;
@ -142,6 +151,9 @@ public abstract class BaseController {
@Autowired
protected TenantService tenantService;
@Autowired
protected TenantProfileService tenantProfileService;
@Autowired
protected CustomerService customerService;
@ -151,11 +163,14 @@ public abstract class BaseController {
@Autowired
protected DeviceService deviceService;
@Autowired
protected DeviceProfileService deviceProfileService;
@Autowired
protected AssetService assetService;
@Autowired
protected AlarmService alarmService;
protected AlarmSubscriptionService alarmService;
@Autowired
protected DeviceCredentialsService deviceCredentialsService;
@ -208,6 +223,9 @@ public abstract class BaseController {
@Autowired
protected TbQueueProducerProvider producerProvider;
@Autowired
protected TbDeviceProfileCache deviceProfileCache;
@Autowired
protected EdgeNotificationService edgeNotificationService;
@ -329,6 +347,30 @@ public abstract class BaseController {
}
}
TenantInfo checkTenantInfoId(TenantId tenantId, Operation operation) throws ThingsboardException {
try {
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
TenantInfo tenant = tenantService.findTenantInfoById(tenantId);
checkNotNull(tenant);
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant);
return tenant;
} catch (Exception e) {
throw handleException(e, false);
}
}
TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation) throws ThingsboardException {
try {
validateId(tenantProfileId, "Incorrect tenantProfileId " + tenantProfileId);
TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(getTenantId(), tenantProfileId);
checkNotNull(tenantProfile);
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, operation);
return tenantProfile;
} catch (Exception e) {
throw handleException(e, false);
}
}
protected TenantId getTenantId() throws ThingsboardException {
return getCurrentUser().getTenantId();
}
@ -377,12 +419,18 @@ public abstract class BaseController {
case DEVICE:
checkDeviceId(new DeviceId(entityId.getId()), operation);
return;
case DEVICE_PROFILE:
checkDeviceProfileId(new DeviceProfileId(entityId.getId()), operation);
return;
case CUSTOMER:
checkCustomerId(new CustomerId(entityId.getId()), operation);
return;
case TENANT:
checkTenantId(new TenantId(entityId.getId()), operation);
return;
case TENANT_PROFILE:
checkTenantProfileId(new TenantProfileId(entityId.getId()), operation);
return;
case RULE_CHAIN:
checkRuleChain(new RuleChainId(entityId.getId()), operation);
return;
@ -442,6 +490,18 @@ public abstract class BaseController {
}
}
DeviceProfile checkDeviceProfileId(DeviceProfileId deviceProfileId, Operation operation) throws ThingsboardException {
try {
validateId(deviceProfileId, "Incorrect deviceProfileId " + deviceProfileId);
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(getCurrentUser().getTenantId(), deviceProfileId);
checkNotNull(deviceProfile);
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE_PROFILE, operation, deviceProfileId, deviceProfile);
return deviceProfile;
} catch (Exception e) {
throw handleException(e, false);
}
}
protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException {
try {
validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
@ -673,6 +733,12 @@ public abstract class BaseController {
case ALARM_CLEAR:
msgType = DataConstants.ALARM_CLEAR;
break;
case ASSIGNED_FROM_TENANT:
msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT;
break;
case ASSIGNED_TO_TENANT:
msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;
break;
case ASSIGNED_TO_EDGE:
msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE;
break;
@ -698,6 +764,16 @@ public abstract class BaseController {
String strCustomerName = extractParameter(String.class, 2, additionalInfo);
metaData.putValue("unassignedCustomerId", strCustomerId);
metaData.putValue("unassignedCustomerName", strCustomerName);
} else if (actionType == ActionType.ASSIGNED_FROM_TENANT) {
String strTenantId = extractParameter(String.class, 0, additionalInfo);
String strTenantName = extractParameter(String.class, 1, additionalInfo);
metaData.putValue("assignedFromTenantId", strTenantId);
metaData.putValue("assignedFromTenantName", strTenantName);
} else if (actionType == ActionType.ASSIGNED_TO_TENANT) {
String strTenantId = extractParameter(String.class, 0, additionalInfo);
String strTenantName = extractParameter(String.class, 1, additionalInfo);
metaData.putValue("assignedToTenantId", strTenantId);
metaData.putValue("assignedToTenantName", strTenantName);
}
if (actionType == ActionType.ASSIGNED_TO_EDGE) {
String strEdgeId = extractParameter(String.class, 1, additionalInfo);
@ -768,6 +844,14 @@ public abstract class BaseController {
return result;
}
protected <E extends HasName> String entityToStr(E entity) {
try {
return json.writeValueAsString(json.valueToTree(entity));
} catch (JsonProcessingException e) {
log.warn("[{}] Failed to convert entity to string!", entity, e);
}
return null;
}
protected void sendNotificationMsgToEdgeService(TenantId tenantId, EntityRelation relation, ActionType edgeEventAction) {
try {
sendNotificationMsgToEdgeService(tenantId, null, null, json.writeValueAsString(relation), EdgeEventType.RELATION, edgeEventAction);

2
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -566,7 +566,7 @@ public class DashboardController extends BaseController {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(dashboardService.findDashboardsByTenantIdAndEdgeId(tenantId, edgeId, pageLink).get());
return checkNotNull(dashboardService.findDashboardsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
} catch (Exception e) {
throw handleException(e);
}

68
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -40,24 +40,29 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
@ -78,6 +83,7 @@ public class DeviceController extends BaseController {
private static final String DEVICE_ID = "deviceId";
private static final String DEVICE_NAME = "deviceName";
private static final String TENANT_ID = "tenantId";
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET)
@ -323,6 +329,7 @@ public class DeviceController extends BaseController {
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String type,
@RequestParam(required = false) String deviceProfileId,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
@ -331,6 +338,9 @@ public class DeviceController extends BaseController {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
if (type != null && type.trim().length() > 0) {
return checkNotNull(deviceService.findDeviceInfosByTenantIdAndType(tenantId, type, pageLink));
} else if (deviceProfileId != null && deviceProfileId.length() > 0) {
DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId));
return checkNotNull(deviceService.findDeviceInfosByTenantIdAndDeviceProfileId(tenantId, profileId, pageLink));
} else {
return checkNotNull(deviceService.findDeviceInfosByTenantId(tenantId, pageLink));
}
@ -387,6 +397,7 @@ public class DeviceController extends BaseController {
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String type,
@RequestParam(required = false) String deviceProfileId,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
@ -398,6 +409,9 @@ public class DeviceController extends BaseController {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
if (type != null && type.trim().length() > 0) {
return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
} else if (deviceProfileId != null && deviceProfileId.length() > 0) {
DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId));
return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(tenantId, customerId, profileId, pageLink));
} else {
return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink));
}
@ -562,6 +576,56 @@ public class DeviceController extends BaseController {
return DataConstants.DEFAULT_SECRET_KEY;
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/device/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public Device assignDeviceToTenant(@PathVariable(TENANT_ID) String strTenantId,
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter(TENANT_ID, strTenantId);
checkParameter(DEVICE_ID, strDeviceId);
try {
DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
Device device = checkDeviceId(deviceId, Operation.ASSIGN_TO_TENANT);
TenantId newTenantId = new TenantId(toUUID(strTenantId));
Tenant newTenant = tenantService.findTenantById(newTenantId);
if (newTenant == null) {
throw new ThingsboardException("Could not find the specified Tenant!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
Device assignedDevice = deviceService.assignDeviceToTenant(newTenantId, device);
logEntityAction(getCurrentUser(), deviceId, assignedDevice,
assignedDevice.getCustomerId(),
ActionType.ASSIGNED_TO_TENANT, null, strTenantId, newTenant.getName());
Tenant currentTenant = tenantService.findTenantById(getTenantId());
pushAssignedFromNotification(currentTenant, newTenantId, assignedDevice);
return assignedDevice;
} catch (Exception e) {
logEntityAction(getCurrentUser(), emptyId(EntityType.DEVICE), null,
null,
ActionType.ASSIGNED_TO_TENANT, e, strTenantId);
throw handleException(e);
}
}
private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) {
String data = entityToStr(assignedDevice);
if (data != null) {
TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data);
tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null);
}
}
private TbMsgMetaData getMetaDataForAssignedFrom(Tenant tenant) {
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("assignedFromTenantId", tenant.getId().getId().toString());
metaData.putValue("assignedFromTenantName", tenant.getName());
return metaData;
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST)
@ResponseBody
@ -642,7 +706,7 @@ public class DeviceController extends BaseController {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(deviceService.findDevicesByTenantIdAndEdgeId(tenantId, edgeId, pageLink).get());
return checkNotNull(deviceService.findDevicesByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
} catch (Exception e) {
throw handleException(e);
}

203
application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java

@ -0,0 +1,203 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@RestController
@TbCoreComponent
@RequestMapping("/api")
@Slf4j
public class DeviceProfileController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody
public DeviceProfile getDeviceProfileById(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
checkParameter("deviceProfileId", strDeviceProfileId);
try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
return checkDeviceProfileId(deviceProfileId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfo/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody
public DeviceProfileInfo getDeviceProfileInfoById(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
checkParameter("deviceProfileId", strDeviceProfileId);
try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
return checkNotNull(deviceProfileService.findDeviceProfileInfoById(getTenantId(), deviceProfileId));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfo/default", method = RequestMethod.GET)
@ResponseBody
public DeviceProfileInfo getDefaultDeviceProfileInfo() throws ThingsboardException {
try {
return checkNotNull(deviceProfileService.findDefaultDeviceProfileInfo(getTenantId()));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile", method = RequestMethod.POST)
@ResponseBody
public DeviceProfile saveDeviceProfile(@RequestBody DeviceProfile deviceProfile) throws ThingsboardException {
try {
boolean created = deviceProfile.getId() == null;
deviceProfile.setTenantId(getTenantId());
checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE);
DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
deviceProfileCache.put(savedDeviceProfile);
tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);
tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), savedDeviceProfile.getId(),
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
logEntityAction(savedDeviceProfile.getId(), savedDeviceProfile,
null,
savedDeviceProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedDeviceProfile;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE_PROFILE), deviceProfile,
null, deviceProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void deleteDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
checkParameter("deviceProfileId", strDeviceProfileId);
try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.DELETE);
deviceProfileService.deleteDeviceProfile(getTenantId(), deviceProfileId);
deviceProfileCache.evict(deviceProfileId);
tbClusterService.onDeviceProfileDelete(deviceProfile, null);
tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), deviceProfile.getId(), ComponentLifecycleEvent.DELETED);
logEntityAction(deviceProfileId, deviceProfile,
null,
ActionType.DELETED, null, strDeviceProfileId);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE_PROFILE),
null,
null,
ActionType.DELETED, e, strDeviceProfileId);
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}/default", method = RequestMethod.POST)
@ResponseBody
public DeviceProfile setDefaultDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
checkParameter("deviceProfileId", strDeviceProfileId);
try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.WRITE);
DeviceProfile previousDefaultDeviceProfile = deviceProfileService.findDefaultDeviceProfile(getTenantId());
if (deviceProfileService.setDefaultDeviceProfile(getTenantId(), deviceProfileId)) {
if (previousDefaultDeviceProfile != null) {
previousDefaultDeviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), previousDefaultDeviceProfile.getId());
logEntityAction(previousDefaultDeviceProfile.getId(), previousDefaultDeviceProfile,
null, ActionType.UPDATED, null);
}
deviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), deviceProfileId);
logEntityAction(deviceProfile.getId(), deviceProfile,
null, ActionType.UPDATED, null);
}
return deviceProfile;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE_PROFILE),
null,
null,
ActionType.UPDATED, e, strDeviceProfileId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<DeviceProfile> getDeviceProfiles(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(deviceProfileService.findDeviceProfiles(getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<DeviceProfileInfo> getDeviceProfileInfos(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
}

79
application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java

@ -0,0 +1,79 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.query.EntityQueryService;
@RestController
@TbCoreComponent
@RequestMapping("/api")
public class EntityQueryController extends BaseController {
@Autowired
private EntityQueryService entityQueryService;
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entitiesQuery/count", method = RequestMethod.POST)
@ResponseBody
public long countEntitiesByQuery(@RequestBody EntityCountQuery query) throws ThingsboardException {
checkNotNull(query);
try {
return this.entityQueryService.countEntitiesByQuery(getCurrentUser(), query);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entitiesQuery/find", method = RequestMethod.POST)
@ResponseBody
public PageData<EntityData> findEntityDataByQuery(@RequestBody EntityDataQuery query) throws ThingsboardException {
checkNotNull(query);
try {
return this.entityQueryService.findEntityDataByQuery(getCurrentUser(), query);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarmsQuery/find", method = RequestMethod.POST)
@ResponseBody
public PageData<AlarmData> findAlarmDataByQuery(@RequestBody AlarmDataQuery query) throws ThingsboardException {
checkNotNull(query);
try {
return this.entityQueryService.findAlarmDataByQuery(getCurrentUser(), query);
} catch (Exception e) {
throw handleException(e);
}
}
}

177
application/src/main/java/org/thingsboard/server/controller/EntityViewController.java

@ -19,7 +19,9 @@ 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;
import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@ -48,11 +50,15 @@ import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
@ -61,10 +67,12 @@ import org.thingsboard.server.service.security.permission.Resource;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID;
import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
@ -79,6 +87,9 @@ public class EntityViewController extends BaseController {
public static final String ENTITY_VIEW_ID = "entityViewId";
@Autowired
private TimeseriesService tsService;
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.GET)
@ResponseBody
@ -111,16 +122,35 @@ public class EntityViewController extends BaseController {
try {
entityView.setTenantId(getCurrentUser().getTenantId());
checkEntity(entityView.getId(), entityView, Resource.ENTITY_VIEW);
List<ListenableFuture<?>> futures = new ArrayList<>();
if (entityView.getId() == null) {
accessControlService
.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, Operation.CREATE, null, entityView);
} else {
EntityView existingEntityView = checkEntityViewId(entityView.getId(), Operation.WRITE);
if (existingEntityView.getKeys() != null) {
if (existingEntityView.getKeys().getAttributes() != null) {
futures.add(deleteAttributesFromEntityView(existingEntityView, DataConstants.CLIENT_SCOPE, existingEntityView.getKeys().getAttributes().getCs(), getCurrentUser()));
futures.add(deleteAttributesFromEntityView(existingEntityView, DataConstants.SERVER_SCOPE, existingEntityView.getKeys().getAttributes().getCs(), getCurrentUser()));
futures.add(deleteAttributesFromEntityView(existingEntityView, DataConstants.SHARED_SCOPE, existingEntityView.getKeys().getAttributes().getCs(), getCurrentUser()));
}
}
List<String> tsKeys = existingEntityView.getKeys() != null && existingEntityView.getKeys().getTimeseries() != null ?
existingEntityView.getKeys().getTimeseries() : Collections.emptyList();
futures.add(deleteLatestFromEntityView(existingEntityView, tsKeys, getCurrentUser()));
}
EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView));
List<ListenableFuture<List<Void>>> futures = new ArrayList<>();
if (savedEntityView.getKeys() != null && savedEntityView.getKeys().getAttributes() != null) {
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs(), getCurrentUser()));
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs(), getCurrentUser()));
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh(), getCurrentUser()));
if (savedEntityView.getKeys() != null) {
if (savedEntityView.getKeys().getAttributes() != null) {
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs(), getCurrentUser()));
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs(), getCurrentUser()));
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh(), getCurrentUser()));
}
futures.add(copyLatestFromEntityToEntityView(savedEntityView, getCurrentUser()));
}
for (ListenableFuture<List<Void>> future : futures) {
for (ListenableFuture<?> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
@ -141,6 +171,125 @@ public class EntityViewController extends BaseController {
}
}
private ListenableFuture<Void> deleteLatestFromEntityView(EntityView entityView, List<String> keys, SecurityUser user) {
EntityViewId entityId = entityView.getId();
SettableFuture<Void> resultFuture = SettableFuture.create();
if (keys != null && !keys.isEmpty()) {
tsSubService.deleteLatest(entityView.getTenantId(), entityId, keys, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void tmp) {
try {
logTimeseriesDeleted(user, entityId, keys, null);
} catch (ThingsboardException e) {
log.error("Failed to log timeseries delete", e);
}
resultFuture.set(tmp);
}
@Override
public void onFailure(Throwable t) {
try {
logTimeseriesDeleted(user, entityId, keys, t);
} catch (ThingsboardException e) {
log.error("Failed to log timeseries delete", e);
}
resultFuture.setException(t);
}
});
} else {
tsSubService.deleteAllLatest(entityView.getTenantId(), entityId, new FutureCallback<Collection<String>>() {
@Override
public void onSuccess(@Nullable Collection<String> keys) {
try {
logTimeseriesDeleted(user, entityId, new ArrayList<>(keys), null);
} catch (ThingsboardException e) {
log.error("Failed to log timeseries delete", e);
}
resultFuture.set(null);
}
@Override
public void onFailure(Throwable t) {
try {
logTimeseriesDeleted(user, entityId, Collections.emptyList(), t);
} catch (ThingsboardException e) {
log.error("Failed to log timeseries delete", e);
}
resultFuture.setException(t);
}
});
}
return resultFuture;
}
private ListenableFuture<Void> deleteAttributesFromEntityView(EntityView entityView, String scope, List<String> keys, SecurityUser user) {
EntityViewId entityId = entityView.getId();
SettableFuture<Void> resultFuture = SettableFuture.create();
if (keys != null && !keys.isEmpty()) {
tsSubService.deleteAndNotify(entityView.getTenantId(), entityId, scope, keys, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void tmp) {
try {
logAttributesDeleted(user, entityId, scope, keys, null);
} catch (ThingsboardException e) {
log.error("Failed to log attribute delete", e);
}
resultFuture.set(tmp);
}
@Override
public void onFailure(Throwable t) {
try {
logAttributesDeleted(user, entityId, scope, keys, t);
} catch (ThingsboardException e) {
log.error("Failed to log attribute delete", e);
}
resultFuture.setException(t);
}
});
} else {
resultFuture.set(null);
}
return resultFuture;
}
private ListenableFuture<List<Void>> copyLatestFromEntityToEntityView(EntityView entityView, SecurityUser user) {
EntityViewId entityId = entityView.getId();
List<String> keys = entityView.getKeys() != null && entityView.getKeys().getTimeseries() != null ?
entityView.getKeys().getTimeseries() : Collections.emptyList();
long startTs = entityView.getStartTimeMs();
long endTs = entityView.getEndTimeMs() == 0 ? Long.MAX_VALUE : entityView.getEndTimeMs();
ListenableFuture<List<String>> keysFuture;
if (keys.isEmpty()) {
keysFuture = Futures.transform(tsService.findAllLatest(user.getTenantId(),
entityView.getEntityId()), latest -> latest.stream().map(TsKvEntry::getKey).collect(Collectors.toList()), MoreExecutors.directExecutor());
} else {
keysFuture = Futures.immediateFuture(keys);
}
ListenableFuture<List<TsKvEntry>> latestFuture = Futures.transformAsync(keysFuture, fetchKeys -> {
List<ReadTsKvQuery> queries = fetchKeys.stream().filter(key -> !isBlank(key)).map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, "DESC")).collect(Collectors.toList());
if (!queries.isEmpty()) {
return tsService.findAll(user.getTenantId(), entityView.getEntityId(), queries);
} else {
return Futures.immediateFuture(null);
}
}, MoreExecutors.directExecutor());
return Futures.transform(latestFuture, latestValues -> {
if (latestValues != null && !latestValues.isEmpty()) {
tsSubService.saveLatestAndNotify(entityView.getTenantId(), entityId, latestValues, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void tmp) {
}
@Override
public void onFailure(Throwable t) {
}
});
}
return null;
}, MoreExecutors.directExecutor());
}
private ListenableFuture<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys, SecurityUser user) throws ThingsboardException {
EntityViewId entityId = entityView.getId();
if (keys != null && !keys.isEmpty()) {
@ -187,10 +336,20 @@ public class EntityViewController extends BaseController {
}
private void logAttributesUpdated(SecurityUser user, EntityId entityId, String scope, List<AttributeKvEntry> attributes, Throwable e) throws ThingsboardException {
logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_UPDATED, toException(e),
logEntityAction(user, entityId, null, null, ActionType.ATTRIBUTES_UPDATED, toException(e),
scope, attributes);
}
private void logAttributesDeleted(SecurityUser user, EntityId entityId, String scope, List<String> keys, Throwable e) throws ThingsboardException {
logEntityAction(user, entityId, null, null, ActionType.ATTRIBUTES_DELETED, toException(e),
scope, keys);
}
private void logTimeseriesDeleted(SecurityUser user, EntityId entityId, List<String> keys, Throwable e) throws ThingsboardException {
logEntityAction(user, entityId, null, null, ActionType.TIMESERIES_DELETED, toException(e),
keys);
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
@ -522,7 +681,7 @@ public class EntityViewController extends BaseController {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndEdgeId(tenantId, edgeId, pageLink).get());
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
} catch (Exception e) {
throw handleException(e);
}

61
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@ -51,7 +52,10 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainData;
import org.thingsboard.server.common.data.rule.RuleChainImportResult;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
@ -60,6 +64,7 @@ import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.install.InstallScripts;
import org.thingsboard.server.service.script.JsInvokeService;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import org.thingsboard.server.service.security.permission.Operation;
@ -82,6 +87,9 @@ public class RuleChainController extends BaseController {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private InstallScripts installScripts;
@Autowired
private EventService eventService;
@ -159,6 +167,27 @@ public class RuleChainController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/device/default", method = RequestMethod.POST)
@ResponseBody
public RuleChain saveRuleChain(@RequestBody DefaultRuleChainCreateRequest request) throws ThingsboardException {
try {
checkNotNull(request);
checkNotNull(request.getName());
RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName());
logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null);
return savedRuleChain;
} catch (Exception e) {
RuleChain ruleChain = new RuleChain();
ruleChain.setName(request.getName());
logEntityAction(emptyId(EntityType.RULE_CHAIN), ruleChain, null, ActionType.ADDED, e);
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/root", method = RequestMethod.POST)
@ResponseBody
@ -395,6 +424,36 @@ public class RuleChainController extends BaseController {
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChains/export", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public RuleChainData exportRuleChains(@RequestParam("limit") int limit) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = new PageLink(limit);
return checkNotNull(ruleChainService.exportTenantRuleChains(tenantId, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChains/import", method = RequestMethod.POST)
@ResponseBody
public void importRuleChains(@RequestBody RuleChainData ruleChainData, @RequestParam(required = false, defaultValue = "false") boolean overwrite) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
List<RuleChainImportResult> importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, overwrite);
if (!CollectionUtils.isEmpty(importResults)) {
for (RuleChainImportResult importResult : importResults) {
tbClusterService.onEntityStateChange(importResult.getTenantId(), importResult.getRuleChainId(), importResult.getLifecycleEvent());
}
}
} catch (Exception e) {
throw handleException(e);
}
}
private String msgToOutput(TbMsg msg) throws Exception {
ObjectNode msgData = objectMapper.createObjectNode();
if (!StringUtils.isEmpty(msg.getData())) {
@ -491,7 +550,7 @@ public class RuleChainController extends BaseController {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink).get());
return checkNotNull(ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
} catch (Exception e) {
throw handleException(e);
}

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

@ -197,19 +197,21 @@ public class TelemetryController extends BaseController {
@RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"})
@ResponseBody
public DeferredResult<ResponseEntity> getTimeseries(
@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
@PathVariable("entityType") String entityType,
@PathVariable("entityId") String entityIdStr,
@RequestParam(name = "keys") String keys,
@RequestParam(name = "startTs") Long startTs,
@RequestParam(name = "endTs") Long endTs,
@RequestParam(name = "interval", defaultValue = "0") Long interval,
@RequestParam(name = "limit", defaultValue = "100") Integer limit,
@RequestParam(name = "agg", defaultValue = "NONE") String aggStr,
@RequestParam(name= "orderBy", defaultValue = "DESC") String orderBy,
@RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
(result, tenantId, entityId) -> {
// If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg))
List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy))
.collect(Collectors.toList());
Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());
@ -355,10 +357,9 @@ public class TelemetryController extends BaseController {
DataConstants.SHARED_SCOPE.equals(scope) ||
DataConstants.CLIENT_SCOPE.equals(scope)) {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> {
ListenableFuture<List<Void>> future = attributesService.removeAll(user.getTenantId(), entityId, scope, keys);
Futures.addCallback(future, new FutureCallback<List<Void>>() {
tsSubService.deleteAndNotify(tenantId, entityId, scope, keys, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable List<Void> tmp) {
public void onSuccess(@Nullable Void tmp) {
logAttributesDeleted(user, entityId, scope, keys, null);
if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) {
DeviceId deviceId = new DeviceId(entityId.getId());
@ -375,7 +376,7 @@ public class TelemetryController extends BaseController {
logAttributesDeleted(user, entityId, scope, keys, t);
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
}, executor);
});
});
} else {
return getImmediateDeferredResult("Invalid attribute scope: " + scope, HttpStatus.BAD_REQUEST);

33
application/src/main/java/org/thingsboard/server/controller/TenantController.java

@ -28,6 +28,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -58,8 +59,20 @@ public class TenantController extends BaseController {
checkParameter("tenantId", strTenantId);
try {
TenantId tenantId = new TenantId(toUUID(strTenantId));
checkTenantId(tenantId, Operation.READ);
return checkNotNull(tenantService.findTenantById(tenantId));
return checkTenantId(tenantId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/tenant/info/{tenantId}", method = RequestMethod.GET)
@ResponseBody
public TenantInfo getTenantInfoById(@PathVariable("tenantId") String strTenantId) throws ThingsboardException {
checkParameter("tenantId", strTenantId);
try {
TenantId tenantId = new TenantId(toUUID(strTenantId));
return checkTenantInfoId(tenantId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
@ -115,4 +128,20 @@ public class TenantController extends BaseController {
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<TenantInfo> getTenantInfos(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(tenantService.findTenantInfos(pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
}

162
application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java

@ -0,0 +1,162 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@RestController
@TbCoreComponent
@RequestMapping("/api")
@Slf4j
public class TenantProfileController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfile/{tenantProfileId}", method = RequestMethod.GET)
@ResponseBody
public TenantProfile getTenantProfileById(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException {
checkParameter("tenantProfileId", strTenantProfileId);
try {
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
return checkTenantProfileId(tenantProfileId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfileInfo/{tenantProfileId}", method = RequestMethod.GET)
@ResponseBody
public EntityInfo getTenantProfileInfoById(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException {
checkParameter("tenantProfileId", strTenantProfileId);
try {
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
return checkNotNull(tenantProfileService.findTenantProfileInfoById(getTenantId(), tenantProfileId));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfileInfo/default", method = RequestMethod.GET)
@ResponseBody
public EntityInfo getDefaultTenantProfileInfo() throws ThingsboardException {
try {
return checkNotNull(tenantProfileService.findDefaultTenantProfileInfo(getTenantId()));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfile", method = RequestMethod.POST)
@ResponseBody
public TenantProfile saveTenantProfile(@RequestBody TenantProfile tenantProfile) throws ThingsboardException {
try {
boolean newTenantProfile = tenantProfile.getId() == null;
if (newTenantProfile) {
accessControlService
.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, Operation.CREATE);
} else {
checkEntityId(tenantProfile.getId(), Operation.WRITE);
}
tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile));
return tenantProfile;
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfile/{tenantProfileId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void deleteTenantProfile(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException {
checkParameter("tenantProfileId", strTenantProfileId);
try {
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
checkTenantProfileId(tenantProfileId, Operation.DELETE);
tenantProfileService.deleteTenantProfile(getTenantId(), tenantProfileId);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfile/{tenantProfileId}/default", method = RequestMethod.POST)
@ResponseBody
public TenantProfile setDefaultTenantProfile(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException {
checkParameter("tenantProfileId", strTenantProfileId);
try {
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
TenantProfile tenantProfile = checkTenantProfileId(tenantProfileId, Operation.WRITE);
tenantProfileService.setDefaultTenantProfile(getTenantId(), tenantProfileId);
return tenantProfile;
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<TenantProfile> getTenantProfiles(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(tenantProfileService.findTenantProfiles(getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<EntityInfo> getTenantProfileInfos(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(tenantProfileService.findTenantProfileInfos(getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
}

22
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -246,6 +246,28 @@ public class UserController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/users", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<User> getUsers(
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
SecurityUser currentUser = getCurrentUser();
if (Authority.TENANT_ADMIN.equals(currentUser.getAuthority())) {
return checkNotNull(userService.findUsersByTenantId(currentUser.getTenantId(), pageLink));
} else {
return checkNotNull(userService.findCustomerUsers(currentUser.getTenantId(), currentUser.getCustomerId(), pageLink));
}
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/users", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody

31
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -28,7 +28,9 @@ import org.thingsboard.server.service.install.DatabaseTsUpgradeService;
import org.thingsboard.server.service.install.EntityDatabaseSchemaService;
import org.thingsboard.server.service.install.SystemDataLoaderService;
import org.thingsboard.server.service.install.TsDatabaseSchemaService;
import org.thingsboard.server.service.install.TsLatestDatabaseSchemaService;
import org.thingsboard.server.service.install.migrate.EntitiesMigrateService;
import org.thingsboard.server.service.install.migrate.TsLatestMigrateService;
import org.thingsboard.server.service.install.update.DataUpdateService;
@Service
@ -51,6 +53,9 @@ public class ThingsboardInstallService {
@Autowired
private TsDatabaseSchemaService tsDatabaseSchemaService;
@Autowired(required = false)
private TsLatestDatabaseSchemaService tsLatestDatabaseSchemaService;
@Autowired
private DatabaseEntitiesUpgradeService databaseEntitiesUpgradeService;
@ -72,6 +77,9 @@ public class ThingsboardInstallService {
@Autowired(required = false)
private EntitiesMigrateService entitiesMigrateService;
@Autowired(required = false)
private TsLatestMigrateService latestMigrateService;
public void performInstall() {
try {
if (isUpgrade) {
@ -82,6 +90,10 @@ public class ThingsboardInstallService {
entitiesMigrateService.migrate();
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
} else if ("3.0.1-cassandra".equals(upgradeFromVersion)) {
log.info("Migrating ThingsBoard latest timeseries data from cassandra to SQL database ...");
latestMigrateService.migrate();
log.info("Updating system data...");
} else {
switch (upgradeFromVersion) {
case "1.2.3": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion
@ -156,6 +168,20 @@ public class ThingsboardInstallService {
}
case "2.5.1":
log.info("Upgrading ThingsBoard from version 2.5.1 to 3.0.0 ...");
case "3.0.1":
log.info("Upgrading ThingsBoard from version 3.0.1 to 3.1.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.0.1");
dataUpdateService.updateData("3.0.1");
case "3.1.0":
log.info("Upgrading ThingsBoard from version 3.1.0 to 3.1.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.1.0");
case "3.1.1":
log.info("Upgrading ThingsBoard from version 3.1.1 to 3.2.0 ...");
if (databaseTsUpgradeService != null) {
databaseTsUpgradeService.upgradeDatabase("3.1.1");
}
databaseEntitiesUpgradeService.upgradeDatabase("3.1.1");
dataUpdateService.updateData("3.1.1");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break;
@ -178,11 +204,16 @@ public class ThingsboardInstallService {
tsDatabaseSchemaService.createDatabaseSchema();
if (tsLatestDatabaseSchemaService != null) {
tsLatestDatabaseSchemaService.createDatabaseSchema();
}
log.info("Loading system data...");
componentDiscoveryService.discoverComponents();
systemDataLoaderService.createSysAdmin();
systemDataLoaderService.createDefaultTenantProfiles();
systemDataLoaderService.createAdminSettings();
systemDataLoaderService.loadSystemWidgets();
// systemDataLoaderService.loadSystemPlugins();

201
application/src/main/java/org/thingsboard/server/service/edge/rpc/init/DefaultSyncEdgeService.java

@ -139,156 +139,129 @@ public class DefaultSyncEdgeService implements SyncEdgeService {
private ListenableFuture<Void> syncRuleChains(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
try {
ListenableFuture<PageData<RuleChain>> future = ruleChainService.findRuleChainsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
return Futures.transform(future, pageData -> {
try {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] rule chains(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (RuleChain ruleChain : pageData.getData()) {
RuleChainUpdateMsg ruleChainUpdateMsg =
ruleChainUpdateMsgConstructor.constructRuleChainUpdatedMsg(
edge.getRootRuleChainId(),
UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE,
ruleChain);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setRuleChainUpdateMsg(ruleChainUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(ruleChain.getId());
}
}
} catch (Exception e) {
log.error("Exception during loading edge rule chain(s) on sync!", e);
PageData<RuleChain> pageData = ruleChainService.findRuleChainsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] rule chains(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (RuleChain ruleChain : pageData.getData()) {
RuleChainUpdateMsg ruleChainUpdateMsg =
ruleChainUpdateMsgConstructor.constructRuleChainUpdatedMsg(
edge.getRootRuleChainId(),
UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE,
ruleChain);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setRuleChainUpdateMsg(ruleChainUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(ruleChain.getId());
}
return null;
}, ctx.getDbCallbackExecutor());
}
} catch (Exception e) {
log.error("Exception during loading edge rule chain(s) on sync!", e);
return Futures.immediateFuture(null);
}
return Futures.immediateFuture(null);
}
private ListenableFuture<Void> syncDevices(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
try {
ListenableFuture<PageData<Device>> future = deviceService.findDevicesByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
return Futures.transform(future, pageData -> {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] device(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (Device device : pageData.getData()) {
DeviceUpdateMsg deviceUpdateMsg =
deviceUpdateMsgConstructor.constructDeviceUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
device);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setDeviceUpdateMsg(deviceUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(device.getId());
}
PageData<Device> pageData = deviceService.findDevicesByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] device(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (Device device : pageData.getData()) {
DeviceUpdateMsg deviceUpdateMsg =
deviceUpdateMsgConstructor.constructDeviceUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
device);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setDeviceUpdateMsg(deviceUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(device.getId());
}
return null;
}, ctx.getDbCallbackExecutor());
}
} catch (Exception e) {
log.error("Exception during loading edge device(s) on sync!", e);
return Futures.immediateFuture(null);
}
return Futures.immediateFuture(null);
}
private ListenableFuture<Void> syncAssets(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
try {
ListenableFuture<PageData<Asset>> future = assetService.findAssetsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
return Futures.transform(future, pageData -> {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] asset(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (Asset asset : pageData.getData()) {
AssetUpdateMsg assetUpdateMsg =
assetUpdateMsgConstructor.constructAssetUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
asset);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setAssetUpdateMsg(assetUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(asset.getId());
}
PageData<Asset> pageData = assetService.findAssetsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] asset(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (Asset asset : pageData.getData()) {
AssetUpdateMsg assetUpdateMsg =
assetUpdateMsgConstructor.constructAssetUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
asset);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setAssetUpdateMsg(assetUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(asset.getId());
}
return null;
}, ctx.getDbCallbackExecutor());
}
} catch (Exception e) {
log.error("Exception during loading edge asset(s) on sync!", e);
return Futures.immediateFuture(null);
}
return Futures.immediateFuture(null);
}
private ListenableFuture<Void> syncEntityViews(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
try {
ListenableFuture<PageData<EntityView>> future = entityViewService.findEntityViewsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
return Futures.transform(future, pageData -> {
try {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] entity view(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (EntityView entityView : pageData.getData()) {
EntityViewUpdateMsg entityViewUpdateMsg =
entityViewUpdateMsgConstructor.constructEntityViewUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
entityView);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setEntityViewUpdateMsg(entityViewUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(entityView.getId());
}
}
} catch (Exception e) {
log.error("Exception during loading edge entity view(s) on sync!", e);
PageData<EntityView> pageData = entityViewService.findEntityViewsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] entity view(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (EntityView entityView : pageData.getData()) {
EntityViewUpdateMsg entityViewUpdateMsg =
entityViewUpdateMsgConstructor.constructEntityViewUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
entityView);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setEntityViewUpdateMsg(entityViewUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(entityView.getId());
}
return null;
}, ctx.getDbCallbackExecutor());
}
} catch (Exception e) {
log.error("Exception during loading edge entity view(s) on sync!", e);
return Futures.immediateFuture(null);
}
return Futures.immediateFuture(null);
}
private ListenableFuture<Void> syncDashboards(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
try {
ListenableFuture<PageData<DashboardInfo>> future = dashboardService.findDashboardsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
return Futures.transform(future, pageData -> {
try {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] dashboard(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (DashboardInfo dashboardInfo : pageData.getData()) {
Dashboard dashboard = dashboardService.findDashboardById(edge.getTenantId(), dashboardInfo.getId());
DashboardUpdateMsg dashboardUpdateMsg =
dashboardUpdateMsgConstructor.constructDashboardUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
dashboard);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setDashboardUpdateMsg(dashboardUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(dashboard.getId());
}
}
} catch (Exception e) {
log.error("Exception during loading edge dashboard(s) on sync!", e);
PageData<DashboardInfo> pageData = dashboardService.findDashboardsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] dashboard(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (DashboardInfo dashboardInfo : pageData.getData()) {
Dashboard dashboard = dashboardService.findDashboardById(edge.getTenantId(), dashboardInfo.getId());
DashboardUpdateMsg dashboardUpdateMsg =
dashboardUpdateMsgConstructor.constructDashboardUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
dashboard);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setDashboardUpdateMsg(dashboardUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(dashboard.getId());
}
return null;
}, ctx.getDbCallbackExecutor());
}
} catch (Exception e) {
log.error("Exception during loading edge dashboard(s) on sync!", e);
return Futures.immediateFuture(null);
}
return Futures.immediateFuture(null);
}
private void syncUsers(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {

1
application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java

@ -49,6 +49,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
log.info("Schema updated.");
break;
case "2.5.0":
case "3.1.1":
break;
default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);

30
application/src/main/java/org/thingsboard/server/service/install/CassandraTsLatestDatabaseSchemaService.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.NoSqlTsLatestDao;
@Service
@NoSqlTsLatestDao
@Profile("install")
public class CassandraTsLatestDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService
implements TsLatestDatabaseSchemaService {
public CassandraTsLatestDatabaseSchemaService() {
super("schema-ts-latest.cql");
}
}

83
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -27,11 +27,15 @@ import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.TenantProfileData;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
@ -46,9 +50,12 @@ import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
@ -82,6 +89,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Autowired
private TenantService tenantService;
@Autowired
private TenantProfileService tenantProfileService;
@Autowired
private CustomerService customerService;
@ -94,6 +104,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Autowired
private DeviceService deviceService;
@Autowired
private DeviceProfileService deviceProfileService;
@Autowired
private AttributesService attributesService;
@ -110,6 +123,50 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
createUser(Authority.SYS_ADMIN, null, null, "sysadmin@thingsboard.org", "sysadmin");
}
@Override
public void createDefaultTenantProfiles() throws Exception {
tenantProfileService.findOrCreateDefaultTenantProfile(TenantId.SYS_TENANT_ID);
TenantProfile isolatedTbCoreProfile = new TenantProfile();
isolatedTbCoreProfile.setDefault(false);
isolatedTbCoreProfile.setName("Isolated TB Core");
isolatedTbCoreProfile.setProfileData(new TenantProfileData());
isolatedTbCoreProfile.setDescription("Isolated TB Core tenant profile");
isolatedTbCoreProfile.setIsolatedTbCore(true);
isolatedTbCoreProfile.setIsolatedTbRuleEngine(false);
try {
tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreProfile);
} catch (DataValidationException e) {
log.warn(e.getMessage());
}
TenantProfile isolatedTbRuleEngineProfile = new TenantProfile();
isolatedTbRuleEngineProfile.setDefault(false);
isolatedTbRuleEngineProfile.setName("Isolated TB Rule Engine");
isolatedTbRuleEngineProfile.setProfileData(new TenantProfileData());
isolatedTbRuleEngineProfile.setDescription("Isolated TB Rule Engine tenant profile");
isolatedTbRuleEngineProfile.setIsolatedTbCore(false);
isolatedTbRuleEngineProfile.setIsolatedTbRuleEngine(true);
try {
tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbRuleEngineProfile);
} catch (DataValidationException e) {
log.warn(e.getMessage());
}
TenantProfile isolatedTbCoreAndTbRuleEngineProfile = new TenantProfile();
isolatedTbCoreAndTbRuleEngineProfile.setDefault(false);
isolatedTbCoreAndTbRuleEngineProfile.setName("Isolated TB Core and TB Rule Engine");
isolatedTbCoreAndTbRuleEngineProfile.setProfileData(new TenantProfileData());
isolatedTbCoreAndTbRuleEngineProfile.setDescription("Isolated TB Core and TB Rule Engine tenant profile");
isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbCore(true);
isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbRuleEngine(true);
try {
tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreAndTbRuleEngineProfile);
} catch (DataValidationException e) {
log.warn(e.getMessage());
}
}
@Override
public void createAdminSettings() throws Exception {
AdminSettings generalSettings = new AdminSettings();
@ -162,16 +219,18 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
createUser(Authority.CUSTOMER_USER, demoTenant.getId(), customerB.getId(), "customerB@thingsboard.org", CUSTOMER_CRED);
createUser(Authority.CUSTOMER_USER, demoTenant.getId(), customerC.getId(), "customerC@thingsboard.org", CUSTOMER_CRED);
createDevice(demoTenant.getId(), customerA.getId(), DEFAULT_DEVICE_TYPE, "Test Device A1", "A1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerA.getId(), DEFAULT_DEVICE_TYPE, "Test Device A2", "A2_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerA.getId(), DEFAULT_DEVICE_TYPE, "Test Device A3", "A3_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerB.getId(), DEFAULT_DEVICE_TYPE, "Test Device B1", "B1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerC.getId(), DEFAULT_DEVICE_TYPE, "Test Device C1", "C1_TEST_TOKEN", null);
DeviceProfile defaultDeviceProfile = this.deviceProfileService.findOrCreateDeviceProfile(demoTenant.getId(), DEFAULT_DEVICE_TYPE);
createDevice(demoTenant.getId(), customerA.getId(), defaultDeviceProfile.getId(), "Test Device A1", "A1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerA.getId(), defaultDeviceProfile.getId(), "Test Device A2", "A2_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerA.getId(), defaultDeviceProfile.getId(), "Test Device A3", "A3_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerB.getId(), defaultDeviceProfile.getId(), "Test Device B1", "B1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerC.getId(), defaultDeviceProfile.getId(), "Test Device C1", "C1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "DHT11 Demo Device", "DHT11_DEMO_TOKEN", "Demo device that is used in sample " +
createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "DHT11 Demo Device", "DHT11_DEMO_TOKEN", "Demo device that is used in sample " +
"applications that upload data from DHT11 temperature and humidity sensor");
createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
"Raspberry Pi GPIO control sample application");
Asset thermostatAlarms = new Asset();
@ -180,8 +239,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
thermostatAlarms.setType("AlarmPropagationAsset");
thermostatAlarms = assetService.saveAsset(thermostatAlarms);
DeviceId t1Id = createDevice(demoTenant.getId(), null, "thermostat", "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
DeviceId t2Id = createDevice(demoTenant.getId(), null, "thermostat", "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
DeviceProfile thermostatDeviceProfile = this.deviceProfileService.findOrCreateDeviceProfile(demoTenant.getId(), "thermostat");
DeviceId t1Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
DeviceId t2Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t1Id, "ToAlarmPropagationAsset"));
relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t2Id, "ToAlarmPropagationAsset"));
@ -257,14 +318,14 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
private Device createDevice(TenantId tenantId,
CustomerId customerId,
String type,
DeviceProfileId deviceProfileId,
String name,
String accessToken,
String description) {
Device device = new Device();
device.setTenantId(tenantId);
device.setCustomerId(customerId);
device.setType(type);
device.setDeviceProfileId(deviceProfileId);
device.setName(name);
if (description != null) {
ObjectNode additionalInfo = objectMapper.createObjectNode();

2
application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java

@ -18,11 +18,9 @@ package org.thingsboard.server.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlDao;
@Service
@HsqlDao
@SqlDao
@Profile("install")
public class HsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
implements EntityDatabaseSchemaService {

42
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -28,7 +28,6 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.dashboard.DashboardService;
@ -36,7 +35,6 @@ import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
@ -59,6 +57,7 @@ public class InstallScripts {
public static final String JSON_DIR = "json";
public static final String SYSTEM_DIR = "system";
public static final String TENANT_DIR = "tenant";
public static final String DEVICE_PROFILE_DIR = "device_profile";
public static final String DEMO_DIR = "demo";
public static final String RULE_CHAINS_DIR = "rule_chains";
public static final String WIDGET_BUNDLES_DIR = "widget_bundles";
@ -85,6 +84,10 @@ public class InstallScripts {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
}
public Path getDeviceProfileDefaultRuleChainTemplateFilePath() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DEVICE_PROFILE_DIR, "rule_chain_template.json");
}
public String getDataDir() {
if (!StringUtils.isEmpty(dataDir)) {
if (!Paths.get(this.dataDir).toFile().isDirectory()) {
@ -110,11 +113,44 @@ public class InstallScripts {
Path tenantChainsDir = getTenantRuleChainsDir();
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(tenantChainsDir, path -> path.toString().endsWith(InstallScripts.JSON_EXT))) {
dirStream.forEach(
path -> loadRuleChainFromFile(tenantId, path)
path -> {
try {
createRuleChainFromFile(tenantId, path, null);
} catch (Exception e) {
log.error("Unable to load rule chain from json: [{}]", path.toString());
throw new RuntimeException("Unable to load rule chain from json", e);
}
}
);
// TODO: voba
// dirStream.forEach(
// path -> loadRuleChainFromFile(tenantId, path)
// );
}
}
public RuleChain createDefaultRuleChain(TenantId tenantId, String ruleChainName) throws IOException {
return createRuleChainFromFile(tenantId, getDeviceProfileDefaultRuleChainTemplateFilePath(), ruleChainName);
}
public RuleChain createRuleChainFromFile(TenantId tenantId, Path templateFilePath, String newRuleChainName) throws IOException {
JsonNode ruleChainJson = objectMapper.readTree(templateFilePath.toFile());
RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
ruleChain.setTenantId(tenantId);
if (!StringUtils.isEmpty(newRuleChainName)) {
ruleChain.setName(newRuleChainName);
}
ruleChain = ruleChainService.saveRuleChain(ruleChain);
ruleChainMetaData.setRuleChainId(ruleChain.getId());
ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);
return ruleChain;
}
public void loadSystemWidgets() throws Exception {
Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {

2
application/src/main/java/org/thingsboard/server/service/install/PsqlEntityDatabaseSchemaService.java

@ -18,10 +18,8 @@ package org.thingsboard.server.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlDao;
@Service
@SqlDao
@PsqlDao
@Profile("install")
public class PsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService

10
application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java

@ -195,6 +195,14 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
}
break;
case "3.1.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Load TTL functions ...");
loadSql(conn, LOAD_TTL_FUNCTIONS_SQL);
log.info("Load Drop Partitions functions ...");
loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL);
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}
@ -239,4 +247,4 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
log.info("Failed to load PostgreSQL upgrade functions due to: {}", e.getMessage());
}
}
}
}

198
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -20,8 +20,14 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.util.SqlDao;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.service.install.sql.SqlDbHelper;
import java.nio.charset.Charset;
@ -30,7 +36,12 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLSyntaxErrorException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.List;
import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO;
import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS;
@ -54,7 +65,6 @@ import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
@Service
@Profile("install")
@Slf4j
@SqlDao
public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService {
private static final String SCHEMA_UPDATE_SQL = "schema_update.sql";
@ -74,6 +84,19 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
@Autowired
private InstallScripts installScripts;
@Autowired
private SystemDataLoaderService systemDataLoaderService;
@Autowired
private TenantService tenantService;
@Autowired
private DeviceService deviceService;
@Autowired
private DeviceProfileService deviceProfileService;
@Override
public void upgradeDatabase(String fromVersion) throws Exception {
switch (fromVersion) {
@ -183,18 +206,22 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.info("Updating schema ...");
try {
conn.createStatement().execute("ALTER TABLE asset ADD COLUMN label varchar(255)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {}
} catch (Exception e) {
}
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.2", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
try {
conn.createStatement().execute("ALTER TABLE device ADD CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {}
} catch (Exception e) {
}
try {
conn.createStatement().execute("ALTER TABLE device_credentials ADD CONSTRAINT device_credentials_id_unq_key UNIQUE (credentials_id)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {}
} catch (Exception e) {
}
try {
conn.createStatement().execute("ALTER TABLE asset ADD CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {}
} catch (Exception e) {
}
log.info("Schema updated.");
}
break;
@ -233,7 +260,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.info("Schema updated.");
}
break;
case "2.5.0":
case "2.6.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.5.0", SCHEMA_UPDATE_SQL);
@ -244,6 +271,143 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
} catch (Exception e) {}
log.info("Schema updated.");
}
break;
case "3.0.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
if (isOldSchema(conn, 3000001)) {
String[] tables = new String[]{"admin_settings", "alarm", "asset", "audit_log", "attribute_kv",
"component_descriptor", "customer", "dashboard", "device", "device_credentials", "event",
"relation", "tb_user", "tenant", "user_credentials", "widget_type", "widgets_bundle",
"rule_chain", "rule_node", "entity_view"};
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.0.1", "schema_update_to_uuid.sql");
loadSql(schemaUpdateFile, conn);
conn.createStatement().execute("call drop_all_idx()");
log.info("Optimizing alarm relations...");
conn.createStatement().execute("DELETE from relation WHERE relation_type_group = 'ALARM' AND relation_type <> 'ALARM_ANY';");
conn.createStatement().execute("DELETE from relation WHERE relation_type_group = 'ALARM' AND relation_type = 'ALARM_ANY' " +
"AND exists(SELECT * FROM alarm WHERE alarm.id = relation.to_id AND alarm.originator_id = relation.from_id)");
log.info("Alarm relations optimized.");
for (String table : tables) {
log.info("Updating table {}.", table);
Statement statement = conn.createStatement();
statement.execute("call update_" + table + "();");
SQLWarning warnings = statement.getWarnings();
if (warnings != null) {
log.info("{}", warnings.getMessage());
SQLWarning nextWarning = warnings.getNextWarning();
while (nextWarning != null) {
log.info("{}", nextWarning.getMessage());
nextWarning = nextWarning.getNextWarning();
}
}
conn.createStatement().execute("DROP PROCEDURE update_" + table);
log.info("Table {} updated.", table);
}
conn.createStatement().execute("call create_all_idx()");
conn.createStatement().execute("DROP PROCEDURE drop_all_idx");
conn.createStatement().execute("DROP PROCEDURE create_all_idx");
conn.createStatement().execute("DROP FUNCTION column_type_to_uuid");
log.info("Updating alarm relations...");
conn.createStatement().execute("UPDATE relation SET relation_type = 'ANY' WHERE relation_type_group = 'ALARM' AND relation_type = 'ALARM_ANY';");
log.info("Alarm relations updated.");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3001000;");
conn.createStatement().execute("VACUUM FULL");
}
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
case "3.1.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.0", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Schema updated.");
}
break;
case "3.1.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
if (isOldSchema(conn, 3001000)) {
try {
conn.createStatement().execute("ALTER TABLE device ADD COLUMN device_profile_id uuid, ADD COLUMN device_data jsonb");
} catch (Exception e) {
}
try {
conn.createStatement().execute("ALTER TABLE tenant ADD COLUMN tenant_profile_id uuid");
} catch (Exception e) {
}
try {
conn.createStatement().execute("CREATE TABLE IF NOT EXISTS rule_node_state (" +
" id uuid NOT NULL CONSTRAINT rule_node_state_pkey PRIMARY KEY," +
" created_time bigint NOT NULL," +
" rule_node_id uuid NOT NULL," +
" entity_type varchar(32) NOT NULL," +
" entity_id uuid NOT NULL," +
" state_data varchar(16384) NOT NULL," +
" CONSTRAINT rule_node_state_unq_key UNIQUE (rule_node_id, entity_id)," +
" CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE)");
} catch (Exception e) {
}
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update_before.sql");
loadSql(schemaUpdateFile, conn);
log.info("Creating default tenant profiles...");
systemDataLoaderService.createDefaultTenantProfiles();
log.info("Updating tenant profiles...");
conn.createStatement().execute("call update_tenant_profiles()");
log.info("Creating default device profiles...");
PageLink pageLink = new PageLink(100);
PageData<Tenant> pageData;
do {
pageData = tenantService.findTenants(pageLink);
for (Tenant tenant : pageData.getData()) {
List<EntitySubtype> deviceTypes = deviceService.findDeviceTypesByTenantId(tenant.getId()).get();
try {
deviceProfileService.createDefaultDeviceProfile(tenant.getId());
} catch (Exception e) {
}
for (EntitySubtype deviceType : deviceTypes) {
try {
deviceProfileService.findOrCreateDeviceProfile(tenant.getId(), deviceType.getType());
} catch (Exception e) {
}
}
}
pageLink = pageLink.nextPageLink();
} while (pageData.hasNext());
log.info("Updating device profiles...");
conn.createStatement().execute("call update_device_profiles()");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update_after.sql");
loadSql(schemaUpdateFile, conn);
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3002000;");
}
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}
@ -254,4 +418,24 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
Thread.sleep(5000);
}
protected boolean isOldSchema(Connection conn, long fromVersion) {
boolean isOldSchema = true;
try {
Statement statement = conn.createStatement();
statement.execute("CREATE TABLE IF NOT EXISTS tb_schema_settings ( schema_version bigint NOT NULL, CONSTRAINT tb_schema_settings_pkey PRIMARY KEY (schema_version));");
Thread.sleep(1000);
ResultSet resultSet = statement.executeQuery("SELECT schema_version FROM tb_schema_settings;");
if (resultSet.next()) {
isOldSchema = resultSet.getLong(1) <= fromVersion;
} else {
resultSet.close();
statement.execute("INSERT INTO tb_schema_settings (schema_version) VALUES (" + fromVersion + ")");
}
statement.close();
} catch (InterruptedException | SQLException e) {
log.info("Failed to check current PostgreSQL schema due to: {}", e.getMessage());
}
return isOldSchema;
}
}

2
application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java

@ -19,6 +19,8 @@ public interface SystemDataLoaderService {
void createSysAdmin() throws Exception;
void createDefaultTenantProfiles() throws Exception;
void createAdminSettings() throws Exception;
void loadSystemWidgets() throws Exception;

4
application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java

@ -177,6 +177,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
}
break;
case "3.1.1":
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}
@ -207,4 +209,4 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
log.info("Failed to load PostgreSQL upgrade functions due to: {}", e.getMessage());
}
}
}
}

19
application/src/main/java/org/thingsboard/server/service/install/TsLatestDatabaseSchemaService.java

@ -0,0 +1,19 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
public interface TsLatestDatabaseSchemaService extends DatabaseSchemaService {
}

2
application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java

@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.util.NoSqlAnyDao;
import org.thingsboard.server.dao.util.SqlDao;
import org.thingsboard.server.service.install.EntityDatabaseSchemaService;
import java.sql.Connection;
@ -42,7 +41,6 @@ import static org.thingsboard.server.service.install.migrate.CassandraToSqlColum
@Service
@Profile("install")
@SqlDao
@NoSqlAnyDao
@Slf4j
public class CassandraEntitiesToSqlMigrateService implements EntitiesMigrateService {

6
application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlTable.java

@ -128,7 +128,7 @@ public class CassandraToSqlTable {
return this.validateColumnData(data);
}
private CassandraToSqlColumnData[] validateColumnData(CassandraToSqlColumnData[] data) {
protected CassandraToSqlColumnData[] validateColumnData(CassandraToSqlColumnData[] data) {
for (int i=0;i<data.length;i++) {
CassandraToSqlColumn column = this.columns.get(i);
if (column.getType() == CassandraToSqlColumnType.STRING) {
@ -148,7 +148,7 @@ public class CassandraToSqlTable {
return data;
}
private void batchInsert(List<CassandraToSqlColumnData[]> batchData, Connection conn) throws SQLException {
protected void batchInsert(List<CassandraToSqlColumnData[]> batchData, Connection conn) throws SQLException {
boolean retry = false;
for (CassandraToSqlColumnData[] data : batchData) {
for (CassandraToSqlColumn column: this.columns) {
@ -269,7 +269,7 @@ public class CassandraToSqlTable {
return Optional.empty();
}
private Statement createCassandraSelectStatement() {
protected Statement createCassandraSelectStatement() {
StringBuilder selectStatementBuilder = new StringBuilder();
selectStatementBuilder.append("SELECT ");
for (CassandraToSqlColumn column : columns) {

233
application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java

@ -0,0 +1,233 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install.migrate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary;
import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey;
import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity;
import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository;
import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository;
import org.thingsboard.server.dao.util.NoSqlTsDao;
import org.thingsboard.server.dao.util.SqlTsLatestDao;
import org.thingsboard.server.service.install.InstallScripts;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.bigintColumn;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.booleanColumn;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.doubleColumn;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.idColumn;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.jsonColumn;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.stringColumn;
@Service
@Profile("install")
@NoSqlTsDao
@SqlTsLatestDao
@Slf4j
public class CassandraTsLatestToSqlMigrateService implements TsLatestMigrateService {
private static final int MAX_KEY_LENGTH = 255;
private static final int MAX_STR_V_LENGTH = 10000000;
@Autowired
private InsertLatestTsRepository insertLatestTsRepository;
@Autowired
protected CassandraCluster cluster;
@Autowired
protected TsKvDictionaryRepository dictionaryRepository;
@Autowired
private InstallScripts installScripts;
@Value("${spring.datasource.url}")
protected String dbUrl;
@Value("${spring.datasource.username}")
protected String dbUserName;
@Value("${spring.datasource.password}")
protected String dbPassword;
private final ConcurrentMap<String, Integer> tsKvDictionaryMap = new ConcurrentHashMap<>();
protected static final ReentrantLock tsCreationLock = new ReentrantLock();
@Override
public void migrate() throws Exception {
log.info("Performing migration of latest timeseries data from cassandra to SQL database ...");
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.0.1", "schema_ts_latest.sql");
loadSql(schemaUpdateFile, conn);
conn.setAutoCommit(false);
for (CassandraToSqlTable table : tables) {
table.migrateToSql(cluster.getSession(), conn);
}
} catch (Exception e) {
log.error("Unexpected error during ThingsBoard entities data migration!", e);
throw e;
}
}
private List<CassandraToSqlTable> tables = Arrays.asList(
new CassandraToSqlTable("ts_kv_latest_cf", "ts_kv_latest",
idColumn("entity_id"),
stringColumn("key"),
bigintColumn("ts"),
booleanColumn("bool_v"),
stringColumn("str_v"),
bigintColumn("long_v"),
doubleColumn("dbl_v"),
jsonColumn("json_v")) {
@Override
protected void batchInsert(List<CassandraToSqlColumnData[]> batchData, Connection conn) {
insertLatestTsRepository
.saveOrUpdate(batchData.stream().map(data -> getTsKvLatestEntity(data)).collect(Collectors.toList()));
}
@Override
protected CassandraToSqlColumnData[] validateColumnData(CassandraToSqlColumnData[] data) {
return data;
}
});
private TsKvLatestEntity getTsKvLatestEntity(CassandraToSqlColumnData[] data) {
TsKvLatestEntity latestEntity = new TsKvLatestEntity();
latestEntity.setEntityId(UUIDConverter.fromString(data[0].getValue()));
latestEntity.setKey(getOrSaveKeyId(data[1].getValue()));
latestEntity.setTs(Long.parseLong(data[2].getValue()));
String strV = data[4].getValue();
if (strV != null) {
if (strV.length() > MAX_STR_V_LENGTH) {
log.warn("[ts_kv_latest] Value size [{}] exceeds maximum size [{}] of column [str_v] and will be truncated!",
strV.length(), MAX_STR_V_LENGTH);
log.warn("Affected data:\n{}", strV);
strV = strV.substring(0, MAX_STR_V_LENGTH);
}
latestEntity.setStrValue(strV);
} else {
Long longV = null;
try {
longV = Long.parseLong(data[5].getValue());
} catch (Exception e) {
}
if (longV != null) {
latestEntity.setLongValue(longV);
} else {
Double doubleV = null;
try {
doubleV = Double.parseDouble(data[6].getValue());
} catch (Exception e) {
}
if (doubleV != null) {
latestEntity.setDoubleValue(doubleV);
} else {
String jsonV = data[7].getValue();
if (StringUtils.isNoneEmpty(jsonV)) {
latestEntity.setJsonValue(jsonV);
} else {
Boolean boolV = null;
try {
boolV = Boolean.parseBoolean(data[3].getValue());
} catch (Exception e) {
}
if (boolV != null) {
latestEntity.setBooleanValue(boolV);
} else {
log.warn("All values in key-value row are nullable ");
}
}
}
}
}
return latestEntity;
}
protected Integer getOrSaveKeyId(String strKey) {
if (strKey.length() > MAX_KEY_LENGTH) {
log.warn("[ts_kv_latest] Value size [{}] exceeds maximum size [{}] of column [key] and will be truncated!",
strKey.length(), MAX_KEY_LENGTH);
log.warn("Affected data:\n{}", strKey);
strKey = strKey.substring(0, MAX_KEY_LENGTH);
}
Integer keyId = tsKvDictionaryMap.get(strKey);
if (keyId == null) {
Optional<TsKvDictionary> tsKvDictionaryOptional;
tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey));
if (!tsKvDictionaryOptional.isPresent()) {
tsCreationLock.lock();
try {
tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey));
if (!tsKvDictionaryOptional.isPresent()) {
TsKvDictionary tsKvDictionary = new TsKvDictionary();
tsKvDictionary.setKey(strKey);
try {
TsKvDictionary saved = dictionaryRepository.save(tsKvDictionary);
tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId());
keyId = saved.getKeyId();
} catch (ConstraintViolationException e) {
tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey));
TsKvDictionary dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!"));
tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId());
keyId = dictionary.getKeyId();
}
} else {
keyId = tsKvDictionaryOptional.get().getKeyId();
}
} finally {
tsCreationLock.unlock();
}
} else {
keyId = tsKvDictionaryOptional.get().getKeyId();
tsKvDictionaryMap.put(strKey, keyId);
}
}
return keyId;
}
private void loadSql(Path sqlFile, Connection conn) throws Exception {
String sql = new String(Files.readAllBytes(sqlFile), Charset.forName("UTF-8"));
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
Thread.sleep(5000);
}
}

21
application/src/main/java/org/thingsboard/server/service/install/migrate/TsLatestMigrateService.java

@ -0,0 +1,21 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install.migrate;
public interface TsLatestMigrateService {
void migrate() throws Exception;
}

170
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -15,20 +15,51 @@
*/
package org.thingsboard.server.service.install.update;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
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;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.service.install.InstallScripts;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
@Service
@Profile("install")
@Slf4j
@ -43,6 +74,12 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired
private InstallScripts installScripts;
@Autowired
private EntityViewService entityViewService;
@Autowired
private TimeseriesService tsService;
@Override
public void updateData(String fromVersion) throws Exception {
switch (fromVersion) {
@ -50,6 +87,14 @@ public class DefaultDataUpdateService implements DataUpdateService {
log.info("Updating data from version 1.4.0 to 2.0.0 ...");
tenantsDefaultRuleChainUpdater.updateEntities(null);
break;
case "3.0.1":
log.info("Updating data from version 3.0.1 to 3.1.0 ...");
tenantsEntityViewsUpdater.updateEntities(null);
break;
case "3.1.1":
log.info("Updating data from version 3.1.1 to 3.2.0 ...");
tenantsRootRuleChainUpdater.updateEntities(null);
break;
default:
throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
}
@ -76,4 +121,127 @@ public class DefaultDataUpdateService implements DataUpdateService {
}
};
}
private PaginatedUpdater<String, Tenant> tenantsRootRuleChainUpdater =
new PaginatedUpdater<String, Tenant>() {
@Override
protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
return tenantService.findTenants(pageLink);
}
@Override
protected void updateEntity(Tenant tenant) {
try {
RuleChain ruleChain = ruleChainService.getRootTenantRuleChain(tenant.getId());
if (ruleChain == null) {
installScripts.createDefaultRuleChains(tenant.getId());
} else {
RuleChainMetaData md = ruleChainService.loadRuleChainMetaData(tenant.getId(), ruleChain.getId());
int oldIdx = md.getFirstNodeIndex();
int newIdx = md.getNodes().size();
if (md.getNodes().size() < oldIdx) {
// Skip invalid rule chains
return;
}
RuleNode oldFirstNode = md.getNodes().get(oldIdx);
if (oldFirstNode.getType().equals(TbDeviceProfileNode.class.getName())) {
// No need to update the rule node twice.
return;
}
RuleNode ruleNode = new RuleNode();
ruleNode.setRuleChainId(ruleChain.getId());
ruleNode.setName("Device Profile Node");
ruleNode.setType(TbDeviceProfileNode.class.getName());
ruleNode.setDebugMode(false);
TbDeviceProfileNodeConfiguration ruleNodeConfiguration = new TbDeviceProfileNodeConfiguration().defaultConfiguration();
ruleNode.setConfiguration(JacksonUtil.valueToTree(ruleNodeConfiguration));
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
additionalInfo.put("description", "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.");
additionalInfo.put("layoutX", 204);
additionalInfo.put("layoutY", 240);
ruleNode.setAdditionalInfo(additionalInfo);
md.getNodes().add(ruleNode);
md.setFirstNodeIndex(newIdx);
md.addConnectionInfo(newIdx, oldIdx, "Success");
ruleChainService.saveRuleChainMetaData(tenant.getId(), md);
}
} catch (Exception e) {
log.error("Unable to update Tenant", e);
}
}
};
private PaginatedUpdater<String, Tenant> tenantsEntityViewsUpdater =
new PaginatedUpdater<String, Tenant>() {
@Override
protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
return tenantService.findTenants(pageLink);
}
@Override
protected void updateEntity(Tenant tenant) {
updateTenantEntityViews(tenant.getId());
}
};
private void updateTenantEntityViews(TenantId tenantId) {
PageLink pageLink = new PageLink(100);
PageData<EntityView> pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);
boolean hasNext = true;
while (hasNext) {
List<ListenableFuture<List<Void>>> updateFutures = new ArrayList<>();
for (EntityView entityView : pageData.getData()) {
updateFutures.add(updateEntityViewLatestTelemetry(entityView));
}
try {
Futures.allAsList(updateFutures).get();
} catch (InterruptedException | ExecutionException e) {
log.error("Failed to copy latest telemetry to entity view", e);
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);
} else {
hasNext = false;
}
}
}
private ListenableFuture<List<Void>> updateEntityViewLatestTelemetry(EntityView entityView) {
EntityViewId entityId = entityView.getId();
List<String> keys = entityView.getKeys() != null && entityView.getKeys().getTimeseries() != null ?
entityView.getKeys().getTimeseries() : Collections.emptyList();
long startTs = entityView.getStartTimeMs();
long endTs = entityView.getEndTimeMs() == 0 ? Long.MAX_VALUE : entityView.getEndTimeMs();
ListenableFuture<List<String>> keysFuture;
if (keys.isEmpty()) {
keysFuture = Futures.transform(tsService.findAllLatest(TenantId.SYS_TENANT_ID,
entityView.getEntityId()), latest -> latest.stream().map(TsKvEntry::getKey).collect(Collectors.toList()), MoreExecutors.directExecutor());
} else {
keysFuture = Futures.immediateFuture(keys);
}
ListenableFuture<List<TsKvEntry>> latestFuture = Futures.transformAsync(keysFuture, fetchKeys -> {
List<ReadTsKvQuery> queries = fetchKeys.stream().filter(key -> !isBlank(key)).map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, "DESC")).collect(Collectors.toList());
if (!queries.isEmpty()) {
return tsService.findAll(TenantId.SYS_TENANT_ID, entityView.getEntityId(), queries);
} else {
return Futures.immediateFuture(null);
}
}, MoreExecutors.directExecutor());
return Futures.transformAsync(latestFuture, latestValues -> {
if (latestValues != null && !latestValues.isEmpty()) {
ListenableFuture<List<Void>> saveFuture = tsService.saveLatest(TenantId.SYS_TENANT_ID, entityId, latestValues);
return saveFuture;
}
return Futures.immediateFuture(null);
}, MoreExecutors.directExecutor());
}
}

99
application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java

@ -0,0 +1,99 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.profile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Service
@Slf4j
public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache {
private final Lock deviceProfileFetchLock = new ReentrantLock();
private final DeviceProfileService deviceProfileService;
private final DeviceService deviceService;
private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfilesMap = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, DeviceProfileId> devicesMap = new ConcurrentHashMap<>();
public DefaultTbDeviceProfileCache(DeviceProfileService deviceProfileService, DeviceService deviceService) {
this.deviceProfileService = deviceProfileService;
this.deviceService = deviceService;
}
@Override
public DeviceProfile get(TenantId tenantId, DeviceProfileId deviceProfileId) {
DeviceProfile profile = deviceProfilesMap.get(deviceProfileId);
if (profile == null) {
profile = deviceProfilesMap.get(deviceProfileId);
if (profile == null) {
deviceProfileFetchLock.lock();
try {
profile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId);
if (profile != null) {
deviceProfilesMap.put(deviceProfileId, profile);
}
} finally {
deviceProfileFetchLock.unlock();
}
}
}
return profile;
}
@Override
public DeviceProfile get(TenantId tenantId, DeviceId deviceId) {
DeviceProfileId profileId = devicesMap.get(deviceId);
if (profileId == null) {
Device device = deviceService.findDeviceById(tenantId, deviceId);
if (device != null) {
profileId = device.getDeviceProfileId();
devicesMap.put(deviceId, profileId);
}
}
return get(tenantId, profileId);
}
@Override
public void put(DeviceProfile profile) {
if (profile.getId() != null) {
deviceProfilesMap.put(profile.getId(), profile);
}
}
@Override
public void evict(DeviceProfileId profileId) {
deviceProfilesMap.remove(profileId);
}
@Override
public void evict(DeviceId deviceId) {
devicesMap.remove(deviceId);
}
}

31
application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.profile;
import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
public interface TbDeviceProfileCache extends RuleEngineDeviceProfileCache {
void put(DeviceProfile profile);
void evict(DeviceProfileId id);
void evict(DeviceId id);
}

103
application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java

@ -0,0 +1,103 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.query;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.LinkedHashMap;
@Service
@Slf4j
@TbCoreComponent
public class DefaultEntityQueryService implements EntityQueryService {
@Autowired
private EntityService entityService;
@Autowired
private AlarmService alarmService;
@Value("${server.ws.max_entities_per_alarm_subscription:1000}")
private int maxEntitiesPerAlarmSubscription;
@Override
public long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query) {
return entityService.countEntitiesByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query);
}
@Override
public PageData<EntityData> findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) {
return entityService.findEntityDataByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query);
}
@Override
public PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) {
EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query);
PageData<EntityData> entities = entityService.findEntityDataByQuery(securityUser.getTenantId(),
securityUser.getCustomerId(), entityDataQuery);
if (entities.getTotalElements() > 0) {
LinkedHashMap<EntityId, EntityData> entitiesMap = new LinkedHashMap<>();
for (EntityData entityData : entities.getData()) {
entitiesMap.put(entityData.getEntityId(), entityData);
}
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(securityUser.getTenantId(),
securityUser.getCustomerId(), query, entitiesMap.keySet());
for (AlarmData alarmData : alarms.getData()) {
EntityId entityId = alarmData.getEntityId();
if (entityId != null) {
EntityData entityData = entitiesMap.get(entityId);
if (entityData != null) {
alarmData.getLatest().putAll(entityData.getLatest());
}
}
}
return alarms;
} else {
return new PageData<>();
}
}
private EntityDataQuery buildEntityDataQuery(AlarmDataQuery query) {
EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder();
EntityDataSortOrder entitiesSortOrder;
if (sortOrder == null || sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) {
entitiesSortOrder = new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, ModelConstants.CREATED_TIME_PROPERTY));
} else {
entitiesSortOrder = sortOrder;
}
EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, entitiesSortOrder);
return new EntityDataQuery(query.getEntityFilter(), edpl, query.getEntityFields(), query.getLatestValues(), query.getKeyFilters());
}
}

34
application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.query;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.service.security.model.SecurityUser;
public interface EntityQueryService {
long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query);
PageData<EntityData> findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query);
PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query);
}

58
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -21,14 +21,20 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
@ -40,7 +46,7 @@ import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import java.util.HashSet;
@ -64,11 +70,13 @@ public class DefaultTbClusterService implements TbClusterService {
private final TbQueueProducerProvider producerProvider;
private final PartitionService partitionService;
private final DataDecodingEncodingService encodingService;
private final TbDeviceProfileCache deviceProfileCache;
public DefaultTbClusterService(TbQueueProducerProvider producerProvider, PartitionService partitionService, DataDecodingEncodingService encodingService) {
public DefaultTbClusterService(TbQueueProducerProvider producerProvider, PartitionService partitionService, DataDecodingEncodingService encodingService, TbDeviceProfileCache deviceProfileCache) {
this.producerProvider = producerProvider;
this.partitionService = partitionService;
this.encodingService = encodingService;
this.deviceProfileCache = deviceProfileCache;
}
@Override
@ -124,6 +132,12 @@ public class DefaultTbClusterService implements TbClusterService {
log.warn("[{}][{}] Received invalid message: {}", tenantId, entityId, tbMsg);
return;
}
} else {
if (entityId.getEntityType().equals(EntityType.DEVICE)) {
tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceId(entityId.getId())));
} else if (entityId.getEntityType().equals(EntityType.DEVICE_PROFILE)) {
tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId())));
}
}
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, entityId);
log.trace("PUSHING msg: {} to:{}", tbMsg, tpi);
@ -135,6 +149,16 @@ public class DefaultTbClusterService implements TbClusterService {
toRuleEngineMsgs.incrementAndGet();
}
private TbMsg transformMsg(TbMsg tbMsg, DeviceProfile deviceProfile) {
if (deviceProfile != null) {
RuleChainId targetRuleChainId = deviceProfile.getDefaultRuleChainId();
if (targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId())) {
tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId);
}
}
return tbMsg;
}
@Override
public void pushNotificationToRuleEngine(String serviceId, FromDeviceRpcResponse response, TbQueueCallback callback) {
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceId);
@ -163,6 +187,36 @@ public class DefaultTbClusterService implements TbClusterService {
broadcast(new ComponentLifecycleMsg(tenantId, entityId, state));
}
@Override
public void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback) {
log.trace("[{}][{}] Processing device profile [{}] change event", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName());
TransportProtos.DeviceProfileUpdateMsg profileUpdateMsg = TransportProtos.DeviceProfileUpdateMsg.newBuilder()
.setData(ByteString.copyFrom(encodingService.encode(deviceProfile))).build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setDeviceProfileUpdateMsg(profileUpdateMsg).build();
broadcast(transportMsg);
}
@Override
public void onDeviceProfileDelete(DeviceProfile deviceProfile, TbQueueCallback callback) {
log.trace("[{}][{}] Processing device profile [{}] delete event", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName());
TransportProtos.DeviceProfileDeleteMsg profileDeleteMsg = TransportProtos.DeviceProfileDeleteMsg.newBuilder()
.setProfileIdMSB(deviceProfile.getId().getId().getMostSignificantBits())
.setProfileIdLSB(deviceProfile.getId().getId().getLeastSignificantBits())
.build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setDeviceProfileDeleteMsg(profileDeleteMsg).build();
broadcast(transportMsg);
}
private void broadcast(ToTransportMsg transportMsg) {
TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> toTransportNfProducer = producerProvider.getTransportNotificationsMsgProducer();
Set<String> tbTransportServices = partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT);
for (String transportServiceId : tbTransportServices) {
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportServiceId);
toTransportNfProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), null);
toTransportNfs.incrementAndGet();
}
}
private void broadcast(ComponentLifecycleMsg msg) {
byte[] msgBytes = encodingService.encode(msg);
TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer();

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

@ -21,16 +21,23 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.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.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.stats.StatsFactory;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
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.TbAttributeUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
@ -43,7 +50,7 @@ import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
@ -84,19 +91,21 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
private final SubscriptionManagerService subscriptionManagerService;
private final TbCoreDeviceRpcService tbCoreDeviceRpcService;
private final EdgeNotificationService edgeNotificationService;
private final TbCoreConsumerStats stats = new TbCoreConsumerStats();
private final TbCoreConsumerStats stats;
public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext,
DeviceStateService stateService, TbLocalSubscriptionService localSubscriptionService,
SubscriptionManagerService subscriptionManagerService, DataDecodingEncodingService encodingService,
TbCoreDeviceRpcService tbCoreDeviceRpcService, EdgeNotificationService edgeNotificationService) {
super(actorContext, encodingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
TbCoreDeviceRpcService tbCoreDeviceRpcService, StatsFactory statsFactory, TbDeviceProfileCache deviceProfileCache,
EdgeNotificationService edgeNotificationService) {
super(actorContext, encodingService, deviceProfileCache, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.stateService = stateService;
this.localSubscriptionService = localSubscriptionService;
this.subscriptionManagerService = subscriptionManagerService;
this.tbCoreDeviceRpcService = tbCoreDeviceRpcService;
this.edgeNotificationService = edgeNotificationService;
this.stats = new TbCoreConsumerStats(statsFactory);
}
@PostConstruct
@ -211,11 +220,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
log.trace("[{}] Forwarding message to RPC service {}", id, toCoreNotification.getFromDeviceRpcResponse());
forwardToCoreRpcService(toCoreNotification.getFromDeviceRpcResponse(), callback);
} else if (toCoreNotification.getComponentLifecycleMsg() != null && !toCoreNotification.getComponentLifecycleMsg().isEmpty()) {
Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreNotification.getComponentLifecycleMsg().toByteArray());
if (actorMsg.isPresent()) {
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get());
actorContext.tellWithHighPriority(actorMsg.get());
}
handleComponentLifecycleMsg(id, toCoreNotification.getComponentLifecycleMsg());
callback.onSuccess();
}
if (statsEnabled) {
@ -235,12 +240,15 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
public void printStats() {
if (statsEnabled) {
stats.printStats();
stats.reset();
}
}
private void forwardToLocalSubMgrService(LocalSubscriptionServiceMsgProto msg, TbCallback callback) {
if (msg.hasSubUpdate()) {
localSubscriptionService.onSubscriptionUpdate(msg.getSubUpdate().getSessionId(), TbSubscriptionUtils.fromProto(msg.getSubUpdate()), callback);
} else if (msg.hasAlarmSubUpdate()) {
localSubscriptionService.onSubscriptionUpdate(msg.getAlarmSubUpdate().getSessionId(), TbSubscriptionUtils.fromProto(msg.getAlarmSubUpdate()), callback);
} else {
throwNotHandled(msg, callback);
}
@ -251,6 +259,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
subscriptionManagerService.addSubscription(TbSubscriptionUtils.fromProto(msg.getAttributeSub()), callback);
} else if (msg.hasTelemetrySub()) {
subscriptionManagerService.addSubscription(TbSubscriptionUtils.fromProto(msg.getTelemetrySub()), callback);
} else if (msg.hasAlarmSub()) {
subscriptionManagerService.addSubscription(TbSubscriptionUtils.fromProto(msg.getAlarmSub()), callback);
} else if (msg.hasSubClose()) {
TbSubscriptionCloseProto closeProto = msg.getSubClose();
subscriptionManagerService.cancelSubscription(closeProto.getSessionId(), closeProto.getSubscriptionId(), callback);
@ -266,6 +276,24 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getScope(), TbSubscriptionUtils.toAttributeKvList(proto.getDataList()), callback);
} else if (msg.hasAttrDelete()) {
TbAttributeDeleteProto proto = msg.getAttrDelete();
subscriptionManagerService.onAttributesDelete(
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getScope(), proto.getKeysList(), callback);
} else if (msg.hasAlarmUpdate()) {
TbAlarmUpdateProto proto = msg.getAlarmUpdate();
subscriptionManagerService.onAlarmUpdate(
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), Alarm.class), callback);
} else if (msg.hasAlarmDelete()) {
TbAlarmDeleteProto proto = msg.getAlarmDelete();
subscriptionManagerService.onAlarmDeleted(
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), Alarm.class), callback);
} else {
throwNotHandled(msg, callback);
}

81
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.queue;
import com.google.protobuf.ByteString;
import com.google.protobuf.ProtocolStringList;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@ -25,12 +26,8 @@ import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.ServiceQueue;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import org.thingsboard.server.common.msg.queue.*;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
@ -41,37 +38,25 @@ import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingDecision;
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingResult;
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStrategy;
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStrategyFactory;
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy;
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategyFactory;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.*;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
import org.thingsboard.server.service.stats.RuleEngineStatisticsService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.*;
import java.util.concurrent.*;
@Service
@TbRuleEngineComponent
@Slf4j
public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<ToRuleEngineNotificationMsg> implements TbRuleEngineConsumerService {
public static final String SUCCESSFUL_STATUS = "successful";
public static final String FAILED_STATUS = "failed";
@Value("${queue.rule-engine.poll-interval}")
private long pollDuration;
@Value("${queue.rule-engine.pack-processing-timeout}")
@ -79,6 +64,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
@Value("${queue.rule-engine.stats.enabled:true}")
private boolean statsEnabled;
private final StatsFactory statsFactory;
private final TbRuleEngineSubmitStrategyFactory submitStrategyFactory;
private final TbRuleEngineProcessingStrategyFactory processingStrategyFactory;
private final TbRuleEngineQueueFactory tbRuleEngineQueueFactory;
@ -95,14 +81,16 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbQueueRuleEngineSettings ruleEngineSettings,
TbRuleEngineQueueFactory tbRuleEngineQueueFactory, RuleEngineStatisticsService statisticsService,
ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
TbRuleEngineDeviceRpcService tbDeviceRpcService) {
super(actorContext, encodingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer());
TbRuleEngineDeviceRpcService tbDeviceRpcService,
StatsFactory statsFactory, TbDeviceProfileCache deviceProfileCache) {
super(actorContext, encodingService, deviceProfileCache, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer());
this.statisticsService = statisticsService;
this.ruleEngineSettings = ruleEngineSettings;
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
this.submitStrategyFactory = submitStrategyFactory;
this.processingStrategyFactory = processingStrategyFactory;
this.tbDeviceRpcService = tbDeviceRpcService;
this.statsFactory = statsFactory;
}
@PostConstruct
@ -111,7 +99,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
for (TbRuleEngineQueueConfiguration configuration : ruleEngineSettings.getQueues()) {
consumerConfigurations.putIfAbsent(configuration.getName(), configuration);
consumers.computeIfAbsent(configuration.getName(), queueName -> tbRuleEngineQueueFactory.createToRuleEngineMsgConsumer(configuration));
consumerStats.put(configuration.getName(), new TbRuleEngineConsumerStats(configuration.getName()));
consumerStats.put(configuration.getName(), new TbRuleEngineConsumerStats(configuration.getName(), statsFactory));
}
submitExecutor = Executors.newSingleThreadExecutor();
}
@ -158,12 +146,14 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
submitStrategy.init(msgs);
while (!stopped) {
TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(submitStrategy);
TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(configuration.getName(), submitStrategy);
submitStrategy.submitAttempt((id, msg) -> submitExecutor.submit(() -> {
log.trace("[{}] Creating callback for message: {}", id, msg.getValue());
ToRuleEngineMsg toRuleEngineMsg = msg.getValue();
TenantId tenantId = new TenantId(new UUID(toRuleEngineMsg.getTenantIdMSB(), toRuleEngineMsg.getTenantIdLSB()));
TbMsgCallback callback = new TbMsgPackCallback(id, tenantId, ctx);
TbMsgCallback callback = statsEnabled ?
new TbMsgPackCallback(id, tenantId, ctx, stats.getTimer(tenantId, SUCCESSFUL_STATUS), stats.getTimer(tenantId, FAILED_STATUS)) :
new TbMsgPackCallback(id, tenantId, ctx);
try {
if (toRuleEngineMsg.getTbMsg() != null && !toRuleEngineMsg.getTbMsg().isEmpty()) {
forwardToRuleEngineActor(configuration.getName(), tenantId, toRuleEngineMsg, callback);
@ -181,6 +171,14 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
}
TbRuleEngineProcessingResult result = new TbRuleEngineProcessingResult(configuration.getName(), timeout, ctx);
if (timeout) {
printFirstOrAll(configuration, ctx, ctx.getPendingMap(), "Timeout");
}
if (!ctx.getFailedMap().isEmpty()) {
printFirstOrAll(configuration, ctx, ctx.getFailedMap(), "Failed");
}
ctx.printProfilerStats();
TbRuleEngineProcessingDecision decision = ackStrategy.analyze(result);
if (statsEnabled) {
stats.log(result, decision.isCommit());
@ -208,6 +206,22 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
});
}
private void printFirstOrAll(TbRuleEngineQueueConfiguration configuration, TbMsgPackProcessingContext ctx, Map<UUID, TbProtoQueueMsg<ToRuleEngineMsg>> map, String prefix) {
boolean printAll = log.isTraceEnabled();
log.info("{} to process [{}] messages", prefix, map.size());
for (Map.Entry<UUID, TbProtoQueueMsg<ToRuleEngineMsg>> pending : map.entrySet()) {
ToRuleEngineMsg tmp = pending.getValue().getValue();
TbMsg tmpMsg = TbMsg.fromBytes(configuration.getName(), tmp.getTbMsg().toByteArray(), TbMsgCallback.EMPTY);
RuleNodeInfo ruleNodeInfo = ctx.getLastVisitedRuleNode(pending.getKey());
if (printAll) {
log.trace("[{}] {} to process message: {}, Last Rule Node: {}", new TenantId(new UUID(tmp.getTenantIdMSB(), tmp.getTenantIdLSB())), prefix, tmpMsg, ruleNodeInfo);
} else {
log.info("[{}] {} to process message: {}, Last Rule Node: {}", new TenantId(new UUID(tmp.getTenantIdMSB(), tmp.getTenantIdLSB())), prefix, tmpMsg, ruleNodeInfo);
break;
}
}
}
@Override
protected ServiceType getServiceType() {
return ServiceType.TB_RULE_ENGINE;
@ -227,11 +241,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
protected void handleNotification(UUID id, TbProtoQueueMsg<ToRuleEngineNotificationMsg> msg, TbCallback callback) throws Exception {
ToRuleEngineNotificationMsg nfMsg = msg.getValue();
if (nfMsg.getComponentLifecycleMsg() != null && !nfMsg.getComponentLifecycleMsg().isEmpty()) {
Optional<TbActorMsg> actorMsg = encodingService.decode(nfMsg.getComponentLifecycleMsg().toByteArray());
if (actorMsg.isPresent()) {
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get());
actorContext.tellWithHighPriority(actorMsg.get());
}
handleComponentLifecycleMsg(id, nfMsg.getComponentLifecycleMsg());
callback.onSuccess();
} else if (nfMsg.hasFromDeviceRpcResponse()) {
TransportProtos.FromDeviceRPCResponseProto proto = nfMsg.getFromDeviceRpcResponse();
@ -269,6 +279,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
consumerStats.forEach((queue, stats) -> {
stats.printStats();
statisticsService.reportQueueStats(ts, stats);
stats.reset();
});
}
}

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

@ -19,7 +19,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
@ -31,15 +33,20 @@ public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService
private final TenantService tenantService;
public DefaultTenantRoutingInfoService(TenantService tenantService) {
private final TenantProfileService tenantProfileService;
public DefaultTenantRoutingInfoService(TenantService tenantService, TenantProfileService tenantProfileService) {
this.tenantService = tenantService;
this.tenantProfileService = tenantProfileService;
}
@Override
public TenantRoutingInfo getRoutingInfo(TenantId tenantId) {
Tenant tenant = tenantService.findTenantById(tenantId);
if (tenant != null) {
return new TenantRoutingInfo(tenantId, tenant.isIsolatedTbCore(), tenant.isIsolatedTbRuleEngine());
// TODO: Tenant Profile from cache
TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId());
return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbCore(), tenantProfile.isIsolatedTbRuleEngine());
} else {
throw new RuntimeException("Tenant not found!");
}

5
application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java

@ -16,6 +16,8 @@
package org.thingsboard.server.service.queue;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.DeviceProfile;
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.plugin.ComponentLifecycleEvent;
@ -49,4 +51,7 @@ public interface TbClusterService {
void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state);
void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback);
void onDeviceProfileDelete(DeviceProfile deviceProfileId, TbQueueCallback callback);
}

125
application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java

@ -16,83 +16,134 @@
package org.thingsboard.server.service.queue;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class TbCoreConsumerStats {
public static final String TOTAL_MSGS = "totalMsgs";
public static final String SESSION_EVENTS = "sessionEvents";
public static final String GET_ATTRIBUTE = "getAttr";
public static final String ATTRIBUTE_SUBSCRIBES = "subToAttr";
public static final String RPC_SUBSCRIBES = "subToRpc";
public static final String TO_DEVICE_RPC_CALL_RESPONSES = "toDevRpc";
public static final String SUBSCRIPTION_INFO = "subInfo";
public static final String DEVICE_CLAIMS = "claimDevice";
public static final String DEVICE_STATES = "deviceState";
public static final String EDGE_EVENTS = "edgeEvents";
public static final String SUBSCRIPTION_MSGS = "subMsgs";
public static final String TO_CORE_NOTIFICATIONS = "coreNfs";
private final AtomicInteger totalCounter = new AtomicInteger(0);
private final AtomicInteger sessionEventCounter = new AtomicInteger(0);
private final AtomicInteger getAttributesCounter = new AtomicInteger(0);
private final AtomicInteger subscribeToAttributesCounter = new AtomicInteger(0);
private final AtomicInteger subscribeToRPCCounter = new AtomicInteger(0);
private final AtomicInteger toDeviceRPCCallResponseCounter = new AtomicInteger(0);
private final AtomicInteger subscriptionInfoCounter = new AtomicInteger(0);
private final AtomicInteger claimDeviceCounter = new AtomicInteger(0);
private final StatsCounter totalCounter;
private final StatsCounter sessionEventCounter;
private final StatsCounter getAttributesCounter;
private final StatsCounter subscribeToAttributesCounter;
private final StatsCounter subscribeToRPCCounter;
private final StatsCounter toDeviceRPCCallResponseCounter;
private final StatsCounter subscriptionInfoCounter;
private final StatsCounter claimDeviceCounter;
private final AtomicInteger deviceStateCounter = new AtomicInteger(0);
private final AtomicInteger edgeNotificationCounter = new AtomicInteger(0);
private final AtomicInteger subscriptionMsgCounter = new AtomicInteger(0);
private final AtomicInteger toCoreNotificationsCounter = new AtomicInteger(0);
private final StatsCounter deviceStateCounter;
private final StatsCounter edgeNotificationCounter;
private final StatsCounter subscriptionMsgCounter;
private final StatsCounter toCoreNotificationsCounter;
private final List<StatsCounter> counters = new ArrayList<>();
public TbCoreConsumerStats(StatsFactory statsFactory) {
String statsKey = StatsType.CORE.getName();
this.totalCounter = statsFactory.createStatsCounter(statsKey, TOTAL_MSGS);
this.sessionEventCounter = statsFactory.createStatsCounter(statsKey, SESSION_EVENTS);
this.getAttributesCounter = statsFactory.createStatsCounter(statsKey, GET_ATTRIBUTE);
this.subscribeToAttributesCounter = statsFactory.createStatsCounter(statsKey, ATTRIBUTE_SUBSCRIBES);
this.subscribeToRPCCounter = statsFactory.createStatsCounter(statsKey, RPC_SUBSCRIBES);
this.toDeviceRPCCallResponseCounter = statsFactory.createStatsCounter(statsKey, TO_DEVICE_RPC_CALL_RESPONSES);
this.subscriptionInfoCounter = statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_INFO);
this.claimDeviceCounter = statsFactory.createStatsCounter(statsKey, DEVICE_CLAIMS);
this.deviceStateCounter = statsFactory.createStatsCounter(statsKey, DEVICE_STATES);
this.edgeNotificationCounter = statsFactory.createStatsCounter(statsKey, EDGE_EVENTS);
this.subscriptionMsgCounter = statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_MSGS);
this.toCoreNotificationsCounter = statsFactory.createStatsCounter(statsKey, TO_CORE_NOTIFICATIONS);
counters.add(totalCounter);
counters.add(sessionEventCounter);
counters.add(getAttributesCounter);
counters.add(subscribeToAttributesCounter);
counters.add(subscribeToRPCCounter);
counters.add(toDeviceRPCCallResponseCounter);
counters.add(subscriptionInfoCounter);
counters.add(claimDeviceCounter);
counters.add(deviceStateCounter);
counters.add(edgeNotificationCounter);
counters.add(subscriptionMsgCounter);
counters.add(toCoreNotificationsCounter);
}
public void log(TransportProtos.TransportToDeviceActorMsg msg) {
totalCounter.incrementAndGet();
totalCounter.increment();
if (msg.hasSessionEvent()) {
sessionEventCounter.incrementAndGet();
sessionEventCounter.increment();
}
if (msg.hasGetAttributes()) {
getAttributesCounter.incrementAndGet();
getAttributesCounter.increment();
}
if (msg.hasSubscribeToAttributes()) {
subscribeToAttributesCounter.incrementAndGet();
subscribeToAttributesCounter.increment();
}
if (msg.hasSubscribeToRPC()) {
subscribeToRPCCounter.incrementAndGet();
subscribeToRPCCounter.increment();
}
if (msg.hasToDeviceRPCCallResponse()) {
toDeviceRPCCallResponseCounter.incrementAndGet();
toDeviceRPCCallResponseCounter.increment();
}
if (msg.hasSubscriptionInfo()) {
subscriptionInfoCounter.incrementAndGet();
subscriptionInfoCounter.increment();
}
if (msg.hasClaimDevice()) {
claimDeviceCounter.incrementAndGet();
claimDeviceCounter.increment();
}
}
public void log(TransportProtos.DeviceStateServiceMsgProto msg) {
totalCounter.incrementAndGet();
deviceStateCounter.incrementAndGet();
totalCounter.increment();
deviceStateCounter.increment();
}
public void log(TransportProtos.EdgeNotificationMsgProto msg) {
totalCounter.incrementAndGet();
edgeNotificationCounter.incrementAndGet();
totalCounter.increment();
edgeNotificationCounter.increment();
}
public void log(TransportProtos.SubscriptionMgrMsgProto msg) {
totalCounter.incrementAndGet();
subscriptionMsgCounter.incrementAndGet();
totalCounter.increment();
subscriptionMsgCounter.increment();
}
public void log(TransportProtos.ToCoreNotificationMsg msg) {
totalCounter.incrementAndGet();
toCoreNotificationsCounter.incrementAndGet();
totalCounter.increment();
toCoreNotificationsCounter.increment();
}
public void printStats() {
int total = totalCounter.getAndSet(0);
int total = totalCounter.get();
if (total > 0) {
log.info("Total [{}] sessionEvents [{}] getAttr [{}] subToAttr [{}] subToRpc [{}] toDevRpc [{}] subInfo [{}] claimDevice [{}]" +
" deviceState [{}] subMgr [{}] coreNfs [{}]",
total, sessionEventCounter.getAndSet(0),
getAttributesCounter.getAndSet(0), subscribeToAttributesCounter.getAndSet(0),
subscribeToRPCCounter.getAndSet(0), toDeviceRPCCallResponseCounter.getAndSet(0),
subscriptionInfoCounter.getAndSet(0), claimDeviceCounter.getAndSet(0)
, deviceStateCounter.getAndSet(0), subscriptionMsgCounter.getAndSet(0), toCoreNotificationsCounter.getAndSet(0));
StringBuilder stats = new StringBuilder();
counters.forEach(counter -> {
stats.append(counter.getName()).append(" = [").append(counter.get()).append("] ");
});
log.info("Core Stats: {}", stats);
}
}
public void reset() {
counters.forEach(StatsCounter::clear);
}
}

32
application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java

@ -15,34 +15,66 @@
*/
package org.thingsboard.server.service.queue;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Slf4j
public class TbMsgPackCallback implements TbMsgCallback {
private final UUID id;
private final TenantId tenantId;
private final TbMsgPackProcessingContext ctx;
private final long startMsgProcessing;
private final Timer successfulMsgTimer;
private final Timer failedMsgTimer;
public TbMsgPackCallback(UUID id, TenantId tenantId, TbMsgPackProcessingContext ctx) {
this(id, tenantId, ctx, null, null);
}
public TbMsgPackCallback(UUID id, TenantId tenantId, TbMsgPackProcessingContext ctx, Timer successfulMsgTimer, Timer failedMsgTimer) {
this.id = id;
this.tenantId = tenantId;
this.ctx = ctx;
this.successfulMsgTimer = successfulMsgTimer;
this.failedMsgTimer = failedMsgTimer;
startMsgProcessing = System.currentTimeMillis();
}
@Override
public void onSuccess() {
log.trace("[{}] ON SUCCESS", id);
if (successfulMsgTimer != null) {
successfulMsgTimer.record(System.currentTimeMillis() - startMsgProcessing, TimeUnit.MILLISECONDS);
}
ctx.onSuccess(id);
}
@Override
public void onFailure(RuleEngineException e) {
log.trace("[{}] ON FAILURE", id, e);
if (failedMsgTimer != null) {
failedMsgTimer.record(System.currentTimeMillis() - startMsgProcessing, TimeUnit.MILLISECONDS);
}
ctx.onFailure(tenantId, id, e);
}
@Override
public void onProcessingStart(RuleNodeInfo ruleNodeInfo) {
log.trace("[{}] ON PROCESSING START: {}", id, ruleNodeInfo);
ctx.onProcessingStart(id, ruleNodeInfo);
}
@Override
public void onProcessingEnd(RuleNodeId ruleNodeId) {
log.trace("[{}] ON PROCESSING END: {}", id, ruleNodeId);
ctx.onProcessingEnd(id, ruleNodeId);
}
}

72
application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java

@ -16,12 +16,17 @@
package org.thingsboard.server.service.queue;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy;
import java.util.Comparator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -29,10 +34,13 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class TbMsgPackProcessingContext {
private final String queueName;
private final TbRuleEngineSubmitStrategy submitStrategy;
@Getter
private final boolean profilerEnabled;
private final AtomicInteger pendingCount;
private final CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
@Getter
@ -44,14 +52,22 @@ public class TbMsgPackProcessingContext {
@Getter
private final ConcurrentMap<TenantId, RuleEngineException> exceptionsMap = new ConcurrentHashMap<>();
public TbMsgPackProcessingContext(TbRuleEngineSubmitStrategy submitStrategy) {
private final ConcurrentMap<UUID, RuleNodeInfo> lastRuleNodeMap = new ConcurrentHashMap<>();
public TbMsgPackProcessingContext(String queueName, TbRuleEngineSubmitStrategy submitStrategy) {
this.queueName = queueName;
this.submitStrategy = submitStrategy;
this.profilerEnabled = log.isDebugEnabled();
this.pendingMap = submitStrategy.getPendingMap();
this.pendingCount = new AtomicInteger(pendingMap.size());
}
public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException {
return processingTimeoutLatch.await(packProcessingTimeout, milliseconds);
boolean success = processingTimeoutLatch.await(packProcessingTimeout, milliseconds);
if (!success && profilerEnabled) {
msgProfilerMap.values().forEach(this::onTimeout);
}
return success;
}
public void onSuccess(UUID id) {
@ -81,4 +97,54 @@ public class TbMsgPackProcessingContext {
processingTimeoutLatch.countDown();
}
}
private final ConcurrentHashMap<UUID, TbMsgProfilerInfo> msgProfilerMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<UUID, TbRuleNodeProfilerInfo> ruleNodeProfilerMap = new ConcurrentHashMap<>();
public void onProcessingStart(UUID id, RuleNodeInfo ruleNodeInfo) {
lastRuleNodeMap.put(id, ruleNodeInfo);
if (profilerEnabled) {
msgProfilerMap.computeIfAbsent(id, TbMsgProfilerInfo::new).onStart(ruleNodeInfo.getRuleNodeId());
ruleNodeProfilerMap.putIfAbsent(ruleNodeInfo.getRuleNodeId().getId(), new TbRuleNodeProfilerInfo(ruleNodeInfo));
}
}
public void onProcessingEnd(UUID id, RuleNodeId ruleNodeId) {
if (profilerEnabled) {
long processingTime = msgProfilerMap.computeIfAbsent(id, TbMsgProfilerInfo::new).onEnd(ruleNodeId);
if (processingTime > 0) {
ruleNodeProfilerMap.computeIfAbsent(ruleNodeId.getId(), TbRuleNodeProfilerInfo::new).record(processingTime);
}
}
}
public void onTimeout(TbMsgProfilerInfo profilerInfo) {
Map.Entry<UUID, Long> ruleNodeInfo = profilerInfo.onTimeout();
if (ruleNodeInfo != null) {
ruleNodeProfilerMap.computeIfAbsent(ruleNodeInfo.getKey(), TbRuleNodeProfilerInfo::new).record(ruleNodeInfo.getValue());
}
}
public RuleNodeInfo getLastVisitedRuleNode(UUID id) {
return lastRuleNodeMap.get(id);
}
public void printProfilerStats() {
if (profilerEnabled) {
log.debug("Top Rule Nodes by max execution time:");
ruleNodeProfilerMap.values().stream()
.sorted(Comparator.comparingLong(TbRuleNodeProfilerInfo::getMaxExecutionTime).reversed()).limit(5)
.forEach(info -> log.debug("[{}][{}] max execution time: {}. {}", queueName, info.getRuleNodeId(), info.getMaxExecutionTime(), info.getLabel()));
log.info("Top Rule Nodes by avg execution time:");
ruleNodeProfilerMap.values().stream()
.sorted(Comparator.comparingDouble(TbRuleNodeProfilerInfo::getAvgExecutionTime).reversed()).limit(5)
.forEach(info -> log.info("[{}][{}] avg execution time: {}. {}", queueName, info.getRuleNodeId(), info.getAvgExecutionTime(), info.getLabel()));
log.info("Top Rule Nodes by execution count:");
ruleNodeProfilerMap.values().stream()
.sorted(Comparator.comparingInt(TbRuleNodeProfilerInfo::getExecutionCount).reversed()).limit(5)
.forEach(info -> log.info("[{}][{}] execution count: {}. {}", queueName, info.getRuleNodeId(), info.getExecutionCount(), info.getLabel()));
}
}
}

85
application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java

@ -0,0 +1,85 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.queue;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
import java.util.AbstractMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class TbMsgProfilerInfo {
private final UUID msgId;
private AtomicLong totalProcessingTime = new AtomicLong();
private Lock stateLock = new ReentrantLock();
private RuleNodeId currentRuleNodeId;
private long stateChangeTime;
public TbMsgProfilerInfo(UUID msgId) {
this.msgId = msgId;
}
public void onStart(RuleNodeId ruleNodeId) {
long currentTime = System.currentTimeMillis();
stateLock.lock();
try {
currentRuleNodeId = ruleNodeId;
stateChangeTime = currentTime;
} finally {
stateLock.unlock();
}
}
public long onEnd(RuleNodeId ruleNodeId) {
long currentTime = System.currentTimeMillis();
stateLock.lock();
try {
if (ruleNodeId.equals(currentRuleNodeId)) {
long processingTime = currentTime - stateChangeTime;
stateChangeTime = currentTime;
totalProcessingTime.addAndGet(processingTime);
currentRuleNodeId = null;
return processingTime;
} else {
log.trace("[{}] Invalid sequence of rule node processing detected. Expected [{}] but was [{}]", msgId, currentRuleNodeId, ruleNodeId);
return 0;
}
} finally {
stateLock.unlock();
}
}
public Map.Entry<UUID, Long> onTimeout() {
long currentTime = System.currentTimeMillis();
stateLock.lock();
try {
if (currentRuleNodeId != null && stateChangeTime > 0) {
long timeoutTime = currentTime - stateChangeTime;
totalProcessingTime.addAndGet(timeoutTime);
return new AbstractMap.SimpleEntry<>(currentRuleNodeId.getId(), timeoutTime);
}
} finally {
stateLock.unlock();
}
return null;
}
}

109
application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java

@ -15,23 +15,23 @@
*/
package org.thingsboard.server.service.queue;
import lombok.Data;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingResult;
import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsType;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Data
public class TbRuleEngineConsumerStats {
public static final String TOTAL_MSGS = "totalMsgs";
@ -43,61 +43,84 @@ public class TbRuleEngineConsumerStats {
public static final String SUCCESSFUL_ITERATIONS = "successfulIterations";
public static final String FAILED_ITERATIONS = "failedIterations";
private final AtomicInteger totalMsgCounter = new AtomicInteger(0);
private final AtomicInteger successMsgCounter = new AtomicInteger(0);
private final AtomicInteger tmpTimeoutMsgCounter = new AtomicInteger(0);
private final AtomicInteger tmpFailedMsgCounter = new AtomicInteger(0);
private final StatsFactory statsFactory;
private final AtomicInteger timeoutMsgCounter = new AtomicInteger(0);
private final AtomicInteger failedMsgCounter = new AtomicInteger(0);
private final StatsCounter totalMsgCounter;
private final StatsCounter successMsgCounter;
private final StatsCounter tmpTimeoutMsgCounter;
private final StatsCounter tmpFailedMsgCounter;
private final AtomicInteger successIterationsCounter = new AtomicInteger(0);
private final AtomicInteger failedIterationsCounter = new AtomicInteger(0);
private final StatsCounter timeoutMsgCounter;
private final StatsCounter failedMsgCounter;
private final Map<String, AtomicInteger> counters = new HashMap<>();
private final StatsCounter successIterationsCounter;
private final StatsCounter failedIterationsCounter;
private final List<StatsCounter> counters = new ArrayList<>();
private final ConcurrentMap<UUID, TbTenantRuleEngineStats> tenantStats = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, Timer> tenantMsgProcessTimers = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, RuleEngineException> tenantExceptions = new ConcurrentHashMap<>();
private final String queueName;
public TbRuleEngineConsumerStats(String queueName) {
public TbRuleEngineConsumerStats(String queueName, StatsFactory statsFactory) {
this.queueName = queueName;
counters.put(TOTAL_MSGS, totalMsgCounter);
counters.put(SUCCESSFUL_MSGS, successMsgCounter);
counters.put(TIMEOUT_MSGS, timeoutMsgCounter);
counters.put(FAILED_MSGS, failedMsgCounter);
counters.put(TMP_TIMEOUT, tmpTimeoutMsgCounter);
counters.put(TMP_FAILED, tmpFailedMsgCounter);
counters.put(SUCCESSFUL_ITERATIONS, successIterationsCounter);
counters.put(FAILED_ITERATIONS, failedIterationsCounter);
this.statsFactory = statsFactory;
String statsKey = StatsType.RULE_ENGINE.getName() + "." + queueName;
this.totalMsgCounter = statsFactory.createStatsCounter(statsKey, TOTAL_MSGS);
this.successMsgCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_MSGS);
this.timeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TIMEOUT_MSGS);
this.failedMsgCounter = statsFactory.createStatsCounter(statsKey, FAILED_MSGS);
this.tmpTimeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_TIMEOUT);
this.tmpFailedMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_FAILED);
this.successIterationsCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_ITERATIONS);
this.failedIterationsCounter = statsFactory.createStatsCounter(statsKey, FAILED_ITERATIONS);
counters.add(totalMsgCounter);
counters.add(successMsgCounter);
counters.add(timeoutMsgCounter);
counters.add(failedMsgCounter);
counters.add(tmpTimeoutMsgCounter);
counters.add(tmpFailedMsgCounter);
counters.add(successIterationsCounter);
counters.add(failedIterationsCounter);
}
public Timer getTimer(TenantId tenantId, String status){
return tenantMsgProcessTimers.computeIfAbsent(tenantId,
id -> statsFactory.createTimer(StatsType.RULE_ENGINE.getName() + "." + queueName,
"tenantId", tenantId.getId().toString(),
"status", status
));
}
public void log(TbRuleEngineProcessingResult msg, boolean finalIterationForPack) {
int success = msg.getSuccessMap().size();
int pending = msg.getPendingMap().size();
int failed = msg.getFailedMap().size();
totalMsgCounter.addAndGet(success + pending + failed);
successMsgCounter.addAndGet(success);
totalMsgCounter.add(success + pending + failed);
successMsgCounter.add(success);
msg.getSuccessMap().values().forEach(m -> getTenantStats(m).logSuccess());
if (finalIterationForPack) {
if (pending > 0 || failed > 0) {
timeoutMsgCounter.addAndGet(pending);
failedMsgCounter.addAndGet(failed);
timeoutMsgCounter.add(pending);
failedMsgCounter.add(failed);
if (pending > 0) {
msg.getPendingMap().values().forEach(m -> getTenantStats(m).logTimeout());
}
if (failed > 0) {
msg.getFailedMap().values().forEach(m -> getTenantStats(m).logFailed());
}
failedIterationsCounter.incrementAndGet();
failedIterationsCounter.increment();
} else {
successIterationsCounter.incrementAndGet();
successIterationsCounter.increment();
}
} else {
failedIterationsCounter.incrementAndGet();
tmpTimeoutMsgCounter.addAndGet(pending);
tmpFailedMsgCounter.addAndGet(failed);
failedIterationsCounter.increment();
tmpTimeoutMsgCounter.add(pending);
tmpFailedMsgCounter.add(failed);
if (pending > 0) {
msg.getPendingMap().values().forEach(m -> getTenantStats(m).logTmpTimeout());
}
@ -113,19 +136,31 @@ public class TbRuleEngineConsumerStats {
return tenantStats.computeIfAbsent(new UUID(reMsg.getTenantIdMSB(), reMsg.getTenantIdLSB()), TbTenantRuleEngineStats::new);
}
public ConcurrentMap<UUID, TbTenantRuleEngineStats> getTenantStats() {
return tenantStats;
}
public String getQueueName() {
return queueName;
}
public ConcurrentMap<TenantId, RuleEngineException> getTenantExceptions() {
return tenantExceptions;
}
public void printStats() {
int total = totalMsgCounter.get();
if (total > 0) {
StringBuilder stats = new StringBuilder();
counters.forEach((label, value) -> {
stats.append(label).append(" = [").append(value.get()).append("] ");
counters.forEach(counter -> {
stats.append(counter.getName()).append(" = [").append(counter.get()).append("] ");
});
log.info("[{}] Stats: {}", queueName, stats);
}
}
public void reset() {
counters.values().forEach(counter -> counter.set(0));
counters.forEach(StatsCounter::clear);
tenantStats.clear();
tenantExceptions.clear();
}

75
application/src/main/java/org/thingsboard/server/service/queue/TbRuleNodeProfilerInfo.java

@ -0,0 +1,75 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.queue;
import lombok.Getter;
import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class TbRuleNodeProfilerInfo {
@Getter
private final UUID ruleNodeId;
@Getter
private final String label;
private AtomicInteger executionCount = new AtomicInteger(0);
private AtomicLong executionTime = new AtomicLong(0);
private AtomicLong maxExecutionTime = new AtomicLong(0);
public TbRuleNodeProfilerInfo(RuleNodeInfo ruleNodeInfo) {
this.ruleNodeId = ruleNodeInfo.getRuleNodeId().getId();
this.label = ruleNodeInfo.toString();
}
public TbRuleNodeProfilerInfo(UUID ruleNodeId) {
this.ruleNodeId = ruleNodeId;
this.label = "";
}
public void record(long processingTime) {
executionCount.incrementAndGet();
executionTime.addAndGet(processingTime);
while (true) {
long value = maxExecutionTime.get();
if (value >= processingTime) {
break;
}
if (maxExecutionTime.compareAndSet(value, processingTime)) {
break;
}
}
}
int getExecutionCount() {
return executionCount.get();
}
long getMaxExecutionTime() {
return maxExecutionTime.get();
}
double getAvgExecutionTime() {
double executionCnt = (double) executionCount.get();
if (executionCnt > 0) {
return executionTime.get() / executionCnt;
} else {
return 0.0;
}
}
}

35
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -15,23 +15,31 @@
*/
package org.thingsboard.server.service.queue.processing;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.EventListener;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbPackCallback;
import org.thingsboard.server.service.queue.TbPackProcessingContext;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -51,12 +59,15 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
protected final ActorSystemContext actorContext;
protected final DataDecodingEncodingService encodingService;
protected final TbDeviceProfileCache deviceProfileCache;
protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer;
public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer) {
public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
TbDeviceProfileCache deviceProfileCache, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer) {
this.actorContext = actorContext;
this.encodingService = encodingService;
this.deviceProfileCache = deviceProfileCache;
this.nfConsumer = nfConsumer;
}
@ -126,18 +137,32 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
});
}
protected void handleComponentLifecycleMsg(UUID id, ByteString nfMsg) {
Optional<TbActorMsg> actorMsgOpt = encodingService.decode(nfMsg.toByteArray());
if (actorMsgOpt.isPresent()) {
TbActorMsg actorMsg = actorMsgOpt.get();
if (actorMsg instanceof ComponentLifecycleMsg) {
ComponentLifecycleMsg componentLifecycleMsg = (ComponentLifecycleMsg) actorMsg;
if (EntityType.DEVICE_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(new DeviceProfileId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.DEVICE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(new DeviceId(componentLifecycleMsg.getEntityId().getId()));
}
}
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg);
actorContext.tellWithHighPriority(actorMsg);
}
}
protected abstract void handleNotification(UUID id, TbProtoQueueMsg<N> msg, TbCallback callback) throws Exception;
@PreDestroy
public void destroy() {
stopped = true;
stopMainConsumers();
if (nfConsumer != null) {
nfConsumer.unsubscribe();
}
if (consumersExecutor != null) {
consumersExecutor.shutdownNow();
}

4
application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java

@ -68,18 +68,20 @@ public class BatchTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitS
int listSize = orderedMsgList.size();
int startIdx = Math.min(packIdx.get() * batchSize, listSize);
int endIdx = Math.min(startIdx + batchSize, listSize);
Map<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> tmpPack;
synchronized (pendingPack) {
pendingPack.clear();
for (int i = startIdx; i < endIdx; i++) {
IdMsgPair pair = orderedMsgList.get(i);
pendingPack.put(pair.uuid, pair.msg);
}
tmpPack = new LinkedHashMap<>(pendingPack);
}
int submitSize = pendingPack.size();
if (log.isDebugEnabled() && submitSize > 0) {
log.debug("[{}] submitting [{}] messages to rule engine", queueName, submitSize);
}
pendingPack.forEach(msgConsumer);
tmpPack.forEach(msgConsumer);
}
}

8
application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingStrategyFactory.java

@ -56,7 +56,9 @@ public class TbRuleEngineProcessingStrategyFactory {
private final boolean retryTimeout;
private final int maxRetries;
private final double maxAllowedFailurePercentage;
private final long pauseBetweenRetries;
private final long maxPauseBetweenRetries;
private long pauseBetweenRetries;
private int initialTotalCount;
private int retryCount;
@ -69,6 +71,7 @@ public class TbRuleEngineProcessingStrategyFactory {
this.maxRetries = configuration.getRetries();
this.maxAllowedFailurePercentage = configuration.getFailurePercentage();
this.pauseBetweenRetries = configuration.getPauseBetweenRetries();
this.maxPauseBetweenRetries = configuration.getMaxPauseBetweenRetries();
}
@Override
@ -108,6 +111,9 @@ public class TbRuleEngineProcessingStrategyFactory {
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (maxPauseBetweenRetries > pauseBetweenRetries) {
pauseBetweenRetries = Math.min(maxPauseBetweenRetries, pauseBetweenRetries * 2);
}
}
return new TbRuleEngineProcessingDecision(false, toReprocess);
}

60
application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java

@ -27,14 +27,17 @@ import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -42,12 +45,14 @@ import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.controller.HttpValidationCallback;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
@ -73,6 +78,7 @@ import java.util.function.BiConsumer;
@Component
public class AccessValidator {
public static final String ONLY_SYSTEM_ADMINISTRATOR_IS_ALLOWED_TO_PERFORM_THIS_OPERATION = "Only system administrator is allowed to perform this operation!";
public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!";
public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!";
public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!";
@ -91,6 +97,9 @@ public class AccessValidator {
@Autowired
protected DeviceService deviceService;
@Autowired
protected DeviceProfileService deviceProfileService;
@Autowired
protected AssetService assetService;
@ -167,6 +176,9 @@ public class AccessValidator {
case DEVICE:
validateDevice(currentUser, operation, entityId, callback);
return;
case DEVICE_PROFILE:
validateDeviceProfile(currentUser, operation, entityId, callback);
return;
case ASSET:
validateAsset(currentUser, operation, entityId, callback);
return;
@ -179,6 +191,12 @@ public class AccessValidator {
case TENANT:
validateTenant(currentUser, operation, entityId, callback);
return;
case TENANT_PROFILE:
validateTenantProfile(currentUser, operation, entityId, callback);
return;
case USER:
validateUser(currentUser, operation, entityId, callback);
return;
case ENTITY_VIEW:
validateEntityView(currentUser, operation, entityId, callback);
return;
@ -211,6 +229,24 @@ public class AccessValidator {
}
}
private void validateDeviceProfile(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(currentUser.getTenantId(), new DeviceProfileId(entityId.getId()));
if (deviceProfile == null) {
callback.onSuccess(ValidationResult.entityNotFound("Device profile with requested id wasn't found!"));
} else {
try {
accessControlService.checkPermission(currentUser, Resource.DEVICE_PROFILE, operation, entityId, deviceProfile);
} catch (ThingsboardException e) {
callback.onSuccess(ValidationResult.accessDenied(e.getMessage()));
}
callback.onSuccess(ValidationResult.ok(deviceProfile));
}
}
}
private void validateAsset(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
@ -318,6 +354,30 @@ public class AccessValidator {
}
}
private void validateTenantProfile(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.ok(null));
} else {
callback.onSuccess(ValidationResult.accessDenied(ONLY_SYSTEM_ADMINISTRATOR_IS_ALLOWED_TO_PERFORM_THIS_OPERATION));
}
}
private void validateUser(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
ListenableFuture<User> userFuture = userService.findUserByIdAsync(currentUser.getTenantId(), new UserId(entityId.getId()));
Futures.addCallback(userFuture, getCallback(callback, user -> {
if (user == null) {
return ValidationResult.entityNotFound("User with requested id wasn't found!");
}
try {
accessControlService.checkPermission(currentUser, Resource.USER, operation, entityId, user);
} catch (ThingsboardException e) {
return ValidationResult.accessDenied(e.getMessage());
}
return ValidationResult.ok(user);
}), executor);
}
private void validateEntityView(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));

4
application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java

@ -52,10 +52,10 @@ public class RawAccessJwtToken implements JwtToken, Serializable {
try {
return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token);
} catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
log.error("Invalid JWT Token", ex);
log.debug("Invalid JWT Token", ex);
throw new BadCredentialsException("Invalid JWT token: ", ex);
} catch (ExpiredJwtException expiredEx) {
log.info("JWT Token is expired", expiredEx);
log.debug("JWT Token is expired", expiredEx);
throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx);
}
}

2
application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java

@ -19,6 +19,6 @@ public enum Operation {
ALL, CREATE, READ, WRITE, DELETE, ASSIGN_TO_CUSTOMER, UNASSIGN_FROM_CUSTOMER, RPC_CALL,
READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES,
ASSIGN_TO_EDGE, UNASSIGN_FROM_EDGE
ASSIGN_TO_TENANT, ASSIGN_TO_EDGE, UNASSIGN_FROM_EDGE
}

4
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -32,7 +32,9 @@ public enum Resource {
RULE_CHAIN(EntityType.RULE_CHAIN),
USER(EntityType.USER),
WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE),
WIDGET_TYPE(EntityType.WIDGET_TYPE);
WIDGET_TYPE(EntityType.WIDGET_TYPE),
TENANT_PROFILE(EntityType.TENANT_PROFILE),
DEVICE_PROFILE(EntityType.DEVICE_PROFILE);
private final EntityType entityType;

1
application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java

@ -39,6 +39,7 @@ public class SysAdminPermissions extends AbstractPermissions {
put(Resource.USER, userPermissionChecker);
put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker);
put(Resource.WIDGET_TYPE, systemEntityPermissionChecker);
put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker);
}
private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() {

2
application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java

@ -37,12 +37,12 @@ public class TenantAdminPermissions extends AbstractPermissions {
put(Resource.CUSTOMER, tenantEntityPermissionChecker);
put(Resource.DASHBOARD, tenantEntityPermissionChecker);
put(Resource.ENTITY_VIEW, tenantEntityPermissionChecker);
put(Resource.EDGE, tenantEntityPermissionChecker);
put(Resource.TENANT, tenantPermissionChecker);
put(Resource.RULE_CHAIN, tenantEntityPermissionChecker);
put(Resource.USER, userPermissionChecker);
put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker);
put(Resource.WIDGET_TYPE, widgetsPermissionChecker);
put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker);
put(Resource.EDGE, tenantEntityPermissionChecker);
}

35
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -15,7 +15,7 @@
*/
package org.thingsboard.server.service.state;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
@ -35,26 +35,27 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.ServiceType;
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.device.DeviceService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
@ -90,7 +91,6 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
@Slf4j
public class DefaultDeviceStateService implements DeviceStateService {
private static final ObjectMapper json = new ObjectMapper();
public static final String ACTIVITY_STATE = "active";
public static final String LAST_CONNECT_TIME = "lastConnectTime";
public static final String LAST_DISCONNECT_TIME = "lastDisconnectTime";
@ -197,15 +197,15 @@ public class DefaultDeviceStateService implements DeviceStateService {
if (lastReportedActivity > 0 && lastReportedActivity > lastSavedActivity) {
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
if (stateData != null) {
DeviceState state = stateData.getState();
stateData.getState().setLastActivityTime(lastReportedActivity);
stateData.getMetaData().putValue("scope", SERVER_SCOPE);
pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
save(deviceId, LAST_ACTIVITY_TIME, lastReportedActivity);
deviceLastSavedActivity.put(deviceId, lastReportedActivity);
DeviceState state = stateData.getState();
state.setLastActivityTime(lastReportedActivity);
if (!state.isActive()) {
state.setActive(true);
save(deviceId, ACTIVITY_STATE, state.isActive());
stateData.getMetaData().putValue("scope", SERVER_SCOPE);
pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
}
}
}
@ -503,8 +503,15 @@ public class DefaultDeviceStateService implements DeviceStateService {
private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) {
DeviceState state = stateData.getState();
try {
TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON
, json.writeValueAsString(state));
String data;
if (msgType.equals(CONNECT_EVENT)) {
ObjectNode stateNode = JacksonUtil.convertValue(state, ObjectNode.class);
stateNode.remove(ACTIVITY_STATE);
data = JacksonUtil.toString(stateNode);
} else {
data = JacksonUtil.toString(state);
}
TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON, data);
clusterService.pushMsgToRuleEngine(stateData.getTenantId(), stateData.getDeviceId(), tbMsg, null);
} catch (Exception e) {
log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e);

84
application/src/main/java/org/thingsboard/server/service/stats/DefaultJsInvokeStats.java

@ -0,0 +1,84 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.stats;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.actors.JsInvokeStats;
import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType;
import javax.annotation.PostConstruct;
@Service
public class DefaultJsInvokeStats implements JsInvokeStats {
private static final String REQUESTS = "requests";
private static final String RESPONSES = "responses";
private static final String FAILURES = "failures";
private StatsCounter requestsCounter;
private StatsCounter responsesCounter;
private StatsCounter failuresCounter;
@Autowired
private StatsFactory statsFactory;
@PostConstruct
public void init() {
String key = StatsType.JS_INVOKE.getName();
this.requestsCounter = statsFactory.createStatsCounter(key, REQUESTS);
this.responsesCounter = statsFactory.createStatsCounter(key, RESPONSES);
this.failuresCounter = statsFactory.createStatsCounter(key, FAILURES);
}
@Override
public void incrementRequests(int amount) {
requestsCounter.add(amount);
}
@Override
public void incrementResponses(int amount) {
responsesCounter.add(amount);
}
@Override
public void incrementFailures(int amount) {
failuresCounter.add(amount);
}
@Override
public int getRequests() {
return requestsCounter.get();
}
@Override
public int getResponses() {
return responsesCounter.get();
}
@Override
public int getFailures() {
return failuresCounter.get();
}
@Override
public void reset() {
requestsCounter.clear();
responsesCounter.clear();
failuresCounter.clear();
}
}

1
application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java

@ -104,7 +104,6 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
}
}
});
ruleEngineStats.reset();
}
private AssetId getServiceAssetId(TenantId tenantId, String queueName) {

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

@ -18,12 +18,12 @@ package org.thingsboard.server.service.subscription;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@ -32,12 +32,14 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
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.msg.queue.ServiceType;
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.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos.*;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateProto;
@ -52,7 +54,8 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.sub.SubscriptionUpdate;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -124,7 +127,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
@Override
public void addSubscription(TbSubscription subscription, TbCallback callback) {
log.trace("[{}][{}][{}] Registering remote subscription for entity [{}]",
log.trace("[{}][{}][{}] Registering subscription for entity [{}]",
subscription.getServiceId(), subscription.getSessionId(), subscription.getSubscriptionId(), subscription.getEntityId());
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, subscription.getTenantId(), subscription.getEntityId());
if (currentPartitions.contains(tpi)) {
@ -146,6 +149,9 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
case ATTRIBUTES:
handleNewAttributeSubscription((TbAttributeSubscription) subscription);
break;
case ALARMS:
handleNewAlarmsSubscription((TbAlarmsSubscription) subscription);
break;
}
}
}
@ -184,7 +190,11 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
removedPartitions.forEach(partition -> {
Set<TbSubscription> subs = partitionedSubscriptions.remove(partition);
if (subs != null) {
subs.forEach(this::removeSubscriptionFromEntityMap);
subs.forEach(sub -> {
if (!serviceId.equals(sub.getServiceId())) {
removeSubscriptionFromEntityMap(sub);
}
});
}
});
}
@ -192,7 +202,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
@Override
public void onTimeSeriesUpdate(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, TbCallback callback) {
onLocalSubUpdate(entityId,
onLocalTelemetrySubUpdate(entityId,
s -> {
if (TbSubscriptionType.TIMESERIES.equals(s.getType())) {
return (TbTimeseriesSubscription) s;
@ -216,7 +226,12 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
@Override
public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback) {
onLocalSubUpdate(entityId,
onAttributesUpdate(tenantId, entityId, scope, attributes, true, callback);
}
@Override
public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, TbCallback callback) {
onLocalTelemetrySubUpdate(entityId,
s -> {
if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) {
return (TbAttributeSubscription) s;
@ -244,7 +259,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
deviceStateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), attribute.getLongValue().orElse(0L));
}
}
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope)) {
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes))
, null);
@ -253,17 +268,77 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
callback.onSuccess();
}
private <T extends TbSubscription> void onLocalSubUpdate(EntityId entityId,
Function<TbSubscription, T> castFunction,
Predicate<T> filterFunction,
Function<T, List<TsKvEntry>> processFunction) {
@Override
public void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) {
onLocalAlarmSubUpdate(entityId,
s -> {
if (TbSubscriptionType.ALARMS.equals(s.getType())) {
return (TbAlarmsSubscription) s;
} else {
return null;
}
},
s -> alarm.getCreatedTime() >= s.getTs(),
s -> alarm,
false
);
callback.onSuccess();
}
@Override
public void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) {
onLocalAlarmSubUpdate(entityId,
s -> {
if (TbSubscriptionType.ALARMS.equals(s.getType())) {
return (TbAlarmsSubscription) s;
} else {
return null;
}
},
s -> alarm.getCreatedTime() >= s.getTs(),
s -> alarm,
true
);
callback.onSuccess();
}
@Override
public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback callback) {
onLocalTelemetrySubUpdate(entityId,
s -> {
if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) {
return (TbAttributeSubscription) s;
} else {
return null;
}
},
s -> (TbAttributeSubscriptionScope.ANY_SCOPE.equals(s.getScope()) || scope.equals(s.getScope().name())),
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;
});
callback.onSuccess();
}
private <T extends TbSubscription> void onLocalTelemetrySubUpdate(EntityId entityId,
Function<TbSubscription, T> castFunction,
Predicate<T> filterFunction,
Function<T, List<TsKvEntry>> processFunction) {
Set<TbSubscription> entitySubscriptions = subscriptionsByEntityId.get(entityId);
if (entitySubscriptions != null) {
entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> {
List<TsKvEntry> subscriptionUpdate = processFunction.apply(s);
if (subscriptionUpdate != null && !subscriptionUpdate.isEmpty()) {
if (serviceId.equals(s.getServiceId())) {
SubscriptionUpdate update = new SubscriptionUpdate(s.getSubscriptionId(), subscriptionUpdate);
TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(s.getSubscriptionId(), subscriptionUpdate);
localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY);
} else {
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId());
@ -276,6 +351,29 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
}
}
private void onLocalAlarmSubUpdate(EntityId entityId,
Function<TbSubscription, TbAlarmsSubscription> castFunction,
Predicate<TbAlarmsSubscription> filterFunction,
Function<TbAlarmsSubscription, Alarm> processFunction, boolean deleted) {
Set<TbSubscription> entitySubscriptions = subscriptionsByEntityId.get(entityId);
if (entitySubscriptions != null) {
entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> {
Alarm alarm = processFunction.apply(s);
if (alarm != null) {
if (serviceId.equals(s.getServiceId())) {
AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, deleted);
localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY);
} else {
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId());
toCoreNotificationsProducer.send(tpi, toProto(s, alarm, deleted), null);
}
}
});
} else {
log.debug("[{}] No device subscriptions to process!", entityId);
}
}
private boolean isInTimeRange(TbTimeseriesSubscription subscription, long kvTime) {
return (subscription.getStartTime() == 0 || subscription.getStartTime() <= kvTime)
&& (subscription.getEndTime() == 0 || subscription.getEndTime() >= kvTime);
@ -319,6 +417,12 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
e -> log.error("Failed to fetch missed updates.", e), tsCallBackExecutor);
}
private void handleNewAlarmsSubscription(TbAlarmsSubscription subscription) {
log.trace("[{}][{}][{}] Processing remote alarm subscription for entity [{}]",
serviceId, subscription.getSessionId(), subscription.getSubscriptionId(), subscription.getEntityId());
//TODO: @dlandiak search all new alarms for this entity.
}
private void handleNewTelemetrySubscription(TbTimeseriesSubscription subscription) {
log.trace("[{}][{}][{}] Processing remote telemetry subscription for entity [{}]",
serviceId, subscription.getSessionId(), subscription.getSubscriptionId(), subscription.getEntityId());
@ -377,4 +481,19 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg);
}
private TbProtoQueueMsg<ToCoreNotificationMsg> toProto(TbSubscription subscription, Alarm alarm, boolean deleted) {
TbAlarmSubscriptionUpdateProto.Builder builder = TbAlarmSubscriptionUpdateProto.newBuilder();
builder.setSessionId(subscription.getSessionId());
builder.setSubscriptionId(subscription.getSubscriptionId());
builder.setAlarm(JacksonUtil.toString(alarm));
builder.setDeleted(deleted);
ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg(
LocalSubscriptionServiceMsgProto.newBuilder()
.setAlarmSubUpdate(builder.build()).build())
.build();
return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg);
}
}

484
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -0,0 +1,484 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.subscription;
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;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.GetTsCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
@Slf4j
@TbCoreComponent
@Service
public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubscriptionService {
private static final int DEFAULT_LIMIT = 100;
private final Map<String, Map<Integer, TbAbstractDataSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>();
@Autowired
private TelemetryWebSocketService wsService;
@Autowired
private EntityService entityService;
@Autowired
private AlarmService alarmService;
@Autowired
private AttributesService attributesService;
@Autowired
@Lazy
private TbLocalSubscriptionService localSubscriptionService;
@Autowired
private TimeseriesService tsService;
@Autowired
private TbServiceInfoProvider serviceInfoProvider;
@Autowired
@Getter
private DbCallbackExecutorService dbCallbackExecutor;
private ScheduledExecutorService scheduler;
@Value("${database.ts.type}")
private String databaseTsType;
@Value("${server.ws.dynamic_page_link.refresh_interval:6}")
private long dynamicPageLinkRefreshInterval;
@Value("${server.ws.dynamic_page_link.refresh_pool_size:1}")
private int dynamicPageLinkRefreshPoolSize;
@Value("${server.ws.max_entities_per_data_subscription:1000}")
private int maxEntitiesPerDataSubscription;
@Value("${server.ws.max_entities_per_alarm_subscription:1000}")
private int maxEntitiesPerAlarmSubscription;
private ExecutorService wsCallBackExecutor;
private boolean tsInSqlDB;
private String serviceId;
private SubscriptionServiceStatistics stats = new SubscriptionServiceStatistics();
@PostConstruct
public void initExecutor() {
serviceId = serviceInfoProvider.getServiceId();
wsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ws-entity-sub-callback"));
tsInSqlDB = databaseTsType.equalsIgnoreCase("sql") || databaseTsType.equalsIgnoreCase("timescale");
ThreadFactory tbThreadFactory = ThingsBoardThreadFactory.forName("ws-entity-sub-scheduler");
if (dynamicPageLinkRefreshPoolSize == 1) {
scheduler = Executors.newSingleThreadScheduledExecutor(tbThreadFactory);
} else {
scheduler = Executors.newScheduledThreadPool(dynamicPageLinkRefreshPoolSize, tbThreadFactory);
}
}
@PreDestroy
public void shutdownExecutor() {
if (wsCallBackExecutor != null) {
wsCallBackExecutor.shutdownNow();
}
if (scheduler != null) {
scheduler.shutdownNow();
}
}
@Override
public void handleCmd(TelemetryWebSocketSessionRef session, EntityDataCmd cmd) {
TbEntityDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
if (ctx != null) {
log.debug("[{}][{}] Updating existing subscriptions using: {}", session.getSessionId(), cmd.getCmdId(), cmd);
if (cmd.getLatestCmd() != null || cmd.getTsCmd() != null || cmd.getHistoryCmd() != null) {
ctx.clearEntitySubscriptions();
}
} else {
log.debug("[{}][{}] Creating new subscription using: {}", session.getSessionId(), cmd.getCmdId(), cmd);
ctx = createSubCtx(session, cmd);
}
ctx.setCurrentCmd(cmd);
if (cmd.getQuery() != null) {
if (ctx.getQuery() == null) {
log.debug("[{}][{}] Initializing data using query: {}", session.getSessionId(), cmd.getCmdId(), cmd.getQuery());
} else {
log.debug("[{}][{}] Updating data using query: {}", session.getSessionId(), cmd.getCmdId(), cmd.getQuery());
}
ctx.setAndResolveQuery(cmd.getQuery());
TenantId tenantId = ctx.getTenantId();
CustomerId customerId = ctx.getCustomerId();
EntityDataQuery query = ctx.getQuery();
//Step 1. Update existing query with the contents of LatestValueCmd
if (cmd.getLatestCmd() != null) {
cmd.getLatestCmd().getKeys().forEach(key -> {
if (!query.getLatestValues().contains(key)) {
query.getLatestValues().add(key);
}
});
}
long start = System.currentTimeMillis();
ctx.fetchData();
long end = System.currentTimeMillis();
stats.getRegularQueryInvocationCnt().incrementAndGet();
stats.getRegularQueryTimeSpent().addAndGet(end - start);
ctx.cancelTasks();
if (ctx.getQuery().getPageLink().isDynamic()) {
//TODO: validate number of dynamic page links against rate limits. Ignore dynamic flag if limit is reached.
TbEntityDataSubCtx finalCtx = ctx;
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
() -> refreshDynamicQuery(tenantId, customerId, finalCtx),
dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
finalCtx.setRefreshTask(task);
}
}
ListenableFuture<TbEntityDataSubCtx> historyFuture;
if (cmd.getHistoryCmd() != null) {
log.trace("[{}][{}] Going to process history command: {}", session.getSessionId(), cmd.getCmdId(), cmd.getHistoryCmd());
historyFuture = handleHistoryCmd(ctx, cmd.getHistoryCmd());
} else {
historyFuture = Futures.immediateFuture(ctx);
}
Futures.addCallback(historyFuture, new FutureCallback<TbEntityDataSubCtx>() {
@Override
public void onSuccess(@Nullable TbEntityDataSubCtx theCtx) {
if (cmd.getLatestCmd() != null) {
handleLatestCmd(theCtx, cmd.getLatestCmd());
} else if (cmd.getTsCmd() != null) {
handleTimeSeriesCmd(theCtx, cmd.getTsCmd());
} else if (!theCtx.isInitialDataSent()) {
EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null, theCtx.getMaxEntitiesPerDataSubscription());
wsService.sendWsMsg(theCtx.getSessionId(), update);
theCtx.setInitialDataSent(true);
}
}
@Override
public void onFailure(Throwable t) {
log.warn("[{}][{}] Failed to process command", session.getSessionId(), cmd.getCmdId());
}
}, wsCallBackExecutor);
}
@Override
public void handleCmd(TelemetryWebSocketSessionRef session, AlarmDataCmd cmd) {
TbAlarmDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
if (ctx == null) {
log.debug("[{}][{}] Creating new alarm subscription using: {}", session.getSessionId(), cmd.getCmdId(), cmd);
ctx = createSubCtx(session, cmd);
}
ctx.setAndResolveQuery(cmd.getQuery());
AlarmDataQuery adq = ctx.getQuery();
long start = System.currentTimeMillis();
ctx.fetchData();
long end = System.currentTimeMillis();
stats.getRegularQueryInvocationCnt().incrementAndGet();
stats.getRegularQueryTimeSpent().addAndGet(end - start);
List<EntityData> entities = ctx.getEntitiesData();
ctx.cancelTasks();
ctx.clearEntitySubscriptions();
if (entities.isEmpty()) {
AlarmDataUpdate update = new AlarmDataUpdate(cmd.getCmdId(), new PageData<>(), null, 0, 0);
wsService.sendWsMsg(ctx.getSessionId(), update);
} else {
ctx.fetchAlarms();
ctx.createSubscriptions(cmd.getQuery().getLatestValues(), true);
if (adq.getPageLink().getTimeWindow() > 0) {
TbAlarmDataSubCtx finalCtx = ctx;
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
finalCtx::cleanupOldAlarms, dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
finalCtx.setRefreshTask(task);
}
}
}
private void refreshDynamicQuery(TenantId tenantId, CustomerId customerId, TbEntityDataSubCtx finalCtx) {
try {
long start = System.currentTimeMillis();
finalCtx.update();
long end = System.currentTimeMillis();
stats.getDynamicQueryInvocationCnt().incrementAndGet();
stats.getDynamicQueryTimeSpent().addAndGet(end - start);
} catch (Exception e) {
log.warn("[{}][{}] Failed to refresh query", finalCtx.getSessionId(), finalCtx.getCmdId(), e);
}
}
@Scheduled(fixedDelayString = "${server.ws.dynamic_page_link.stats:10000}")
public void printStats() {
int alarmQueryInvocationCntValue = stats.getAlarmQueryInvocationCnt().getAndSet(0);
long alarmQueryInvocationTimeValue = stats.getAlarmQueryTimeSpent().getAndSet(0);
int regularQueryInvocationCntValue = stats.getRegularQueryInvocationCnt().getAndSet(0);
long regularQueryInvocationTimeValue = stats.getRegularQueryTimeSpent().getAndSet(0);
int dynamicQueryInvocationCntValue = stats.getDynamicQueryInvocationCnt().getAndSet(0);
long dynamicQueryInvocationTimeValue = stats.getDynamicQueryTimeSpent().getAndSet(0);
long dynamicQueryCnt = subscriptionsBySessionId.values().stream().map(Map::values).count();
if (regularQueryInvocationCntValue > 0 || dynamicQueryInvocationCntValue > 0 || dynamicQueryCnt > 0 || alarmQueryInvocationCntValue > 0) {
log.info("Stats: regularQueryInvocationCnt = [{}], regularQueryInvocationTime = [{}], " +
"dynamicQueryCnt = [{}] dynamicQueryInvocationCnt = [{}], dynamicQueryInvocationTime = [{}], " +
"alarmQueryInvocationCnt = [{}], alarmQueryInvocationTime = [{}]",
regularQueryInvocationCntValue, regularQueryInvocationTimeValue,
dynamicQueryCnt, dynamicQueryInvocationCntValue, dynamicQueryInvocationTimeValue,
alarmQueryInvocationCntValue, alarmQueryInvocationTimeValue);
}
}
private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription);
ctx.setAndResolveQuery(cmd.getQuery());
sessionSubs.put(cmd.getCmdId(), ctx);
return ctx;
}
private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription);
ctx.setAndResolveQuery(cmd.getQuery());
sessionSubs.put(cmd.getCmdId(), ctx);
return ctx;
}
private <T extends TbAbstractDataSubCtx> T getSubCtx(String sessionId, int cmdId) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId);
if (sessionSubs != null) {
return (T) sessionSubs.get(cmdId);
} else {
return null;
}
}
private ListenableFuture<TbEntityDataSubCtx> handleTimeSeriesCmd(TbEntityDataSubCtx ctx, TimeSeriesCmd cmd) {
log.debug("[{}][{}] Fetching time-series data for last {} ms for keys: ({})", ctx.getSessionId(), ctx.getCmdId(), cmd.getTimeWindow(), cmd.getKeys());
return handleGetTsCmd(ctx, cmd, true);
}
private ListenableFuture<TbEntityDataSubCtx> handleHistoryCmd(TbEntityDataSubCtx ctx, EntityHistoryCmd cmd) {
log.debug("[{}][{}] Fetching history data for start {} and end {} ms for keys: ({})", ctx.getSessionId(), ctx.getCmdId(), cmd.getStartTs(), cmd.getEndTs(), cmd.getKeys());
return handleGetTsCmd(ctx, cmd, false);
}
private ListenableFuture<TbEntityDataSubCtx> handleGetTsCmd(TbEntityDataSubCtx ctx, GetTsCmd cmd, boolean subscribe) {
List<String> keys = cmd.getKeys();
List<ReadTsKvQuery> finalTsKvQueryList;
List<ReadTsKvQuery> tsKvQueryList = cmd.getKeys().stream().map(key -> new BaseReadTsKvQuery(
key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), cmd.getAgg()
)).collect(Collectors.toList());
if (cmd.isFetchLatestPreviousPoint()) {
finalTsKvQueryList = new ArrayList<>(tsKvQueryList);
finalTsKvQueryList.addAll(cmd.getKeys().stream().map(key -> new BaseReadTsKvQuery(
key, cmd.getStartTs() - TimeUnit.DAYS.toMillis(365), cmd.getStartTs(), cmd.getInterval(), 1, cmd.getAgg()
)).collect(Collectors.toList()));
} else {
finalTsKvQueryList = tsKvQueryList;
}
Map<EntityData, ListenableFuture<List<TsKvEntry>>> fetchResultMap = new HashMap<>();
ctx.getData().getData().forEach(entityData -> fetchResultMap.put(entityData,
tsService.findAll(ctx.getTenantId(), entityData.getEntityId(), finalTsKvQueryList)));
return Futures.transform(Futures.allAsList(fetchResultMap.values()), f -> {
fetchResultMap.forEach((entityData, future) -> {
Map<String, List<TsValue>> keyData = new LinkedHashMap<>();
cmd.getKeys().forEach(key -> keyData.put(key, new ArrayList<>()));
try {
List<TsKvEntry> entityTsData = future.get();
if (entityTsData != null) {
entityTsData.forEach(entry -> keyData.get(entry.getKey()).add(new TsValue(entry.getTs(), entry.getValueAsString())));
}
keyData.forEach((k, v) -> entityData.getTimeseries().put(k, v.toArray(new TsValue[v.size()])));
if (cmd.isFetchLatestPreviousPoint()) {
entityData.getTimeseries().values().forEach(dataArray -> {
Arrays.sort(dataArray, (o1, o2) -> Long.compare(o2.getTs(), o1.getTs()));
});
}
} catch (InterruptedException | ExecutionException e) {
log.warn("[{}][{}][{}] Failed to fetch historical data", ctx.getSessionId(), ctx.getCmdId(), entityData.getEntityId(), e);
wsService.sendWsMsg(ctx.getSessionId(),
new EntityDataUpdate(ctx.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR.getCode(), "Failed to fetch historical data!"));
}
});
EntityDataUpdate update;
if (!ctx.isInitialDataSent()) {
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
ctx.setInitialDataSent(true);
} else {
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription());
}
wsService.sendWsMsg(ctx.getSessionId(), update);
if (subscribe) {
ctx.createSubscriptions(keys.stream().map(key -> new EntityKey(EntityKeyType.TIME_SERIES, key)).collect(Collectors.toList()), false);
}
ctx.getData().getData().forEach(ed -> ed.getTimeseries().clear());
return ctx;
}, wsCallBackExecutor);
}
private void handleLatestCmd(TbEntityDataSubCtx ctx, LatestValueCmd latestCmd) {
log.trace("[{}][{}] Going to process latest command: {}", ctx.getSessionId(), ctx.getCmdId(), latestCmd);
//Fetch the latest values for telemetry keys (in case they are not copied from NoSQL to SQL DB in hybrid mode.
if (!tsInSqlDB) {
log.trace("[{}][{}] Going to fetch missing latest values: {}", ctx.getSessionId(), ctx.getCmdId(), latestCmd);
List<String> allTsKeys = latestCmd.getKeys().stream()
.filter(key -> key.getType().equals(EntityKeyType.TIME_SERIES))
.map(EntityKey::getKey).collect(Collectors.toList());
Map<EntityData, ListenableFuture<Map<String, TsValue>>> missingTelemetryFutures = new HashMap<>();
for (EntityData entityData : ctx.getData().getData()) {
Map<EntityKeyType, Map<String, TsValue>> latestEntityData = entityData.getLatest();
Map<String, TsValue> tsEntityData = latestEntityData.get(EntityKeyType.TIME_SERIES);
Set<String> missingTsKeys = new LinkedHashSet<>(allTsKeys);
if (tsEntityData != null) {
missingTsKeys.removeAll(tsEntityData.keySet());
} else {
tsEntityData = new HashMap<>();
latestEntityData.put(EntityKeyType.TIME_SERIES, tsEntityData);
}
ListenableFuture<List<TsKvEntry>> missingTsData = tsService.findLatest(ctx.getTenantId(), entityData.getEntityId(), missingTsKeys);
missingTelemetryFutures.put(entityData, Futures.transform(missingTsData, this::toTsValue, MoreExecutors.directExecutor()));
}
Futures.addCallback(Futures.allAsList(missingTelemetryFutures.values()), new FutureCallback<List<Map<String, TsValue>>>() {
@Override
public void onSuccess(@Nullable List<Map<String, TsValue>> result) {
missingTelemetryFutures.forEach((key, value) -> {
try {
key.getLatest().get(EntityKeyType.TIME_SERIES).putAll(value.get());
} catch (InterruptedException | ExecutionException e) {
log.warn("[{}][{}] Failed to lookup latest telemetry: {}:{}", ctx.getSessionId(), ctx.getCmdId(), key.getEntityId(), allTsKeys, e);
}
});
EntityDataUpdate update;
if (!ctx.isInitialDataSent()) {
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
ctx.setInitialDataSent(true);
} else {
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription());
}
wsService.sendWsMsg(ctx.getSessionId(), update);
ctx.createSubscriptions(latestCmd.getKeys(), true);
}
@Override
public void onFailure(Throwable t) {
log.warn("[{}][{}] Failed to process websocket command: {}:{}", ctx.getSessionId(), ctx.getCmdId(), ctx.getQuery(), latestCmd, t);
wsService.sendWsMsg(ctx.getSessionId(),
new EntityDataUpdate(ctx.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR.getCode(), "Failed to process websocket command!"));
}
}, wsCallBackExecutor);
} else {
if (!ctx.isInitialDataSent()) {
EntityDataUpdate update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
wsService.sendWsMsg(ctx.getSessionId(), update);
ctx.setInitialDataSent(true);
}
ctx.createSubscriptions(latestCmd.getKeys(), true);
}
}
private Map<String, TsValue> toTsValue(List<TsKvEntry> data) {
return data.stream().collect(Collectors.toMap(TsKvEntry::getKey, value -> new TsValue(value.getTs(), value.getValueAsString())));
}
@Override
public void cancelSubscription(String sessionId, UnsubscribeCmd cmd) {
cleanupAndCancel(getSubCtx(sessionId, cmd.getCmdId()));
}
private void cleanupAndCancel(TbAbstractDataSubCtx ctx) {
if (ctx != null) {
ctx.cancelTasks();
ctx.clearEntitySubscriptions();
ctx.clearDynamicValueSubscriptions();
}
}
@Override
public void cancelAllSessionSubscriptions(String sessionId) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId);
if (sessionSubs != null) {
sessionSubs.values().stream().filter(sub -> sub instanceof TbEntityDataSubCtx).map(sub -> (TbEntityDataSubCtx) sub).forEach(this::cleanupAndCancel);
}
}
private int getLimit(int limit) {
return limit == 0 ? DEFAULT_LIMIT : limit;
}
}

51
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java

@ -36,8 +36,9 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.sub.SubscriptionUpdate;
import org.thingsboard.server.service.telemetry.DefaultTelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -58,9 +59,6 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
private final Set<TopicPartitionInfo> currentPartitions = ConcurrentHashMap.newKeySet();
private final Map<String, Map<Integer, TbSubscription>> subscriptionsBySessionId = new ConcurrentHashMap<>();
@Autowired
private TelemetryWebSocketService wsService;
@Autowired
private EntityViewService entityViewService;
@ -117,11 +115,6 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
//TODO 3.1: replace null callbacks with callbacks from websocket service.
@Override
public void addSubscription(TbSubscription subscription) {
EntityId entityId = subscription.getEntityId();
// Telemetry subscription on Entity Views are handled differently, because we need to allow only certain keys and time ranges;
if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW) && TbSubscriptionType.TIMESERIES.equals(subscription.getType())) {
subscription = resolveEntityViewSubscription((TbTimeseriesSubscription) subscription);
}
pushSubscriptionToManagerService(subscription, true);
registerSubscription(subscription);
}
@ -141,7 +134,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
}
@Override
public void onSubscriptionUpdate(String sessionId, SubscriptionUpdate update, TbCallback callback) {
public void onSubscriptionUpdate(String sessionId, TelemetrySubscriptionUpdate update, TbCallback callback) {
TbSubscription subscription = subscriptionsBySessionId
.getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId());
if (subscription != null) {
@ -155,7 +148,17 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
update.getLatestValues().forEach((key, value) -> attrSub.getKeyStates().put(key, value));
break;
}
wsService.sendWsMsg(sessionId, update);
subscription.getUpdateConsumer().accept(sessionId, update);
}
callback.onSuccess();
}
@Override
public void onSubscriptionUpdate(String sessionId, AlarmSubscriptionUpdate update, TbCallback callback) {
TbSubscription subscription = subscriptionsBySessionId
.getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId());
if (subscription != null && subscription.getType() == TbSubscriptionType.ALARMS) {
subscription.getUpdateConsumer().accept(sessionId, update);
}
callback.onSuccess();
}
@ -196,30 +199,6 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
}
}
private TbSubscription resolveEntityViewSubscription(TbTimeseriesSubscription subscription) {
EntityView entityView = entityViewService.findEntityViewById(TenantId.SYS_TENANT_ID, new EntityViewId(subscription.getEntityId().getId()));
Map<String, Long> keyStates;
if (subscription.isAllKeys()) {
keyStates = entityView.getKeys().getTimeseries().stream().collect(Collectors.toMap(k -> k, k -> 0L));
} else {
keyStates = subscription.getKeyStates().entrySet()
.stream().filter(entry -> entityView.getKeys().getTimeseries().contains(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
return TbTimeseriesSubscription.builder()
.serviceId(subscription.getServiceId())
.sessionId(subscription.getSessionId())
.subscriptionId(subscription.getSubscriptionId())
.tenantId(subscription.getTenantId())
.entityId(entityView.getEntityId())
.startTime(entityView.getStartTimeMs())
.endTime(entityView.getEndTimeMs())
.allKeys(false)
.keyStates(keyStates).build();
}
private void registerSubscription(TbSubscription subscription) {
Map<Integer, TbSubscription> sessionSubscriptions = subscriptionsBySessionId.computeIfAbsent(subscription.getSessionId(), k -> new ConcurrentHashMap<>());
sessionSubscriptions.put(subscription.getSubscriptionId(), subscription);

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

@ -16,12 +16,13 @@
package org.thingsboard.server.service.subscription;
import org.springframework.context.ApplicationListener;
import org.thingsboard.server.common.data.alarm.Alarm;
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.TsKvEntry;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import java.util.List;
@ -35,4 +36,13 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback);
void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, TbCallback callback);
void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty);
void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
}

31
application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionServiceStatistics.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.subscription;
import lombok.Data;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@Data
public class SubscriptionServiceStatistics {
private AtomicInteger alarmQueryInvocationCnt = new AtomicInteger();
private AtomicInteger regularQueryInvocationCnt = new AtomicInteger();
private AtomicInteger dynamicQueryInvocationCnt = new AtomicInteger();
private AtomicLong alarmQueryTimeSpent = new AtomicLong();
private AtomicLong regularQueryTimeSpent = new AtomicLong();
private AtomicLong dynamicQueryTimeSpent = new AtomicLong();
}

470
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java

@ -0,0 +1,470 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.subscription;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
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.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AbstractDataQuery;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.FilterPredicateType;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Data
public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends EntityDataPageLink>> {
protected final String serviceId;
protected final SubscriptionServiceStatistics stats;
protected final TelemetryWebSocketService wsService;
protected final EntityService entityService;
protected final TbLocalSubscriptionService localSubscriptionService;
protected final AttributesService attributesService;
protected final TelemetryWebSocketSessionRef sessionRef;
protected final int cmdId;
protected final Map<Integer, EntityId> subToEntityIdMap;
protected final Set<Integer> subToDynamicValueKeySet;
@Getter
protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues;
@Getter
protected PageData<EntityData> data;
@Getter
@Setter
protected T query;
@Setter
protected volatile ScheduledFuture<?> refreshTask;
public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
AttributesService attributesService, SubscriptionServiceStatistics stats,
TelemetryWebSocketSessionRef sessionRef, int cmdId) {
this.serviceId = serviceId;
this.wsService = wsService;
this.entityService = entityService;
this.localSubscriptionService = localSubscriptionService;
this.attributesService = attributesService;
this.stats = stats;
this.sessionRef = sessionRef;
this.cmdId = cmdId;
this.subToEntityIdMap = new ConcurrentHashMap<>();
this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet();
this.dynamicValues = new ConcurrentHashMap<>();
}
public void setAndResolveQuery(T query) {
dynamicValues.clear();
this.query = query;
if (query.getKeyFilters() != null) {
for (KeyFilter filter : query.getKeyFilters()) {
registerDynamicValues(filter.getPredicate());
}
}
resolve(getTenantId(), getCustomerId(), getUserId());
}
public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) {
List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>();
for (DynamicValueKey key : dynamicValues.keySet()) {
switch (key.getSourceType()) {
case CURRENT_TENANT:
futures.add(resolveEntityValue(tenantId, tenantId, key));
break;
case CURRENT_CUSTOMER:
if (customerId != null && !customerId.isNullUid()) {
futures.add(resolveEntityValue(tenantId, customerId, key));
}
break;
case CURRENT_USER:
if (userId != null && !userId.isNullUid()) {
futures.add(resolveEntityValue(tenantId, userId, key));
}
break;
}
}
try {
Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>();
for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) {
tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub);
}
for (EntityId entityId : tmpSubMap.keySet()) {
Map<String, Long> keyStates = new HashMap<>();
Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId);
dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs()));
int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet();
TbAttributeSubscription sub = TbAttributeSubscription.builder()
.serviceId(serviceId)
.sessionId(sessionRef.getSessionId())
.subscriptionId(subIdx)
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap))
.allKeys(false)
.keyStates(keyStates)
.scope(TbAttributeSubscriptionScope.SERVER_SCOPE)
.build();
subToDynamicValueKeySet.add(subIdx);
localSubscriptionService.addSubscription(sub);
}
} catch (InterruptedException | ExecutionException e) {
log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet());
}
}
private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate,
Map<String, DynamicValueKeySub> dynamicValueKeySubMap) {
Map<String, TsValue> latestUpdate = new HashMap<>();
subscriptionUpdate.getData().forEach((k, v) -> {
Object[] data = (Object[]) v.get(0);
latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1]));
});
boolean invalidateFilter = false;
for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) {
String k = entry.getKey();
TsValue tsValue = entry.getValue();
DynamicValueKeySub sub = dynamicValueKeySubMap.get(k);
if (sub.updateValue(tsValue)) {
invalidateFilter = true;
updateDynamicValuesByKey(sub, tsValue);
}
}
if (invalidateFilter) {
update();
}
}
public void fetchData() {
this.data = findEntityData();
}
protected PageData<EntityData> findEntityData() {
PageData<EntityData> result = entityService.findEntityDataByQuery(getTenantId(), getCustomerId(), buildEntityDataQuery());
if (log.isTraceEnabled()) {
result.getData().forEach(ed -> {
log.trace("[{}][{}] EntityData: {}", getSessionId(), getCmdId(), ed);
});
}
return result;
}
protected synchronized void update() {
long start = System.currentTimeMillis();
PageData<EntityData> newData = findEntityData();
long end = System.currentTimeMillis();
stats.getRegularQueryInvocationCnt().incrementAndGet();
stats.getRegularQueryTimeSpent().addAndGet(end - start);
Map<EntityId, EntityData> oldDataMap;
if (data != null && !data.getData().isEmpty()) {
oldDataMap = data.getData().stream().collect(Collectors.toMap(EntityData::getEntityId, Function.identity(), (a, b) -> a));
} else {
oldDataMap = Collections.emptyMap();
}
Map<EntityId, EntityData> newDataMap = newData.getData().stream().collect(Collectors.toMap(EntityData::getEntityId, Function.identity(), (a,b)-> a));
if (oldDataMap.size() == newDataMap.size() && oldDataMap.keySet().equals(newDataMap.keySet())) {
log.trace("[{}][{}] No updates to entity data found", sessionRef.getSessionId(), cmdId);
} else {
this.data = newData;
doUpdate(newDataMap);
}
}
protected abstract void doUpdate(Map<EntityId, EntityData> newDataMap);
protected abstract EntityDataQuery buildEntityDataQuery();
public List<EntityData> getEntitiesData() {
return data.getData();
}
@Data
private static class DynamicValueKeySub {
private final DynamicValueKey key;
private final EntityId entityId;
private long lastUpdateTs;
private String lastUpdateValue;
boolean updateValue(TsValue value) {
if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) {
this.lastUpdateTs = value.getTs();
this.lastUpdateValue = value.getValue();
return true;
} else {
return false;
}
}
}
private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) {
ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId,
TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute());
return Futures.transform(entry, attributeOpt -> {
DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId);
if (attributeOpt.isPresent()) {
AttributeKvEntry attribute = attributeOpt.get();
sub.setLastUpdateTs(attribute.getLastUpdateTs());
sub.setLastUpdateValue(attribute.getValueAsString());
updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString()));
}
return sub;
}, MoreExecutors.directExecutor());
}
private void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) {
DynamicValueKey dvk = sub.getKey();
switch (dvk.getPredicateType()) {
case STRING:
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue()));
break;
case NUMERIC:
try {
Double dValue = Double.parseDouble(tsValue.getValue());
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue));
} catch (NumberFormatException e) {
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null));
}
break;
case BOOLEAN:
Boolean bValue = Boolean.parseBoolean(tsValue.getValue());
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue));
break;
}
}
private void registerDynamicValues(KeyFilterPredicate predicate) {
switch (predicate.getType()) {
case STRING:
case NUMERIC:
case BOOLEAN:
Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate);
if (value.isPresent()) {
DynamicValue dynamicValue = value.get();
DynamicValueKey key = new DynamicValueKey(
predicate.getType(),
dynamicValue.getSourceType(),
dynamicValue.getSourceAttribute());
dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue);
}
break;
case COMPLEX:
((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues);
}
}
private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) {
if (predicate.getValue().getUserValue() == null) {
return Optional.ofNullable(predicate.getValue().getDynamicValue());
} else {
return Optional.empty();
}
}
public String getSessionId() {
return sessionRef.getSessionId();
}
public TenantId getTenantId() {
return sessionRef.getSecurityCtx().getTenantId();
}
public CustomerId getCustomerId() {
return sessionRef.getSecurityCtx().getCustomerId();
}
public UserId getUserId() {
return sessionRef.getSecurityCtx().getId();
}
public void clearEntitySubscriptions() {
if (subToEntityIdMap != null) {
for (Integer subId : subToEntityIdMap.keySet()) {
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId);
}
subToEntityIdMap.clear();
}
}
public void clearDynamicValueSubscriptions() {
if (subToDynamicValueKeySet != null) {
for (Integer subId : subToDynamicValueKeySet) {
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId);
}
subToDynamicValueKeySet.clear();
}
}
public void setRefreshTask(ScheduledFuture<?> task) {
this.refreshTask = task;
}
public void cancelTasks() {
if (this.refreshTask != null) {
log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId);
this.refreshTask.cancel(true);
}
}
public void createSubscriptions(List<EntityKey> keys, boolean resultToLatestValues) {
Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys);
for (EntityData entityData : data.getData()) {
List<TbSubscription> entitySubscriptions = addSubscriptions(entityData, keysByType, resultToLatestValues);
entitySubscriptions.forEach(localSubscriptionService::addSubscription);
}
}
protected Map<EntityKeyType, List<EntityKey>> getEntityKeyByTypeMap(List<EntityKey> keys) {
Map<EntityKeyType, List<EntityKey>> keysByType = new HashMap<>();
keys.forEach(key -> keysByType.computeIfAbsent(key.getType(), k -> new ArrayList<>()).add(key));
return keysByType;
}
protected List<TbSubscription> addSubscriptions(EntityData entityData, Map<EntityKeyType, List<EntityKey>> keysByType, boolean resultToLatestValues) {
List<TbSubscription> subscriptionList = new ArrayList<>();
keysByType.forEach((keysType, keysList) -> {
int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet();
subToEntityIdMap.put(subIdx, entityData.getEntityId());
switch (keysType) {
case TIME_SERIES:
subscriptionList.add(createTsSub(entityData, subIdx, keysList, resultToLatestValues));
break;
case CLIENT_ATTRIBUTE:
subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.CLIENT_SCOPE, keysList));
break;
case SHARED_ATTRIBUTE:
subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.SHARED_SCOPE, keysList));
break;
case SERVER_ATTRIBUTE:
subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.SERVER_SCOPE, keysList));
break;
case ATTRIBUTE:
subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.ANY_SCOPE, keysList));
break;
}
});
return subscriptionList;
}
private TbSubscription createAttrSub(EntityData entityData, int subIdx, EntityKeyType keysType, TbAttributeSubscriptionScope scope, List<EntityKey> subKeys) {
Map<String, Long> keyStates = buildKeyStats(entityData, keysType, subKeys);
log.trace("[{}][{}][{}] Creating attributes subscription for [{}] with keys: {}", serviceId, cmdId, subIdx, entityData.getEntityId(), keyStates);
return TbAttributeSubscription.builder()
.serviceId(serviceId)
.sessionId(sessionRef.getSessionId())
.subscriptionId(subIdx)
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityData.getEntityId())
.updateConsumer((s, subscriptionUpdate) -> sendWsMsg(s, subscriptionUpdate, keysType))
.allKeys(false)
.keyStates(keyStates)
.scope(scope)
.build();
}
private TbSubscription createTsSub(EntityData entityData, int subIdx, List<EntityKey> subKeys, boolean resultToLatestValues) {
Map<String, Long> keyStates = buildKeyStats(entityData, EntityKeyType.TIME_SERIES, subKeys);
if (entityData.getTimeseries() != null) {
entityData.getTimeseries().forEach((k, v) -> {
long ts = Arrays.stream(v).map(TsValue::getTs).max(Long::compareTo).orElse(0L);
log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, ts);
keyStates.put(k, ts);
});
}
log.trace("[{}][{}][{}] Creating time-series subscription for [{}] with keys: {}", serviceId, cmdId, subIdx, entityData.getEntityId(), keyStates);
return TbTimeseriesSubscription.builder()
.serviceId(serviceId)
.sessionId(sessionRef.getSessionId())
.subscriptionId(subIdx)
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityData.getEntityId())
.updateConsumer((sessionId, subscriptionUpdate) -> sendWsMsg(sessionId, subscriptionUpdate, EntityKeyType.TIME_SERIES, resultToLatestValues))
.allKeys(false)
.keyStates(keyStates)
.build();
}
private void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType) {
sendWsMsg(sessionId, subscriptionUpdate, keyType, true);
}
private Map<String, Long> buildKeyStats(EntityData entityData, EntityKeyType keysType, List<EntityKey> subKeys) {
Map<String, Long> keyStates = new HashMap<>();
subKeys.forEach(key -> keyStates.put(key.getKey(), 0L));
if (entityData.getLatest() != null) {
Map<String, TsValue> currentValues = entityData.getLatest().get(keysType);
if (currentValues != null) {
currentValues.forEach((k, v) -> {
log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, v.getTs());
keyStates.put(k, v.getTs());
});
}
}
return keyStates;
}
abstract void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues);
@Data
private static class DynamicValueKey {
@Getter
private final FilterPredicateType predicateType;
@Getter
private final DynamicValueSourceType sourceType;
@Getter
private final String sourceAttribute;
}
}

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

Loading…
Cancel
Save