Browse Source

Initial commit

pull/1/head
Andrew Shvayka 9 years ago
commit
c22bf33def
  1. 32
      .gitignore
  2. 201
      LICENSE
  3. 29
      README.md
  4. 1
      application/.gitignore
  5. 134
      application/build.gradle
  6. 417
      application/pom.xml
  7. 44
      application/src/main/conf/logback.xml
  8. 19
      application/src/main/conf/thingsboard.conf
  9. 46
      application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java
  10. 191
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  11. 226
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  12. 89
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
  13. 366
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  14. 32
      application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java
  15. 28
      application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java
  16. 151
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java
  17. 233
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java
  18. 53
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginCallbackMessage.java
  19. 306
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
  20. 30
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginTerminationMsg.java
  21. 66
      application/src/main/java/org/thingsboard/server/actors/plugin/RuleToPluginMsgWrapper.java
  22. 111
      application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
  23. 27
      application/src/main/java/org/thingsboard/server/actors/plugin/TimeoutScheduler.java
  24. 170
      application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java
  25. 27
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcBroadcastMsg.java
  26. 192
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
  27. 118
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java
  28. 29
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionClosedMsg.java
  29. 31
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionConnectedMsg.java
  30. 35
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionCreateRequestMsg.java
  31. 29
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionDisconnectedMsg.java
  32. 29
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionTellMsg.java
  33. 30
      application/src/main/java/org/thingsboard/server/actors/rpc/SessionActorInfo.java
  34. 104
      application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingContext.java
  35. 42
      application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java
  36. 43
      application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java
  37. 20
      application/src/main/java/org/thingsboard/server/actors/rule/CompoundRuleActorChain.java
  38. 90
      application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java
  39. 24
      application/src/main/java/org/thingsboard/server/actors/rule/RuleActorChain.java
  40. 339
      application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java
  41. 107
      application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java
  42. 33
      application/src/main/java/org/thingsboard/server/actors/rule/RuleContextAwareMsgProcessor.java
  43. 89
      application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java
  44. 31
      application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingMsg.java
  45. 30
      application/src/main/java/org/thingsboard/server/actors/rule/RuleTerminationMsg.java
  46. 36
      application/src/main/java/org/thingsboard/server/actors/rule/RuleToPluginTimeoutMsg.java
  47. 34
      application/src/main/java/org/thingsboard/server/actors/rule/RulesProcessedMsg.java
  48. 40
      application/src/main/java/org/thingsboard/server/actors/rule/SimpleRuleActorChain.java
  49. 31
      application/src/main/java/org/thingsboard/server/actors/service/ActorService.java
  50. 169
      application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
  51. 31
      application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java
  52. 32
      application/src/main/java/org/thingsboard/server/actors/service/ContextBasedCreator.java
  53. 234
      application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
  54. 24
      application/src/main/java/org/thingsboard/server/actors/service/RestMsgProcessor.java
  55. 24
      application/src/main/java/org/thingsboard/server/actors/service/WebSocketMsgProcessor.java
  56. 133
      application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java
  57. 119
      application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java
  58. 139
      application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java
  59. 154
      application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java
  60. 26
      application/src/main/java/org/thingsboard/server/actors/session/SessionTerminationMsg.java
  61. 93
      application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java
  62. 135
      application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
  63. 31
      application/src/main/java/org/thingsboard/server/actors/shared/ActorTerminationMsg.java
  64. 55
      application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
  65. 29
      application/src/main/java/org/thingsboard/server/actors/shared/SessionTimeoutMsg.java
  66. 83
      application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java
  67. 41
      application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java
  68. 41
      application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java
  69. 126
      application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java
  70. 35
      application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java
  71. 34
      application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java
  72. 75
      application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
  73. 32
      application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistMsg.java
  74. 18
      application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistTick.java
  75. 40
      application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java
  76. 184
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  77. 55
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/CurrentServerInstanceService.java
  78. 37
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java
  79. 28
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryServiceListener.java
  80. 75
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java
  81. 52
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstance.java
  82. 24
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstanceService.java
  83. 207
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
  84. 33
      application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java
  85. 142
      application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java
  86. 273
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java
  87. 53
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java
  88. 130
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java
  89. 45
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSessionListener.java
  90. 50
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java
  91. 63
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcSessionCreationFuture.java
  92. 190
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  93. 35
      application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java
  94. 49
      application/src/main/java/org/thingsboard/server/utils/MiscUtils.java
  95. 97
      application/src/main/proto/cluster.proto
  96. 26
      application/src/main/proto/discovery.proto
  97. 163
      application/src/main/resources/actor-system.conf
  98. 3
      application/src/main/resources/banner.txt
  99. 5
      application/src/main/resources/i18n/messages.properties
  100. 35
      application/src/main/resources/logback.xml

32
.gitignore

@ -0,0 +1,32 @@
output/**
*.class
*~
*.iml
*/.idea/**
.idea/**
.idea
*.log
*.log.[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
*/.classpath
.classpath
*/.project
.project
.cache/**
target/
build/
tmp_deb_control/
tmp_rpm_control/
tmp_sh/
.gwt/
.settings/
/bin
bin/
**/dependency-reduced-pom.xml
pom.xml.versionsBackup
.DS_Store
**/.gradle
**/local.properties
**/build
**/target
**/Californium.properties
**/.env

201
LICENSE

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016 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.

29
README.md

@ -0,0 +1,29 @@
# iotrules
IoT Rules Engine
**Docker usage**
**start platfrom using docker:**
- install docker
- cd to 'docker' folder
- create folder for cassandra data directory on your local env (host)
- `mkdir /home/user/data_dir`
- modify .env file to point to the directory created in previous step
- start ./deploy.sh script to run all the services
**start-up for local development**
cassandra with thingsboard schema (9042 and 9061 ports are exposed).
zookeper services (2181 port is exposed).
9042, 9061 and 2181 ports must be free so 'Thingsboard' server that is running outside docker container is able to connect to services.
you can change these ports in docker-compose.static.yml file to some others, but 'Thingsbaord' application.yml file must be updated accordingly.
if you would like to change cassandra port, change it to "9999:9042" for example and update cassandra.node_list entry in application.yml file to localhost:9999.
- install docker
- cd to 'docker' folder
- create folder for cassandra data directory on your local env (host)
- `mkdir /home/user/data_dir`
- modify .env file to point to the directory created in previous step
- start ./deploy_cassandra_zookeeper.sh script to run cassandra with thingsboard schema and zookeper services
- Start boot class: _org.thingsboard.server.ThingsboardServerApplication_

1
application/.gitignore

@ -0,0 +1 @@
!bin/

134
application/build.gradle

@ -0,0 +1,134 @@
/**
* Copyright © 2016 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.
*/
buildscript {
ext {
osPackageVersion = "3.8.0"
}
repositories {
jcenter()
}
dependencies {
classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}")
}
}
apply plugin: "nebula.ospackage"
buildDir = projectBuildDir
version = projectVersion
distsDirName = "./"
// OS Package plugin configuration
ospackage {
packageName = pkgName
version = "${project.version}"
release = 1
os = LINUX
type = BINARY
into pkgInstallFolder
user pkgName
permissionGroup pkgName
// Copy the actual .jar file
from(mainJar) {
// Strip the version from the jar filename
rename { String fileName ->
fileName.replace("-${project.version}", "")
}
fileMode 0500
into "bin"
}
// Copy the config files
from("target/conf") {
fileType CONFIG | NOREPLACE
fileMode 0754
into "conf"
}
// Copy the data files
from("target/data") {
fileType CONFIG | NOREPLACE
fileMode 0754
into "data"
}
// Copy the extensions files
from("target/extensions") {
into "extensions"
}
}
// Configure our RPM build task
buildRpm {
arch = NOARCH
version = projectVersion.replace('-', '')
archiveName = "${pkgName}.rpm"
requires("java-1.8.0")
preInstall file("${buildDir}/control/rpm/preinst")
postInstall file("${buildDir}/control/rpm/postinst")
preUninstall file("${buildDir}/control/rpm/prerm")
postUninstall file("${buildDir}/control/rpm/postrm")
user pkgName
permissionGroup pkgName
// Copy the system unit files
from("${buildDir}/control/${pkgName}.service") {
addParentDirs = false
fileMode 0644
into "/usr/lib/systemd/system"
}
directory(pkgLogFolder, 0755)
link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
}
// Same as the buildRpm task
buildDeb {
arch = "all"
archiveName = "${pkgName}.deb"
requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer")
configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf")
configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml")
configurationFile("${pkgInstallFolder}/conf/logback.xml")
configurationFile("${pkgInstallFolder}/conf/actor-system.conf")
preInstall file("${buildDir}/control/deb/preinst")
postInstall file("${buildDir}/control/deb/postinst")
preUninstall file("${buildDir}/control/deb/prerm")
postUninstall file("${buildDir}/control/deb/postrm")
user pkgName
permissionGroup pkgName
directory(pkgLogFolder, 0755)
link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar")
link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
}

417
application/pom.xml

@ -0,0 +1,417 @@
<!--
Copyright © 2016 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>server</artifactId>
</parent>
<groupId>org.thingsboard.server</groupId>
<artifactId>application</artifactId>
<packaging>jar</packaging>
<name>Thingsboard Server Application</name>
<url>http://thingsboard.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/..</main.dir>
<pkg.name>thingsboard</pkg.name>
<pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
</properties>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${netty.version}</version>
<!-- Explicitly bring in the linux classifier, test may fail on 32-bit linux -->
<classifier>linux-x86_64</classifier>
</dependency>
<dependency>
<groupId>org.thingsboard.server</groupId>
<artifactId>extensions-api</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.server</groupId>
<artifactId>extensions-core</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.server.common</groupId>
<artifactId>transport</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.server.transport</groupId>
<artifactId>http</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.server.transport</groupId>
<artifactId>coap</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.server.transport</groupId>
<artifactId>mqtt</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.server</groupId>
<artifactId>dao</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.server</groupId>
<artifactId>dao</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.takari.junit</groupId>
<artifactId>takari-cpsuite</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.cassandraunit</groupId>
<artifactId>cassandra-unit</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.thingsboard.server</groupId>
<artifactId>ui</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_${scala.version}</artifactId>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-slf4j_${scala.version}</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${pkg.name}-${project.version}</finalName>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surfire.version}</version>
<configuration>
<systemPropertyVariables>
<spring.config.name>thingsboard</spring.config.name>
</systemPropertyVariables>
<includes>
<include>**/*TestSuite.java</include>
</includes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-conf</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/conf</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>logback.xml</exclude>
</excludes>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-service-conf</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/conf</outputDirectory>
<resources>
<resource>
<directory>src/main/conf</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-control</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/control</outputDirectory>
<resources>
<resource>
<directory>src/main/scripts/control</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-data-cql</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/data</outputDirectory>
<resources>
<resource>
<directory>../dao/src/main/resources</directory>
<includes>
<include>**/*.cql</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-extensions</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/extensions</outputDirectory>
<artifactItems>
<artifactItem>
<groupId>org.thingsboard.server.extensions</groupId>
<artifactId>extension-rabbitmq</artifactId>
<classifier>extension</classifier>
</artifactItem>
<artifactItem>
<groupId>org.thingsboard.server.extensions</groupId>
<artifactId>extension-rest-api-call</artifactId>
<classifier>extension</classifier>
</artifactItem>
<artifactItem>
<groupId>org.thingsboard.server.extensions</groupId>
<artifactId>extension-kafka</artifactId>
<classifier>extension</classifier>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Implementation-Title>Thingsboard</Implementation-Title>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>ZIP</layout>
<executable>true</executable>
<excludeDevtools>true</excludeDevtools>
<embeddedLaunchScriptProperties>
<confFolder>${pkg.installFolder}/conf</confFolder>
<logFolder>${pkg.logFolder}</logFolder>
<logFilename>${pkg.name}.out</logFilename>
</embeddedLaunchScriptProperties>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.fortasoft</groupId>
<artifactId>gradle-maven-plugin</artifactId>
<configuration>
<tasks>
<task>build</task>
<task>buildDeb</task>
<task>buildRpm</task>
</tasks>
<args>
<arg>-PprojectBuildDir=${project.build.directory}</arg>
<arg>-PprojectVersion=${project.version}</arg>
<arg>-PmainJar=${project.build.directory}/${project.build.finalName}.${project.packaging}</arg>
<arg>-PpkgName=${pkg.name}</arg>
<arg>-PpkgInstallFolder=${pkg.installFolder}</arg>
<arg>-PpkgLogFolder=${pkg.logFolder}</arg>
</args>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>invoke</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

44
application/src/main/conf/logback.xml

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright © 2016 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.
-->
<!DOCTYPE configuration>
<configuration>
<appender name="fileLogAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${pkg.logFolder}/${pkg.name}.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.thingsboard.server" level="INFO" />
<logger name="akka" level="INFO" />
<root level="INFO">
<appender-ref ref="fileLogAppender"/>
</root>
</configuration>

19
application/src/main/conf/thingsboard.conf

@ -0,0 +1,19 @@
#
# Copyright © 2016 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.
#
export JAVA_OPTS="$JAVA_OPTS"
export LOG_FILENAME=${pkg.name}.out
export LOADER_PATH=${pkg.installFolder}/conf,${pkg.installFolder}/extensions

46
application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java

@ -0,0 +1,46 @@
/**
* Copyright © 2016 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;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import java.util.Arrays;
@EnableAutoConfiguration
@SpringBootApplication
@ComponentScan({"org.thingsboard.server"})
public class ThingsboardServerApplication {
private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "thingsboard";
public static void main(String[] args) {
SpringApplication.run(ThingsboardServerApplication.class, updateArguments(args));
}
private static String[] updateArguments(String[] args) {
if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) {
String[] modifiedArgs = new String[args.length + 1];
System.arraycopy(args, 0, modifiedArgs, 0, args.length);
modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM;
return modifiedArgs;
}
return args;
}
}

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

@ -0,0 +1,191 @@
/**
* Copyright © 2016 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.actors;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Scheduler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Optional;
@Component
public class ActorSystemContext {
private static final String AKKA_CONF_FILE_NAME = "actor-system.conf";
protected final ObjectMapper mapper = new ObjectMapper();
@Getter @Setter private ActorService actorService;
@Autowired
@Getter private DiscoveryService discoveryService;
@Autowired
@Getter @Setter private ComponentDiscoveryService componentService;
@Autowired
@Getter private ClusterRoutingService routingService;
@Autowired
@Getter private ClusterRpcService rpcService;
@Autowired
@Getter private DeviceAuthService deviceAuthService;
@Autowired
@Getter private DeviceService deviceService;
@Autowired
@Getter private TenantService tenantService;
@Autowired
@Getter private CustomerService customerService;
@Autowired
@Getter private RuleService ruleService;
@Autowired
@Getter private PluginService pluginService;
@Autowired
@Getter private TimeseriesService tsService;
@Autowired
@Getter private AttributesService attributesService;
@Autowired
@Getter private EventService eventService;
@Autowired
@Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint;
@Value("${actors.session.sync.timeout}")
@Getter private long syncSessionTimeout;
@Value("${actors.plugin.termination.delay}")
@Getter private long pluginActorTerminationDelay;
@Value("${actors.plugin.processing.timeout}")
@Getter private long pluginProcessingTimeout;
@Value("${actors.plugin.error_persist_frequency}")
@Getter private long pluginErrorPersistFrequency;
@Value("${actors.rule.termination.delay}")
@Getter private long ruleActorTerminationDelay;
@Value("${actors.rule.error_persist_frequency}")
@Getter private long ruleErrorPersistFrequency;
@Value("${actors.statistics.enabled}")
@Getter private boolean statisticsEnabled;
@Value("${actors.statistics.persist_frequency}")
@Getter private long statisticsPersistFrequency;
@Getter @Setter private ActorSystem actorSystem;
@Getter @Setter private ActorRef appActor;
@Getter @Setter private ActorRef sessionManagerActor;
@Getter @Setter private ActorRef statsActor;
@Getter private final Config config;
public ActorSystemContext() {
config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load());
}
public Scheduler getScheduler() {
return actorSystem.scheduler();
}
public void persistError(TenantId tenantId, EntityId entityId, String method, Exception e) {
Event event = new Event();
event.setTenantId(tenantId);
event.setEntityId(entityId);
event.setType(DataConstants.ERROR);
event.setBody(toBodyJson(discoveryService.getCurrentServer().getServerAddress(), method, toString(e)));
persistEvent(event);
}
public void persistLifecycleEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent lcEvent, Exception e) {
Event event = new Event();
event.setTenantId(tenantId);
event.setEntityId(entityId);
event.setType(DataConstants.LC_EVENT);
event.setBody(toBodyJson(discoveryService.getCurrentServer().getServerAddress(), lcEvent, Optional.ofNullable(e)));
persistEvent(event);
}
private void persistEvent(Event event) {
eventService.save(event);
}
private String toString(Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
private JsonNode toBodyJson(ServerAddress server, ComponentLifecycleEvent event, Optional<Exception> e) {
ObjectNode node = mapper.createObjectNode().put("server", server.toString()).put("event", event.name());
if (e.isPresent()) {
node = node.put("success", false);
node = node.put("error", toString(e.get()));
} else {
node = node.put("success", true);
}
return node;
}
private JsonNode toBodyJson(ServerAddress server, String method, String body) {
return mapper.createObjectNode().put("server", server.toString()).put("method", method).put("error", body);
}
}

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

@ -0,0 +1,226 @@
/**
* Copyright © 2016 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.actors.app;
import akka.actor.*;
import akka.actor.SupervisorStrategy.Directive;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.japi.Function;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.plugin.PluginTerminationMsg;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.plugin.PluginManager;
import org.thingsboard.server.actors.shared.plugin.SystemPluginManager;
import org.thingsboard.server.actors.shared.rule.RuleManager;
import org.thingsboard.server.actors.shared.rule.SystemRuleManager;
import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
import org.thingsboard.server.actors.tenant.TenantActor;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg;
import scala.concurrent.duration.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class AppActor extends ContextAwareActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
private final RuleManager ruleManager;
private final PluginManager pluginManager;
private final TenantService tenantService;
private final Map<TenantId, ActorRef> tenantActors;
private AppActor(ActorSystemContext systemContext) {
super(systemContext);
this.ruleManager = new SystemRuleManager(systemContext);
this.pluginManager = new SystemPluginManager(systemContext);
this.tenantService = systemContext.getTenantService();
this.tenantActors = new HashMap<>();
}
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
@Override
public void preStart() {
logger.info("Starting main system actor.");
try {
ruleManager.init(this.context());
pluginManager.init(this.context());
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(link -> tenantService.findTenants(link), ENTITY_PACK_LIMIT);
for (Tenant tenant : tenantIterator) {
logger.debug("[{}] Creating tenant actor", tenant.getId());
getOrCreateTenantActor(tenant.getId());
logger.debug("Tenant actor created.");
}
logger.info("Main system actor started.");
} catch (Exception e) {
logger.error(e, "Unknown failure");
}
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("Received message: {}", msg);
if (msg instanceof ToDeviceActorMsg) {
processDeviceMsg((ToDeviceActorMsg) msg);
} else if (msg instanceof ToPluginActorMsg) {
onToPluginMsg((ToPluginActorMsg) msg);
} else if (msg instanceof ToRuleActorMsg) {
onToRuleMsg((ToRuleActorMsg) msg);
} else if (msg instanceof ToDeviceActorNotificationMsg) {
onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
} else if (msg instanceof Terminated) {
processTermination((Terminated) msg);
} else if (msg instanceof ClusterEventMsg) {
broadcast(msg);
} else if (msg instanceof ComponentLifecycleMsg) {
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
} else if (msg instanceof PluginTerminationMsg) {
onPluginTerminated((PluginTerminationMsg) msg);
} else {
logger.warning("Unknown message: {}!", msg);
}
}
private void onPluginTerminated(PluginTerminationMsg msg) {
pluginManager.remove(msg.getId());
}
private void broadcast(Object msg) {
pluginManager.broadcast(msg);
tenantActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
private void onToRuleMsg(ToRuleActorMsg msg) {
ActorRef target;
if (SYSTEM_TENANT.equals(msg.getTenantId())) {
target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId());
} else {
target = getOrCreateTenantActor(msg.getTenantId());
}
target.tell(msg, ActorRef.noSender());
}
private void onToPluginMsg(ToPluginActorMsg msg) {
ActorRef target;
if (SYSTEM_TENANT.equals(msg.getPluginTenantId())) {
target = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId());
} else {
target = getOrCreateTenantActor(msg.getPluginTenantId());
}
target.tell(msg, ActorRef.noSender());
}
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
ActorRef target = null;
if (SYSTEM_TENANT.equals(msg.getTenantId())) {
if (msg.getPluginId().isPresent()) {
target = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId().get());
} else if (msg.getRuleId().isPresent()) {
Optional<ActorRef> ref = ruleManager.update(this.context(), msg.getRuleId().get(), msg.getEvent());
if (ref.isPresent()) {
target = ref.get();
} else {
logger.debug("Failed to find actor for rule: [{}]", msg.getRuleId());
return;
}
}
} else {
target = getOrCreateTenantActor(msg.getTenantId());
}
if (target != null) {
target.tell(msg, ActorRef.noSender());
}
}
private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) {
getOrCreateTenantActor(msg.getTenantId()).tell(msg, ActorRef.noSender());
}
private void processDeviceMsg(ToDeviceActorMsg toDeviceActorMsg) {
TenantId tenantId = toDeviceActorMsg.getTenantId();
ActorRef tenantActor = getOrCreateTenantActor(tenantId);
if (toDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) {
tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain()), context().self());
} else {
tenantActor.tell(toDeviceActorMsg, context().self());
}
}
private ActorRef getOrCreateTenantActor(TenantId tenantId) {
ActorRef tenantActor = tenantActors.get(tenantId);
if (tenantActor == null) {
tenantActor = context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId))
.withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString());
tenantActors.put(tenantId, tenantActor);
}
return tenantActor;
}
private void processTermination(Terminated message) {
ActorRef terminated = message.actor();
if (terminated instanceof LocalActorRef) {
logger.debug("Removed actor: {}", terminated);
} else {
throw new IllegalStateException("Remote actors are not supported!");
}
}
public static class ActorCreator extends ContextBasedCreator<AppActor> {
private static final long serialVersionUID = 1L;
public ActorCreator(ActorSystemContext context) {
super(context);
}
@Override
public AppActor create() throws Exception {
return new AppActor(context);
}
}
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, Directive>() {
@Override
public Directive apply(Throwable t) {
logger.error(t, "Unknown failure");
if (t instanceof RuntimeException) {
return SupervisorStrategy.restart();
} else {
return SupervisorStrategy.stop();
}
}
});
}

89
application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java

@ -0,0 +1,89 @@
/**
* Copyright © 2016 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.actors.device;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.rule.RulesProcessedMsg;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.*;
public class DeviceActor extends ContextAwareActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
private final TenantId tenantId;
private final DeviceId deviceId;
private final DeviceActorMessageProcessor processor;
private DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) {
super(systemContext);
this.tenantId = tenantId;
this.deviceId = deviceId;
this.processor = new DeviceActorMessageProcessor(systemContext, logger, deviceId);
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof RuleChainDeviceMsg) {
processor.process(context(), (RuleChainDeviceMsg) msg);
} else if (msg instanceof RulesProcessedMsg) {
processor.onRulesProcessedMsg(context(), (RulesProcessedMsg) msg);
} else if (msg instanceof ToDeviceActorMsg) {
processor.process(context(), (ToDeviceActorMsg) msg);
} else if (msg instanceof ToDeviceActorNotificationMsg) {
if (msg instanceof DeviceAttributesEventNotificationMsg) {
processor.processAttributesUpdate(context(), (DeviceAttributesEventNotificationMsg) msg);
} else if (msg instanceof ToDeviceRpcRequestPluginMsg) {
processor.processRpcRequest(context(), (ToDeviceRpcRequestPluginMsg) msg);
}
} else if (msg instanceof TimeoutMsg) {
processor.processTimeout(context(), (TimeoutMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
processor.processClusterEventMsg((ClusterEventMsg) msg);
} else {
logger.debug("[{}][{}] Unknown msg type.", tenantId, deviceId, msg.getClass().getName());
}
}
public static class ActorCreator extends ContextBasedCreator<DeviceActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
private final DeviceId deviceId;
public ActorCreator(ActorSystemContext context, TenantId tenantId, DeviceId deviceId) {
super(context);
this.tenantId = tenantId;
this.deviceId = deviceId;
}
@Override
public DeviceActor create() throws Exception {
return new DeviceActor(context, tenantId, deviceId);
}
}
}

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

@ -0,0 +1,366 @@
/**
* Copyright © 2016 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.actors.device;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.rule.ChainProcessingContext;
import org.thingsboard.server.actors.rule.ChainProcessingMetaData;
import org.thingsboard.server.actors.rule.RuleProcessingMsg;
import org.thingsboard.server.actors.rule.RulesProcessedMsg;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.core.AttributesUpdateNotification;
import org.thingsboard.server.common.msg.core.BasicCommandAckResponse;
import org.thingsboard.server.common.msg.core.BasicToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.core.SessionCloseMsg;
import org.thingsboard.server.common.msg.core.ToDeviceRpcRequestMsg;
import org.thingsboard.server.common.msg.core.ToDeviceRpcResponseMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.kv.BasicAttributeKVMsg;
import org.thingsboard.server.common.msg.session.FromDeviceMsg;
import org.thingsboard.server.common.msg.session.MsgType;
import org.thingsboard.server.common.msg.session.SessionType;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.extensions.api.device.DeviceAttributes;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutIntMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
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.UUID;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* @author Andrew Shvayka
*/
public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
private final DeviceId deviceId;
private final Map<SessionId, SessionInfo> attributeSubscriptions;
private final Map<SessionId, SessionInfo> rpcSubscriptions;
private final Map<Integer, ToDeviceRpcRequestMetadata> rpcPendingMap;
private int rpcSeq = 0;
private DeviceAttributes deviceAttributes;
public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, DeviceId deviceId) {
super(systemContext, logger);
this.deviceId = deviceId;
this.attributeSubscriptions = new HashMap<>();
this.rpcSubscriptions = new HashMap<>();
this.rpcPendingMap = new HashMap<>();
refreshAttributes();
}
private void refreshAttributes() {
this.deviceAttributes = new DeviceAttributes(fetchAttributes(DataConstants.CLIENT_SCOPE),
fetchAttributes(DataConstants.SERVER_SCOPE), fetchAttributes(DataConstants.SHARED_SCOPE));
}
void processRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg) {
ToDeviceRpcRequest request = msg.getMsg();
ToDeviceRpcRequestBody body = request.getBody();
ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
rpcSeq++,
body.getMethod(),
body.getParams()
);
long timeout = request.getExpirationTime() - System.currentTimeMillis();
if (timeout <= 0) {
logger.debug("[{}][{}] Ignoring message due to exp time reached", deviceId, request.getId(), request.getExpirationTime());
return;
}
boolean sent = rpcSubscriptions.size() > 0;
Set<SessionId> syncSessionSet = new HashSet<>();
rpcSubscriptions.entrySet().forEach(sub -> {
ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(rpcRequest, sub.getKey());
sendMsgToSessionActor(response, sub.getValue().getServer());
if (SessionType.SYNC == sub.getValue().getType()) {
syncSessionSet.add(sub.getKey());
}
});
syncSessionSet.forEach(rpcSubscriptions::remove);
if (request.isOneway() && sent) {
ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(msg, (String) null);
context.parent().tell(responsePluginMsg, ActorRef.noSender());
logger.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId());
} else {
registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout);
}
if (sent) {
logger.debug("[{}] RPC request {} is sent!", deviceId, request.getId());
} else {
logger.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId());
}
}
private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
rpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent));
TimeoutIntMsg timeoutMsg = new TimeoutIntMsg(rpcRequest.getRequestId(), timeout);
scheduleMsgWithDelay(context, timeoutMsg, timeoutMsg.getTimeout());
}
public void processTimeout(ActorContext context, TimeoutMsg msg) {
ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(msg.getId());
if (requestMd != null) {
logger.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION);
context.parent().tell(responsePluginMsg, ActorRef.noSender());
}
}
private void sendPendingRequests(ActorContext context, SessionId sessionId, SessionType type, Optional<ServerAddress> server) {
if (!rpcPendingMap.isEmpty()) {
logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, rpcPendingMap.size(), sessionId);
if (type == SessionType.SYNC) {
logger.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId);
rpcSubscriptions.remove(sessionId);
}
} else {
logger.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId);
}
Set<UUID> sentOneWayIds = new HashSet<>();
if (type == SessionType.ASYNC) {
rpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds));
} else {
rpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds));
}
sentOneWayIds.forEach(rpcPendingMap::remove);
}
private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(ActorContext context, SessionId sessionId, Optional<ServerAddress> server, Set<UUID> sentOneWayIds) {
return entry -> {
ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg();
ToDeviceRpcRequestBody body = request.getBody();
if (request.isOneway()) {
sentOneWayIds.add(request.getId());
ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(entry.getValue().getMsg(), (String) null);
context.parent().tell(responsePluginMsg, ActorRef.noSender());
}
ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
entry.getKey(),
body.getMethod(),
body.getParams()
);
ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(rpcRequest, sessionId);
sendMsgToSessionActor(response, server);
};
}
void process(ActorContext context, ToDeviceActorMsg msg) {
processSubscriptionCommands(context, msg);
processRpcResponses(context, msg);
processSessionStateMsgs(msg);
}
void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) {
//TODO: improve this procedure to fetch only changed attributes.
refreshAttributes();
//TODO: support attributes deletion
Set<AttributeKey> keys = msg.getKeys();
if (attributeSubscriptions.size() > 0) {
ToDeviceMsg notification = null;
if (msg.isDeleted()) {
List<AttributeKey> sharedKeys = keys.stream()
.filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
.collect(Collectors.toList());
notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromDeleted(sharedKeys));
} else {
List<AttributeKvEntry> attributes = keys.stream()
.filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
.map(key -> deviceAttributes.getServerPublicAttribute(key.getAttributeKey()))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
if (attributes.size() > 0) {
notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromShared(attributes));
} else {
logger.debug("[{}] No public server side attributes changed!", deviceId);
}
}
if (notification != null) {
ToDeviceMsg finalNotification = notification;
attributeSubscriptions.entrySet().forEach(sub -> {
ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(finalNotification, sub.getKey());
sendMsgToSessionActor(response, sub.getValue().getServer());
});
}
} else {
logger.debug("[{}] No registered attributes subscriptions to process!", deviceId);
}
}
void process(ActorContext context, RuleChainDeviceMsg srcMsg) {
ChainProcessingMetaData md = new ChainProcessingMetaData(srcMsg.getRuleChain(),
srcMsg.getToDeviceActorMsg(), deviceAttributes, context.self());
ChainProcessingContext ctx = new ChainProcessingContext(md);
if (ctx.getChainLength() > 0) {
RuleProcessingMsg msg = new RuleProcessingMsg(ctx);
ActorRef ruleActorRef = ctx.getCurrentActor();
ruleActorRef.tell(msg, ActorRef.noSender());
} else {
context.self().tell(new RulesProcessedMsg(ctx), context.self());
}
}
void processRpcResponses(ActorContext context, ToDeviceActorMsg msg) {
SessionId sessionId = msg.getSessionId();
FromDeviceMsg inMsg = msg.getPayload();
if (inMsg.getMsgType() == MsgType.TO_DEVICE_RPC_RESPONSE) {
logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId);
ToDeviceRpcResponseMsg responseMsg = (ToDeviceRpcResponseMsg) inMsg;
ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(responseMsg.getRequestId());
boolean success = requestMd != null;
if (success) {
ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), responseMsg.getData());
Optional<ServerAddress> pluginServerAddress = requestMd.getMsg().getServerAddress();
if (pluginServerAddress.isPresent()) {
systemContext.getRpcService().tell(pluginServerAddress.get(), responsePluginMsg);
logger.debug("[{}] Rpc command response sent to remote plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
} else {
context.parent().tell(responsePluginMsg, ActorRef.noSender());
logger.debug("[{}] Rpc command response sent to local plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
}
} else {
logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
}
if (msg.getSessionType() == SessionType.SYNC) {
BasicCommandAckResponse response = success
? BasicCommandAckResponse.onSuccess(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId())
: BasicCommandAckResponse.onError(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException());
sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(response, msg.getSessionId()), msg.getServerAddress());
}
}
}
public void processClusterEventMsg(ClusterEventMsg msg) {
if (!msg.isAdded()) {
logger.debug("[{}] Clearing attributes/rpc subscription for server [{}]", deviceId, msg.getServerAddress());
Predicate<Map.Entry<SessionId, SessionInfo>> filter = e -> e.getValue().getServer()
.map(serverAddress -> serverAddress.equals(msg.getServerAddress())).orElse(false);
attributeSubscriptions.entrySet().removeIf(filter);
rpcSubscriptions.entrySet().removeIf(filter);
}
}
private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data) {
return toPluginRpcResponseMsg(requestMsg, data, null);
}
private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, RpcError error) {
return toPluginRpcResponseMsg(requestMsg, null, error);
}
private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data, RpcError error) {
return new ToPluginRpcResponseDeviceMsg(
requestMsg.getPluginId(),
requestMsg.getPluginTenantId(),
new FromDeviceRpcResponse(requestMsg.getMsg().getId(),
data,
error
)
);
}
void onRulesProcessedMsg(ActorContext context, RulesProcessedMsg msg) {
ChainProcessingContext ctx = msg.getCtx();
ToDeviceActorMsg inMsg = ctx.getInMsg();
SessionId sid = inMsg.getSessionId();
ToDeviceSessionActorMsg response;
if (ctx.getResponse() != null) {
response = new BasicToDeviceSessionActorMsg(ctx.getResponse(), sid);
} else {
response = new BasicToDeviceSessionActorMsg(ctx.getError(), sid);
}
sendMsgToSessionActor(response, inMsg.getServerAddress());
}
private void processSubscriptionCommands(ActorContext context, ToDeviceActorMsg msg) {
SessionId sessionId = msg.getSessionId();
SessionType sessionType = msg.getSessionType();
FromDeviceMsg inMsg = msg.getPayload();
if (inMsg.getMsgType() == MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) {
logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId);
attributeSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress()));
} else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) {
logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId);
attributeSubscriptions.remove(sessionId);
} else if (inMsg.getMsgType() == MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) {
logger.debug("[{}] Registering rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType);
rpcSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress()));
sendPendingRequests(context, sessionId, sessionType, msg.getServerAddress());
} else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) {
logger.debug("[{}] Canceling rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType);
rpcSubscriptions.remove(sessionId);
}
}
private void processSessionStateMsgs(ToDeviceActorMsg msg) {
SessionId sessionId = msg.getSessionId();
FromDeviceMsg inMsg = msg.getPayload();
if (inMsg instanceof SessionCloseMsg) {
logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId);
attributeSubscriptions.remove(sessionId);
rpcSubscriptions.remove(sessionId);
}
}
private void sendMsgToSessionActor(ToDeviceSessionActorMsg response, Optional<ServerAddress> sessionAddress) {
if (sessionAddress.isPresent()) {
ServerAddress address = sessionAddress.get();
logger.debug("{} Forwarding msg: {}", address, response);
systemContext.getRpcService().tell(sessionAddress.get(), response);
} else {
systemContext.getSessionManagerActor().tell(response, ActorRef.noSender());
}
}
private List<AttributeKvEntry> fetchAttributes(String attributeType) {
return systemContext.getAttributesService().findAll(this.deviceId, attributeType);
}
}

32
application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016 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.actors.device;
import lombok.Data;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.session.SessionType;
import java.util.Optional;
/**
* @author Andrew Shvayka
*/
@Data
public class SessionInfo {
private final SessionType type;
private final Optional<ServerAddress> server;
}

28
application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016 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.actors.device;
import lombok.Data;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
/**
* @author Andrew Shvayka
*/
@Data
public class ToDeviceRpcRequestMetadata {
private final ToDeviceRpcRequestPluginMsg msg;
private final boolean sent;
}

151
application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java

@ -0,0 +1,151 @@
/**
* Copyright © 2016 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.actors.plugin;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ComponentActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.stats.StatsPersistTick;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
import org.thingsboard.server.extensions.api.rules.RuleException;
public class PluginActor extends ComponentActor<PluginId, PluginActorMessageProcessor> {
private PluginActor(ActorSystemContext systemContext, TenantId tenantId, PluginId pluginId) {
super(systemContext, tenantId, pluginId);
setProcessor(new PluginActorMessageProcessor(tenantId, pluginId, systemContext,
logger, context().parent(), context().self()));
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof PluginWebsocketMsg) {
onWebsocketMsg((PluginWebsocketMsg<?>) msg);
} else if (msg instanceof PluginRestMsg) {
onRestMsg((PluginRestMsg) msg);
} else if (msg instanceof PluginCallbackMessage) {
onPluginCallback((PluginCallbackMessage) msg);
} else if (msg instanceof RuleToPluginMsgWrapper) {
onRuleToPluginMsg((RuleToPluginMsgWrapper) msg);
} else if (msg instanceof PluginRpcMsg) {
onRpcMsg((PluginRpcMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
onClusterEventMsg((ClusterEventMsg) msg);
} else if (msg instanceof ComponentLifecycleMsg) {
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
} else if (msg instanceof ToPluginRpcResponseDeviceMsg) {
onRpcResponse((ToPluginRpcResponseDeviceMsg) msg);
} else if (msg instanceof PluginTerminationMsg) {
logger.info("[{}][{}] Going to terminate plugin actor.", tenantId, id);
context().parent().tell(msg, ActorRef.noSender());
context().stop(self());
} else if (msg instanceof TimeoutMsg) {
onTimeoutMsg(context(), (TimeoutMsg) msg);
} else if (msg instanceof StatsPersistTick) {
onStatsPersistTick(id);
} else {
logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName());
}
}
private void onPluginCallback(PluginCallbackMessage msg) {
try {
processor.onPluginCallbackMsg(msg);
} catch (Exception e) {
logAndPersist("onPluginCallbackMsg", e);
}
}
private void onTimeoutMsg(ActorContext context, TimeoutMsg msg) {
processor.onTimeoutMsg(context, msg);
}
private void onRpcResponse(ToPluginRpcResponseDeviceMsg msg) {
processor.onDeviceRpcMsg(msg.getResponse());
}
private void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException {
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg());
try {
processor.onRuleToPluginMsg(msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onRuleMsg", e);
}
}
private void onWebsocketMsg(PluginWebsocketMsg<?> msg) {
logger.debug("[{}] Going to process web socket msg: {}", id, msg);
try {
processor.onWebsocketMsg(msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onWebsocketMsg", e);
}
}
private void onRestMsg(PluginRestMsg msg) {
logger.debug("[{}] Going to process rest msg: {}", id, msg);
try {
processor.onRestMsg(msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onRestMsg", e);
}
}
private void onRpcMsg(PluginRpcMsg msg) {
try {
logger.debug("[{}] Going to process rpc msg: {}", id, msg);
processor.onRpcMsg(msg);
} catch (Exception e) {
logAndPersist("onRpcMsg", e);
}
}
public static class ActorCreator extends ContextBasedCreator<PluginActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
private final PluginId pluginId;
public ActorCreator(ActorSystemContext context, TenantId tenantId, PluginId pluginId) {
super(context);
this.tenantId = tenantId;
this.pluginId = pluginId;
}
@Override
public PluginActor create() throws Exception {
return new PluginActor(context, tenantId, pluginId);
}
}
@Override
protected long getErrorPersistFrequency() {
return systemContext.getPluginErrorPersistFrequency();
}
}

233
application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java

@ -0,0 +1,233 @@
/**
* Copyright © 2016 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.actors.plugin;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.plugins.Plugin;
import org.thingsboard.server.extensions.api.plugins.PluginInitializationException;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
import org.thingsboard.server.extensions.api.rules.RuleException;
/**
* @author Andrew Shvayka
*/
public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId> {
private final SharedPluginProcessingContext pluginCtx;
private final PluginProcessingContext trustedCtx;
private PluginMetaData pluginMd;
private Plugin pluginImpl;
private ComponentLifecycleState state;
protected PluginActorMessageProcessor(TenantId tenantId, PluginId pluginId, ActorSystemContext systemContext
, LoggingAdapter logger, ActorRef parent, ActorRef self) {
super(systemContext, logger, tenantId, pluginId);
this.pluginCtx = new SharedPluginProcessingContext(systemContext, tenantId, pluginId, parent, self);
this.trustedCtx = new PluginProcessingContext(pluginCtx, null);
}
@Override
public void start() throws Exception {
logger.info("[{}] Going to start plugin actor.", entityId);
pluginMd = systemContext.getPluginService().findPluginById(entityId);
if (pluginMd == null) {
throw new PluginInitializationException("Plugin not found!");
}
if (pluginMd.getConfiguration() == null) {
throw new PluginInitializationException("Plugin metadata is empty!");
}
state = pluginMd.getState();
if (state == ComponentLifecycleState.ACTIVE) {
logger.info("[{}] Plugin is active. Going to initialize plugin.", entityId);
initComponent();
} else {
logger.info("[{}] Plugin is suspended. Skipping plugin initialization.", entityId);
}
}
@Override
public void stop() throws Exception {
onStop();
}
private void initComponent() {
try {
pluginImpl = initComponent(pluginMd.getClazz(), ComponentType.PLUGIN, mapper.writeValueAsString(pluginMd.getConfiguration()));
} catch (InstantiationException e) {
throw new PluginInitializationException("No default constructor for plugin implementation!", e);
} catch (IllegalAccessException e) {
throw new PluginInitializationException("Illegal Access Exception during plugin initialization!", e);
} catch (ClassNotFoundException e) {
throw new PluginInitializationException("Plugin Class not found!", e);
} catch (JsonProcessingException e) {
throw new PluginInitializationException("Plugin Configuration is invalid!", e);
} catch (Exception e) {
throw new PluginInitializationException(e.getMessage(), e);
}
}
public void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException {
if (state == ComponentLifecycleState.ACTIVE) {
pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg());
} else {
//TODO: reply with plugin suspended message
}
}
public void onWebsocketMsg(PluginWebsocketMsg<?> msg) {
if (state == ComponentLifecycleState.ACTIVE) {
pluginImpl.process(new PluginProcessingContext(pluginCtx, msg.getSecurityCtx()), msg);
} else {
//TODO: reply with plugin suspended message
}
}
public void onRestMsg(PluginRestMsg msg) {
if (state == ComponentLifecycleState.ACTIVE) {
pluginImpl.process(new PluginProcessingContext(pluginCtx, msg.getSecurityCtx()), msg);
}
}
public void onRpcMsg(PluginRpcMsg msg) {
if (state == ComponentLifecycleState.ACTIVE) {
pluginImpl.process(trustedCtx, msg.getRpcMsg());
} else {
//TODO: reply with plugin suspended message
}
}
public void onPluginCallbackMsg(PluginCallbackMessage msg) {
if (state == ComponentLifecycleState.ACTIVE) {
if (msg.isSuccess()) {
msg.getCallback().onSuccess(trustedCtx, msg.getV());
} else {
msg.getCallback().onFailure(trustedCtx, msg.getE());
}
} else {
//TODO: reply with plugin suspended message
}
}
public void onTimeoutMsg(ActorContext context, TimeoutMsg<?> msg) {
if (state == ComponentLifecycleState.ACTIVE) {
pluginImpl.process(trustedCtx, msg);
}
}
public void onDeviceRpcMsg(FromDeviceRpcResponse response) {
if (state == ComponentLifecycleState.ACTIVE) {
pluginImpl.process(trustedCtx, response);
}
}
@Override
public void onClusterEventMsg(ClusterEventMsg msg) {
if (state == ComponentLifecycleState.ACTIVE) {
ServerAddress address = msg.getServerAddress();
if (msg.isAdded()) {
logger.debug("[{}] Going to process server add msg: {}", entityId, address);
pluginImpl.onServerAdded(trustedCtx, address);
} else {
logger.debug("[{}] Going to process server remove msg: {}", entityId, address);
pluginImpl.onServerRemoved(trustedCtx, address);
}
}
}
@Override
public void onCreated(ActorContext context) {
logger.info("[{}] Going to process onCreated plugin.", entityId);
}
@Override
public void onUpdate(ActorContext context) throws Exception {
PluginMetaData oldPluginMd = systemContext.getPluginService().findPluginById(entityId);
pluginMd = systemContext.getPluginService().findPluginById(entityId);
boolean requiresRestart = false;
logger.info("[{}] Plugin configuration was updated from {} to {}.", entityId, oldPluginMd, pluginMd);
if (!oldPluginMd.getClazz().equals(pluginMd.getClazz())) {
logger.info("[{}] Plugin requires restart due to clazz change from {} to {}.",
entityId, oldPluginMd.getClazz(), pluginMd.getClazz());
requiresRestart = true;
} else if (oldPluginMd.getConfiguration().equals(pluginMd.getConfiguration())) {
logger.info("[{}] Plugin requires restart due to configuration change from {} to {}.",
entityId, oldPluginMd.getConfiguration(), pluginMd.getConfiguration());
requiresRestart = true;
}
if (requiresRestart) {
this.state = ComponentLifecycleState.SUSPENDED;
if (pluginImpl != null) {
pluginImpl.stop(trustedCtx);
}
start();
}
}
@Override
public void onStop(ActorContext context) {
onStop();
scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay());
}
private void onStop() {
logger.info("[{}] Going to process onStop plugin.", entityId);
this.state = ComponentLifecycleState.SUSPENDED;
if (pluginImpl != null) {
pluginImpl.stop(trustedCtx);
}
}
@Override
public void onActivate(ActorContext context) throws Exception {
logger.info("[{}] Going to process onActivate plugin.", entityId);
this.state = ComponentLifecycleState.ACTIVE;
if (pluginImpl != null) {
pluginImpl.resume(trustedCtx);
logger.info("[{}] Plugin resumed.", entityId);
} else {
start();
}
}
@Override
public void onSuspend(ActorContext context) {
logger.info("[{}] Going to process onSuspend plugin.", entityId);
this.state = ComponentLifecycleState.SUSPENDED;
if (pluginImpl != null) {
pluginImpl.suspend(trustedCtx);
}
}
}

53
application/src/main/java/org/thingsboard/server/actors/plugin/PluginCallbackMessage.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016 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.actors.plugin;
import lombok.Data;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
import java.util.Optional;
/**
* @author Andrew Shvayka
*/
@ToString
public final class PluginCallbackMessage<V> {
@Getter
private final PluginCallback<V> callback;
@Getter
private final boolean success;
@Getter
private final V v;
@Getter
private final Exception e;
public static <V> PluginCallbackMessage<V> onSuccess(PluginCallback<V> callback, V data) {
return new PluginCallbackMessage<V>(true, callback, data, null);
}
public static <V> PluginCallbackMessage<V> onError(PluginCallback<V> callback, Exception e) {
return new PluginCallbackMessage<V>(false, callback, null, e);
}
private PluginCallbackMessage(boolean success, PluginCallback<V> callback, V v, Exception e) {
this.success = success;
this.callback = callback;
this.v = v;
this.e = e;
}
}

306
application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java

@ -0,0 +1,306 @@
/**
* Copyright © 2016 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.actors.plugin;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Row;
import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
import akka.actor.ActorRef;
import javax.annotation.Nullable;
@Slf4j
public final class PluginProcessingContext implements PluginContext {
private static final Executor executor = Executors.newSingleThreadExecutor();
private final SharedPluginProcessingContext pluginCtx;
private final Optional<PluginApiCallSecurityContext> securityCtx;
public PluginProcessingContext(SharedPluginProcessingContext pluginCtx, PluginApiCallSecurityContext securityCtx) {
super();
this.pluginCtx = pluginCtx;
this.securityCtx = Optional.ofNullable(securityCtx);
}
@Override
public void sendPluginRpcMsg(RpcMsg msg) {
this.pluginCtx.rpcService.tell(new PluginRpcMsg(pluginCtx.tenantId, pluginCtx.pluginId, msg));
}
@Override
public void send(PluginWebsocketMsg<?> wsMsg) throws IOException {
pluginCtx.msgEndpoint.send(wsMsg);
}
@Override
public void close(PluginWebsocketSessionRef sessionRef) throws IOException {
pluginCtx.msgEndpoint.close(sessionRef);
}
@Override
public void saveAttributes(DeviceId deviceId, String scope, List<AttributeKvEntry> attributes, PluginCallback<Void> callback) {
validate(deviceId);
Set<AttributeKey> keys = new HashSet<>();
for (AttributeKvEntry attribute : attributes) {
keys.add(new AttributeKey(scope, attribute.getKey()));
}
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(deviceId, scope, attributes);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> {
onDeviceAttributesChanged(deviceId, keys);
return null;
}), executor);
}
@Override
public Optional<AttributeKvEntry> loadAttribute(DeviceId deviceId, String attributeType, String attributeKey) {
validate(deviceId);
AttributeKvEntry attribute = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey);
return Optional.ofNullable(attribute);
}
@Override
public List<AttributeKvEntry> loadAttributes(DeviceId deviceId, String attributeType, List<String> attributeKeys) {
validate(deviceId);
List<AttributeKvEntry> result = new ArrayList<>(attributeKeys.size());
for (String attributeKey : attributeKeys) {
AttributeKvEntry attribute = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey);
if (attribute != null) {
result.add(attribute);
}
}
return result;
}
@Override
public List<AttributeKvEntry> loadAttributes(DeviceId deviceId, String attributeType) {
validate(deviceId);
return pluginCtx.attributesService.findAll(deviceId, attributeType);
}
@Override
public void removeAttributes(DeviceId deviceId, String scope, List<String> keys) {
validate(deviceId);
pluginCtx.attributesService.removeAll(deviceId, scope, keys);
onDeviceAttributesDeleted(deviceId, keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet()));
}
@Override
public void saveTsData(DeviceId deviceId, TsKvEntry entry, PluginCallback<Void> callback) {
validate(deviceId);
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entry);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor);
}
@Override
public void saveTsData(DeviceId deviceId, List<TsKvEntry> entries, PluginCallback<Void> callback) {
validate(deviceId);
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entries);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor);
}
@Override
public List<TsKvEntry> loadTimeseries(DeviceId deviceId, TsKvQuery query) {
validate(deviceId);
return pluginCtx.tsService.find(DataConstants.DEVICE, deviceId, query);
}
@Override
public void loadLatestTimeseries(DeviceId deviceId, PluginCallback<List<TsKvEntry>> callback) {
validate(deviceId);
ResultSetFuture future = pluginCtx.tsService.findAllLatest(DataConstants.DEVICE, deviceId);
Futures.addCallback(future, getCallback(callback, pluginCtx.tsService::convertResultSetToTsKvEntryList), executor);
}
@Override
public void loadLatestTimeseries(DeviceId deviceId, Collection<String> keys, PluginCallback<List<TsKvEntry>> callback) {
validate(deviceId);
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.findLatest(DataConstants.DEVICE, deviceId, keys);
Futures.addCallback(rsListFuture, getListCallback(callback, rsList ->
{
List<TsKvEntry> result = new ArrayList<>();
for (ResultSet rs : rsList) {
Row row = rs.one();
if (row != null) {
result.add(pluginCtx.tsService.convertResultToTsKvEntry(row));
}
}
return result;
}), executor);
}
@Override
public void reply(PluginToRuleMsg<?> msg) {
pluginCtx.parentActor.tell(msg, ActorRef.noSender());
}
@Override
public boolean checkAccess(DeviceId deviceId) {
try {
return validate(deviceId);
} catch (IllegalStateException | IllegalArgumentException e) {
return false;
}
}
@Override
public PluginId getPluginId() {
return pluginCtx.pluginId;
}
@Override
public Optional<PluginApiCallSecurityContext> getSecurityCtx() {
return securityCtx;
}
private void onDeviceAttributesChanged(DeviceId deviceId, AttributeKey key) {
onDeviceAttributesChanged(deviceId, Collections.singleton(key));
}
private void onDeviceAttributesDeleted(DeviceId deviceId, Set<AttributeKey> keys) {
Device device = pluginCtx.deviceService.findDeviceById(deviceId);
pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), deviceId, keys));
}
private void onDeviceAttributesChanged(DeviceId deviceId, Set<AttributeKey> keys) {
Device device = pluginCtx.deviceService.findDeviceById(deviceId);
pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onUpdate(device.getTenantId(), deviceId, keys));
}
private <T> FutureCallback<List<ResultSet>> getListCallback(final PluginCallback<T> callback, Function<List<ResultSet>, T> transformer) {
return new FutureCallback<List<ResultSet>>() {
@Override
public void onSuccess(@Nullable List<ResultSet> result) {
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender());
}
@Override
public void onFailure(Throwable t) {
if (t instanceof Exception) {
pluginCtx.self().tell(PluginCallbackMessage.onError(callback, (Exception) t), ActorRef.noSender());
} else {
log.error("Critical error: {}", t.getMessage(), t);
}
}
};
}
private <T> FutureCallback<ResultSet> getCallback(final PluginCallback<T> callback, Function<ResultSet, T> transformer) {
return new FutureCallback<ResultSet>() {
@Override
public void onSuccess(@Nullable ResultSet result) {
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender());
}
@Override
public void onFailure(Throwable t) {
if (t instanceof Exception) {
pluginCtx.self().tell(PluginCallbackMessage.onError(callback, (Exception) t), ActorRef.noSender());
} else {
log.error("Critical error: {}", t.getMessage(), t);
}
}
};
}
// TODO: replace with our own exceptions
private boolean validate(DeviceId deviceId) {
if (securityCtx.isPresent()) {
PluginApiCallSecurityContext ctx = securityCtx.get();
if (ctx.isTenantAdmin() || ctx.isCustomerUser()) {
Device device = pluginCtx.deviceService.findDeviceById(deviceId);
if (device == null) {
throw new IllegalStateException("Device not found!");
} else {
if (!device.getTenantId().equals(ctx.getTenantId())) {
throw new IllegalArgumentException("Device belongs to different tenant!");
} else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) {
throw new IllegalArgumentException("Device belongs to different customer!");
}
}
} else {
return false;
}
}
return true;
}
@Override
public Optional<ServerAddress> resolve(DeviceId deviceId) {
return pluginCtx.routingService.resolve(deviceId);
}
@Override
public void getDevice(DeviceId deviceId, PluginCallback<Device> callback) {
//TODO: add caching here with async api.
Device device = pluginCtx.deviceService.findDeviceById(deviceId);
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, device), ActorRef.noSender());
}
@Override
public void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback<List<Device>> callback) {
//TODO: add caching here with async api.
List<Device> devices = pluginCtx.deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, new TextPageLink(limit)).getData();
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, devices), ActorRef.noSender());
}
@Override
public void sendRpcRequest(ToDeviceRpcRequest msg) {
pluginCtx.sendRpcRequest(msg);
}
@Override
public void scheduleTimeoutMsg(TimeoutMsg msg) {
pluginCtx.scheduleTimeoutMsg(msg);
}
}

30
application/src/main/java/org/thingsboard/server/actors/plugin/PluginTerminationMsg.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016 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.actors.plugin;
import org.thingsboard.server.actors.shared.ActorTerminationMsg;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.SessionId;
/**
* @author Andrew Shvayka
*/
public class PluginTerminationMsg extends ActorTerminationMsg<PluginId> {
public PluginTerminationMsg(PluginId id) {
super(id);
}
}

66
application/src/main/java/org/thingsboard/server/actors/plugin/RuleToPluginMsgWrapper.java

@ -0,0 +1,66 @@
/**
* Copyright © 2016 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.actors.plugin;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.aware.RuleAwareMsg;
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
public class RuleToPluginMsgWrapper implements ToPluginActorMsg, RuleAwareMsg {
private final TenantId pluginTenantId;
private final PluginId pluginId;
private final TenantId ruleTenantId;
private final RuleId ruleId;
private final RuleToPluginMsg<?> msg;
public RuleToPluginMsgWrapper(TenantId pluginTenantId, PluginId pluginId, TenantId ruleTenantId, RuleId ruleId, RuleToPluginMsg<?> msg) {
super();
this.pluginTenantId = pluginTenantId;
this.pluginId = pluginId;
this.ruleTenantId = ruleTenantId;
this.ruleId = ruleId;
this.msg = msg;
}
@Override
public TenantId getPluginTenantId() {
return pluginTenantId;
}
@Override
public PluginId getPluginId() {
return pluginId;
}
public TenantId getRuleTenantId() {
return ruleTenantId;
}
@Override
public RuleId getRuleId() {
return ruleId;
}
public RuleToPluginMsg<?> getMsg() {
return msg;
}
}

111
application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java

@ -0,0 +1,111 @@
/**
* Copyright © 2016 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.actors.plugin;
import akka.actor.ActorRef;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import scala.concurrent.duration.Duration;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
@Slf4j
public final class SharedPluginProcessingContext {
final ActorRef parentActor;
final ActorRef currentActor;
final ActorSystemContext systemContext;
final PluginWebSocketMsgEndpoint msgEndpoint;
final DeviceService deviceService;
final TimeseriesService tsService;
final AttributesService attributesService;
final ClusterRpcService rpcService;
final ClusterRoutingService routingService;
final PluginId pluginId;
final TenantId tenantId;
public SharedPluginProcessingContext(ActorSystemContext sysContext, TenantId tenantId, PluginId pluginId,
ActorRef parentActor, ActorRef self) {
super();
this.tenantId = tenantId;
this.pluginId = pluginId;
this.parentActor = parentActor;
this.currentActor = self;
this.systemContext = sysContext;
this.msgEndpoint = sysContext.getWsMsgEndpoint();
this.tsService = sysContext.getTsService();
this.attributesService = sysContext.getAttributesService();
this.deviceService = sysContext.getDeviceService();
this.rpcService = sysContext.getRpcService();
this.routingService = sysContext.getRoutingService();
}
public PluginId getPluginId() {
return pluginId;
}
public void toDeviceActor(DeviceAttributesEventNotificationMsg msg) {
forward(msg.getDeviceId(), msg, rpcService::tell);
}
public void sendRpcRequest(ToDeviceRpcRequest msg) {
log.trace("[{}] Forwarding msg {} to device actor!", pluginId, msg);
ToDeviceRpcRequestPluginMsg rpcMsg = new ToDeviceRpcRequestPluginMsg(pluginId, tenantId, msg);
forward(msg.getDeviceId(), rpcMsg, rpcService::tell);
}
private <T> void forward(DeviceId deviceId, T msg, BiConsumer<ServerAddress, T> rpcFunction) {
Optional<ServerAddress> instance = routingService.resolve(deviceId);
if (instance.isPresent()) {
log.trace("[{}] Forwarding msg {} to remote device actor!", pluginId, msg);
rpcFunction.accept(instance.get(), msg);
} else {
log.trace("[{}] Forwarding msg {} to local device actor!", pluginId, msg);
parentActor.tell(msg, ActorRef.noSender());
}
}
public void scheduleTimeoutMsg(TimeoutMsg msg) {
log.debug("Scheduling msg {} with delay {} ms", msg, msg.getTimeout());
systemContext.getScheduler().scheduleOnce(
Duration.create(msg.getTimeout(), TimeUnit.MILLISECONDS),
currentActor,
msg,
systemContext.getActorSystem().dispatcher(),
ActorRef.noSender());
}
public ActorRef self() {
return currentActor;
}
}

27
application/src/main/java/org/thingsboard/server/actors/plugin/TimeoutScheduler.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016 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.actors.plugin;
import akka.actor.ActorRef;
/**
* @author Andrew Shvayka
*/
public interface TimeoutScheduler {
void scheduleMsgWithDelay(Object msg, long delayInMs);
}

170
application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java

@ -0,0 +1,170 @@
/**
* Copyright © 2016 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.actors.rpc;
import akka.actor.ActorRef;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.SerializationUtils;
import org.springframework.util.StringUtils;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.*;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.rpc.GrpcSession;
import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener;
import java.io.Serializable;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@Slf4j
public class BasicRpcSessionListener implements GrpcSessionListener {
private final ActorSystemContext context;
private final ActorService service;
private final ActorRef manager;
private final ActorRef self;
public BasicRpcSessionListener(ActorSystemContext context, ActorRef manager, ActorRef self) {
this.context = context;
this.service = context.getActorService();
this.manager = manager;
this.self = self;
}
@Override
public void onConnected(GrpcSession session) {
log.info("{} session started -> {}", getType(session), session.getRemoteServer());
if (!session.isClient()) {
manager.tell(new RpcSessionConnectedMsg(session.getRemoteServer(), session.getSessionId()), self);
}
}
@Override
public void onDisconnected(GrpcSession session) {
log.info("{} session closed -> {}", getType(session), session.getRemoteServer());
manager.tell(new RpcSessionDisconnectedMsg(session.isClient(), session.getRemoteServer()), self);
}
@Override
public void onToPluginRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcMessage msg) {
if (log.isTraceEnabled()) {
log.trace("{} session [{}] received plugin msg {}", getType(session), session.getRemoteServer(), msg);
}
service.onMsg(convert(session.getRemoteServer(), msg));
}
@Override
public void onToDeviceActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorRpcMessage msg) {
log.trace("{} session [{}] received device actor msg {}", getType(session), session.getRemoteServer(), msg);
service.onMsg((ToDeviceActorMsg) deserialize(msg.getData().toByteArray()));
}
@Override
public void onToDeviceActorNotificationRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorNotificationRpcMessage msg) {
log.trace("{} session [{}] received device actor notification msg {}", getType(session), session.getRemoteServer(), msg);
service.onMsg((ToDeviceActorNotificationMsg) deserialize(msg.getData().toByteArray()));
}
@Override
public void onToDeviceSessionActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceSessionActorRpcMessage msg) {
log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg);
service.onMsg((ToDeviceSessionActorMsg) deserialize(msg.getData().toByteArray()));
}
@Override
public void onToDeviceRpcRequestRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg);
service.onMsg(deserialize(session.getRemoteServer(), msg));
}
@Override
public void onFromDeviceRpcResponseRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) {
log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg);
service.onMsg(deserialize(session.getRemoteServer(), msg));
}
@Override
public void onToAllNodesRpcMessage(GrpcSession session, ClusterAPIProtos.ToAllNodesRpcMessage msg) {
log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg);
service.onMsg((ToAllNodesMsg) deserialize(msg.getData().toByteArray()));
}
@Override
public void onError(GrpcSession session, Throwable t) {
log.warn("{} session got error -> {}", getType(session), session.getRemoteServer(), t);
manager.tell(new RpcSessionClosedMsg(session.isClient(), session.getRemoteServer()), self);
session.close();
}
private static String getType(GrpcSession session) {
return session.isClient() ? "Client" : "Server";
}
private static PluginRpcMsg convert(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcMessage msg) {
ClusterAPIProtos.PluginAddress address = msg.getAddress();
TenantId tenantId = new TenantId(toUUID(address.getTenantId()));
PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
RpcMsg rpcMsg = new RpcMsg(serverAddress, msg.getClazz(), msg.getData().toByteArray());
return new PluginRpcMsg(tenantId, pluginId, rpcMsg);
}
private static UUID toUUID(ClusterAPIProtos.Uid uid) {
return new UUID(uid.getPluginUuidMsb(), uid.getPluginUuidLsb());
}
private static ToDeviceRpcRequestPluginMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
ClusterAPIProtos.PluginAddress address = msg.getAddress();
TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId()));
PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
TenantId deviceTenantId = new TenantId(toUUID(msg.getDeviceTenantId()));
DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId()));
ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams());
ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody);
return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request);
}
private static ToPluginRpcResponseDeviceMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) {
ClusterAPIProtos.PluginAddress address = msg.getAddress();
TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId()));
PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
RpcError error = !StringUtils.isEmpty(msg.getError()) ? RpcError.valueOf(msg.getError()) : null;
FromDeviceRpcResponse response = new FromDeviceRpcResponse(toUUID(msg.getMsgId()), msg.getResponse(), error);
return new ToPluginRpcResponseDeviceMsg(pluginId, pluginTenantId, response);
}
@SuppressWarnings("unchecked")
private static <T extends Serializable> T deserialize(byte[] data) {
return (T) SerializationUtils.deserialize(data);
}
}

27
application/src/main/java/org/thingsboard/server/actors/rpc/RpcBroadcastMsg.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016 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.actors.rpc;
import lombok.Data;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
/**
* @author Andrew Shvayka
*/
@Data
public final class RpcBroadcastMsg {
private final ClusterAPIProtos.ToRpcServerMessage msg;
}

192
application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java

@ -0,0 +1,192 @@
/**
* Copyright © 2016 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.actors.rpc;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import java.util.*;
/**
* @author Andrew Shvayka
*/
public class RpcManagerActor extends ContextAwareActor {
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
private final Map<ServerAddress, SessionActorInfo> sessionActors;
private final Map<ServerAddress, Queue<ClusterAPIProtos.ToRpcServerMessage>> pendingMsgs;
private final ServerAddress instance;
public RpcManagerActor(ActorSystemContext systemContext) {
super(systemContext);
this.sessionActors = new HashMap<>();
this.pendingMsgs = new HashMap<>();
this.instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress();
systemContext.getDiscoveryService().getOtherServers().stream()
.filter(otherServer -> otherServer.getServerAddress().compareTo(instance) > 0)
.forEach(otherServer -> onCreateSessionRequest(
new RpcSessionCreateRequestMsg(UUID.randomUUID(), otherServer.getServerAddress(), null)));
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof RpcSessionTellMsg) {
onMsg((RpcSessionTellMsg) msg);
} else if (msg instanceof RpcBroadcastMsg) {
onMsg((RpcBroadcastMsg) msg);
} else if (msg instanceof RpcSessionCreateRequestMsg) {
onCreateSessionRequest((RpcSessionCreateRequestMsg) msg);
} else if (msg instanceof RpcSessionConnectedMsg) {
onSessionConnected((RpcSessionConnectedMsg) msg);
} else if (msg instanceof RpcSessionDisconnectedMsg) {
onSessionDisconnected((RpcSessionDisconnectedMsg) msg);
} else if (msg instanceof RpcSessionClosedMsg) {
onSessionClosed((RpcSessionClosedMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
onClusterEvent((ClusterEventMsg) msg);
}
}
private void onMsg(RpcBroadcastMsg msg) {
log.debug("Forwarding msg to session actors {}", msg);
sessionActors.keySet().forEach(address -> onMsg(new RpcSessionTellMsg(address, msg.getMsg())));
pendingMsgs.values().forEach(queue -> queue.add(msg.getMsg()));
}
private void onMsg(RpcSessionTellMsg msg) {
ServerAddress address = msg.getServerAddress();
SessionActorInfo session = sessionActors.get(address);
if (session != null) {
log.debug("{} Forwarding msg to session actor", address);
session.actor.tell(msg, ActorRef.noSender());
} else {
log.debug("{} Storing msg to pending queue", address);
Queue<ClusterAPIProtos.ToRpcServerMessage> queue = pendingMsgs.get(address);
if (queue == null) {
queue = new LinkedList<>();
pendingMsgs.put(address, queue);
}
queue.add(msg.getMsg());
}
}
@Override
public void postStop() {
sessionActors.clear();
pendingMsgs.clear();
}
private void onClusterEvent(ClusterEventMsg msg) {
ServerAddress server = msg.getServerAddress();
if (server.compareTo(instance) > 0) {
if (msg.isAdded()) {
onCreateSessionRequest(new RpcSessionCreateRequestMsg(UUID.randomUUID(), server, null));
} else {
onSessionClose(false, server);
}
}
}
private void onSessionConnected(RpcSessionConnectedMsg msg) {
register(msg.getRemoteAddress(), msg.getId(), context().sender());
}
private void onSessionDisconnected(RpcSessionDisconnectedMsg msg) {
boolean reconnect = msg.isClient() && isRegistered(msg.getRemoteAddress());
onSessionClose(reconnect, msg.getRemoteAddress());
}
private void onSessionClosed(RpcSessionClosedMsg msg) {
boolean reconnect = msg.isClient() && isRegistered(msg.getRemoteAddress());
onSessionClose(reconnect, msg.getRemoteAddress());
}
private boolean isRegistered(ServerAddress address) {
for (ServerInstance server : systemContext.getDiscoveryService().getOtherServers()) {
if (server.getServerAddress().equals(address)) {
return true;
}
}
return false;
}
private void onSessionClose(boolean reconnect, ServerAddress remoteAddress) {
log.debug("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect);
SessionActorInfo sessionRef = sessionActors.get(remoteAddress);
if (context().sender().equals(sessionRef.actor)) {
sessionActors.remove(remoteAddress);
pendingMsgs.remove(remoteAddress);
if (reconnect) {
onCreateSessionRequest(new RpcSessionCreateRequestMsg(sessionRef.sessionId, remoteAddress, null));
}
}
}
private void onCreateSessionRequest(RpcSessionCreateRequestMsg msg) {
ActorRef actorRef = createSessionActor(msg);
if (msg.getRemoteAddress() != null) {
register(msg.getRemoteAddress(), msg.getMsgUid(), actorRef);
}
}
private void register(ServerAddress remoteAddress, UUID uuid, ActorRef sender) {
sessionActors.put(remoteAddress, new SessionActorInfo(uuid, sender));
log.debug("[{}][{}] Registering session actor.", remoteAddress, uuid);
Queue<ClusterAPIProtos.ToRpcServerMessage> data = pendingMsgs.remove(remoteAddress);
if (data != null) {
log.debug("[{}][{}] Forwarding {} pending messages.", remoteAddress, uuid, data.size());
data.forEach(msg -> sender.tell(new RpcSessionTellMsg(remoteAddress, msg), ActorRef.noSender()));
} else {
log.debug("[{}][{}] No pending messages to forward.", remoteAddress, uuid);
}
}
private ActorRef createSessionActor(RpcSessionCreateRequestMsg msg) {
log.debug("[{}] Creating session actor.", msg.getMsgUid());
ActorRef actor = context().actorOf(
Props.create(new RpcSessionActor.ActorCreator(systemContext, msg.getMsgUid())).withDispatcher(DefaultActorService.RPC_DISPATCHER_NAME));
actor.tell(msg, context().self());
return actor;
}
public static class ActorCreator extends ContextBasedCreator<RpcManagerActor> {
private static final long serialVersionUID = 1L;
public ActorCreator(ActorSystemContext context) {
super(context);
}
@Override
public RpcManagerActor create() throws Exception {
return new RpcManagerActor(context);
}
}
}

118
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java

@ -0,0 +1,118 @@
/**
* Copyright © 2016 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.actors.rpc;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import io.grpc.Channel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc;
import org.thingsboard.server.service.cluster.rpc.GrpcSession;
import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
public class RpcSessionActor extends ContextAwareActor {
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
private final UUID sessionId;
private GrpcSession session;
private GrpcSessionListener listener;
public RpcSessionActor(ActorSystemContext systemContext, UUID sessionId) {
super(systemContext);
this.sessionId = sessionId;
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof RpcSessionTellMsg) {
tell((RpcSessionTellMsg) msg);
} else if (msg instanceof RpcSessionCreateRequestMsg) {
initSession((RpcSessionCreateRequestMsg) msg);
}
}
private void tell(RpcSessionTellMsg msg) {
session.sendMsg(msg.getMsg());
}
@Override
public void postStop() {
log.info("Closing session -> {}", session.getRemoteServer());
session.close();
}
private void initSession(RpcSessionCreateRequestMsg msg) {
log.info("[{}] Initializing session", context().self());
ServerAddress remoteServer = msg.getRemoteAddress();
listener = new BasicRpcSessionListener(systemContext, context().parent(), context().self());
if (msg.getRemoteAddress() == null) {
// Server session
session = new GrpcSession(listener);
session.setOutputStream(msg.getResponseObserver());
session.initInputStream();
session.initOutputStream();
systemContext.getRpcService().onSessionCreated(msg.getMsgUid(), session.getInputStream());
} else {
// Client session
Channel channel = ManagedChannelBuilder.forAddress(remoteServer.getHost(), remoteServer.getPort()).usePlaintext(true).build();
session = new GrpcSession(remoteServer, listener);
session.initInputStream();
ClusterRpcServiceGrpc.ClusterRpcServiceStub stub = ClusterRpcServiceGrpc.newStub(channel);
StreamObserver<ClusterAPIProtos.ToRpcServerMessage> outputStream = stub.handlePluginMsgs(session.getInputStream());
session.setOutputStream(outputStream);
session.initOutputStream();
outputStream.onNext(toConnectMsg());
}
}
public static class ActorCreator extends ContextBasedCreator<RpcSessionActor> {
private static final long serialVersionUID = 1L;
private final UUID sessionId;
public ActorCreator(ActorSystemContext context, UUID sessionId) {
super(context);
this.sessionId = sessionId;
}
@Override
public RpcSessionActor create() throws Exception {
return new RpcSessionActor(context, sessionId);
}
}
private ClusterAPIProtos.ToRpcServerMessage toConnectMsg() {
ServerAddress instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress();
return ClusterAPIProtos.ToRpcServerMessage.newBuilder().setConnectMsg(
ClusterAPIProtos.ConnectRpcMessage.newBuilder().setServerAddress(
ClusterAPIProtos.ServerAddress.newBuilder().setHost(instance.getHost()).setPort(instance.getPort()).build()).build()).build();
}
}

29
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionClosedMsg.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016 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.actors.rpc;
import lombok.Data;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
/**
* @author Andrew Shvayka
*/
@Data
public final class RpcSessionClosedMsg {
private final boolean client;
private final ServerAddress remoteAddress;
}

31
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionConnectedMsg.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016 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.actors.rpc;
import lombok.Data;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@Data
public final class RpcSessionConnectedMsg {
private final ServerAddress remoteAddress;
private final UUID id;
}

35
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionCreateRequestMsg.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016 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.actors.rpc;
import io.grpc.stub.StreamObserver;
import lombok.Data;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@Data
public final class RpcSessionCreateRequestMsg {
private final UUID msgUid;
private final ServerAddress remoteAddress;
private final StreamObserver<ClusterAPIProtos.ToRpcServerMessage> responseObserver;
}

29
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionDisconnectedMsg.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016 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.actors.rpc;
import lombok.Data;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
/**
* @author Andrew Shvayka
*/
@Data
public final class RpcSessionDisconnectedMsg {
private final boolean client;
private final ServerAddress remoteAddress;
}

29
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionTellMsg.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016 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.actors.rpc;
import lombok.Data;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
/**
* @author Andrew Shvayka
*/
@Data
public final class RpcSessionTellMsg {
private final ServerAddress serverAddress;
private final ClusterAPIProtos.ToRpcServerMessage msg;
}

30
application/src/main/java/org/thingsboard/server/actors/rpc/SessionActorInfo.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016 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.actors.rpc;
import akka.actor.ActorRef;
import lombok.Data;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@Data
public final class SessionActorInfo {
protected final UUID sessionId;
protected final ActorRef actor;
}

104
application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingContext.java

@ -0,0 +1,104 @@
/**
* Copyright © 2016 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.actors.rule;
import akka.actor.ActorRef;
import org.thingsboard.server.common.msg.core.RuleEngineError;
import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.extensions.api.device.DeviceAttributes;
public class ChainProcessingContext {
private final ChainProcessingMetaData md;
private final int index;
private final RuleEngineError error;
private ToDeviceMsg response;
public ChainProcessingContext(ChainProcessingMetaData md) {
super();
this.md = md;
this.index = 0;
this.error = RuleEngineError.NO_RULES;
}
private ChainProcessingContext(ChainProcessingContext other, int indexOffset, RuleEngineError error) {
super();
this.md = other.md;
this.index = other.index + indexOffset;
this.error = error;
this.response = other.response;
if (this.index < 0 || this.index >= this.md.chain.size()) {
throw new IllegalArgumentException("Can't apply offset " + indexOffset + " to the chain!");
}
}
public ActorRef getDeviceActor() {
return md.originator;
}
public ActorRef getCurrentActor() {
return md.chain.getRuleActorMd(index).getActorRef();
}
public boolean hasNext() {
return (getChainLength() - 1) > index;
}
public boolean isFailure() {
return (error != null && error.isCritical()) || (response != null && !response.isSuccess());
}
public ChainProcessingContext getNext() {
return new ChainProcessingContext(this, 1, this.error);
}
public ChainProcessingContext withError(RuleEngineError error) {
if (error != null && (this.error == null || this.error.getPriority() < error.getPriority())) {
return new ChainProcessingContext(this, 0, error);
} else {
return this;
}
}
public int getChainLength() {
return md.chain.size();
}
public ToDeviceActorMsg getInMsg() {
return md.inMsg;
}
public DeviceAttributes getAttributes() {
return md.deviceAttributes;
}
public ToDeviceMsg getResponse() {
return response;
}
public void mergeResponse(ToDeviceMsg response) {
// TODO add merge logic
this.response = response;
}
public RuleEngineErrorMsg getError() {
return new RuleEngineErrorMsg(md.inMsg.getPayload().getMsgType(), error);
}
}

42
application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016 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.actors.rule;
import org.thingsboard.server.extensions.api.device.DeviceAttributes;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import akka.actor.ActorRef;
/**
* Immutable part of chain processing data;
*
* @author ashvayka
*/
public final class ChainProcessingMetaData {
final RuleActorChain chain;
final ToDeviceActorMsg inMsg;
final ActorRef originator;
final DeviceAttributes deviceAttributes;
public ChainProcessingMetaData(RuleActorChain chain, ToDeviceActorMsg inMsg, DeviceAttributes deviceAttributes, ActorRef originator) {
super();
this.chain = chain;
this.inMsg = inMsg;
this.originator = originator;
this.deviceAttributes = deviceAttributes;
}
}

43
application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016 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.actors.rule;
public class ComplexRuleActorChain implements RuleActorChain {
private final RuleActorChain systemChain;
private final RuleActorChain tenantChain;
public ComplexRuleActorChain(RuleActorChain systemChain, RuleActorChain tenantChain) {
super();
this.systemChain = systemChain;
this.tenantChain = tenantChain;
}
@Override
public int size() {
return systemChain.size() + tenantChain.size();
}
@Override
public RuleActorMetaData getRuleActorMd(int index) {
if (index < systemChain.size()) {
return systemChain.getRuleActorMd(index);
} else {
return tenantChain.getRuleActorMd(index - systemChain.size());
}
}
}

20
application/src/main/java/org/thingsboard/server/actors/rule/CompoundRuleActorChain.java

@ -0,0 +1,20 @@
/**
* Copyright © 2016 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.actors.rule;
public class CompoundRuleActorChain {
}

90
application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java

@ -0,0 +1,90 @@
/**
* Copyright © 2016 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.actors.rule;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ComponentActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.stats.StatsPersistTick;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
public class RuleActor extends ComponentActor<RuleId, RuleActorMessageProcessor> {
private RuleActor(ActorSystemContext systemContext, TenantId tenantId, RuleId ruleId) {
super(systemContext, tenantId, ruleId);
setProcessor(new RuleActorMessageProcessor(tenantId, ruleId, systemContext, logger));
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("[{}] Received message: {}", id, msg);
if (msg instanceof RuleProcessingMsg) {
try {
processor.onRuleProcessingMsg(context(), (RuleProcessingMsg) msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onDeviceMsg", e);
}
} else if (msg instanceof PluginToRuleMsg<?>) {
try {
processor.onPluginMsg(context(), (PluginToRuleMsg<?>) msg);
} catch (Exception e) {
logAndPersist("onPluginMsg", e);
}
} else if (msg instanceof ComponentLifecycleMsg) {
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
onClusterEventMsg((ClusterEventMsg) msg);
} else if (msg instanceof RuleToPluginTimeoutMsg) {
try {
processor.onTimeoutMsg(context(), (RuleToPluginTimeoutMsg) msg);
} catch (Exception e) {
logAndPersist("onTimeoutMsg", e);
}
} else if (msg instanceof StatsPersistTick) {
onStatsPersistTick(id);
} else {
logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName());
}
}
public static class ActorCreator extends ContextBasedCreator<RuleActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
private final RuleId ruleId;
public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleId ruleId) {
super(context);
this.tenantId = tenantId;
this.ruleId = ruleId;
}
@Override
public RuleActor create() throws Exception {
return new RuleActor(context, tenantId, ruleId);
}
}
@Override
protected long getErrorPersistFrequency() {
return systemContext.getRuleErrorPersistFrequency();
}
}

24
application/src/main/java/org/thingsboard/server/actors/rule/RuleActorChain.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016 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.actors.rule;
public interface RuleActorChain {
int size();
RuleActorMetaData getRuleActorMd(int index);
}

339
application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java

@ -0,0 +1,339 @@
/**
* Copyright © 2016 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.actors.rule;
import java.util.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.core.BasicRequest;
import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
import org.thingsboard.server.common.msg.core.RuleEngineError;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.MsgType;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.common.msg.session.ex.ProcessingTimeoutException;
import org.thingsboard.server.extensions.api.rules.*;
import org.thingsboard.server.extensions.api.plugins.PluginAction;
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
import com.fasterxml.jackson.databind.JsonNode;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
private final RuleProcessingContext ruleCtx;
private final Map<UUID, RuleProcessingMsg> pendingMsgMap;
private RuleMetaData ruleMd;
private ComponentLifecycleState state;
private List<RuleFilter> filters;
private RuleProcessor processor;
private PluginAction action;
private TenantId pluginTenantId;
private PluginId pluginId;
protected RuleActorMessageProcessor(TenantId tenantId, RuleId ruleId, ActorSystemContext systemContext, LoggingAdapter logger) {
super(systemContext, logger, tenantId, ruleId);
this.pendingMsgMap = new HashMap<>();
this.ruleCtx = new RuleProcessingContext(systemContext, ruleId);
}
@Override
public void start() throws Exception {
logger.info("[{}][{}] Starting rule actor.", entityId, tenantId);
ruleMd = systemContext.getRuleService().findRuleById(entityId);
if (ruleMd == null) {
throw new RuleInitializationException("Rule not found!");
}
state = ruleMd.getState();
if (state == ComponentLifecycleState.ACTIVE) {
logger.info("[{}] Rule is active. Going to initialize rule components.", entityId);
initComponent();
} else {
logger.info("[{}] Rule is suspended. Skipping rule components initialization.", entityId);
}
logger.info("[{}][{}] Started rule actor.", entityId, tenantId);
}
@Override
public void stop() throws Exception {
onStop();
}
private void initComponent() throws RuleException {
try {
if (!ruleMd.getFilters().isArray()) {
throw new RuntimeException("Filters are not array!");
}
fetchPluginInfo();
initFilters();
initProcessor();
initAction();
} catch (RuntimeException e) {
throw new RuleInitializationException("Unknown runtime exception!", e);
} catch (InstantiationException e) {
throw new RuleInitializationException("No default constructor for rule implementation!", e);
} catch (IllegalAccessException e) {
throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e);
} catch (ClassNotFoundException e) {
throw new RuleInitializationException("Rule Class not found!", e);
} catch (Exception e) {
throw new RuleException(e.getMessage(), e);
}
}
private void initAction() throws Exception {
JsonNode actionMd = ruleMd.getAction();
action = initComponent(actionMd);
}
private void initProcessor() throws Exception {
if (ruleMd.getProcessor() != null && !ruleMd.getProcessor().isNull()) {
processor = initComponent(ruleMd.getProcessor());
}
}
private void initFilters() throws Exception {
filters = new ArrayList<>(ruleMd.getFilters().size());
for (int i = 0; i < ruleMd.getFilters().size(); i++) {
filters.add(initComponent(ruleMd.getFilters().get(i)));
}
}
private void fetchPluginInfo() {
PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken());
pluginTenantId = pluginMd.getTenantId();
pluginId = pluginMd.getId();
}
protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException {
if (state != ComponentLifecycleState.ACTIVE) {
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_ACTIVE_RULES);
return;
}
ChainProcessingContext chainCtx = msg.getCtx();
ToDeviceActorMsg inMsg = chainCtx.getInMsg();
ruleCtx.update(inMsg, chainCtx.getAttributes());
logger.debug("[{}] Going to filter in msg: {}", entityId, inMsg);
for (RuleFilter filter : filters) {
if (!filter.filter(ruleCtx, inMsg)) {
logger.debug("[{}] In msg is NOT valid for processing by current rule: {}", entityId, inMsg);
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_FILTERS_MATCHED);
return;
}
}
RuleProcessingMetaData inMsgMd;
if (processor != null) {
logger.debug("[{}] Going to process in msg: {}", entityId, inMsg);
inMsgMd = processor.process(ruleCtx, inMsg);
} else {
inMsgMd = new RuleProcessingMetaData();
}
logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg);
Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd);
if (ruleToPluginMsgOptional.isPresent()) {
RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get();
logger.debug("[{}] Device msg is converter to: {}", entityId, ruleToPluginMsg);
context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self());
if (action.isOneWayAction()) {
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
} else {
pendingMsgMap.put(ruleToPluginMsg.getUid(), msg);
scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout());
}
} else {
logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId);
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_REQUEST_FROM_ACTIONS);
return;
}
}
public void onPluginMsg(ActorContext context, PluginToRuleMsg<?> msg) {
RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid());
if (pendingMsg != null) {
ChainProcessingContext ctx = pendingMsg.getCtx();
Optional<ToDeviceMsg> ruleResponseOptional = action.convert(msg);
if (ruleResponseOptional.isPresent()) {
ctx.mergeResponse(ruleResponseOptional.get());
pushToNextRule(context, ctx, null);
} else {
pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS);
}
} else {
logger.warning("[{}] Processing timeout detected: [{}]", entityId, msg.getUid());
}
}
public void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) {
RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId());
if (pendingMsg != null) {
logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg);
ChainProcessingContext ctx = pendingMsg.getCtx();
pushToNextRule(context, ctx, RuleEngineError.PLUGIN_TIMEOUT);
}
}
private void pushToNextRule(ActorContext context, ChainProcessingContext ctx, RuleEngineError error) {
if (error != null) {
ctx = ctx.withError(error);
}
if (ctx.isFailure()) {
logger.debug("[{}] Forwarding processing chain to device actor due to failure.", ctx.getInMsg().getDeviceId());
ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
} else if (!ctx.hasNext()) {
logger.debug("[{}] Forwarding processing chain to device actor due to end of chain.", ctx.getInMsg().getDeviceId());
ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
} else {
logger.debug("[{}] Forwarding processing chain to next rule actor.", ctx.getInMsg().getDeviceId());
ChainProcessingContext nextTask = ctx.getNext();
nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self());
}
}
@Override
public void onCreated(ActorContext context) {
logger.info("[{}] Going to process onCreated rule.", entityId);
}
@Override
public void onUpdate(ActorContext context) throws RuleException {
RuleMetaData oldRuleMd = ruleMd;
ruleMd = systemContext.getRuleService().findRuleById(entityId);
logger.info("[{}] Rule configuration was updated from {} to {}.", entityId, oldRuleMd, ruleMd);
try {
fetchPluginInfo();
if (!Objects.equals(oldRuleMd.getFilters(), ruleMd.getFilters())) {
logger.info("[{}] Rule filters require restart due to json change from {} to {}.",
entityId, mapper.writeValueAsString(oldRuleMd.getFilters()), mapper.writeValueAsString(ruleMd.getFilters()));
stopFilters();
initFilters();
}
if (!Objects.equals(oldRuleMd.getProcessor(), ruleMd.getProcessor())) {
logger.info("[{}] Rule processor require restart due to configuration change.", entityId);
stopProcessor();
initProcessor();
}
if (!Objects.equals(oldRuleMd.getAction(), ruleMd.getAction())) {
logger.info("[{}] Rule action require restart due to configuration change.", entityId);
stopAction();
initAction();
}
} catch (RuntimeException e) {
throw new RuleInitializationException("Unknown runtime exception!", e);
} catch (InstantiationException e) {
throw new RuleInitializationException("No default constructor for rule implementation!", e);
} catch (IllegalAccessException e) {
throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e);
} catch (ClassNotFoundException e) {
throw new RuleInitializationException("Rule Class not found!", e);
} catch (JsonProcessingException e) {
throw new RuleInitializationException("Rule configuration is invalid!", e);
} catch (Exception e) {
throw new RuleInitializationException(e.getMessage(), e);
}
}
@Override
public void onActivate(ActorContext context) throws Exception {
logger.info("[{}] Going to process onActivate rule.", entityId);
this.state = ComponentLifecycleState.ACTIVE;
if (action != null) {
if (filters != null) {
filters.forEach(f -> f.resume());
}
if (processor != null) {
processor.resume();
}
if (action != null) {
action.resume();
}
logger.info("[{}] Rule resumed.", entityId);
} else {
start();
}
}
@Override
public void onSuspend(ActorContext context) {
logger.info("[{}] Going to process onSuspend rule.", entityId);
this.state = ComponentLifecycleState.SUSPENDED;
if (filters != null) {
filters.forEach(f -> f.suspend());
}
if (processor != null) {
processor.suspend();
}
if (action != null) {
action.suspend();
}
}
@Override
public void onStop(ActorContext context) {
logger.info("[{}] Going to process onStop rule.", entityId);
onStop();
scheduleMsgWithDelay(context, new RuleTerminationMsg(entityId), systemContext.getRuleActorTerminationDelay());
}
private void onStop() {
this.state = ComponentLifecycleState.SUSPENDED;
stopFilters();
stopProcessor();
stopAction();
}
@Override
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
}
private void stopAction() {
if (action != null) {
action.stop();
}
}
private void stopProcessor() {
if (processor != null) {
processor.stop();
}
}
private void stopFilters() {
if (filters != null) {
filters.forEach(f -> f.stop());
}
}
}

107
application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java

@ -0,0 +1,107 @@
/**
* Copyright © 2016 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.actors.rule;
import java.util.Comparator;
import org.thingsboard.server.common.data.id.RuleId;
import akka.actor.ActorRef;
public class RuleActorMetaData {
private final RuleId ruleId;
private final boolean systemRule;
private final int weight;
private final ActorRef actorRef;
public static final Comparator<RuleActorMetaData> RULE_ACTOR_MD_COMPARATOR = new Comparator<RuleActorMetaData>() {
@Override
public int compare(RuleActorMetaData r1, RuleActorMetaData r2) {
if (r1.isSystemRule() && !r2.isSystemRule()) {
return 1;
} else if (!r1.isSystemRule() && r2.isSystemRule()) {
return -1;
} else {
return Integer.compare(r2.getWeight(), r1.getWeight());
}
}
};
public static RuleActorMetaData systemRule(RuleId ruleId, int weight, ActorRef actorRef) {
return new RuleActorMetaData(ruleId, true, weight, actorRef);
}
public static RuleActorMetaData tenantRule(RuleId ruleId, int weight, ActorRef actorRef) {
return new RuleActorMetaData(ruleId, false, weight, actorRef);
}
private RuleActorMetaData(RuleId ruleId, boolean systemRule, int weight, ActorRef actorRef) {
super();
this.ruleId = ruleId;
this.systemRule = systemRule;
this.weight = weight;
this.actorRef = actorRef;
}
public RuleId getRuleId() {
return ruleId;
}
public boolean isSystemRule() {
return systemRule;
}
public int getWeight() {
return weight;
}
public ActorRef getActorRef() {
return actorRef;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((ruleId == null) ? 0 : ruleId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RuleActorMetaData other = (RuleActorMetaData) obj;
if (ruleId == null) {
if (other.ruleId != null)
return false;
} else if (!ruleId.equals(other.ruleId))
return false;
return true;
}
@Override
public String toString() {
return "RuleActorMetaData [ruleId=" + ruleId + ", systemRule=" + systemRule + ", weight=" + weight + ", actorRef=" + actorRef + "]";
}
}

33
application/src/main/java/org/thingsboard/server/actors/rule/RuleContextAwareMsgProcessor.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016 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.actors.rule;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.common.data.id.RuleId;
import akka.event.LoggingAdapter;
public class RuleContextAwareMsgProcessor extends AbstractContextAwareMsgProcessor {
private final RuleId ruleId;
protected RuleContextAwareMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, RuleId ruleId) {
super(systemContext, logger);
this.ruleId = ruleId;
}
}

89
application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java

@ -0,0 +1,89 @@
/**
* Copyright © 2016 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.actors.rule;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.extensions.api.device.DeviceAttributes;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.rules.RuleContext;
import java.util.Optional;
public class RuleProcessingContext implements RuleContext {
private final TimeseriesService tsService;
private final EventService eventService;
private final RuleId ruleId;
private TenantId tenantId;
private CustomerId customerId;
private DeviceId deviceId;
private DeviceAttributes deviceAttributes;
RuleProcessingContext(ActorSystemContext systemContext, RuleId ruleId) {
this.tsService = systemContext.getTsService();
this.eventService = systemContext.getEventService();
this.ruleId = ruleId;
}
void update(ToDeviceActorMsg toDeviceActorMsg, DeviceAttributes attributes) {
this.tenantId = toDeviceActorMsg.getTenantId();
this.customerId = toDeviceActorMsg.getCustomerId();
this.deviceId = toDeviceActorMsg.getDeviceId();
this.deviceAttributes = attributes;
}
@Override
public RuleId getRuleId() {
return ruleId;
}
@Override
public DeviceAttributes getDeviceAttributes() {
return deviceAttributes;
}
@Override
public Event save(Event event) {
checkEvent(event);
return eventService.save(event);
}
@Override
public Optional<Event> saveIfNotExists(Event event) {
checkEvent(event);
return eventService.saveIfNotExists(event);
}
@Override
public Optional<Event> findEvent(String eventType, String eventUid) {
return eventService.findEvent(tenantId, deviceId, eventType, eventUid);
}
private void checkEvent(Event event) {
if (event.getTenantId() == null) {
event.setTenantId(tenantId);
} else if (!tenantId.equals(event.getTenantId())) {
throw new IllegalArgumentException("Invalid Tenant id!");
}
if (event.getEntityId() == null) {
event.setEntityId(deviceId);
}
}
}

31
application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingMsg.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016 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.actors.rule;
public class RuleProcessingMsg {
private final ChainProcessingContext ctx;
public RuleProcessingMsg(ChainProcessingContext ctx) {
super();
this.ctx = ctx;
}
public ChainProcessingContext getCtx() {
return ctx;
}
}

30
application/src/main/java/org/thingsboard/server/actors/rule/RuleTerminationMsg.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016 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.actors.rule;
import org.thingsboard.server.actors.shared.ActorTerminationMsg;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
/**
* @author Andrew Shvayka
*/
public class RuleTerminationMsg extends ActorTerminationMsg<RuleId> {
public RuleTerminationMsg(RuleId id) {
super(id);
}
}

36
application/src/main/java/org/thingsboard/server/actors/rule/RuleToPluginTimeoutMsg.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016 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.actors.rule;
import java.io.Serializable;
import java.util.UUID;
public class RuleToPluginTimeoutMsg implements Serializable {
private static final long serialVersionUID = 1L;
private final UUID msgId;
public RuleToPluginTimeoutMsg(UUID msgId) {
super();
this.msgId = msgId;
}
public UUID getMsgId() {
return msgId;
}
}

34
application/src/main/java/org/thingsboard/server/actors/rule/RulesProcessedMsg.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016 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.actors.rule;
public class RulesProcessedMsg {
private final ChainProcessingContext ctx;
public RulesProcessedMsg(ChainProcessingContext ctx) {
super();
this.ctx = ctx;
}
public ChainProcessingContext getCtx() {
return ctx;
}
@Override
public String toString() {
return "RulesProcessedMsg [ctx=" + ctx + "]";
}
}

40
application/src/main/java/org/thingsboard/server/actors/rule/SimpleRuleActorChain.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016 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.actors.rule;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class SimpleRuleActorChain implements RuleActorChain {
private final List<RuleActorMetaData> rules;
public SimpleRuleActorChain(Set<RuleActorMetaData> ruleSet) {
rules = new ArrayList<>(ruleSet);
Collections.sort(rules, RuleActorMetaData.RULE_ACTOR_MD_COMPARATOR);
}
public int size() {
return rules.size();
}
public RuleActorMetaData getRuleActorMd(int index) {
return rules.get(index);
}
}

31
application/src/main/java/org/thingsboard/server/actors/service/ActorService.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016 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.actors.service;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener;
import org.thingsboard.server.service.cluster.rpc.RpcMsgListener;
public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor, RestMsgProcessor, RpcMsgListener, DiscoveryServiceListener {
void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state);
void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state);
}

169
application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java

@ -0,0 +1,169 @@
/**
* Copyright © 2016 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.actors.service;
import akka.actor.ActorRef;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.actors.stats.StatsPersistMsg;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
/**
* @author Andrew Shvayka
*/
public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgProcessor<T>> extends ContextAwareActor {
protected final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
private long lastPersistedErrorTs = 0L;
protected final TenantId tenantId;
protected final T id;
protected P processor;
private long messagesProcessed;
private long errorsOccurred;
public ComponentActor(ActorSystemContext systemContext, TenantId tenantId, T id) {
super(systemContext);
this.tenantId = tenantId;
this.id = id;
}
protected void setProcessor(P processor) {
this.processor = processor;
}
@Override
public void preStart() {
try {
processor.start();
logLifecycleEvent(ComponentLifecycleEvent.STARTED);
if (systemContext.isStatisticsEnabled()) {
scheduleStatsPersistTick();
}
} catch (Exception e) {
logger.warning("[{}][{}] Failed to start {} processor: {}", tenantId, id, id.getEntityType(), e);
logAndPersist("OnStart", e, true);
logLifecycleEvent(ComponentLifecycleEvent.STARTED, e);
}
}
private void scheduleStatsPersistTick() {
try {
processor.scheduleStatsPersistTick(context(), systemContext.getStatisticsPersistFrequency());
} catch (Exception e) {
logger.error("[{}][{}] Failed to schedule statistics store message. No statistics is going to be stored: {}", tenantId, id, e.getMessage());
logAndPersist("onScheduleStatsPersistMsg", e);
}
}
@Override
public void postStop() {
try {
processor.stop();
logLifecycleEvent(ComponentLifecycleEvent.STOPPED);
} catch (Exception e) {
logger.warning("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage());
logAndPersist("OnStop", e, true);
logLifecycleEvent(ComponentLifecycleEvent.STOPPED, e);
}
}
protected void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
try {
switch (msg.getEvent()) {
case CREATED:
processor.onCreated(context());
break;
case UPDATED:
processor.onUpdate(context());
break;
case ACTIVATED:
processor.onActivate(context());
break;
case SUSPENDED:
processor.onSuspend(context());
break;
case DELETED:
processor.onStop(context());
}
logLifecycleEvent(msg.getEvent());
} catch (Exception e) {
logAndPersist("onLifecycleMsg", e, true);
logLifecycleEvent(msg.getEvent(), e);
}
}
protected void onClusterEventMsg(ClusterEventMsg msg) {
try {
processor.onClusterEventMsg(msg);
} catch (Exception e) {
logAndPersist("onClusterEventMsg", e);
}
}
protected void onStatsPersistTick(EntityId entityId) {
try {
systemContext.getStatsActor().tell(new StatsPersistMsg(messagesProcessed, errorsOccurred, tenantId, entityId), ActorRef.noSender());
resetStatsCounters();
} catch (Exception e) {
logAndPersist("onStatsPersistTick", e);
}
}
private void resetStatsCounters() {
messagesProcessed = 0;
errorsOccurred = 0;
}
protected void increaseMessagesProcessedCount() {
messagesProcessed++;
}
protected void logAndPersist(String method, Exception e) {
logAndPersist(method, e, false);
}
private void logAndPersist(String method, Exception e, boolean critical) {
errorsOccurred++;
if (critical) {
logger.warning("[{}][{}] Failed to process {} msg: {}", id, tenantId, method, e);
} else {
logger.debug("[{}][{}] Failed to process {} msg: {}", id, tenantId, method, e);
}
long ts = System.currentTimeMillis();
if (ts - lastPersistedErrorTs > getErrorPersistFrequency()) {
systemContext.persistError(tenantId, id, method, e);
lastPersistedErrorTs = ts;
}
}
protected void logLifecycleEvent(ComponentLifecycleEvent event) {
logLifecycleEvent(event, null);
}
protected void logLifecycleEvent(ComponentLifecycleEvent event, Exception e) {
systemContext.persistLifecycleEvent(tenantId, id, event, e);
}
protected abstract long getErrorPersistFrequency();
}

31
application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016 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.actors.service;
import akka.actor.UntypedActor;
import org.thingsboard.server.actors.ActorSystemContext;
public abstract class ContextAwareActor extends UntypedActor {
public static final int ENTITY_PACK_LIMIT = 1024;
protected final ActorSystemContext systemContext;
public ContextAwareActor(ActorSystemContext systemContext) {
super();
this.systemContext = systemContext;
}
}

32
application/src/main/java/org/thingsboard/server/actors/service/ContextBasedCreator.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016 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.actors.service;
import org.thingsboard.server.actors.ActorSystemContext;
import akka.japi.Creator;
public abstract class ContextBasedCreator<T> implements Creator<T> {
private static final long serialVersionUID = 1L;
protected final ActorSystemContext context;
public ContextBasedCreator(ActorSystemContext context) {
super();
this.context = context;
}
}

234
application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java

@ -0,0 +1,234 @@
/**
* Copyright © 2016 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.actors.service;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.actor.Terminated;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.app.AppActor;
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg;
import org.thingsboard.server.actors.rpc.RpcManagerActor;
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
import org.thingsboard.server.actors.session.SessionManagerActor;
import org.thingsboard.server.actors.stats.StatsActor;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.aware.SessionAwareMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Service
@Slf4j
public class DefaultActorService implements ActorService {
private static final String ACTOR_SYSTEM_NAME = "Akka";
public static final String APP_DISPATCHER_NAME = "app-dispatcher";
public static final String CORE_DISPATCHER_NAME = "core-dispatcher";
public static final String RULE_DISPATCHER_NAME = "rule-dispatcher";
public static final String PLUGIN_DISPATCHER_NAME = "plugin-dispatcher";
public static final String SESSION_DISPATCHER_NAME = "session-dispatcher";
public static final String RPC_DISPATCHER_NAME = "rpc-dispatcher";
@Autowired
private ActorSystemContext actorContext;
@Autowired
private ClusterRpcService rpcService;
@Autowired
private DiscoveryService discoveryService;
private ActorSystem system;
private ActorRef appActor;
private ActorRef sessionManagerActor;
private ActorRef rpcManagerActor;
@PostConstruct
public void initActorSystem() {
log.info("Initializing Actor system. {}", actorContext.getRuleService());
actorContext.setActorService(this);
system = ActorSystem.create(ACTOR_SYSTEM_NAME, actorContext.getConfig());
actorContext.setActorSystem(system);
appActor = system.actorOf(Props.create(new AppActor.ActorCreator(actorContext)).withDispatcher(APP_DISPATCHER_NAME), "appActor");
actorContext.setAppActor(appActor);
sessionManagerActor = system.actorOf(Props.create(new SessionManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME),
"sessionManagerActor");
actorContext.setSessionManagerActor(sessionManagerActor);
rpcManagerActor = system.actorOf(Props.create(new RpcManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME),
"rpcManagerActor");
ActorRef statsActor = system.actorOf(Props.create(new StatsActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME), "statsActor");
actorContext.setStatsActor(statsActor);
rpcService.init(this);
discoveryService.addListener(this);
log.info("Actor system initialized.");
}
@PreDestroy
public void stopActorSystem() {
Future<Terminated> status = system.terminate();
try {
Terminated terminated = Await.result(status, Duration.Inf());
log.info("Actor system terminated: {}", terminated);
} catch (Exception e) {
log.error("Failed to terminate actor system.", e);
}
}
@Override
public void process(SessionAwareMsg msg) {
if (msg instanceof SessionAwareMsg) {
log.debug("Processing session aware msg: {}", msg);
sessionManagerActor.tell(msg, ActorRef.noSender());
}
}
@Override
public void process(PluginWebsocketMsg<?> msg) {
log.debug("Processing websocket msg: {}", msg);
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void process(PluginRestMsg msg) {
log.debug("Processing rest msg: {}", msg);
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(ToPluginActorMsg msg) {
log.trace("Processing plugin rpc msg: {}", msg);
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(ToDeviceActorMsg msg) {
log.trace("Processing device rpc msg: {}", msg);
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(ToDeviceActorNotificationMsg msg) {
log.trace("Processing notification rpc msg: {}", msg);
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(ToDeviceSessionActorMsg msg) {
log.trace("Processing session rpc msg: {}", msg);
sessionManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(ToAllNodesMsg msg) {
log.trace("Processing broadcast rpc msg: {}", msg);
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(RpcSessionCreateRequestMsg msg) {
log.trace("Processing session create msg: {}", msg);
rpcManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(RpcSessionTellMsg msg) {
log.trace("Processing session rpc msg: {}", msg);
rpcManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(RpcBroadcastMsg msg) {
log.trace("Processing broadcast rpc msg: {}", msg);
rpcManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void onServerAdded(ServerInstance server) {
log.trace("Processing onServerAdded msg: {}", server);
broadcast(new ClusterEventMsg(server.getServerAddress(), true));
}
@Override
public void onServerUpdated(ServerInstance server) {
}
@Override
public void onServerRemoved(ServerInstance server) {
log.trace("Processing onServerRemoved msg: {}", server);
broadcast(new ClusterEventMsg(server.getServerAddress(), false));
}
@Override
public void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state) {
log.trace("[{}] Processing onPluginStateChange event: {}", pluginId, state);
broadcast(ComponentLifecycleMsg.forPlugin(tenantId, pluginId, state));
}
@Override
public void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state) {
log.trace("[{}] Processing onRuleStateChange event: {}", ruleId, state);
broadcast(ComponentLifecycleMsg.forRule(tenantId, ruleId, state));
}
public void broadcast(ToAllNodesMsg msg) {
rpcService.broadcast(msg);
appActor.tell(msg, ActorRef.noSender());
}
private void broadcast(ClusterEventMsg msg) {
this.appActor.tell(msg, ActorRef.noSender());
this.sessionManagerActor.tell(msg, ActorRef.noSender());
this.rpcManagerActor.tell(msg, ActorRef.noSender());
}
}

24
application/src/main/java/org/thingsboard/server/actors/service/RestMsgProcessor.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016 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.actors.service;
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
public interface RestMsgProcessor {
void process(PluginRestMsg msg);
}

24
application/src/main/java/org/thingsboard/server/actors/service/WebSocketMsgProcessor.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016 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.actors.service;
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
public interface WebSocketMsgProcessor {
void process(PluginWebsocketMsg<?> msg);
}

133
application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java

@ -0,0 +1,133 @@
/**
* Copyright © 2016 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.actors.session;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.core.AttributesSubscribeMsg;
import org.thingsboard.server.common.msg.core.ResponseMsg;
import org.thingsboard.server.common.msg.core.RpcSubscribeMsg;
import org.thingsboard.server.common.msg.core.SessionCloseMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.*;
import akka.actor.ActorContext;
import akka.event.LoggingAdapter;
import org.thingsboard.server.common.msg.session.ex.SessionException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
Map<Integer, ToDeviceActorMsg> pendingMap = new HashMap<>();
private Optional<ServerAddress> currentTargetServer;
private boolean subscribedToAttributeUpdates;
private boolean subscribedToRpcCommands;
public ASyncMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) {
super(ctx, logger, sessionId);
}
@Override
protected void processToDeviceActorMsg(ActorContext ctx, ToDeviceActorSessionMsg msg) {
updateSessionCtx(msg, SessionType.ASYNC);
ToDeviceActorMsg pendingMsg = toDeviceMsg(msg);
FromDeviceMsg fromDeviceMsg = pendingMsg.getPayload();
switch (fromDeviceMsg.getMsgType()) {
case POST_TELEMETRY_REQUEST:
case POST_ATTRIBUTES_REQUEST:
FromDeviceRequestMsg requestMsg = (FromDeviceRequestMsg) fromDeviceMsg;
if (requestMsg.getRequestId() >= 0) {
logger.debug("[{}] Pending request {} registered", requestMsg.getRequestId(), requestMsg.getMsgType());
//TODO: handle duplicates.
pendingMap.put(requestMsg.getRequestId(), pendingMsg);
}
break;
case SUBSCRIBE_ATTRIBUTES_REQUEST:
subscribedToAttributeUpdates = true;
break;
case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
subscribedToAttributeUpdates = false;
break;
case SUBSCRIBE_RPC_COMMANDS_REQUEST:
subscribedToRpcCommands = true;
break;
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
subscribedToRpcCommands = false;
break;
}
currentTargetServer = forwardToAppActor(ctx, pendingMsg);
}
@Override
public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) {
try {
switch (msg.getMsgType()) {
case STATUS_CODE_RESPONSE:
case GET_ATTRIBUTES_RESPONSE:
ResponseMsg responseMsg = (ResponseMsg) msg;
if (responseMsg.getRequestId() >= 0) {
logger.debug("[{}] Pending request processed: {}", responseMsg.getRequestId(), responseMsg);
pendingMap.remove(responseMsg.getRequestId());
}
break;
}
sessionCtx.onMsg(new BasicSessionActorToAdaptorMsg(this.sessionCtx, msg));
} catch (SessionException e) {
logger.warning("Failed to push session response msg", e);
}
}
@Override
public void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg) {
// TODO Auto-generated method stub
}
protected void cleanupSession(ActorContext ctx) {
toDeviceMsg(new SessionCloseMsg()).ifPresent(msg -> forwardToAppActor(ctx, msg));
}
@Override
public void processClusterEvent(ActorContext context, ClusterEventMsg msg) {
if (pendingMap.size() > 0 || subscribedToAttributeUpdates || subscribedToRpcCommands) {
Optional<ServerAddress> newTargetServer = systemContext.getRoutingService().resolve(getDeviceId());
if (!newTargetServer.equals(currentTargetServer)) {
currentTargetServer = newTargetServer;
pendingMap.values().stream().forEach(v -> {
forwardToAppActor(context, v, currentTargetServer);
if (currentTargetServer.isPresent()) {
logger.debug("[{}] Forwarded msg to new server: {}", sessionId, currentTargetServer.get());
} else {
logger.debug("[{}] Forwarded msg to local server.", sessionId);
}
});
if (subscribedToAttributeUpdates) {
toDeviceMsg(new AttributesSubscribeMsg()).ifPresent(m -> forwardToAppActor(context, m, currentTargetServer));
logger.debug("[{}] Forwarded attributes subscription.", sessionId);
}
if (subscribedToRpcCommands) {
toDeviceMsg(new RpcSubscribeMsg()).ifPresent(m -> forwardToAppActor(context, m, currentTargetServer));
logger.debug("[{}] Forwarded rpc commands subscription.", sessionId);
}
}
}
}
}

119
application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java

@ -0,0 +1,119 @@
/**
* Copyright © 2016 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.actors.session;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.device.BasicToDeviceActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.*;
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
import java.util.Optional;
abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgProcessor {
protected final SessionId sessionId;
protected SessionContext sessionCtx;
protected ToDeviceActorMsg toDeviceActorMsgPrototype;
protected AbstractSessionActorMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) {
super(ctx, logger);
this.sessionId = sessionId;
}
protected abstract void processToDeviceActorMsg(ActorContext ctx, ToDeviceActorSessionMsg msg);
protected abstract void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg);
protected abstract void processToDeviceMsg(ActorContext context, ToDeviceMsg msg);
public abstract void processClusterEvent(ActorContext context, ClusterEventMsg msg);
protected void processSessionCtrlMsg(ActorContext ctx, SessionCtrlMsg msg) {
if (msg instanceof SessionCloseMsg) {
cleanupSession(ctx);
terminateSession(ctx, sessionId);
}
}
protected void cleanupSession(ActorContext ctx) {
}
protected void updateSessionCtx(ToDeviceActorSessionMsg msg, SessionType type) {
sessionCtx = msg.getSessionMsg().getSessionContext();
toDeviceActorMsgPrototype = new BasicToDeviceActorMsg(msg, type);
}
protected ToDeviceActorMsg toDeviceMsg(ToDeviceActorSessionMsg msg) {
AdaptorToSessionActorMsg adaptorMsg = msg.getSessionMsg();
return new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, adaptorMsg.getMsg());
}
protected Optional<ToDeviceActorMsg> toDeviceMsg(FromDeviceMsg msg) {
if (toDeviceActorMsgPrototype != null) {
return Optional.of(new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, msg));
} else {
return Optional.empty();
}
}
protected Optional<ServerAddress> forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward) {
Optional<ServerAddress> address = systemContext.getRoutingService().resolve(toForward.getDeviceId());
forwardToAppActor(ctx, toForward, address);
return address;
}
protected Optional<ServerAddress> forwardToAppActorIfAdressChanged(ActorContext ctx, ToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) {
Optional<ServerAddress> newAddress = systemContext.getRoutingService().resolve(toForward.getDeviceId());
if (!newAddress.equals(oldAddress)) {
if (newAddress.isPresent()) {
systemContext.getRpcService().tell(newAddress.get(),
toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer()));
} else {
getAppActor().tell(toForward, ctx.self());
}
}
return newAddress;
}
protected void forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward, Optional<ServerAddress> address) {
if (address.isPresent()) {
systemContext.getRpcService().tell(address.get(),
toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer()));
} else {
getAppActor().tell(toForward, ctx.self());
}
}
public static void terminateSession(ActorContext ctx, SessionId sessionId) {
ctx.parent().tell(new SessionTerminationMsg(sessionId), ActorRef.noSender());
ctx.stop(ctx.self());
}
public DeviceId getDeviceId() {
return toDeviceActorMsgPrototype.getDeviceId();
}
}

139
application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java

@ -0,0 +1,139 @@
/**
* Copyright © 2016 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.actors.session;
import akka.actor.OneForOneStrategy;
import akka.actor.SupervisorStrategy;
import akka.japi.Function;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg;
import org.thingsboard.server.common.msg.session.SessionCtrlMsg;
import org.thingsboard.server.common.msg.session.SessionMsg;
import org.thingsboard.server.common.msg.session.SessionType;
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import scala.concurrent.duration.Duration;
public class SessionActor extends ContextAwareActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
private final SessionId sessionId;
private AbstractSessionActorMsgProcessor processor;
private SessionActor(ActorSystemContext systemContext, SessionId sessionId) {
super(systemContext);
this.sessionId = sessionId;
}
@Override
public SupervisorStrategy supervisorStrategy() {
return new OneForOneStrategy(-1, Duration.Inf(),
throwable -> {
logger.error(throwable, "Unknown session error");
if (throwable instanceof Error) {
return OneForOneStrategy.escalate();
} else {
return OneForOneStrategy.resume();
}
});
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("[{}] Processing: {}.", sessionId, msg);
if (msg instanceof ToDeviceActorSessionMsg) {
processDeviceMsg((ToDeviceActorSessionMsg) msg);
} else if (msg instanceof ToDeviceSessionActorMsg) {
processToDeviceMsg((ToDeviceSessionActorMsg) msg);
} else if (msg instanceof SessionTimeoutMsg) {
processTimeoutMsg((SessionTimeoutMsg) msg);
} else if (msg instanceof SessionCtrlMsg) {
processSessionCtrlMsg((SessionCtrlMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
processClusterEvent((ClusterEventMsg) msg);
} else {
logger.warning("[{}] Unknown msg: {}", sessionId, msg);
}
}
private void processClusterEvent(ClusterEventMsg msg) {
processor.processClusterEvent(context(), msg);
}
private void processDeviceMsg(ToDeviceActorSessionMsg msg) {
initProcessor(msg);
processor.processToDeviceActorMsg(context(), msg);
}
private void processToDeviceMsg(ToDeviceSessionActorMsg msg) {
processor.processToDeviceMsg(context(), msg.getMsg());
}
private void processTimeoutMsg(SessionTimeoutMsg msg) {
if (processor != null) {
processor.processTimeoutMsg(context(), msg);
} else {
logger.warning("[{}] Can't process timeout msg: {} without processor", sessionId, msg);
}
}
private void processSessionCtrlMsg(SessionCtrlMsg msg) {
if (processor != null) {
processor.processSessionCtrlMsg(context(), msg);
} else if (msg instanceof SessionCloseMsg) {
AbstractSessionActorMsgProcessor.terminateSession(context(), sessionId);
} else {
logger.warning("[{}] Can't process session ctrl msg: {} without processor", sessionId, msg);
}
}
private void initProcessor(ToDeviceActorSessionMsg msg) {
if (processor == null) {
SessionMsg sessionMsg = (SessionMsg) msg.getSessionMsg();
if (sessionMsg.getSessionContext().getSessionType() == SessionType.SYNC) {
processor = new SyncMsgProcessor(systemContext, logger, sessionId);
} else {
processor = new ASyncMsgProcessor(systemContext, logger, sessionId);
}
}
}
public static class ActorCreator extends ContextBasedCreator<SessionActor> {
private static final long serialVersionUID = 1L;
private final SessionId sessionId;
public ActorCreator(ActorSystemContext context, SessionId sessionId) {
super(context);
this.sessionId = sessionId;
}
@Override
public SessionActor create() throws Exception {
return new SessionActor(context, sessionId);
}
}
}

154
application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java

@ -0,0 +1,154 @@
/**
* Copyright © 2016 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.actors.session;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import akka.actor.*;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.aware.SessionAwareMsg;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.core.SessionCloseMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.session.SessionCtrlMsg;
public class SessionManagerActor extends ContextAwareActor {
private static final int INITIAL_SESSION_MAP_SIZE = 1024;
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
private final Map<String, ActorRef> sessionActors;
public SessionManagerActor(ActorSystemContext systemContext) {
super(systemContext);
this.sessionActors = new HashMap<>(INITIAL_SESSION_MAP_SIZE);
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof SessionAwareMsg) {
forwardToSessionActor((SessionAwareMsg) msg);
} else if (msg instanceof SessionTerminationMsg) {
onSessionTermination((SessionTerminationMsg) msg);
} else if (msg instanceof Terminated) {
onTermination((Terminated) msg);
} else if (msg instanceof SessionTimeoutMsg) {
onSessionTimeout((SessionTimeoutMsg) msg);
} else if (msg instanceof SessionCtrlMsg) {
onSessionCtrlMsg((SessionCtrlMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
broadcast(msg);
}
}
private void broadcast(Object msg) {
sessionActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
private void onSessionTimeout(SessionTimeoutMsg msg) {
String sessionIdStr = msg.getSessionId().toUidStr();
ActorRef sessionActor = sessionActors.get(sessionIdStr);
if (sessionActor != null) {
sessionActor.tell(msg, ActorRef.noSender());
}
}
private void onSessionCtrlMsg(SessionCtrlMsg msg) {
String sessionIdStr = msg.getSessionId().toUidStr();
ActorRef sessionActor = sessionActors.get(sessionIdStr);
if (sessionActor != null) {
sessionActor.tell(msg, ActorRef.noSender());
}
}
private void onSessionTermination(SessionTerminationMsg msg) {
String sessionIdStr = msg.getId().toUidStr();
ActorRef sessionActor = sessionActors.remove(sessionIdStr);
if (sessionActor != null) {
log.debug("[{}] Removed session actor.", sessionIdStr);
//TODO: onSubscriptionUpdate device actor about session close;
} else {
log.debug("[{}] Session actor was already removed.", sessionIdStr);
}
}
private void forwardToSessionActor(SessionAwareMsg msg) {
if (msg instanceof ToDeviceSessionActorMsg || msg instanceof SessionCloseMsg) {
String sessionIdStr = msg.getSessionId().toUidStr();
ActorRef sessionActor = sessionActors.get(sessionIdStr);
if (sessionActor != null) {
sessionActor.tell(msg, ActorRef.noSender());
} else {
log.debug("[{}] Session actor was already removed.", sessionIdStr);
}
} else {
try {
getOrCreateSessionActor(msg.getSessionId()).tell(msg, self());
} catch (InvalidActorNameException e) {
log.info("Invalid msg : {}", msg);
}
}
}
private ActorRef getOrCreateSessionActor(SessionId sessionId) {
String sessionIdStr = sessionId.toUidStr();
ActorRef sessionActor = sessionActors.get(sessionIdStr);
if (sessionActor == null) {
log.debug("[{}] Creating session actor.", sessionIdStr);
sessionActor = context().actorOf(
Props.create(new SessionActor.ActorCreator(systemContext, sessionId)).withDispatcher(DefaultActorService.SESSION_DISPATCHER_NAME),
sessionIdStr);
sessionActors.put(sessionIdStr, sessionActor);
log.debug("[{}] Created session actor.", sessionIdStr);
}
return sessionActor;
}
private void onTermination(Terminated message) {
ActorRef terminated = message.actor();
if (terminated instanceof LocalActorRef) {
log.info("Removed actor: {}.", terminated);
//TODO: cleanup session actors map
} else {
throw new IllegalStateException("Remote actors are not supported!");
}
}
public static class ActorCreator extends ContextBasedCreator<SessionManagerActor> {
private static final long serialVersionUID = 1L;
public ActorCreator(ActorSystemContext context) {
super(context);
}
@Override
public SessionManagerActor create() throws Exception {
return new SessionManagerActor(context);
}
}
}

26
application/src/main/java/org/thingsboard/server/actors/session/SessionTerminationMsg.java

@ -0,0 +1,26 @@
/**
* Copyright © 2016 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.actors.session;
import org.thingsboard.server.actors.shared.ActorTerminationMsg;
import org.thingsboard.server.common.data.id.SessionId;
public class SessionTerminationMsg extends ActorTerminationMsg<SessionId> {
public SessionTerminationMsg(SessionId id) {
super(id);
}
}

93
application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java

@ -0,0 +1,93 @@
/**
* Copyright © 2016 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.actors.session;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.*;
import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg;
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
import org.thingsboard.server.common.msg.session.ex.SessionException;
import akka.actor.ActorContext;
import akka.event.LoggingAdapter;
import java.util.Optional;
class SyncMsgProcessor extends AbstractSessionActorMsgProcessor {
private ToDeviceActorMsg pendingMsg;
private Optional<ServerAddress> currentTargetServer;
private boolean pendingResponse;
public SyncMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) {
super(ctx, logger, sessionId);
}
@Override
protected void processToDeviceActorMsg(ActorContext ctx, ToDeviceActorSessionMsg msg) {
updateSessionCtx(msg, SessionType.SYNC);
pendingMsg = toDeviceMsg(msg);
pendingResponse = true;
currentTargetServer = forwardToAppActor(ctx, pendingMsg);
scheduleMsgWithDelay(ctx, new SessionTimeoutMsg(sessionId), getTimeout(systemContext, msg.getSessionMsg().getSessionContext()), ctx.parent());
}
public void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg) {
if (pendingResponse) {
try {
sessionCtx.onMsg(new SessionCloseMsg(sessionId, true));
} catch (SessionException e) {
logger.warning("Failed to push session close msg", e);
}
terminateSession(context, this.sessionId);
}
}
public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) {
try {
sessionCtx.onMsg(new BasicSessionActorToAdaptorMsg(this.sessionCtx, msg));
pendingResponse = false;
} catch (SessionException e) {
logger.warning("Failed to push session response msg", e);
}
terminateSession(context, this.sessionId);
}
@Override
public void processClusterEvent(ActorContext context, ClusterEventMsg msg) {
if (pendingResponse) {
Optional<ServerAddress> newTargetServer = forwardToAppActorIfAdressChanged(context, pendingMsg, currentTargetServer);
if (logger.isDebugEnabled()) {
if (!newTargetServer.equals(currentTargetServer)) {
if (newTargetServer.isPresent()) {
logger.debug("[{}] Forwarded msg to new server: {}", sessionId, newTargetServer.get());
} else {
logger.debug("[{}] Forwarded msg to local server.", sessionId);
}
}
}
currentTargetServer = newTargetServer;
}
}
private long getTimeout(ActorSystemContext ctx, SessionContext sessionCtx) {
return sessionCtx.getTimeout() > 0 ? sessionCtx.getTimeout() : ctx.getSyncSessionTimeout();
}
}

135
application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java

@ -0,0 +1,135 @@
/**
* Copyright © 2016 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.actors.shared;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Scheduler;
import akka.event.LoggingAdapter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.extensions.api.component.*;
import scala.concurrent.ExecutionContextExecutor;
import scala.concurrent.duration.Duration;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public abstract class AbstractContextAwareMsgProcessor {
protected final ActorSystemContext systemContext;
protected final LoggingAdapter logger;
protected final ObjectMapper mapper = new ObjectMapper();
protected AbstractContextAwareMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger) {
super();
this.systemContext = systemContext;
this.logger = logger;
}
protected ActorRef getAppActor() {
return systemContext.getAppActor();
}
protected Scheduler getScheduler() {
return systemContext.getScheduler();
}
protected ExecutionContextExecutor getSystemDispatcher() {
return systemContext.getActorSystem().dispatcher();
}
protected void schedulePeriodicMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, long periodInMs) {
schedulePeriodicMsgWithDelay(ctx, msg, delayInMs, periodInMs, ctx.self());
}
protected void schedulePeriodicMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, long periodInMs, ActorRef target) {
logger.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs);
getScheduler().schedule(Duration.create(delayInMs, TimeUnit.MILLISECONDS), Duration.create(periodInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null);
}
protected void scheduleMsgWithDelay(ActorContext ctx, Object msg, long delayInMs) {
scheduleMsgWithDelay(ctx, msg, delayInMs, ctx.self());
}
protected void scheduleMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, ActorRef target) {
logger.debug("Scheduling msg {} with delay {} ms", msg, delayInMs);
getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null);
}
protected <T extends ConfigurableComponent> T initComponent(JsonNode componentNode) throws Exception {
ComponentConfiguration configuration = new ComponentConfiguration(
componentNode.get("clazz").asText(),
componentNode.get("name").asText(),
mapper.writeValueAsString(componentNode.get("configuration"))
);
logger.info("Initializing [{}][{}] component", configuration.getName(), configuration.getClazz());
ComponentDescriptor componentDescriptor = systemContext.getComponentService().getComponent(configuration.getClazz())
.orElseThrow(() -> new InstantiationException("Component Not found!"));
return initComponent(componentDescriptor, configuration);
}
protected <T extends ConfigurableComponent> T initComponent(ComponentDescriptor componentDefinition, ComponentConfiguration configuration)
throws Exception {
return initComponent(componentDefinition.getClazz(), componentDefinition.getType(), configuration.getConfiguration());
}
protected <T extends ConfigurableComponent> T initComponent(String clazz, ComponentType type, String configuration)
throws Exception {
Class<?> componentClazz = Class.forName(clazz);
T component = (T) (componentClazz.newInstance());
Class<?> configurationClazz;
switch (type) {
case FILTER:
configurationClazz = ((Filter) componentClazz.getAnnotation(Filter.class)).configuration();
break;
case PROCESSOR:
configurationClazz = ((Processor) componentClazz.getAnnotation(Processor.class)).configuration();
break;
case ACTION:
configurationClazz = ((Action) componentClazz.getAnnotation(Action.class)).configuration();
break;
case PLUGIN:
configurationClazz = ((Plugin) componentClazz.getAnnotation(Plugin.class)).configuration();
break;
default:
throw new IllegalStateException("Component with type: " + type + " is not supported!");
}
component.init(decode(configuration, configurationClazz));
return component;
}
public <C> C decode(String configuration, Class<C> configurationClazz) throws IOException, RuntimeException {
logger.info("Initializing using configuration: {}", configuration);
return mapper.readValue(configuration, configurationClazz);
}
@Data
@AllArgsConstructor
private static class ComponentConfiguration {
private final String clazz;
private final String name;
private final String configuration;
}
}

31
application/src/main/java/org/thingsboard/server/actors/shared/ActorTerminationMsg.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016 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.actors.shared;
public abstract class ActorTerminationMsg<T> {
private final T id;
public ActorTerminationMsg(T id) {
super();
this.id = id;
}
public T getId() {
return id;
}
}

55
application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java

@ -0,0 +1,55 @@
/**
* Copyright © 2016 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.actors.shared;
import akka.actor.ActorContext;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.stats.StatsPersistTick;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
public abstract class ComponentMsgProcessor<T> extends AbstractContextAwareMsgProcessor {
protected final TenantId tenantId;
protected final T entityId;
protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) {
super(systemContext, logger);
this.tenantId = tenantId;
this.entityId = id;
}
public abstract void start() throws Exception;
public abstract void stop() throws Exception;
public abstract void onCreated(ActorContext context) throws Exception;
public abstract void onUpdate(ActorContext context) throws Exception;
public abstract void onActivate(ActorContext context) throws Exception;
public abstract void onSuspend(ActorContext context) throws Exception;
public abstract void onStop(ActorContext context) throws Exception;
public abstract void onClusterEventMsg(ClusterEventMsg msg) throws Exception;
public void scheduleStatsPersistTick(ActorContext context, long statsPersistFrequency) {
schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency);
}
}

29
application/src/main/java/org/thingsboard/server/actors/shared/SessionTimeoutMsg.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016 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.actors.shared;
import lombok.Data;
import org.thingsboard.server.common.data.id.SessionId;
import java.io.Serializable;
@Data
public class SessionTimeoutMsg implements Serializable {
private static final long serialVersionUID = 1L;
private final SessionId sessionId;
}

83
application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java

@ -0,0 +1,83 @@
/**
* Copyright © 2016 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.actors.shared.plugin;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.plugin.PluginActor;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.dao.plugin.PluginService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Props;
@Slf4j
public abstract class PluginManager {
protected final ActorSystemContext systemContext;
protected final PluginService pluginService;
protected final Map<PluginId, ActorRef> pluginActors;
public PluginManager(ActorSystemContext systemContext) {
this.systemContext = systemContext;
this.pluginService = systemContext.getPluginService();
this.pluginActors = new HashMap<>();
}
public void init(ActorContext context) {
PageDataIterable<PluginMetaData> pluginIterator = new PageDataIterable<>(getFetchPluginsFunction(),
ContextAwareActor.ENTITY_PACK_LIMIT);
for (PluginMetaData plugin : pluginIterator) {
log.debug("[{}] Creating plugin actor", plugin.getId());
getOrCreatePluginActor(context, plugin.getId());
log.debug("Plugin actor created.");
}
}
abstract FetchFunction<PluginMetaData> getFetchPluginsFunction();
abstract TenantId getTenantId();
public ActorRef getOrCreatePluginActor(ActorContext context, PluginId pluginId) {
ActorRef pluginActor = pluginActors.get(pluginId);
if (pluginActor == null) {
pluginActor = context.actorOf(Props.create(new PluginActor.ActorCreator(systemContext, getTenantId(), pluginId))
.withDispatcher(DefaultActorService.PLUGIN_DISPATCHER_NAME), pluginId.toString());
pluginActors.put(pluginId, pluginActor);
}
return pluginActor;
}
public void broadcast(Object msg) {
pluginActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
public void remove(PluginId id) {
pluginActors.remove(id);
}
}

41
application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016 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.actors.shared.plugin;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.dao.plugin.BasePluginService;
import org.thingsboard.server.dao.plugin.PluginService;
public class SystemPluginManager extends PluginManager {
public SystemPluginManager(ActorSystemContext systemContext) {
super(systemContext);
}
@Override
FetchFunction<PluginMetaData> getFetchPluginsFunction() {
return link -> pluginService.findSystemPlugins(link);
}
@Override
TenantId getTenantId() {
return BasePluginService.SYSTEM_TENANT;
}
}

41
application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016 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.actors.shared.plugin;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
public class TenantPluginManager extends PluginManager {
private final TenantId tenantId;
public TenantPluginManager(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext);
this.tenantId = tenantId;
}
@Override
FetchFunction<PluginMetaData> getFetchPluginsFunction() {
return link -> pluginService.findTenantPlugins(tenantId, link);
}
@Override
TenantId getTenantId() {
return tenantId;
}
}

126
application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java

@ -0,0 +1,126 @@
/**
* Copyright © 2016 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.actors.shared.rule;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Props;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.rule.RuleActor;
import org.thingsboard.server.actors.rule.RuleActorChain;
import org.thingsboard.server.actors.rule.RuleActorMetaData;
import org.thingsboard.server.actors.rule.SimpleRuleActorChain;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.rule.RuleService;
import java.util.*;
public abstract class RuleManager {
protected static final Logger logger = LoggerFactory.getLogger(RuleManager.class);
protected final ActorSystemContext systemContext;
protected final RuleService ruleService;
protected final Map<RuleId, ActorRef> ruleActors;
protected final TenantId tenantId;
Map<RuleMetaData, RuleActorMetaData> ruleMap = new HashMap<>();
private RuleActorChain ruleChain;
public RuleManager(ActorSystemContext systemContext, TenantId tenantId) {
this.systemContext = systemContext;
this.ruleService = systemContext.getRuleService();
this.ruleActors = new HashMap<>();
this.tenantId = tenantId;
}
public void init(ActorContext context) {
PageDataIterable<RuleMetaData> ruleIterator = new PageDataIterable<>(getFetchRulesFunction(),
ContextAwareActor.ENTITY_PACK_LIMIT);
ruleMap = new HashMap<>();
for (RuleMetaData rule : ruleIterator) {
logger.debug("[{}] Creating rule actor {}", rule.getId(), rule);
ActorRef ref = getOrCreateRuleActor(context, rule.getId());
RuleActorMetaData actorMd = RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref);
ruleMap.put(rule, actorMd);
logger.debug("[{}] Rule actor created.", rule.getId());
}
refreshRuleChain();
}
public Optional<ActorRef> update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) {
RuleMetaData rule = null;
if (event != ComponentLifecycleEvent.DELETED) {
rule = systemContext.getRuleService().findRuleById(ruleId);
}
if (rule == null) {
rule = ruleMap.keySet().stream().filter(r -> r.getId().equals(ruleId)).findFirst().orElse(null);
rule.setState(ComponentLifecycleState.SUSPENDED);
}
if (rule != null) {
RuleActorMetaData actorMd = ruleMap.get(rule);
if (actorMd == null) {
ActorRef ref = getOrCreateRuleActor(context, rule.getId());
actorMd = RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref);
ruleMap.put(rule, actorMd);
}
refreshRuleChain();
return Optional.of(actorMd.getActorRef());
} else {
logger.warn("[{}] Can't process unknown rule!", rule.getId());
return Optional.empty();
}
}
abstract FetchFunction<RuleMetaData> getFetchRulesFunction();
public ActorRef getOrCreateRuleActor(ActorContext context, RuleId ruleId) {
ActorRef ruleActor = ruleActors.get(ruleId);
if (ruleActor == null) {
ruleActor = context.actorOf(Props.create(new RuleActor.ActorCreator(systemContext, tenantId, ruleId))
.withDispatcher(DefaultActorService.RULE_DISPATCHER_NAME), ruleId.toString());
ruleActors.put(ruleId, ruleActor);
}
return ruleActor;
}
public RuleActorChain getRuleChain() {
return ruleChain;
}
private void refreshRuleChain() {
Set<RuleActorMetaData> activeRuleSet = new HashSet<>();
for (Map.Entry<RuleMetaData, RuleActorMetaData> rule : ruleMap.entrySet()) {
if (rule.getKey().getState() == ComponentLifecycleState.ACTIVE) {
activeRuleSet.add(rule.getValue());
}
}
ruleChain = new SimpleRuleActorChain(activeRuleSet);
}
}

35
application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016 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.actors.shared.rule;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.model.ModelConstants;
public class SystemRuleManager extends RuleManager {
public SystemRuleManager(ActorSystemContext systemContext) {
super(systemContext, new TenantId(ModelConstants.NULL_UUID));
}
@Override
FetchFunction<RuleMetaData> getFetchRulesFunction() {
return link -> ruleService.findSystemRules(link);
}
}

34
application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016 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.actors.shared.rule;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.rule.RuleMetaData;
public class TenantRuleManager extends RuleManager {
public TenantRuleManager(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext, tenantId);
}
@Override
FetchFunction<RuleMetaData> getFetchRulesFunction() {
return link -> ruleService.findTenantRules(tenantId, link);
}
}

75
application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java

@ -0,0 +1,75 @@
/**
* Copyright © 2016 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.actors.stats;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
public class StatsActor extends ContextAwareActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
private final ObjectMapper mapper = new ObjectMapper();
public StatsActor(ActorSystemContext context) {
super(context);
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("Received message: {}", msg);
if (msg instanceof StatsPersistMsg) {
try {
onStatsPersistMsg((StatsPersistMsg) msg);
} catch (Exception e) {
logger.warning("Failed to persist statistics: {}", msg, e);
}
}
}
public void onStatsPersistMsg(StatsPersistMsg msg) throws Exception {
Event event = new Event();
event.setEntityId(msg.getEntityId());
event.setTenantId(msg.getTenantId());
event.setType(DataConstants.STATS);
event.setBody(toBodyJson(systemContext.getDiscoveryService().getCurrentServer().getServerAddress(), msg.getMessagesProcessed(), msg.getErrorsOccurred()));
systemContext.getEventService().save(event);
}
private JsonNode toBodyJson(ServerAddress server, long messagesProcessed, long errorsOccurred) {
return mapper.createObjectNode().put("server", server.toString()).put("messagesProcessed", messagesProcessed).put("errorsOccurred", errorsOccurred);
}
public static class ActorCreator extends ContextBasedCreator<StatsActor> {
private static final long serialVersionUID = 1L;
public ActorCreator(ActorSystemContext context) {
super(context);
}
@Override
public StatsActor create() throws Exception {
return new StatsActor(context);
}
}
}

32
application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistMsg.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016 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.actors.stats;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@AllArgsConstructor
@Getter
@ToString
public final class StatsPersistMsg {
private long messagesProcessed;
private long errorsOccurred;
private TenantId tenantId;
private EntityId entityId;
}

18
application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistTick.java

@ -0,0 +1,18 @@
/**
* Copyright © 2016 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.actors.stats;
public final class StatsPersistTick {}

40
application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016 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.actors.tenant;
import org.thingsboard.server.actors.rule.RuleActorChain;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
public class RuleChainDeviceMsg {
private final ToDeviceActorMsg toDeviceActorMsg;
private final RuleActorChain ruleChain;
public RuleChainDeviceMsg(ToDeviceActorMsg toDeviceActorMsg, RuleActorChain ruleChain) {
super();
this.toDeviceActorMsg = toDeviceActorMsg;
this.ruleChain = ruleChain;
}
public ToDeviceActorMsg getToDeviceActorMsg() {
return toDeviceActorMsg;
}
public RuleActorChain getRuleChain() {
return ruleChain;
}
}

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

@ -0,0 +1,184 @@
/**
* Copyright © 2016 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.actors.tenant;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.device.DeviceActor;
import org.thingsboard.server.actors.plugin.PluginTerminationMsg;
import org.thingsboard.server.actors.rule.ComplexRuleActorChain;
import org.thingsboard.server.actors.rule.RuleActorChain;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.plugin.PluginManager;
import org.thingsboard.server.actors.shared.plugin.TenantPluginManager;
import org.thingsboard.server.actors.shared.rule.RuleManager;
import org.thingsboard.server.actors.shared.rule.TenantRuleManager;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg;
public class TenantActor extends ContextAwareActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
private final TenantId tenantId;
private final RuleManager ruleManager;
private final PluginManager pluginManager;
private final Map<DeviceId, ActorRef> deviceActors;
private TenantActor(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext);
this.tenantId = tenantId;
this.ruleManager = new TenantRuleManager(systemContext, tenantId);
this.pluginManager = new TenantPluginManager(systemContext, tenantId);
this.deviceActors = new HashMap<>();
}
@Override
public void preStart() {
logger.info("[{}] Starting tenant actor.", tenantId);
try {
ruleManager.init(this.context());
pluginManager.init(this.context());
logger.info("[{}] Tenant actor started.", tenantId);
} catch (Exception e) {
logger.error(e, "[{}] Unknown failure", tenantId);
}
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("[{}] Received message: {}", tenantId, msg);
if (msg instanceof RuleChainDeviceMsg) {
process((RuleChainDeviceMsg) msg);
} else if (msg instanceof ToDeviceActorMsg) {
onToDeviceActorMsg((ToDeviceActorMsg) msg);
} else if (msg instanceof ToPluginActorMsg) {
onToPluginMsg((ToPluginActorMsg) msg);
} else if (msg instanceof ToRuleActorMsg) {
onToRuleMsg((ToRuleActorMsg) msg);
} else if (msg instanceof ToDeviceActorNotificationMsg) {
onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
broadcast(msg);
} else if (msg instanceof ComponentLifecycleMsg) {
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
} else if (msg instanceof PluginTerminationMsg) {
onPluginTerminated((PluginTerminationMsg) msg);
} else {
logger.warning("[{}] Unknown message: {}!", tenantId, msg);
}
}
private void broadcast(Object msg) {
pluginManager.broadcast(msg);
deviceActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
private void onToDeviceActorMsg(ToDeviceActorMsg msg) {
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
}
private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) {
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
}
private void onToRuleMsg(ToRuleActorMsg msg) {
ActorRef target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId());
target.tell(msg, ActorRef.noSender());
}
private void onToPluginMsg(ToPluginActorMsg msg) {
if (msg.getPluginTenantId().equals(tenantId)) {
ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId());
pluginActor.tell(msg, ActorRef.noSender());
} else {
context().parent().tell(msg, ActorRef.noSender());
}
}
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
if (msg.getPluginId().isPresent()) {
ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId().get());
pluginActor.tell(msg, ActorRef.noSender());
} else if (msg.getRuleId().isPresent()) {
ActorRef target;
Optional<ActorRef> ref = ruleManager.update(this.context(), msg.getRuleId().get(), msg.getEvent());
if (ref.isPresent()) {
target = ref.get();
} else {
logger.debug("Failed to find actor for rule: [{}]", msg.getRuleId());
return;
}
target.tell(msg, ActorRef.noSender());
} else {
logger.debug("[{}] Invalid component lifecycle msg.", tenantId);
}
}
private void onPluginTerminated(PluginTerminationMsg msg) {
pluginManager.remove(msg.getId());
}
private void process(RuleChainDeviceMsg msg) {
ToDeviceActorMsg toDeviceActorMsg = msg.getToDeviceActorMsg();
ActorRef deviceActor = getOrCreateDeviceActor(toDeviceActorMsg.getDeviceId());
RuleActorChain chain = new ComplexRuleActorChain(msg.getRuleChain(), ruleManager.getRuleChain());
deviceActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, chain), context().self());
}
private ActorRef getOrCreateDeviceActor(DeviceId deviceId) {
ActorRef deviceActor = deviceActors.get(deviceId);
if (deviceActor == null) {
deviceActor = context().actorOf(Props.create(new DeviceActor.ActorCreator(systemContext, tenantId, deviceId))
.withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), deviceId.toString());
deviceActors.put(deviceId, deviceActor);
}
return deviceActor;
}
public static class ActorCreator extends ContextBasedCreator<TenantActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
public ActorCreator(ActorSystemContext context, TenantId tenantId) {
super(context);
this.tenantId = tenantId;
}
@Override
public TenantActor create() throws Exception {
return new TenantActor(context, tenantId);
}
}
}

55
application/src/main/java/org/thingsboard/server/service/cluster/discovery/CurrentServerInstanceService.java

@ -0,0 +1,55 @@
/**
* Copyright © 2016 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.cluster.discovery;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.thingsboard.server.gen.discovery.ServerInstanceProtos;
import javax.annotation.PostConstruct;
import static org.thingsboard.server.utils.MiscUtils.missingProperty;
/**
* @author Andrew Shvayka
*/
@Service
@Slf4j
public class CurrentServerInstanceService implements ServerInstanceService {
@Value("${rpc.bind_host}")
private String rpcHost;
@Value("${rpc.bind_port}")
private Integer rpcPort;
private ServerInstance self;
@PostConstruct
public void init() {
Assert.hasLength(rpcHost, missingProperty("rpc.bind_host"));
Assert.notNull(rpcPort, missingProperty("rpc.bind_port"));
self = new ServerInstance(ServerInstanceProtos.ServerInfo.newBuilder().setHost(rpcHost).setPort(rpcPort).setTs(System.currentTimeMillis()).build());
log.info("Current server instance: [{};{}]", self.getHost(), self.getPort());
}
@Override
public ServerInstance getSelf() {
return self;
}
}

37
application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016 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.cluster.discovery;
import java.util.List;
/**
* @author Andrew Shvayka
*/
public interface DiscoveryService {
void publishCurrentServer();
void unpublishCurrentServer();
ServerInstance getCurrentServer();
List<ServerInstance> getOtherServers();
boolean addListener(DiscoveryServiceListener listener);
boolean removeListener(DiscoveryServiceListener listener);
}

28
application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryServiceListener.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016 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.cluster.discovery;
/**
* @author Andrew Shvayka
*/
public interface DiscoveryServiceListener {
void onServerAdded(ServerInstance server);
void onServerUpdated(ServerInstance server);
void onServerRemoved(ServerInstance server);
}

75
application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java

@ -0,0 +1,75 @@
/**
* Copyright © 2016 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.cluster.discovery;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;
import org.thingsboard.server.service.environment.EnvironmentLogService;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
/**
* @author Andrew Shvayka
*/
@Service
@ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "false", matchIfMissing = true)
@Slf4j
@DependsOn("environmentLogService")
public class DummyDiscoveryService implements DiscoveryService {
@Autowired
private ServerInstanceService serverInstance;
@PostConstruct
public void init() {
log.info("Initializing...");
}
@Override
public void publishCurrentServer() {
}
@Override
public void unpublishCurrentServer() {
}
@Override
public ServerInstance getCurrentServer() {
return serverInstance.getSelf();
}
@Override
public List<ServerInstance> getOtherServers() {
return Collections.emptyList();
}
@Override
public boolean addListener(DiscoveryServiceListener listener) {
return false;
}
@Override
public boolean removeListener(DiscoveryServiceListener listener) {
return false;
}
}

52
application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstance.java

@ -0,0 +1,52 @@
/**
* Copyright © 2016 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.cluster.discovery;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.discovery.ServerInstanceProtos.ServerInfo;
/**
* @author Andrew Shvayka
*/
@ToString
@EqualsAndHashCode(exclude = {"serverInfo", "serverAddress"})
public final class ServerInstance implements Comparable<ServerInstance> {
@Getter(AccessLevel.PACKAGE)
private final ServerInfo serverInfo;
@Getter
private final String host;
@Getter
private final int port;
@Getter
private final ServerAddress serverAddress;
public ServerInstance(ServerInfo serverInfo) {
this.serverInfo = serverInfo;
this.host = serverInfo.getHost();
this.port = serverInfo.getPort();
this.serverAddress = new ServerAddress(host, port);
}
@Override
public int compareTo(ServerInstance o) {
return this.serverAddress.compareTo(o.serverAddress);
}
}

24
application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstanceService.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016 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.cluster.discovery;
/**
* @author Andrew Shvayka
*/
public interface ServerInstanceService {
ServerInstance getSelf();
}

207
application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java

@ -0,0 +1,207 @@
/**
* Copyright © 2016 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.cluster.discovery;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.RetryForever;
import org.apache.curator.utils.CloseableUtils;
import org.apache.zookeeper.CreateMode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.thingsboard.server.gen.discovery.ServerInstanceProtos.ServerInfo;
import org.thingsboard.server.utils.MiscUtils;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
/**
* @author Andrew Shvayka
*/
@Service
@ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "true", matchIfMissing = false)
@Slf4j
public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheListener, ApplicationListener<ApplicationReadyEvent> {
@Value("${zk.url}")
private String zkUrl;
@Value("${zk.retry_interval_ms}")
private Integer zkRetryInterval;
@Value("${zk.connection_timeout_ms}")
private Integer zkConnectionTimeout;
@Value("${zk.session_timeout_ms}")
private Integer zkSessionTimeout;
@Value("${zk.zk_dir}")
private String zkDir;
private String zkNodesDir;
@Autowired
private ServerInstanceService serverInstance;
private final List<DiscoveryServiceListener> listeners = new CopyOnWriteArrayList<>();
private CuratorFramework client;
private PathChildrenCache cache;
private String nodePath;
@PostConstruct
public void init() {
log.info("Initializing...");
Assert.hasLength(zkUrl, MiscUtils.missingProperty("zk.url"));
Assert.notNull(zkRetryInterval, MiscUtils.missingProperty("zk.retry_interval_ms"));
Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms"));
Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms"));
log.info("Initializing discovery service using ZK connect string: {}", zkUrl);
zkNodesDir = zkDir + "/nodes";
try {
client = CuratorFrameworkFactory.newClient(zkUrl, zkSessionTimeout, zkConnectionTimeout, new RetryForever(zkRetryInterval));
client.start();
client.blockUntilConnected();
cache = new PathChildrenCache(client, zkNodesDir, true);
cache.getListenable().addListener(this);
cache.start();
} catch (Exception e) {
log.error("Failed to connect to ZK: {}", e.getMessage(), e);
CloseableUtils.closeQuietly(client);
throw new RuntimeException(e);
}
}
@PreDestroy
public void destroy() {
unpublishCurrentServer();
CloseableUtils.closeQuietly(client);
log.info("Stopped discovery service");
}
@Override
public void publishCurrentServer() {
try {
ServerInstance self = this.serverInstance.getSelf();
log.info("[{}:{}] Creating ZK node for current instance", self.getHost(), self.getPort());
nodePath = client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", self.getServerInfo().toByteArray());
log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath);
} catch (Exception e) {
log.error("Failed to create ZK node", e);
throw new RuntimeException(e);
}
}
@Override
public void unpublishCurrentServer() {
try {
if (nodePath != null) {
client.delete().forPath(nodePath);
}
} catch (Exception e) {
log.error("Failed to delete ZK node {}", nodePath, e);
throw new RuntimeException(e);
}
}
@Override
public ServerInstance getCurrentServer() {
return serverInstance.getSelf();
}
@Override
public List<ServerInstance> getOtherServers() {
return cache.getCurrentData().stream()
.filter(cd -> !cd.getPath().equals(nodePath))
.map(cd -> {
try {
return new ServerInstance(ServerInfo.parseFrom(cd.getData()));
} catch (InvalidProtocolBufferException e) {
log.error("Failed to decode ZK node", e);
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
}
@Override
public boolean addListener(DiscoveryServiceListener listener) {
return listeners.add(listener);
}
@Override
public boolean removeListener(DiscoveryServiceListener listener) {
return listeners.remove(listener);
}
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
publishCurrentServer();
getOtherServers().stream().forEach(
server -> log.info("Found active server: [{}:{}]", server.getHost(), server.getPort())
);
}
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
ChildData data = pathChildrenCacheEvent.getData();
if (data == null) {
log.debug("Ignoring {} due to empty child data", pathChildrenCacheEvent);
return;
} else if (data.getData() == null) {
log.debug("Ignoring {} due to empty child's data", pathChildrenCacheEvent);
return;
} else if (nodePath != null && nodePath.equals(data.getPath())) {
log.debug("Ignoring event about current server {}", pathChildrenCacheEvent);
return;
}
ServerInstance instance;
try {
instance = new ServerInstance(ServerInfo.parseFrom(data.getData()));
} catch (IOException e) {
log.error("Failed to decode server instance for node {}", data.getPath(), e);
throw e;
}
log.info("Processing [{}] event for [{}:{}]", pathChildrenCacheEvent.getType(), instance.getHost(), instance.getPort());
switch (pathChildrenCacheEvent.getType()) {
case CHILD_ADDED:
listeners.stream().forEach(listener -> listener.onServerAdded(instance));
break;
case CHILD_UPDATED:
listeners.stream().forEach(listener -> listener.onServerUpdated(instance));
break;
case CHILD_REMOVED:
listeners.stream().forEach(listener -> listener.onServerRemoved(instance));
break;
}
}
}

33
application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016 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.cluster.routing;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import java.util.Optional;
/**
* @author Andrew Shvayka
*/
public interface ClusterRoutingService {
ServerAddress getCurrentServer();
Optional<ServerAddress> resolve(UUIDBased entityId);
}

142
application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java

@ -0,0 +1,142 @@
/**
* Copyright © 2016 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.cluster.routing;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
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.springframework.util.Assert;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import org.thingsboard.server.utils.MiscUtils;
import javax.annotation.PostConstruct;
import java.util.Optional;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
/**
* Cluster service implementation based on consistent hash ring
*/
@Service
@Slf4j
public class ConsistentClusterRoutingService implements ClusterRoutingService, DiscoveryServiceListener {
@Autowired
private DiscoveryService discoveryService;
@Value("${cluster.hash_function_name}")
private String hashFunctionName;
@Value("${cluster.vitrual_nodes_size}")
private Integer virtualNodesSize;
private ServerInstance currentServer;
private HashFunction hashFunction;
private final ConcurrentNavigableMap<Long, ServerInstance> circle =
new ConcurrentSkipListMap<>();
@PostConstruct
public void init() {
log.info("Initializing Cluster routing service!");
hashFunction = MiscUtils.forName(hashFunctionName);
discoveryService.addListener(this);
this.currentServer = discoveryService.getCurrentServer();
addNode(discoveryService.getCurrentServer());
for (ServerInstance instance : discoveryService.getOtherServers()) {
addNode(instance);
}
logCircle();
log.info("Cluster routing service initialized!");
}
@Override
public ServerAddress getCurrentServer() {
return discoveryService.getCurrentServer().getServerAddress();
}
@Override
public Optional<ServerAddress> resolve(UUIDBased entityId) {
Assert.notNull(entityId);
if (circle.isEmpty()) {
return Optional.empty();
}
Long hash = hashFunction.newHasher().putLong(entityId.getId().getMostSignificantBits())
.putLong(entityId.getId().getLeastSignificantBits()).hash().asLong();
if (!circle.containsKey(hash)) {
ConcurrentNavigableMap<Long, ServerInstance> tailMap =
circle.tailMap(hash);
hash = tailMap.isEmpty() ?
circle.firstKey() : tailMap.firstKey();
}
ServerInstance result = circle.get(hash);
if (!currentServer.equals(result)) {
return Optional.of(result.getServerAddress());
} else {
return Optional.empty();
}
}
@Override
public void onServerAdded(ServerInstance server) {
log.debug("On server added event: {}", server);
addNode(server);
logCircle();
}
@Override
public void onServerUpdated(ServerInstance server) {
log.debug("Ignoring server onUpdate event: {}", server);
}
@Override
public void onServerRemoved(ServerInstance server) {
log.debug("On server removed event: {}", server);
removeNode(server);
logCircle();
}
private void addNode(ServerInstance instance) {
for (int i = 0; i < virtualNodesSize; i++) {
circle.put(hash(instance, i).asLong(), instance);
}
}
private void removeNode(ServerInstance instance) {
for (int i = 0; i < virtualNodesSize; i++) {
circle.remove(hash(instance, i).asLong());
}
}
private HashCode hash(ServerInstance instance, int i) {
return hashFunction.newHasher().putString(instance.getHost(), MiscUtils.UTF8).putInt(instance.getPort()).putInt(i).hash();
}
private void logCircle() {
log.trace("Consistent Hash Circle Start");
circle.entrySet().stream().forEach((e) -> log.debug("{} -> {}", e.getKey(), e.getValue().getServerAddress()));
log.trace("Consistent Hash Circle End");
}
}

273
application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java

@ -0,0 +1,273 @@
/**
* Copyright © 2016 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.cluster.rpc;
import com.google.protobuf.ByteString;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.SerializationUtils;
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg;
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import org.thingsboard.server.service.cluster.discovery.ServerInstanceService;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author Andrew Shvayka
*/
@Service
@Slf4j
public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceImplBase implements ClusterRpcService {
@Autowired
private ServerInstanceService instanceService;
private RpcMsgListener listener;
private Server server;
private ServerInstance instance;
private ConcurrentMap<UUID, RpcSessionCreationFuture> pendingSessionMap = new ConcurrentHashMap<>();
public void init(RpcMsgListener listener) {
this.listener = listener;
log.info("Initializing RPC service!");
instance = instanceService.getSelf();
server = ServerBuilder.forPort(instance.getPort()).addService(this).build();
log.info("Going to start RPC server using port: {}", instance.getPort());
try {
server.start();
} catch (IOException e) {
log.error("Failed to start RPC server!", e);
throw new RuntimeException("Failed to start RPC server!");
}
log.info("RPC service initialized!");
}
@Override
public void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ToRpcServerMessage> msg) {
RpcSessionCreationFuture future = pendingSessionMap.remove(msgUid);
if (future != null) {
try {
future.onMsg(msg);
} catch (InterruptedException e) {
log.warn("Failed to report created session!");
}
} else {
log.warn("Failed to lookup pending session!");
}
}
@Override
public StreamObserver<ClusterAPIProtos.ToRpcServerMessage> handlePluginMsgs(StreamObserver<ClusterAPIProtos.ToRpcServerMessage> responseObserver) {
log.info("Processing new session.");
return createSession(new RpcSessionCreateRequestMsg(UUID.randomUUID(), null, responseObserver));
}
@PreDestroy
public void stop() {
if (server != null) {
log.info("Going to onStop RPC server");
server.shutdownNow();
try {
server.awaitTermination();
log.info("RPC server stopped!");
} catch (InterruptedException e) {
log.warn("Failed to onStop RPC server!");
}
}
}
@Override
public void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward) {
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
.setToDeviceActorRpcMsg(toProtoMsg(toForward)).build();
tell(serverAddress, msg);
}
@Override
public void tell(ServerAddress serverAddress, ToDeviceActorNotificationMsg toForward) {
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
.setToDeviceActorNotificationRpcMsg(toProtoMsg(toForward)).build();
tell(serverAddress, msg);
}
@Override
public void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward) {
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
.setToDeviceRpcRequestRpcMsg(toProtoMsg(toForward)).build();
tell(serverAddress, msg);
}
@Override
public void tell(ServerAddress serverAddress, ToPluginRpcResponseDeviceMsg toForward) {
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
.setToPluginRpcResponseRpcMsg(toProtoMsg(toForward)).build();
tell(serverAddress, msg);
}
@Override
public void tell(ServerAddress serverAddress, ToDeviceSessionActorMsg toForward) {
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
.setToDeviceSessionActorRpcMsg(toProtoMsg(toForward)).build();
tell(serverAddress, msg);
}
@Override
public void tell(PluginRpcMsg toForward) {
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
.setToPluginRpcMsg(toProtoMsg(toForward)).build();
tell(toForward.getRpcMsg().getServerAddress(), msg);
}
@Override
public void broadcast(ToAllNodesMsg toForward) {
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
.setToAllNodesRpcMsg(toProtoMsg(toForward)).build();
listener.onMsg(new RpcBroadcastMsg(msg));
}
private void tell(ServerAddress serverAddress, ClusterAPIProtos.ToRpcServerMessage msg) {
listener.onMsg(new RpcSessionTellMsg(serverAddress, msg));
}
private StreamObserver<ClusterAPIProtos.ToRpcServerMessage> createSession(RpcSessionCreateRequestMsg msg) {
RpcSessionCreationFuture future = new RpcSessionCreationFuture();
pendingSessionMap.put(msg.getMsgUid(), future);
listener.onMsg(msg);
try {
StreamObserver<ClusterAPIProtos.ToRpcServerMessage> observer = future.get();
log.info("Processed new session.");
return observer;
} catch (Exception e) {
log.info("Failed to process session.", e);
throw new RuntimeException(e);
}
}
private static ClusterAPIProtos.ToDeviceActorRpcMessage toProtoMsg(ToDeviceActorMsg msg) {
return ClusterAPIProtos.ToDeviceActorRpcMessage.newBuilder().setData(
ByteString.copyFrom(SerializationUtils.serialize(msg))
).build();
}
private static ClusterAPIProtos.ToDeviceActorNotificationRpcMessage toProtoMsg(ToDeviceActorNotificationMsg msg) {
return ClusterAPIProtos.ToDeviceActorNotificationRpcMessage.newBuilder().setData(
ByteString.copyFrom(SerializationUtils.serialize(msg))
).build();
}
private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestPluginMsg msg) {
ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.Builder builder = ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.newBuilder();
ToDeviceRpcRequest request = msg.getMsg();
builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder()
.setTenantId(toUid(msg.getPluginTenantId().getId()))
.setPluginId(toUid(msg.getPluginId().getId()))
.build());
builder.setDeviceTenantId(toUid(msg.getTenantId()));
builder.setDeviceId(toUid(msg.getDeviceId()));
builder.setMsgId(toUid(request.getId()));
builder.setOneway(request.isOneway());
builder.setExpTime(request.getExpirationTime());
builder.setMethod(request.getBody().getMethod());
builder.setParams(request.getBody().getParams());
return builder.build();
}
private static ClusterAPIProtos.ToPluginRpcResponseRpcMessage toProtoMsg(ToPluginRpcResponseDeviceMsg msg) {
ClusterAPIProtos.ToPluginRpcResponseRpcMessage.Builder builder = ClusterAPIProtos.ToPluginRpcResponseRpcMessage.newBuilder();
FromDeviceRpcResponse request = msg.getResponse();
builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder()
.setTenantId(toUid(msg.getPluginTenantId().getId()))
.setPluginId(toUid(msg.getPluginId().getId()))
.build());
builder.setMsgId(toUid(request.getId()));
request.getResponse().ifPresent(builder::setResponse);
request.getError().ifPresent(e -> builder.setError(e.name()));
return builder.build();
}
private ClusterAPIProtos.ToAllNodesRpcMessage toProtoMsg(ToAllNodesMsg msg) {
return ClusterAPIProtos.ToAllNodesRpcMessage.newBuilder().setData(
ByteString.copyFrom(SerializationUtils.serialize(msg))
).build();
}
private ClusterAPIProtos.ToPluginRpcMessage toProtoMsg(PluginRpcMsg msg) {
return ClusterAPIProtos.ToPluginRpcMessage.newBuilder()
.setClazz(msg.getRpcMsg().getMsgClazz())
.setData(ByteString.copyFrom(msg.getRpcMsg().getMsgData()))
.setAddress(ClusterAPIProtos.PluginAddress.newBuilder()
.setTenantId(toUid(msg.getPluginTenantId().getId()))
.setPluginId(toUid(msg.getPluginId().getId()))
.build()
).build();
}
private static ClusterAPIProtos.Uid toUid(EntityId uuid) {
return toUid(uuid.getId());
}
private static ClusterAPIProtos.Uid toUid(UUID uuid) {
return ClusterAPIProtos.Uid.newBuilder().setPluginUuidMsb(uuid.getMostSignificantBits()).setPluginUuidLsb(
uuid.getLeastSignificantBits()).build();
}
private static ClusterAPIProtos.ToDeviceSessionActorRpcMessage toProtoMsg(ToDeviceSessionActorMsg msg) {
return ClusterAPIProtos.ToDeviceSessionActorRpcMessage.newBuilder().setData(
ByteString.copyFrom(SerializationUtils.serialize(msg))
).build();
}
}

53
application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016 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.cluster.rpc;
import io.grpc.stub.StreamObserver;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
public interface ClusterRpcService {
void init(RpcMsgListener listener);
void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward);
void tell(ServerAddress serverAddress, ToDeviceSessionActorMsg toForward);
void tell(ServerAddress serverAddress, ToDeviceActorNotificationMsg toForward);
void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward);
void tell(ServerAddress serverAddress, ToPluginRpcResponseDeviceMsg toForward);
void tell(PluginRpcMsg toForward);
void broadcast(ToAllNodesMsg msg);
void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ToRpcServerMessage> inputStream);
}

130
application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java

@ -0,0 +1,130 @@
/**
* Copyright © 2016 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.cluster.rpc;
import io.grpc.stub.StreamObserver;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import java.io.Closeable;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@Data
@Slf4j
final public class GrpcSession implements Closeable {
private final UUID sessionId;
private final boolean client;
private final GrpcSessionListener listener;
private StreamObserver<ClusterAPIProtos.ToRpcServerMessage> inputStream;
private StreamObserver<ClusterAPIProtos.ToRpcServerMessage> outputStream;
private boolean connected;
private ServerAddress remoteServer;
public GrpcSession(GrpcSessionListener listener) {
this(null, listener);
}
public GrpcSession(ServerAddress remoteServer, GrpcSessionListener listener) {
this.sessionId = UUID.randomUUID();
this.listener = listener;
if (remoteServer != null) {
this.client = true;
this.connected = true;
this.remoteServer = remoteServer;
} else {
this.client = false;
}
}
public void initInputStream() {
this.inputStream = new StreamObserver<ClusterAPIProtos.ToRpcServerMessage>() {
@Override
public void onNext(ClusterAPIProtos.ToRpcServerMessage msg) {
if (!connected) {
if (msg.hasConnectMsg()) {
connected = true;
ClusterAPIProtos.ServerAddress rpcAddress = msg.getConnectMsg().getServerAddress();
remoteServer = new ServerAddress(rpcAddress.getHost(), rpcAddress.getPort());
listener.onConnected(GrpcSession.this);
}
}
if (connected) {
if (msg.hasToPluginRpcMsg()) {
listener.onToPluginRpcMsg(GrpcSession.this, msg.getToPluginRpcMsg());
}
if (msg.hasToDeviceActorRpcMsg()) {
listener.onToDeviceActorRpcMsg(GrpcSession.this, msg.getToDeviceActorRpcMsg());
}
if (msg.hasToDeviceSessionActorRpcMsg()) {
listener.onToDeviceSessionActorRpcMsg(GrpcSession.this, msg.getToDeviceSessionActorRpcMsg());
}
if (msg.hasToDeviceActorNotificationRpcMsg()) {
listener.onToDeviceActorNotificationRpcMsg(GrpcSession.this, msg.getToDeviceActorNotificationRpcMsg());
}
if (msg.hasToDeviceRpcRequestRpcMsg()) {
listener.onToDeviceRpcRequestRpcMsg(GrpcSession.this, msg.getToDeviceRpcRequestRpcMsg());
}
if (msg.hasToPluginRpcResponseRpcMsg()) {
listener.onFromDeviceRpcResponseRpcMsg(GrpcSession.this, msg.getToPluginRpcResponseRpcMsg());
}
if (msg.hasToAllNodesRpcMsg()) {
listener.onToAllNodesRpcMessage(GrpcSession.this, msg.getToAllNodesRpcMsg());
}
}
}
@Override
public void onError(Throwable t) {
listener.onError(GrpcSession.this, t);
}
@Override
public void onCompleted() {
outputStream.onCompleted();
listener.onDisconnected(GrpcSession.this);
}
};
}
public void initOutputStream() {
if (client) {
listener.onConnected(GrpcSession.this);
}
}
public void sendMsg(ClusterAPIProtos.ToRpcServerMessage msg) {
outputStream.onNext(msg);
}
public void onError(Throwable t) {
outputStream.onError(t);
}
@Override
public void close() {
try {
outputStream.onCompleted();
} catch (IllegalStateException e) {
log.debug("[{}] Failed to close output stream: {}", sessionId, e.getMessage());
}
}
}

45
application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSessionListener.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016 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.cluster.rpc;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
/**
* @author Andrew Shvayka
*/
public interface GrpcSessionListener {
void onConnected(GrpcSession session);
void onDisconnected(GrpcSession session);
void onToPluginRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcMessage msg);
void onToDeviceActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorRpcMessage msg);
void onToDeviceActorNotificationRpcMsg(GrpcSession grpcSession, ClusterAPIProtos.ToDeviceActorNotificationRpcMessage msg);
void onToDeviceSessionActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceSessionActorRpcMessage msg);
void onToAllNodesRpcMessage(GrpcSession grpcSession, ClusterAPIProtos.ToAllNodesRpcMessage toAllNodesRpcMessage);
void onToDeviceRpcRequestRpcMsg(GrpcSession grpcSession, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toDeviceRpcRequestRpcMsg);
void onFromDeviceRpcResponseRpcMsg(GrpcSession grpcSession, ClusterAPIProtos.ToPluginRpcResponseRpcMessage toPluginRpcResponseRpcMsg);
void onError(GrpcSession session, Throwable t);
}

50
application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016 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.cluster.rpc;
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg;
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
/**
* @author Andrew Shvayka
*/
public interface RpcMsgListener {
void onMsg(ToDeviceActorMsg msg);
void onMsg(ToDeviceActorNotificationMsg msg);
void onMsg(ToDeviceSessionActorMsg msg);
void onMsg(ToAllNodesMsg nodeMsg);
void onMsg(ToPluginActorMsg msg);
void onMsg(RpcSessionCreateRequestMsg msg);
void onMsg(RpcSessionTellMsg rpcSessionTellMsg);
void onMsg(RpcBroadcastMsg rpcBroadcastMsg);
}

63
application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcSessionCreationFuture.java

@ -0,0 +1,63 @@
/**
* Copyright © 2016 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.cluster.rpc;
import io.grpc.stub.StreamObserver;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import java.util.concurrent.*;
/**
* @author Andrew Shvayka
*/
public class RpcSessionCreationFuture implements Future<StreamObserver<ClusterAPIProtos.ToRpcServerMessage>> {
private final BlockingQueue<StreamObserver<ClusterAPIProtos.ToRpcServerMessage>> queue = new ArrayBlockingQueue<>(1);
public void onMsg(StreamObserver<ClusterAPIProtos.ToRpcServerMessage> result) throws InterruptedException {
queue.put(result);
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return false;
}
@Override
public StreamObserver<ClusterAPIProtos.ToRpcServerMessage> get() throws InterruptedException, ExecutionException {
return this.queue.take();
}
@Override
public StreamObserver<ClusterAPIProtos.ToRpcServerMessage> get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
StreamObserver<ClusterAPIProtos.ToRpcServerMessage> result = this.queue.poll(timeout, unit);
if (result == null) {
throw new TimeoutException();
} else {
return result;
}
}
}

190
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java

@ -0,0 +1,190 @@
/**
* Copyright © 2016 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.component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.dao.component.ComponentDescriptorService;
import org.thingsboard.server.extensions.api.component.*;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class AnnotationComponentDiscoveryService implements ComponentDiscoveryService {
@Value("${plugins.scan_packages}")
private String[] scanPackages;
@Autowired
private ComponentDescriptorService componentDescriptorService;
private Map<String, ComponentDescriptor> components = new HashMap<>();
private Map<ComponentType, List<ComponentDescriptor>> componentsMap = new HashMap<>();
private ObjectMapper mapper = new ObjectMapper();
@PostConstruct
public void init() {
registerComponents(ComponentType.FILTER, Filter.class);
registerComponents(ComponentType.PROCESSOR, Processor.class);
registerComponents(ComponentType.ACTION, Action.class);
registerComponents(ComponentType.PLUGIN, Plugin.class);
log.info("Found following definitions: {}", components.values());
}
private void registerComponents(ComponentType type, Class<? extends Annotation> annotation) {
List<ComponentDescriptor> components = persist(getBeanDefinitions(annotation), type);
componentsMap.put(type, components);
registerComponents(components);
}
private void registerComponents(Collection<ComponentDescriptor> comps) {
comps.stream().forEach(c -> components.put(c.getClazz(), c));
}
private List<ComponentDescriptor> persist(Set<BeanDefinition> filterDefs, ComponentType type) {
List<ComponentDescriptor> result = new ArrayList<>();
for (BeanDefinition def : filterDefs) {
ComponentDescriptor scannedComponent = new ComponentDescriptor();
String clazzName = def.getBeanClassName();
try {
scannedComponent.setType(type);
Class<?> clazz = Class.forName(clazzName);
String descriptorResourceName;
switch (type) {
case FILTER:
Filter filterAnnotation = clazz.getAnnotation(Filter.class);
scannedComponent.setName(filterAnnotation.name());
scannedComponent.setScope(filterAnnotation.scope());
descriptorResourceName = filterAnnotation.descriptor();
break;
case PROCESSOR:
Processor processorAnnotation = clazz.getAnnotation(Processor.class);
scannedComponent.setName(processorAnnotation.name());
scannedComponent.setScope(processorAnnotation.scope());
descriptorResourceName = processorAnnotation.descriptor();
break;
case ACTION:
Action actionAnnotation = clazz.getAnnotation(Action.class);
scannedComponent.setName(actionAnnotation.name());
scannedComponent.setScope(actionAnnotation.scope());
descriptorResourceName = actionAnnotation.descriptor();
break;
case PLUGIN:
Plugin pluginAnnotation = clazz.getAnnotation(Plugin.class);
scannedComponent.setName(pluginAnnotation.name());
scannedComponent.setScope(pluginAnnotation.scope());
descriptorResourceName = pluginAnnotation.descriptor();
for (Class<?> actionClazz : pluginAnnotation.actions()) {
ComponentDescriptor actionComponent = getComponent(actionClazz.getName())
.orElseThrow(() -> {
log.error("Can't initialize plugin {}, due to missing action {}!", def.getBeanClassName(), actionClazz.getName());
return new ClassNotFoundException("Action: " + actionClazz.getName() + "is missing!");
});
if (actionComponent.getType() != ComponentType.ACTION) {
log.error("Plugin {} action {} has wrong component type!", def.getBeanClassName(), actionClazz.getName(), actionComponent.getType());
throw new RuntimeException("Plugin " + def.getBeanClassName() + "action " + actionClazz.getName() + " has wrong component type!");
}
}
scannedComponent.setActions(Arrays.asList(pluginAnnotation.actions()).stream().map(action -> action.getName()).collect(Collectors.joining(",")));
break;
default:
throw new RuntimeException(type + " is not supported yet!");
}
scannedComponent.setConfigurationDescriptor(mapper.readTree(
Resources.toString(Resources.getResource(descriptorResourceName), Charsets.UTF_8)));
scannedComponent.setClazz(clazzName);
log.info("Processing scanned component: {}", scannedComponent);
} catch (Exception e) {
log.error("Can't initialize component {}, due to {}", def.getBeanClassName(), e.getMessage(), e);
throw new RuntimeException(e);
}
ComponentDescriptor persistedComponent = componentDescriptorService.findByClazz(clazzName);
if (persistedComponent == null) {
log.info("Persisting new component: {}", scannedComponent);
scannedComponent = componentDescriptorService.saveComponent(scannedComponent);
} else if (scannedComponent.equals(persistedComponent)) {
log.info("Component is already persisted: {}", persistedComponent);
scannedComponent = persistedComponent;
} else {
log.info("Component {} will be updated to {}", persistedComponent, scannedComponent);
componentDescriptorService.deleteByClazz(persistedComponent.getClazz());
scannedComponent.setId(persistedComponent.getId());
scannedComponent = componentDescriptorService.saveComponent(scannedComponent);
}
result.add(scannedComponent);
}
return result;
}
private Set<BeanDefinition> getBeanDefinitions(Class<? extends Annotation> componentType) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(componentType));
Set<BeanDefinition> defs = new HashSet<>();
for (String scanPackage : scanPackages) {
defs.addAll(scanner.findCandidateComponents(scanPackage));
}
return defs;
}
@Override
public List<ComponentDescriptor> getComponents(ComponentType type) {
return Collections.unmodifiableList(componentsMap.get(type));
}
@Override
public Optional<ComponentDescriptor> getComponent(String clazz) {
return Optional.ofNullable(components.get(clazz));
}
@Override
public List<ComponentDescriptor> getPluginActions(String pluginClazz) {
Optional<ComponentDescriptor> pluginOpt = getComponent(pluginClazz);
if (pluginOpt.isPresent()) {
ComponentDescriptor plugin = pluginOpt.get();
if (ComponentType.PLUGIN != plugin.getType()) {
throw new IllegalArgumentException(pluginClazz + " is not a plugin!");
}
List<ComponentDescriptor> result = new ArrayList<>();
for (String action : plugin.getActions().split(",")) {
getComponent(action).ifPresent(v -> result.add(v));
}
return result;
} else {
throw new IllegalArgumentException(pluginClazz + " is not a component!");
}
}
}

35
application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016 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.component;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import java.util.List;
import java.util.Optional;
/**
* @author Andrew Shvayka
*/
public interface ComponentDiscoveryService {
List<ComponentDescriptor> getComponents(ComponentType type);
Optional<ComponentDescriptor> getComponent(String clazz);
List<ComponentDescriptor> getPluginActions(String pluginClazz);
}

49
application/src/main/java/org/thingsboard/server/utils/MiscUtils.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016 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.utils;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import java.nio.charset.Charset;
/**
* @author Andrew Shvayka
*/
public class MiscUtils {
public static final Charset UTF8 = Charset.forName("UTF-8");
public static String missingProperty(String propertyName) {
return "The " + propertyName + " property need to be set!";
}
public static HashFunction forName(String name) {
switch (name) {
case "murmur3_32":
return Hashing.murmur3_32();
case "murmur3_128":
return Hashing.murmur3_128();
case "crc32":
return Hashing.crc32();
case "md5":
return Hashing.md5();
default:
throw new IllegalArgumentException("Can't find hash function with name " + name);
}
}
}

97
application/src/main/proto/cluster.proto

@ -0,0 +1,97 @@
/**
* Copyright © 2016 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.
*/
syntax = "proto3";
package cluster;
option java_package = "org.thingsboard.server.gen.cluster";
option java_outer_classname = "ClusterAPIProtos";
message ServerAddress {
string host = 1;
int32 port = 2;
}
message Uid {
sint64 pluginUuidMsb = 1;
sint64 pluginUuidLsb = 2;
}
message PluginAddress {
Uid pluginId = 1;
Uid tenantId = 2;
}
message ToPluginRpcMessage {
PluginAddress address = 1;
int32 clazz = 2;
bytes data = 3;
}
message ToDeviceActorRpcMessage {
bytes data = 1;
}
message ToDeviceSessionActorRpcMessage {
bytes data = 1;
}
message ToDeviceActorNotificationRpcMessage {
bytes data = 1;
}
message ToAllNodesRpcMessage {
bytes data = 1;
}
message ConnectRpcMessage {
ServerAddress serverAddress = 1;
}
message ToDeviceRpcRequestRpcMessage {
PluginAddress address = 1;
Uid deviceTenantId = 2;
Uid deviceId = 3;
Uid msgId = 4;
bool oneway = 5;
int64 expTime = 6;
string method = 7;
string params = 8;
}
message ToPluginRpcResponseRpcMessage {
PluginAddress address = 1;
Uid msgId = 2;
string response = 3;
string error = 4;
}
message ToRpcServerMessage {
ConnectRpcMessage connectMsg = 1;
ToPluginRpcMessage toPluginRpcMsg = 2;
ToDeviceActorRpcMessage toDeviceActorRpcMsg = 3;
ToDeviceSessionActorRpcMessage toDeviceSessionActorRpcMsg = 4;
ToDeviceActorNotificationRpcMessage toDeviceActorNotificationRpcMsg = 5;
ToAllNodesRpcMessage toAllNodesRpcMsg = 6;
ToDeviceRpcRequestRpcMessage toDeviceRpcRequestRpcMsg = 7;
ToPluginRpcResponseRpcMessage toPluginRpcResponseRpcMsg = 8;
}
service ClusterRpcService {
rpc handlePluginMsgs(stream ToRpcServerMessage) returns (stream ToRpcServerMessage) {}
}

26
application/src/main/proto/discovery.proto

@ -0,0 +1,26 @@
/**
* Copyright © 2016 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.
*/
syntax = "proto3";
package discovery;
option java_package = "org.thingsboard.server.gen.discovery";
option java_outer_classname = "ServerInstanceProtos";
message ServerInfo {
string host = 1;
int32 port = 2;
int64 ts = 3;
}

163
application/src/main/resources/actor-system.conf

@ -0,0 +1,163 @@
#
# Copyright © 2016 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.
#
akka {
# JVM shutdown, System.exit(-1), in case of a fatal error,
# such as OutOfMemoryError
jvm-exit-on-fatal-error = off
loglevel = "INFO"
loggers = ["akka.event.slf4j.Slf4jLogger"]
}
# This dispatcher is used for app
app-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
# Min number of threads to cap factor-based parallelism number to
parallelism-min = 2
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 12
# The parallelism factor is used to determine thread pool size using the
# following formula: ceil(available processors * factor). Resulting size
# is then bounded by the parallelism-min and parallelism-max values.
parallelism-factor = 1.0
}
# How long time the dispatcher will wait for new actors until it shuts down
shutdown-timeout = 1s
# Throughput defines the number of messages that are processed in a batch
# before the thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
}
# This dispatcher is used for rpc actors
rpc-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
# Min number of threads to cap factor-based parallelism number to
parallelism-min = 2
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 12
# The parallelism factor is used to determine thread pool size using the
# following formula: ceil(available processors * factor). Resulting size
# is then bounded by the parallelism-min and parallelism-max values.
parallelism-factor = 0.5
}
# How long time the dispatcher will wait for new actors until it shuts down
shutdown-timeout = 1s
# Throughput defines the number of messages that are processed in a batch
# before the thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
}
# This dispatcher is used for auth
core-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
# Min number of threads to cap factor-based parallelism number to
parallelism-min = 2
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 12
# The parallelism factor is used to determine thread pool size using the
# following formula: ceil(available processors * factor). Resulting size
# is then bounded by the parallelism-min and parallelism-max values.
parallelism-factor = 1.0
}
# How long time the dispatcher will wait for new actors until it shuts down
shutdown-timeout = 1s
# Throughput defines the number of messages that are processed in a batch
# before the thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
}
# This dispatcher is used for rule actors
rule-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
# Min number of threads to cap factor-based parallelism number to
parallelism-min = 2
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 12
# The parallelism factor is used to determine thread pool size using the
# following formula: ceil(available processors * factor). Resulting size
# is then bounded by the parallelism-min and parallelism-max values.
parallelism-factor = 1.0
}
# How long time the dispatcher will wait for new actors until it shuts down
shutdown-timeout = 1s
# Throughput defines the number of messages that are processed in a batch
# before the thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
}
# This dispatcher is used for rule actors
plugin-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
# Min number of threads to cap factor-based parallelism number to
parallelism-min = 2
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 12
# The parallelism factor is used to determine thread pool size using the
# following formula: ceil(available processors * factor). Resulting size
# is then bounded by the parallelism-min and parallelism-max values.
parallelism-factor = 1.0
}
# How long time the dispatcher will wait for new actors until it shuts down
shutdown-timeout = 1s
# Throughput defines the number of messages that are processed in a batch
# before the thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
}
# This dispatcher is used for rule actors
session-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
# Min number of threads to cap factor-based parallelism number to
parallelism-min = 2
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 12
# The parallelism factor is used to determine thread pool size using the
# following formula: ceil(available processors * factor). Resulting size
# is then bounded by the parallelism-min and parallelism-max values.
parallelism-factor = 1.0
}
# How long time the dispatcher will wait for new actors until it shuts down
shutdown-timeout = 1s
# Throughput defines the number of messages that are processed in a batch
# before the thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
}

3
application/src/main/resources/banner.txt

@ -0,0 +1,3 @@
===================================================
:: ${application.title} :: ${application.formatted-version}
===================================================

5
application/src/main/resources/i18n/messages.properties

@ -0,0 +1,5 @@
test.message.subject=Test message from Thingsboard
activation.subject=Your account activation on Thingsboard
account.activated.subject=Thingsboard - your account has been activated
reset.password.subject=Thingsboard - Password reset has been requested
password.was.reset.subject=Thingsboard - your account password has been reset

35
application/src/main/resources/logback.xml

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright © 2016 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.
-->
<!DOCTYPE configuration>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.thingsboard.server" level="TRACE" />
<logger name="akka" level="INFO" />
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

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

Loading…
Cancel
Save