Commit c22bf33defb1982446789d9b4ce6b05a8849dfca

Authored by Andrew Shvayka
0 parents

Initial commit

Showing 51 changed files with 4815 additions and 0 deletions

Too many changes to show.

To preserve performance only 51 of 466 files are displayed.

  1 +output/**
  2 +*.class
  3 +*~
  4 +*.iml
  5 +*/.idea/**
  6 +.idea/**
  7 +.idea
  8 +*.log
  9 +*.log.[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
  10 +*/.classpath
  11 +.classpath
  12 +*/.project
  13 +.project
  14 +.cache/**
  15 +target/
  16 +build/
  17 +tmp_deb_control/
  18 +tmp_rpm_control/
  19 +tmp_sh/
  20 +.gwt/
  21 +.settings/
  22 +/bin
  23 +bin/
  24 +**/dependency-reduced-pom.xml
  25 +pom.xml.versionsBackup
  26 +.DS_Store
  27 +**/.gradle
  28 +**/local.properties
  29 +**/build
  30 +**/target
  31 +**/Californium.properties
  32 +**/.env
... ...
  1 + Apache License
  2 + Version 2.0, January 2004
  3 + http://www.apache.org/licenses/
  4 +
  5 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
  6 +
  7 + 1. Definitions.
  8 +
  9 + "License" shall mean the terms and conditions for use, reproduction,
  10 + and distribution as defined by Sections 1 through 9 of this document.
  11 +
  12 + "Licensor" shall mean the copyright owner or entity authorized by
  13 + the copyright owner that is granting the License.
  14 +
  15 + "Legal Entity" shall mean the union of the acting entity and all
  16 + other entities that control, are controlled by, or are under common
  17 + control with that entity. For the purposes of this definition,
  18 + "control" means (i) the power, direct or indirect, to cause the
  19 + direction or management of such entity, whether by contract or
  20 + otherwise, or (ii) ownership of fifty percent (50%) or more of the
  21 + outstanding shares, or (iii) beneficial ownership of such entity.
  22 +
  23 + "You" (or "Your") shall mean an individual or Legal Entity
  24 + exercising permissions granted by this License.
  25 +
  26 + "Source" form shall mean the preferred form for making modifications,
  27 + including but not limited to software source code, documentation
  28 + source, and configuration files.
  29 +
  30 + "Object" form shall mean any form resulting from mechanical
  31 + transformation or translation of a Source form, including but
  32 + not limited to compiled object code, generated documentation,
  33 + and conversions to other media types.
  34 +
  35 + "Work" shall mean the work of authorship, whether in Source or
  36 + Object form, made available under the License, as indicated by a
  37 + copyright notice that is included in or attached to the work
  38 + (an example is provided in the Appendix below).
  39 +
  40 + "Derivative Works" shall mean any work, whether in Source or Object
  41 + form, that is based on (or derived from) the Work and for which the
  42 + editorial revisions, annotations, elaborations, or other modifications
  43 + represent, as a whole, an original work of authorship. For the purposes
  44 + of this License, Derivative Works shall not include works that remain
  45 + separable from, or merely link (or bind by name) to the interfaces of,
  46 + the Work and Derivative Works thereof.
  47 +
  48 + "Contribution" shall mean any work of authorship, including
  49 + the original version of the Work and any modifications or additions
  50 + to that Work or Derivative Works thereof, that is intentionally
  51 + submitted to Licensor for inclusion in the Work by the copyright owner
  52 + or by an individual or Legal Entity authorized to submit on behalf of
  53 + the copyright owner. For the purposes of this definition, "submitted"
  54 + means any form of electronic, verbal, or written communication sent
  55 + to the Licensor or its representatives, including but not limited to
  56 + communication on electronic mailing lists, source code control systems,
  57 + and issue tracking systems that are managed by, or on behalf of, the
  58 + Licensor for the purpose of discussing and improving the Work, but
  59 + excluding communication that is conspicuously marked or otherwise
  60 + designated in writing by the copyright owner as "Not a Contribution."
  61 +
  62 + "Contributor" shall mean Licensor and any individual or Legal Entity
  63 + on behalf of whom a Contribution has been received by Licensor and
  64 + subsequently incorporated within the Work.
  65 +
  66 + 2. Grant of Copyright License. Subject to the terms and conditions of
  67 + this License, each Contributor hereby grants to You a perpetual,
  68 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable
  69 + copyright license to reproduce, prepare Derivative Works of,
  70 + publicly display, publicly perform, sublicense, and distribute the
  71 + Work and such Derivative Works in Source or Object form.
  72 +
  73 + 3. Grant of Patent License. Subject to the terms and conditions of
  74 + this License, each Contributor hereby grants to You a perpetual,
  75 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable
  76 + (except as stated in this section) patent license to make, have made,
  77 + use, offer to sell, sell, import, and otherwise transfer the Work,
  78 + where such license applies only to those patent claims licensable
  79 + by such Contributor that are necessarily infringed by their
  80 + Contribution(s) alone or by combination of their Contribution(s)
  81 + with the Work to which such Contribution(s) was submitted. If You
  82 + institute patent litigation against any entity (including a
  83 + cross-claim or counterclaim in a lawsuit) alleging that the Work
  84 + or a Contribution incorporated within the Work constitutes direct
  85 + or contributory patent infringement, then any patent licenses
  86 + granted to You under this License for that Work shall terminate
  87 + as of the date such litigation is filed.
  88 +
  89 + 4. Redistribution. You may reproduce and distribute copies of the
  90 + Work or Derivative Works thereof in any medium, with or without
  91 + modifications, and in Source or Object form, provided that You
  92 + meet the following conditions:
  93 +
  94 + (a) You must give any other recipients of the Work or
  95 + Derivative Works a copy of this License; and
  96 +
  97 + (b) You must cause any modified files to carry prominent notices
  98 + stating that You changed the files; and
  99 +
  100 + (c) You must retain, in the Source form of any Derivative Works
  101 + that You distribute, all copyright, patent, trademark, and
  102 + attribution notices from the Source form of the Work,
  103 + excluding those notices that do not pertain to any part of
  104 + the Derivative Works; and
  105 +
  106 + (d) If the Work includes a "NOTICE" text file as part of its
  107 + distribution, then any Derivative Works that You distribute must
  108 + include a readable copy of the attribution notices contained
  109 + within such NOTICE file, excluding those notices that do not
  110 + pertain to any part of the Derivative Works, in at least one
  111 + of the following places: within a NOTICE text file distributed
  112 + as part of the Derivative Works; within the Source form or
  113 + documentation, if provided along with the Derivative Works; or,
  114 + within a display generated by the Derivative Works, if and
  115 + wherever such third-party notices normally appear. The contents
  116 + of the NOTICE file are for informational purposes only and
  117 + do not modify the License. You may add Your own attribution
  118 + notices within Derivative Works that You distribute, alongside
  119 + or as an addendum to the NOTICE text from the Work, provided
  120 + that such additional attribution notices cannot be construed
  121 + as modifying the License.
  122 +
  123 + You may add Your own copyright statement to Your modifications and
  124 + may provide additional or different license terms and conditions
  125 + for use, reproduction, or distribution of Your modifications, or
  126 + for any such Derivative Works as a whole, provided Your use,
  127 + reproduction, and distribution of the Work otherwise complies with
  128 + the conditions stated in this License.
  129 +
  130 + 5. Submission of Contributions. Unless You explicitly state otherwise,
  131 + any Contribution intentionally submitted for inclusion in the Work
  132 + by You to the Licensor shall be under the terms and conditions of
  133 + this License, without any additional terms or conditions.
  134 + Notwithstanding the above, nothing herein shall supersede or modify
  135 + the terms of any separate license agreement you may have executed
  136 + with Licensor regarding such Contributions.
  137 +
  138 + 6. Trademarks. This License does not grant permission to use the trade
  139 + names, trademarks, service marks, or product names of the Licensor,
  140 + except as required for reasonable and customary use in describing the
  141 + origin of the Work and reproducing the content of the NOTICE file.
  142 +
  143 + 7. Disclaimer of Warranty. Unless required by applicable law or
  144 + agreed to in writing, Licensor provides the Work (and each
  145 + Contributor provides its Contributions) on an "AS IS" BASIS,
  146 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  147 + implied, including, without limitation, any warranties or conditions
  148 + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
  149 + PARTICULAR PURPOSE. You are solely responsible for determining the
  150 + appropriateness of using or redistributing the Work and assume any
  151 + risks associated with Your exercise of permissions under this License.
  152 +
  153 + 8. Limitation of Liability. In no event and under no legal theory,
  154 + whether in tort (including negligence), contract, or otherwise,
  155 + unless required by applicable law (such as deliberate and grossly
  156 + negligent acts) or agreed to in writing, shall any Contributor be
  157 + liable to You for damages, including any direct, indirect, special,
  158 + incidental, or consequential damages of any character arising as a
  159 + result of this License or out of the use or inability to use the
  160 + Work (including but not limited to damages for loss of goodwill,
  161 + work stoppage, computer failure or malfunction, or any and all
  162 + other commercial damages or losses), even if such Contributor
  163 + has been advised of the possibility of such damages.
  164 +
  165 + 9. Accepting Warranty or Additional Liability. While redistributing
  166 + the Work or Derivative Works thereof, You may choose to offer,
  167 + and charge a fee for, acceptance of support, warranty, indemnity,
  168 + or other liability obligations and/or rights consistent with this
  169 + License. However, in accepting such obligations, You may act only
  170 + on Your own behalf and on Your sole responsibility, not on behalf
  171 + of any other Contributor, and only if You agree to indemnify,
  172 + defend, and hold each Contributor harmless for any liability
  173 + incurred by, or claims asserted against, such Contributor by reason
  174 + of your accepting any such warranty or additional liability.
  175 +
  176 + END OF TERMS AND CONDITIONS
  177 +
  178 + APPENDIX: How to apply the Apache License to your work.
  179 +
  180 + To apply the Apache License to your work, attach the following
  181 + boilerplate notice, with the fields enclosed by brackets "{}"
  182 + replaced with your own identifying information. (Don't include
  183 + the brackets!) The text should be enclosed in the appropriate
  184 + comment syntax for the file format. We also recommend that a
  185 + file or class name and description of purpose be included on the
  186 + same "printed page" as the copyright notice for easier
  187 + identification within third-party archives.
  188 +
  189 + Copyright 2016 The Thingsboard Authors
  190 +
  191 + Licensed under the Apache License, Version 2.0 (the "License");
  192 + you may not use this file except in compliance with the License.
  193 + You may obtain a copy of the License at
  194 +
  195 + http://www.apache.org/licenses/LICENSE-2.0
  196 +
  197 + Unless required by applicable law or agreed to in writing, software
  198 + distributed under the License is distributed on an "AS IS" BASIS,
  199 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  200 + See the License for the specific language governing permissions and
  201 + limitations under the License.
... ...
  1 +# iotrules
  2 +IoT Rules Engine
  3 +
  4 +**Docker usage**
  5 +
  6 +**start platfrom using docker:**
  7 +- install docker
  8 +- cd to 'docker' folder
  9 +- create folder for cassandra data directory on your local env (host)
  10 + - `mkdir /home/user/data_dir`
  11 +- modify .env file to point to the directory created in previous step
  12 +- start ./deploy.sh script to run all the services
  13 +
  14 +
  15 +**start-up for local development**
  16 +
  17 +cassandra with thingsboard schema (9042 and 9061 ports are exposed).
  18 +zookeper services (2181 port is exposed).
  19 +9042, 9061 and 2181 ports must be free so 'Thingsboard' server that is running outside docker container is able to connect to services.
  20 +you can change these ports in docker-compose.static.yml file to some others, but 'Thingsbaord' application.yml file must be updated accordingly.
  21 +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.
  22 +
  23 +- install docker
  24 +- cd to 'docker' folder
  25 +- create folder for cassandra data directory on your local env (host)
  26 + - `mkdir /home/user/data_dir`
  27 +- modify .env file to point to the directory created in previous step
  28 +- start ./deploy_cassandra_zookeeper.sh script to run cassandra with thingsboard schema and zookeper services
  29 +- Start boot class: _org.thingsboard.server.ThingsboardServerApplication_
... ...
  1 +!bin/
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +buildscript {
  18 + ext {
  19 + osPackageVersion = "3.8.0"
  20 + }
  21 + repositories {
  22 + jcenter()
  23 + }
  24 + dependencies {
  25 + classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}")
  26 + }
  27 +}
  28 +
  29 +apply plugin: "nebula.ospackage"
  30 +
  31 +buildDir = projectBuildDir
  32 +version = projectVersion
  33 +distsDirName = "./"
  34 +
  35 +// OS Package plugin configuration
  36 +ospackage {
  37 + packageName = pkgName
  38 + version = "${project.version}"
  39 + release = 1
  40 + os = LINUX
  41 + type = BINARY
  42 +
  43 + into pkgInstallFolder
  44 +
  45 + user pkgName
  46 + permissionGroup pkgName
  47 +
  48 + // Copy the actual .jar file
  49 + from(mainJar) {
  50 + // Strip the version from the jar filename
  51 + rename { String fileName ->
  52 + fileName.replace("-${project.version}", "")
  53 + }
  54 + fileMode 0500
  55 + into "bin"
  56 + }
  57 +
  58 + // Copy the config files
  59 + from("target/conf") {
  60 + fileType CONFIG | NOREPLACE
  61 + fileMode 0754
  62 + into "conf"
  63 + }
  64 +
  65 + // Copy the data files
  66 + from("target/data") {
  67 + fileType CONFIG | NOREPLACE
  68 + fileMode 0754
  69 + into "data"
  70 + }
  71 +
  72 + // Copy the extensions files
  73 + from("target/extensions") {
  74 + into "extensions"
  75 + }
  76 +}
  77 +
  78 +// Configure our RPM build task
  79 +buildRpm {
  80 +
  81 + arch = NOARCH
  82 +
  83 + version = projectVersion.replace('-', '')
  84 + archiveName = "${pkgName}.rpm"
  85 +
  86 + requires("java-1.8.0")
  87 +
  88 + preInstall file("${buildDir}/control/rpm/preinst")
  89 + postInstall file("${buildDir}/control/rpm/postinst")
  90 + preUninstall file("${buildDir}/control/rpm/prerm")
  91 + postUninstall file("${buildDir}/control/rpm/postrm")
  92 +
  93 + user pkgName
  94 + permissionGroup pkgName
  95 +
  96 + // Copy the system unit files
  97 + from("${buildDir}/control/${pkgName}.service") {
  98 + addParentDirs = false
  99 + fileMode 0644
  100 + into "/usr/lib/systemd/system"
  101 + }
  102 +
  103 + directory(pkgLogFolder, 0755)
  104 + link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
  105 + link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
  106 +}
  107 +
  108 +// Same as the buildRpm task
  109 +buildDeb {
  110 +
  111 + arch = "all"
  112 +
  113 + archiveName = "${pkgName}.deb"
  114 +
  115 + requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer")
  116 +
  117 + configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf")
  118 + configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml")
  119 + configurationFile("${pkgInstallFolder}/conf/logback.xml")
  120 + configurationFile("${pkgInstallFolder}/conf/actor-system.conf")
  121 +
  122 + preInstall file("${buildDir}/control/deb/preinst")
  123 + postInstall file("${buildDir}/control/deb/postinst")
  124 + preUninstall file("${buildDir}/control/deb/prerm")
  125 + postUninstall file("${buildDir}/control/deb/postrm")
  126 +
  127 + user pkgName
  128 + permissionGroup pkgName
  129 +
  130 + directory(pkgLogFolder, 0755)
  131 + link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar")
  132 + link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
  133 + link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
  134 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  19 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  20 + <modelVersion>4.0.0</modelVersion>
  21 + <parent>
  22 + <groupId>org.thingsboard</groupId>
  23 + <version>0.0.1-SNAPSHOT</version>
  24 + <artifactId>server</artifactId>
  25 + </parent>
  26 + <groupId>org.thingsboard.server</groupId>
  27 + <artifactId>application</artifactId>
  28 + <packaging>jar</packaging>
  29 +
  30 + <name>Thingsboard Server Application</name>
  31 + <url>http://thingsboard.org</url>
  32 +
  33 + <properties>
  34 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  35 + <main.dir>${basedir}/..</main.dir>
  36 + <pkg.name>thingsboard</pkg.name>
  37 + <pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder>
  38 + <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
  39 + </properties>
  40 +
  41 + <dependencies>
  42 + <dependency>
  43 + <groupId>io.netty</groupId>
  44 + <artifactId>netty-transport-native-epoll</artifactId>
  45 + <version>${netty.version}</version>
  46 + <!-- Explicitly bring in the linux classifier, test may fail on 32-bit linux -->
  47 + <classifier>linux-x86_64</classifier>
  48 + </dependency>
  49 + <dependency>
  50 + <groupId>org.thingsboard.server</groupId>
  51 + <artifactId>extensions-api</artifactId>
  52 + </dependency>
  53 + <dependency>
  54 + <groupId>org.thingsboard.server</groupId>
  55 + <artifactId>extensions-core</artifactId>
  56 + </dependency>
  57 + <dependency>
  58 + <groupId>org.thingsboard.server.common</groupId>
  59 + <artifactId>transport</artifactId>
  60 + </dependency>
  61 + <dependency>
  62 + <groupId>org.thingsboard.server.transport</groupId>
  63 + <artifactId>http</artifactId>
  64 + </dependency>
  65 + <dependency>
  66 + <groupId>org.thingsboard.server.transport</groupId>
  67 + <artifactId>coap</artifactId>
  68 + </dependency>
  69 + <dependency>
  70 + <groupId>org.thingsboard.server.transport</groupId>
  71 + <artifactId>mqtt</artifactId>
  72 + </dependency>
  73 + <dependency>
  74 + <groupId>org.thingsboard.server</groupId>
  75 + <artifactId>dao</artifactId>
  76 + </dependency>
  77 + <dependency>
  78 + <groupId>org.thingsboard.server</groupId>
  79 + <artifactId>dao</artifactId>
  80 + <type>test-jar</type>
  81 + <scope>test</scope>
  82 + </dependency>
  83 + <dependency>
  84 + <groupId>io.takari.junit</groupId>
  85 + <artifactId>takari-cpsuite</artifactId>
  86 + <scope>test</scope>
  87 + </dependency>
  88 + <dependency>
  89 + <groupId>org.cassandraunit</groupId>
  90 + <artifactId>cassandra-unit</artifactId>
  91 + <exclusions>
  92 + <exclusion>
  93 + <groupId>org.slf4j</groupId>
  94 + <artifactId>slf4j-log4j12</artifactId>
  95 + </exclusion>
  96 + </exclusions>
  97 + <scope>test</scope>
  98 + </dependency>
  99 + <dependency>
  100 + <groupId>org.thingsboard.server</groupId>
  101 + <artifactId>ui</artifactId>
  102 + <version>${project.version}</version>
  103 + <scope>runtime</scope>
  104 + </dependency>
  105 + <dependency>
  106 + <groupId>org.springframework.boot</groupId>
  107 + <artifactId>spring-boot-starter-security</artifactId>
  108 + </dependency>
  109 + <dependency>
  110 + <groupId>org.springframework.boot</groupId>
  111 + <artifactId>spring-boot-starter-web</artifactId>
  112 + </dependency>
  113 + <dependency>
  114 + <groupId>org.springframework.boot</groupId>
  115 + <artifactId>spring-boot-starter-websocket</artifactId>
  116 + </dependency>
  117 + <dependency>
  118 + <groupId>io.jsonwebtoken</groupId>
  119 + <artifactId>jjwt</artifactId>
  120 + </dependency>
  121 + <dependency>
  122 + <groupId>joda-time</groupId>
  123 + <artifactId>joda-time</artifactId>
  124 + </dependency>
  125 + <dependency>
  126 + <groupId>org.apache.velocity</groupId>
  127 + <artifactId>velocity</artifactId>
  128 + </dependency>
  129 + <dependency>
  130 + <groupId>org.apache.velocity</groupId>
  131 + <artifactId>velocity-tools</artifactId>
  132 + </dependency>
  133 + <dependency>
  134 + <groupId>org.springframework</groupId>
  135 + <artifactId>spring-context-support</artifactId>
  136 + </dependency>
  137 + <dependency>
  138 + <groupId>org.springframework.boot</groupId>
  139 + <artifactId>spring-boot-starter-test</artifactId>
  140 + <scope>test</scope>
  141 + </dependency>
  142 + <dependency>
  143 + <groupId>org.springframework.security</groupId>
  144 + <artifactId>spring-security-test</artifactId>
  145 + <scope>test</scope>
  146 + </dependency>
  147 + <dependency>
  148 + <groupId>com.jayway.jsonpath</groupId>
  149 + <artifactId>json-path</artifactId>
  150 + <scope>test</scope>
  151 + </dependency>
  152 + <dependency>
  153 + <groupId>com.jayway.jsonpath</groupId>
  154 + <artifactId>json-path-assert</artifactId>
  155 + <scope>test</scope>
  156 + </dependency>
  157 + <dependency>
  158 + <groupId>com.typesafe.akka</groupId>
  159 + <artifactId>akka-actor_${scala.version}</artifactId>
  160 + </dependency>
  161 + <dependency>
  162 + <groupId>com.typesafe.akka</groupId>
  163 + <artifactId>akka-slf4j_${scala.version}</artifactId>
  164 + </dependency>
  165 + <dependency>
  166 + <groupId>org.slf4j</groupId>
  167 + <artifactId>slf4j-api</artifactId>
  168 + </dependency>
  169 + <dependency>
  170 + <groupId>org.slf4j</groupId>
  171 + <artifactId>log4j-over-slf4j</artifactId>
  172 + </dependency>
  173 + <dependency>
  174 + <groupId>ch.qos.logback</groupId>
  175 + <artifactId>logback-core</artifactId>
  176 + </dependency>
  177 + <dependency>
  178 + <groupId>ch.qos.logback</groupId>
  179 + <artifactId>logback-classic</artifactId>
  180 + </dependency>
  181 + <dependency>
  182 + <groupId>junit</groupId>
  183 + <artifactId>junit</artifactId>
  184 + <scope>test</scope>
  185 + </dependency>
  186 + <dependency>
  187 + <groupId>org.mockito</groupId>
  188 + <artifactId>mockito-all</artifactId>
  189 + <scope>test</scope>
  190 + </dependency>
  191 + <dependency>
  192 + <groupId>javax.mail</groupId>
  193 + <artifactId>mail</artifactId>
  194 + </dependency>
  195 + <dependency>
  196 + <groupId>org.apache.curator</groupId>
  197 + <artifactId>curator-recipes</artifactId>
  198 + </dependency>
  199 + <dependency>
  200 + <groupId>com.google.protobuf</groupId>
  201 + <artifactId>protobuf-java</artifactId>
  202 + </dependency>
  203 + <dependency>
  204 + <groupId>io.grpc</groupId>
  205 + <artifactId>grpc-netty</artifactId>
  206 + </dependency>
  207 + <dependency>
  208 + <groupId>io.grpc</groupId>
  209 + <artifactId>grpc-protobuf</artifactId>
  210 + </dependency>
  211 + <dependency>
  212 + <groupId>io.grpc</groupId>
  213 + <artifactId>grpc-stub</artifactId>
  214 + </dependency>
  215 + </dependencies>
  216 +
  217 + <build>
  218 + <finalName>${pkg.name}-${project.version}</finalName>
  219 + <resources>
  220 + <resource>
  221 + <directory>${project.basedir}/src/main/resources</directory>
  222 + </resource>
  223 + </resources>
  224 + <plugins>
  225 + <plugin>
  226 + <groupId>org.apache.maven.plugins</groupId>
  227 + <artifactId>maven-surefire-plugin</artifactId>
  228 + <version>${surfire.version}</version>
  229 + <configuration>
  230 + <systemPropertyVariables>
  231 + <spring.config.name>thingsboard</spring.config.name>
  232 + </systemPropertyVariables>
  233 + <includes>
  234 + <include>**/*TestSuite.java</include>
  235 + </includes>
  236 + </configuration>
  237 + </plugin>
  238 + <plugin>
  239 + <groupId>org.apache.maven.plugins</groupId>
  240 + <artifactId>maven-resources-plugin</artifactId>
  241 + <executions>
  242 + <execution>
  243 + <id>copy-conf</id>
  244 + <phase>process-resources</phase>
  245 + <goals>
  246 + <goal>copy-resources</goal>
  247 + </goals>
  248 + <configuration>
  249 + <outputDirectory>${project.build.directory}/conf</outputDirectory>
  250 + <resources>
  251 + <resource>
  252 + <directory>src/main/resources</directory>
  253 + <excludes>
  254 + <exclude>logback.xml</exclude>
  255 + </excludes>
  256 + <filtering>false</filtering>
  257 + </resource>
  258 + </resources>
  259 + </configuration>
  260 + </execution>
  261 + <execution>
  262 + <id>copy-service-conf</id>
  263 + <phase>process-resources</phase>
  264 + <goals>
  265 + <goal>copy-resources</goal>
  266 + </goals>
  267 + <configuration>
  268 + <outputDirectory>${project.build.directory}/conf</outputDirectory>
  269 + <resources>
  270 + <resource>
  271 + <directory>src/main/conf</directory>
  272 + <filtering>true</filtering>
  273 + </resource>
  274 + </resources>
  275 + </configuration>
  276 + </execution>
  277 + <execution>
  278 + <id>copy-control</id>
  279 + <phase>process-resources</phase>
  280 + <goals>
  281 + <goal>copy-resources</goal>
  282 + </goals>
  283 + <configuration>
  284 + <outputDirectory>${project.build.directory}/control</outputDirectory>
  285 + <resources>
  286 + <resource>
  287 + <directory>src/main/scripts/control</directory>
  288 + <filtering>true</filtering>
  289 + </resource>
  290 + </resources>
  291 + </configuration>
  292 + </execution>
  293 + <execution>
  294 + <id>copy-data-cql</id>
  295 + <phase>process-resources</phase>
  296 + <goals>
  297 + <goal>copy-resources</goal>
  298 + </goals>
  299 + <configuration>
  300 + <outputDirectory>${project.build.directory}/data</outputDirectory>
  301 + <resources>
  302 + <resource>
  303 + <directory>../dao/src/main/resources</directory>
  304 + <includes>
  305 + <include>**/*.cql</include>
  306 + </includes>
  307 + <filtering>false</filtering>
  308 + </resource>
  309 + </resources>
  310 + </configuration>
  311 + </execution>
  312 + </executions>
  313 + </plugin>
  314 + <plugin>
  315 + <groupId>org.apache.maven.plugins</groupId>
  316 + <artifactId>maven-dependency-plugin</artifactId>
  317 + <executions>
  318 + <execution>
  319 + <id>copy-extensions</id>
  320 + <phase>package</phase>
  321 + <goals>
  322 + <goal>copy</goal>
  323 + </goals>
  324 + <configuration>
  325 + <outputDirectory>${project.build.directory}/extensions</outputDirectory>
  326 + <artifactItems>
  327 + <artifactItem>
  328 + <groupId>org.thingsboard.server.extensions</groupId>
  329 + <artifactId>extension-rabbitmq</artifactId>
  330 + <classifier>extension</classifier>
  331 + </artifactItem>
  332 + <artifactItem>
  333 + <groupId>org.thingsboard.server.extensions</groupId>
  334 + <artifactId>extension-rest-api-call</artifactId>
  335 + <classifier>extension</classifier>
  336 + </artifactItem>
  337 + <artifactItem>
  338 + <groupId>org.thingsboard.server.extensions</groupId>
  339 + <artifactId>extension-kafka</artifactId>
  340 + <classifier>extension</classifier>
  341 + </artifactItem>
  342 + </artifactItems>
  343 + </configuration>
  344 + </execution>
  345 + </executions>
  346 + </plugin>
  347 + <plugin>
  348 + <groupId>org.apache.maven.plugins</groupId>
  349 + <artifactId>maven-jar-plugin</artifactId>
  350 + <configuration>
  351 + <archive>
  352 + <manifestEntries>
  353 + <Implementation-Title>Thingsboard</Implementation-Title>
  354 + <Implementation-Version>${project.version}</Implementation-Version>
  355 + </manifestEntries>
  356 + </archive>
  357 + </configuration>
  358 + </plugin>
  359 + <plugin>
  360 + <groupId>org.springframework.boot</groupId>
  361 + <artifactId>spring-boot-maven-plugin</artifactId>
  362 + <configuration>
  363 + <layout>ZIP</layout>
  364 + <executable>true</executable>
  365 + <excludeDevtools>true</excludeDevtools>
  366 + <embeddedLaunchScriptProperties>
  367 + <confFolder>${pkg.installFolder}/conf</confFolder>
  368 + <logFolder>${pkg.logFolder}</logFolder>
  369 + <logFilename>${pkg.name}.out</logFilename>
  370 + </embeddedLaunchScriptProperties>
  371 + </configuration>
  372 + <executions>
  373 + <execution>
  374 + <goals>
  375 + <goal>repackage</goal>
  376 + </goals>
  377 + </execution>
  378 + </executions>
  379 + </plugin>
  380 + <plugin>
  381 + <groupId>org.fortasoft</groupId>
  382 + <artifactId>gradle-maven-plugin</artifactId>
  383 + <configuration>
  384 + <tasks>
  385 + <task>build</task>
  386 + <task>buildDeb</task>
  387 + <task>buildRpm</task>
  388 + </tasks>
  389 + <args>
  390 + <arg>-PprojectBuildDir=${project.build.directory}</arg>
  391 + <arg>-PprojectVersion=${project.version}</arg>
  392 + <arg>-PmainJar=${project.build.directory}/${project.build.finalName}.${project.packaging}</arg>
  393 + <arg>-PpkgName=${pkg.name}</arg>
  394 + <arg>-PpkgInstallFolder=${pkg.installFolder}</arg>
  395 + <arg>-PpkgLogFolder=${pkg.logFolder}</arg>
  396 + </args>
  397 + </configuration>
  398 + <executions>
  399 + <execution>
  400 + <phase>package</phase>
  401 + <goals>
  402 + <goal>invoke</goal>
  403 + </goals>
  404 + </execution>
  405 + </executions>
  406 + </plugin>
  407 + <plugin>
  408 + <groupId>org.xolstice.maven.plugins</groupId>
  409 + <artifactId>protobuf-maven-plugin</artifactId>
  410 + </plugin>
  411 + <plugin>
  412 + <groupId>org.codehaus.mojo</groupId>
  413 + <artifactId>build-helper-maven-plugin</artifactId>
  414 + </plugin>
  415 + </plugins>
  416 + </build>
  417 +</project>
... ...
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 +
  4 + Copyright © 2016 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<!DOCTYPE configuration>
  20 +<configuration>
  21 +
  22 + <appender name="fileLogAppender"
  23 + class="ch.qos.logback.core.rolling.RollingFileAppender">
  24 + <file>${pkg.logFolder}/${pkg.name}.log</file>
  25 + <rollingPolicy
  26 + class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
  27 + <fileNamePattern>${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
  28 + <maxFileSize>100MB</maxFileSize>
  29 + <maxHistory>30</maxHistory>
  30 + <totalSizeCap>3GB</totalSizeCap>
  31 + </rollingPolicy>
  32 + <encoder>
  33 + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
  34 + </encoder>
  35 + </appender>
  36 +
  37 + <logger name="org.thingsboard.server" level="INFO" />
  38 + <logger name="akka" level="INFO" />
  39 +
  40 + <root level="INFO">
  41 + <appender-ref ref="fileLogAppender"/>
  42 + </root>
  43 +
  44 +</configuration>
... ...
  1 +#
  2 +# Copyright © 2016 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +export JAVA_OPTS="$JAVA_OPTS"
  18 +export LOG_FILENAME=${pkg.name}.out
  19 +export LOADER_PATH=${pkg.installFolder}/conf,${pkg.installFolder}/extensions
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server;
  17 +
  18 +import org.springframework.boot.SpringApplication;
  19 +import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  20 +import org.springframework.boot.autoconfigure.SpringBootApplication;
  21 +import org.springframework.context.annotation.ComponentScan;
  22 +
  23 +import java.util.Arrays;
  24 +
  25 +@EnableAutoConfiguration
  26 +@SpringBootApplication
  27 +@ComponentScan({"org.thingsboard.server"})
  28 +public class ThingsboardServerApplication {
  29 +
  30 + private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
  31 + private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "thingsboard";
  32 +
  33 + public static void main(String[] args) {
  34 + SpringApplication.run(ThingsboardServerApplication.class, updateArguments(args));
  35 + }
  36 +
  37 + private static String[] updateArguments(String[] args) {
  38 + if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) {
  39 + String[] modifiedArgs = new String[args.length + 1];
  40 + System.arraycopy(args, 0, modifiedArgs, 0, args.length);
  41 + modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM;
  42 + return modifiedArgs;
  43 + }
  44 + return args;
  45 + }
  46 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors;
  17 +
  18 +import akka.actor.ActorRef;
  19 +import akka.actor.ActorSystem;
  20 +import akka.actor.Scheduler;
  21 +import com.fasterxml.jackson.databind.JsonNode;
  22 +import com.fasterxml.jackson.databind.ObjectMapper;
  23 +import com.fasterxml.jackson.databind.node.ObjectNode;
  24 +import com.typesafe.config.Config;
  25 +import com.typesafe.config.ConfigFactory;
  26 +import lombok.Getter;
  27 +import lombok.Setter;
  28 +import org.springframework.beans.factory.annotation.Autowired;
  29 +import org.springframework.beans.factory.annotation.Value;
  30 +import org.springframework.stereotype.Component;
  31 +import org.thingsboard.server.actors.service.ActorService;
  32 +import org.thingsboard.server.common.data.DataConstants;
  33 +import org.thingsboard.server.common.data.Event;
  34 +import org.thingsboard.server.common.data.id.EntityId;
  35 +import org.thingsboard.server.common.data.id.TenantId;
  36 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  37 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  38 +import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  39 +import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
  40 +import org.thingsboard.server.dao.attributes.AttributesService;
  41 +import org.thingsboard.server.dao.customer.CustomerService;
  42 +import org.thingsboard.server.dao.device.DeviceService;
  43 +import org.thingsboard.server.dao.event.EventService;
  44 +import org.thingsboard.server.dao.plugin.PluginService;
  45 +import org.thingsboard.server.dao.rule.RuleService;
  46 +import org.thingsboard.server.dao.tenant.TenantService;
  47 +import org.thingsboard.server.dao.timeseries.TimeseriesService;
  48 +import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
  49 +import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
  50 +import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
  51 +import org.thingsboard.server.service.component.ComponentDiscoveryService;
  52 +
  53 +import java.io.PrintWriter;
  54 +import java.io.StringWriter;
  55 +import java.util.Optional;
  56 +
  57 +@Component
  58 +public class ActorSystemContext {
  59 + private static final String AKKA_CONF_FILE_NAME = "actor-system.conf";
  60 +
  61 + protected final ObjectMapper mapper = new ObjectMapper();
  62 +
  63 + @Getter @Setter private ActorService actorService;
  64 +
  65 + @Autowired
  66 + @Getter private DiscoveryService discoveryService;
  67 +
  68 + @Autowired
  69 + @Getter @Setter private ComponentDiscoveryService componentService;
  70 +
  71 + @Autowired
  72 + @Getter private ClusterRoutingService routingService;
  73 +
  74 + @Autowired
  75 + @Getter private ClusterRpcService rpcService;
  76 +
  77 + @Autowired
  78 + @Getter private DeviceAuthService deviceAuthService;
  79 +
  80 + @Autowired
  81 + @Getter private DeviceService deviceService;
  82 +
  83 + @Autowired
  84 + @Getter private TenantService tenantService;
  85 +
  86 + @Autowired
  87 + @Getter private CustomerService customerService;
  88 +
  89 + @Autowired
  90 + @Getter private RuleService ruleService;
  91 +
  92 + @Autowired
  93 + @Getter private PluginService pluginService;
  94 +
  95 + @Autowired
  96 + @Getter private TimeseriesService tsService;
  97 +
  98 + @Autowired
  99 + @Getter private AttributesService attributesService;
  100 +
  101 + @Autowired
  102 + @Getter private EventService eventService;
  103 +
  104 + @Autowired
  105 + @Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint;
  106 +
  107 + @Value("${actors.session.sync.timeout}")
  108 + @Getter private long syncSessionTimeout;
  109 +
  110 + @Value("${actors.plugin.termination.delay}")
  111 + @Getter private long pluginActorTerminationDelay;
  112 +
  113 + @Value("${actors.plugin.processing.timeout}")
  114 + @Getter private long pluginProcessingTimeout;
  115 +
  116 + @Value("${actors.plugin.error_persist_frequency}")
  117 + @Getter private long pluginErrorPersistFrequency;
  118 +
  119 + @Value("${actors.rule.termination.delay}")
  120 + @Getter private long ruleActorTerminationDelay;
  121 +
  122 + @Value("${actors.rule.error_persist_frequency}")
  123 + @Getter private long ruleErrorPersistFrequency;
  124 +
  125 + @Value("${actors.statistics.enabled}")
  126 + @Getter private boolean statisticsEnabled;
  127 +
  128 + @Value("${actors.statistics.persist_frequency}")
  129 + @Getter private long statisticsPersistFrequency;
  130 +
  131 + @Getter @Setter private ActorSystem actorSystem;
  132 +
  133 + @Getter @Setter private ActorRef appActor;
  134 +
  135 + @Getter @Setter private ActorRef sessionManagerActor;
  136 +
  137 + @Getter @Setter private ActorRef statsActor;
  138 +
  139 + @Getter private final Config config;
  140 +
  141 + public ActorSystemContext() {
  142 + config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load());
  143 + }
  144 +
  145 + public Scheduler getScheduler() {
  146 + return actorSystem.scheduler();
  147 + }
  148 +
  149 + public void persistError(TenantId tenantId, EntityId entityId, String method, Exception e) {
  150 + Event event = new Event();
  151 + event.setTenantId(tenantId);
  152 + event.setEntityId(entityId);
  153 + event.setType(DataConstants.ERROR);
  154 + event.setBody(toBodyJson(discoveryService.getCurrentServer().getServerAddress(), method, toString(e)));
  155 + persistEvent(event);
  156 + }
  157 +
  158 + public void persistLifecycleEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent lcEvent, Exception e) {
  159 + Event event = new Event();
  160 + event.setTenantId(tenantId);
  161 + event.setEntityId(entityId);
  162 + event.setType(DataConstants.LC_EVENT);
  163 + event.setBody(toBodyJson(discoveryService.getCurrentServer().getServerAddress(), lcEvent, Optional.ofNullable(e)));
  164 + persistEvent(event);
  165 + }
  166 +
  167 + private void persistEvent(Event event) {
  168 + eventService.save(event);
  169 + }
  170 +
  171 + private String toString(Exception e) {
  172 + StringWriter sw = new StringWriter();
  173 + e.printStackTrace(new PrintWriter(sw));
  174 + return sw.toString();
  175 + }
  176 +
  177 + private JsonNode toBodyJson(ServerAddress server, ComponentLifecycleEvent event, Optional<Exception> e) {
  178 + ObjectNode node = mapper.createObjectNode().put("server", server.toString()).put("event", event.name());
  179 + if (e.isPresent()) {
  180 + node = node.put("success", false);
  181 + node = node.put("error", toString(e.get()));
  182 + } else {
  183 + node = node.put("success", true);
  184 + }
  185 + return node;
  186 + }
  187 +
  188 + private JsonNode toBodyJson(ServerAddress server, String method, String body) {
  189 + return mapper.createObjectNode().put("server", server.toString()).put("method", method).put("error", body);
  190 + }
  191 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.app;
  17 +
  18 +import akka.actor.*;
  19 +import akka.actor.SupervisorStrategy.Directive;
  20 +import akka.event.Logging;
  21 +import akka.event.LoggingAdapter;
  22 +import akka.japi.Function;
  23 +import org.thingsboard.server.actors.ActorSystemContext;
  24 +import org.thingsboard.server.actors.plugin.PluginTerminationMsg;
  25 +import org.thingsboard.server.actors.service.ContextAwareActor;
  26 +import org.thingsboard.server.actors.service.ContextBasedCreator;
  27 +import org.thingsboard.server.actors.service.DefaultActorService;
  28 +import org.thingsboard.server.actors.shared.plugin.PluginManager;
  29 +import org.thingsboard.server.actors.shared.plugin.SystemPluginManager;
  30 +import org.thingsboard.server.actors.shared.rule.RuleManager;
  31 +import org.thingsboard.server.actors.shared.rule.SystemRuleManager;
  32 +import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
  33 +import org.thingsboard.server.actors.tenant.TenantActor;
  34 +import org.thingsboard.server.common.data.Tenant;
  35 +import org.thingsboard.server.common.data.id.TenantId;
  36 +import org.thingsboard.server.common.data.page.PageDataIterable;
  37 +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  38 +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  39 +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
  40 +import org.thingsboard.server.dao.model.ModelConstants;
  41 +import org.thingsboard.server.dao.tenant.TenantService;
  42 +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
  43 +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
  44 +import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg;
  45 +import scala.concurrent.duration.Duration;
  46 +
  47 +import java.util.HashMap;
  48 +import java.util.Map;
  49 +import java.util.Optional;
  50 +
  51 +public class AppActor extends ContextAwareActor {
  52 +
  53 + private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
  54 +
  55 + public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
  56 + private final RuleManager ruleManager;
  57 + private final PluginManager pluginManager;
  58 + private final TenantService tenantService;
  59 + private final Map<TenantId, ActorRef> tenantActors;
  60 +
  61 + private AppActor(ActorSystemContext systemContext) {
  62 + super(systemContext);
  63 + this.ruleManager = new SystemRuleManager(systemContext);
  64 + this.pluginManager = new SystemPluginManager(systemContext);
  65 + this.tenantService = systemContext.getTenantService();
  66 + this.tenantActors = new HashMap<>();
  67 + }
  68 +
  69 + @Override
  70 + public SupervisorStrategy supervisorStrategy() {
  71 + return strategy;
  72 + }
  73 +
  74 + @Override
  75 + public void preStart() {
  76 + logger.info("Starting main system actor.");
  77 + try {
  78 + ruleManager.init(this.context());
  79 + pluginManager.init(this.context());
  80 +
  81 + PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(link -> tenantService.findTenants(link), ENTITY_PACK_LIMIT);
  82 + for (Tenant tenant : tenantIterator) {
  83 + logger.debug("[{}] Creating tenant actor", tenant.getId());
  84 + getOrCreateTenantActor(tenant.getId());
  85 + logger.debug("Tenant actor created.");
  86 + }
  87 +
  88 + logger.info("Main system actor started.");
  89 + } catch (Exception e) {
  90 + logger.error(e, "Unknown failure");
  91 + }
  92 + }
  93 +
  94 + @Override
  95 + public void onReceive(Object msg) throws Exception {
  96 + logger.debug("Received message: {}", msg);
  97 + if (msg instanceof ToDeviceActorMsg) {
  98 + processDeviceMsg((ToDeviceActorMsg) msg);
  99 + } else if (msg instanceof ToPluginActorMsg) {
  100 + onToPluginMsg((ToPluginActorMsg) msg);
  101 + } else if (msg instanceof ToRuleActorMsg) {
  102 + onToRuleMsg((ToRuleActorMsg) msg);
  103 + } else if (msg instanceof ToDeviceActorNotificationMsg) {
  104 + onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
  105 + } else if (msg instanceof Terminated) {
  106 + processTermination((Terminated) msg);
  107 + } else if (msg instanceof ClusterEventMsg) {
  108 + broadcast(msg);
  109 + } else if (msg instanceof ComponentLifecycleMsg) {
  110 + onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
  111 + } else if (msg instanceof PluginTerminationMsg) {
  112 + onPluginTerminated((PluginTerminationMsg) msg);
  113 + } else {
  114 + logger.warning("Unknown message: {}!", msg);
  115 + }
  116 + }
  117 +
  118 + private void onPluginTerminated(PluginTerminationMsg msg) {
  119 + pluginManager.remove(msg.getId());
  120 + }
  121 +
  122 + private void broadcast(Object msg) {
  123 + pluginManager.broadcast(msg);
  124 + tenantActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
  125 + }
  126 +
  127 + private void onToRuleMsg(ToRuleActorMsg msg) {
  128 + ActorRef target;
  129 + if (SYSTEM_TENANT.equals(msg.getTenantId())) {
  130 + target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId());
  131 + } else {
  132 + target = getOrCreateTenantActor(msg.getTenantId());
  133 + }
  134 + target.tell(msg, ActorRef.noSender());
  135 + }
  136 +
  137 + private void onToPluginMsg(ToPluginActorMsg msg) {
  138 + ActorRef target;
  139 + if (SYSTEM_TENANT.equals(msg.getPluginTenantId())) {
  140 + target = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId());
  141 + } else {
  142 + target = getOrCreateTenantActor(msg.getPluginTenantId());
  143 + }
  144 + target.tell(msg, ActorRef.noSender());
  145 + }
  146 +
  147 + private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
  148 + ActorRef target = null;
  149 + if (SYSTEM_TENANT.equals(msg.getTenantId())) {
  150 + if (msg.getPluginId().isPresent()) {
  151 + target = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId().get());
  152 + } else if (msg.getRuleId().isPresent()) {
  153 + Optional<ActorRef> ref = ruleManager.update(this.context(), msg.getRuleId().get(), msg.getEvent());
  154 + if (ref.isPresent()) {
  155 + target = ref.get();
  156 + } else {
  157 + logger.debug("Failed to find actor for rule: [{}]", msg.getRuleId());
  158 + return;
  159 + }
  160 + }
  161 + } else {
  162 + target = getOrCreateTenantActor(msg.getTenantId());
  163 + }
  164 + if (target != null) {
  165 + target.tell(msg, ActorRef.noSender());
  166 + }
  167 + }
  168 +
  169 + private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) {
  170 + getOrCreateTenantActor(msg.getTenantId()).tell(msg, ActorRef.noSender());
  171 + }
  172 +
  173 + private void processDeviceMsg(ToDeviceActorMsg toDeviceActorMsg) {
  174 + TenantId tenantId = toDeviceActorMsg.getTenantId();
  175 + ActorRef tenantActor = getOrCreateTenantActor(tenantId);
  176 + if (toDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) {
  177 + tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain()), context().self());
  178 + } else {
  179 + tenantActor.tell(toDeviceActorMsg, context().self());
  180 + }
  181 + }
  182 +
  183 + private ActorRef getOrCreateTenantActor(TenantId tenantId) {
  184 + ActorRef tenantActor = tenantActors.get(tenantId);
  185 + if (tenantActor == null) {
  186 + tenantActor = context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId))
  187 + .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString());
  188 + tenantActors.put(tenantId, tenantActor);
  189 + }
  190 + return tenantActor;
  191 + }
  192 +
  193 + private void processTermination(Terminated message) {
  194 + ActorRef terminated = message.actor();
  195 + if (terminated instanceof LocalActorRef) {
  196 + logger.debug("Removed actor: {}", terminated);
  197 + } else {
  198 + throw new IllegalStateException("Remote actors are not supported!");
  199 + }
  200 + }
  201 +
  202 + public static class ActorCreator extends ContextBasedCreator<AppActor> {
  203 + private static final long serialVersionUID = 1L;
  204 +
  205 + public ActorCreator(ActorSystemContext context) {
  206 + super(context);
  207 + }
  208 +
  209 + @Override
  210 + public AppActor create() throws Exception {
  211 + return new AppActor(context);
  212 + }
  213 + }
  214 +
  215 + private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, Directive>() {
  216 + @Override
  217 + public Directive apply(Throwable t) {
  218 + logger.error(t, "Unknown failure");
  219 + if (t instanceof RuntimeException) {
  220 + return SupervisorStrategy.restart();
  221 + } else {
  222 + return SupervisorStrategy.stop();
  223 + }
  224 + }
  225 + });
  226 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.device;
  17 +
  18 +import akka.event.Logging;
  19 +import akka.event.LoggingAdapter;
  20 +import org.thingsboard.server.actors.ActorSystemContext;
  21 +import org.thingsboard.server.actors.rule.RulesProcessedMsg;
  22 +import org.thingsboard.server.actors.service.ContextAwareActor;
  23 +import org.thingsboard.server.actors.service.ContextBasedCreator;
  24 +import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
  25 +import org.thingsboard.server.common.data.id.DeviceId;
  26 +import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  28 +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  29 +import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
  30 +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
  31 +import org.thingsboard.server.extensions.api.plugins.msg.*;
  32 +
  33 +public class DeviceActor extends ContextAwareActor {
  34 +
  35 + private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
  36 +
  37 + private final TenantId tenantId;
  38 + private final DeviceId deviceId;
  39 + private final DeviceActorMessageProcessor processor;
  40 +
  41 + private DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) {
  42 + super(systemContext);
  43 + this.tenantId = tenantId;
  44 + this.deviceId = deviceId;
  45 + this.processor = new DeviceActorMessageProcessor(systemContext, logger, deviceId);
  46 + }
  47 +
  48 + @Override
  49 + public void onReceive(Object msg) throws Exception {
  50 + if (msg instanceof RuleChainDeviceMsg) {
  51 + processor.process(context(), (RuleChainDeviceMsg) msg);
  52 + } else if (msg instanceof RulesProcessedMsg) {
  53 + processor.onRulesProcessedMsg(context(), (RulesProcessedMsg) msg);
  54 + } else if (msg instanceof ToDeviceActorMsg) {
  55 + processor.process(context(), (ToDeviceActorMsg) msg);
  56 + } else if (msg instanceof ToDeviceActorNotificationMsg) {
  57 + if (msg instanceof DeviceAttributesEventNotificationMsg) {
  58 + processor.processAttributesUpdate(context(), (DeviceAttributesEventNotificationMsg) msg);
  59 + } else if (msg instanceof ToDeviceRpcRequestPluginMsg) {
  60 + processor.processRpcRequest(context(), (ToDeviceRpcRequestPluginMsg) msg);
  61 + }
  62 + } else if (msg instanceof TimeoutMsg) {
  63 + processor.processTimeout(context(), (TimeoutMsg) msg);
  64 + } else if (msg instanceof ClusterEventMsg) {
  65 + processor.processClusterEventMsg((ClusterEventMsg) msg);
  66 + } else {
  67 + logger.debug("[{}][{}] Unknown msg type.", tenantId, deviceId, msg.getClass().getName());
  68 + }
  69 + }
  70 +
  71 + public static class ActorCreator extends ContextBasedCreator<DeviceActor> {
  72 + private static final long serialVersionUID = 1L;
  73 +
  74 + private final TenantId tenantId;
  75 + private final DeviceId deviceId;
  76 +
  77 + public ActorCreator(ActorSystemContext context, TenantId tenantId, DeviceId deviceId) {
  78 + super(context);
  79 + this.tenantId = tenantId;
  80 + this.deviceId = deviceId;
  81 + }
  82 +
  83 + @Override
  84 + public DeviceActor create() throws Exception {
  85 + return new DeviceActor(context, tenantId, deviceId);
  86 + }
  87 + }
  88 +
  89 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.device;
  17 +
  18 +import akka.actor.ActorContext;
  19 +import akka.actor.ActorRef;
  20 +import akka.event.LoggingAdapter;
  21 +import org.thingsboard.server.actors.ActorSystemContext;
  22 +import org.thingsboard.server.actors.rule.ChainProcessingContext;
  23 +import org.thingsboard.server.actors.rule.ChainProcessingMetaData;
  24 +import org.thingsboard.server.actors.rule.RuleProcessingMsg;
  25 +import org.thingsboard.server.actors.rule.RulesProcessedMsg;
  26 +import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
  27 +import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
  28 +import org.thingsboard.server.common.data.DataConstants;
  29 +import org.thingsboard.server.common.data.id.DeviceId;
  30 +import org.thingsboard.server.common.data.id.SessionId;
  31 +import org.thingsboard.server.common.data.kv.AttributeKey;
  32 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  33 +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  34 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  35 +import org.thingsboard.server.common.msg.core.AttributesUpdateNotification;
  36 +import org.thingsboard.server.common.msg.core.BasicCommandAckResponse;
  37 +import org.thingsboard.server.common.msg.core.BasicToDeviceSessionActorMsg;
  38 +import org.thingsboard.server.common.msg.core.SessionCloseMsg;
  39 +import org.thingsboard.server.common.msg.core.ToDeviceRpcRequestMsg;
  40 +import org.thingsboard.server.common.msg.core.ToDeviceRpcResponseMsg;
  41 +import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
  42 +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  43 +import org.thingsboard.server.common.msg.kv.BasicAttributeKVMsg;
  44 +import org.thingsboard.server.common.msg.session.FromDeviceMsg;
  45 +import org.thingsboard.server.common.msg.session.MsgType;
  46 +import org.thingsboard.server.common.msg.session.SessionType;
  47 +import org.thingsboard.server.common.msg.session.ToDeviceMsg;
  48 +import org.thingsboard.server.extensions.api.device.DeviceAttributes;
  49 +import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
  50 +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
  51 +import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
  52 +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutIntMsg;
  53 +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
  54 +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
  55 +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody;
  56 +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
  57 +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
  58 +
  59 +import java.util.HashMap;
  60 +import java.util.HashSet;
  61 +import java.util.List;
  62 +import java.util.Map;
  63 +import java.util.Optional;
  64 +import java.util.Set;
  65 +import java.util.UUID;
  66 +import java.util.concurrent.TimeoutException;
  67 +import java.util.function.Consumer;
  68 +import java.util.function.Predicate;
  69 +import java.util.stream.Collectors;
  70 +
  71 +/**
  72 + * @author Andrew Shvayka
  73 + */
  74 +public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
  75 +
  76 + private final DeviceId deviceId;
  77 + private final Map<SessionId, SessionInfo> attributeSubscriptions;
  78 + private final Map<SessionId, SessionInfo> rpcSubscriptions;
  79 +
  80 + private final Map<Integer, ToDeviceRpcRequestMetadata> rpcPendingMap;
  81 +
  82 + private int rpcSeq = 0;
  83 + private DeviceAttributes deviceAttributes;
  84 +
  85 + public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, DeviceId deviceId) {
  86 + super(systemContext, logger);
  87 + this.deviceId = deviceId;
  88 + this.attributeSubscriptions = new HashMap<>();
  89 + this.rpcSubscriptions = new HashMap<>();
  90 + this.rpcPendingMap = new HashMap<>();
  91 + refreshAttributes();
  92 + }
  93 +
  94 + private void refreshAttributes() {
  95 + this.deviceAttributes = new DeviceAttributes(fetchAttributes(DataConstants.CLIENT_SCOPE),
  96 + fetchAttributes(DataConstants.SERVER_SCOPE), fetchAttributes(DataConstants.SHARED_SCOPE));
  97 + }
  98 +
  99 + void processRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg) {
  100 + ToDeviceRpcRequest request = msg.getMsg();
  101 + ToDeviceRpcRequestBody body = request.getBody();
  102 + ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
  103 + rpcSeq++,
  104 + body.getMethod(),
  105 + body.getParams()
  106 + );
  107 +
  108 + long timeout = request.getExpirationTime() - System.currentTimeMillis();
  109 + if (timeout <= 0) {
  110 + logger.debug("[{}][{}] Ignoring message due to exp time reached", deviceId, request.getId(), request.getExpirationTime());
  111 + return;
  112 + }
  113 +
  114 + boolean sent = rpcSubscriptions.size() > 0;
  115 + Set<SessionId> syncSessionSet = new HashSet<>();
  116 + rpcSubscriptions.entrySet().forEach(sub -> {
  117 + ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(rpcRequest, sub.getKey());
  118 + sendMsgToSessionActor(response, sub.getValue().getServer());
  119 + if (SessionType.SYNC == sub.getValue().getType()) {
  120 + syncSessionSet.add(sub.getKey());
  121 + }
  122 + });
  123 + syncSessionSet.forEach(rpcSubscriptions::remove);
  124 +
  125 + if (request.isOneway() && sent) {
  126 + ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(msg, (String) null);
  127 + context.parent().tell(responsePluginMsg, ActorRef.noSender());
  128 + logger.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId());
  129 + } else {
  130 + registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout);
  131 + }
  132 + if (sent) {
  133 + logger.debug("[{}] RPC request {} is sent!", deviceId, request.getId());
  134 + } else {
  135 + logger.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId());
  136 + }
  137 +
  138 + }
  139 +
  140 + private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
  141 + rpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent));
  142 + TimeoutIntMsg timeoutMsg = new TimeoutIntMsg(rpcRequest.getRequestId(), timeout);
  143 + scheduleMsgWithDelay(context, timeoutMsg, timeoutMsg.getTimeout());
  144 + }
  145 +
  146 + public void processTimeout(ActorContext context, TimeoutMsg msg) {
  147 + ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(msg.getId());
  148 + if (requestMd != null) {
  149 + logger.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
  150 + ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION);
  151 + context.parent().tell(responsePluginMsg, ActorRef.noSender());
  152 + }
  153 + }
  154 +
  155 + private void sendPendingRequests(ActorContext context, SessionId sessionId, SessionType type, Optional<ServerAddress> server) {
  156 + if (!rpcPendingMap.isEmpty()) {
  157 + logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, rpcPendingMap.size(), sessionId);
  158 + if (type == SessionType.SYNC) {
  159 + logger.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId);
  160 + rpcSubscriptions.remove(sessionId);
  161 + }
  162 + } else {
  163 + logger.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId);
  164 + }
  165 + Set<UUID> sentOneWayIds = new HashSet<>();
  166 + if (type == SessionType.ASYNC) {
  167 + rpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds));
  168 + } else {
  169 + rpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds));
  170 + }
  171 +
  172 + sentOneWayIds.forEach(rpcPendingMap::remove);
  173 + }
  174 +
  175 + private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(ActorContext context, SessionId sessionId, Optional<ServerAddress> server, Set<UUID> sentOneWayIds) {
  176 + return entry -> {
  177 + ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg();
  178 + ToDeviceRpcRequestBody body = request.getBody();
  179 + if (request.isOneway()) {
  180 + sentOneWayIds.add(request.getId());
  181 + ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(entry.getValue().getMsg(), (String) null);
  182 + context.parent().tell(responsePluginMsg, ActorRef.noSender());
  183 + }
  184 + ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
  185 + entry.getKey(),
  186 + body.getMethod(),
  187 + body.getParams()
  188 + );
  189 + ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(rpcRequest, sessionId);
  190 + sendMsgToSessionActor(response, server);
  191 + };
  192 + }
  193 +
  194 + void process(ActorContext context, ToDeviceActorMsg msg) {
  195 + processSubscriptionCommands(context, msg);
  196 + processRpcResponses(context, msg);
  197 + processSessionStateMsgs(msg);
  198 + }
  199 +
  200 + void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) {
  201 + //TODO: improve this procedure to fetch only changed attributes.
  202 + refreshAttributes();
  203 + //TODO: support attributes deletion
  204 + Set<AttributeKey> keys = msg.getKeys();
  205 + if (attributeSubscriptions.size() > 0) {
  206 + ToDeviceMsg notification = null;
  207 + if (msg.isDeleted()) {
  208 + List<AttributeKey> sharedKeys = keys.stream()
  209 + .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
  210 + .collect(Collectors.toList());
  211 + notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromDeleted(sharedKeys));
  212 + } else {
  213 + List<AttributeKvEntry> attributes = keys.stream()
  214 + .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
  215 + .map(key -> deviceAttributes.getServerPublicAttribute(key.getAttributeKey()))
  216 + .filter(Optional::isPresent)
  217 + .map(Optional::get)
  218 + .collect(Collectors.toList());
  219 + if (attributes.size() > 0) {
  220 + notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromShared(attributes));
  221 + } else {
  222 + logger.debug("[{}] No public server side attributes changed!", deviceId);
  223 + }
  224 + }
  225 + if (notification != null) {
  226 + ToDeviceMsg finalNotification = notification;
  227 + attributeSubscriptions.entrySet().forEach(sub -> {
  228 + ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(finalNotification, sub.getKey());
  229 + sendMsgToSessionActor(response, sub.getValue().getServer());
  230 + });
  231 + }
  232 + } else {
  233 + logger.debug("[{}] No registered attributes subscriptions to process!", deviceId);
  234 + }
  235 + }
  236 +
  237 + void process(ActorContext context, RuleChainDeviceMsg srcMsg) {
  238 + ChainProcessingMetaData md = new ChainProcessingMetaData(srcMsg.getRuleChain(),
  239 + srcMsg.getToDeviceActorMsg(), deviceAttributes, context.self());
  240 + ChainProcessingContext ctx = new ChainProcessingContext(md);
  241 + if (ctx.getChainLength() > 0) {
  242 + RuleProcessingMsg msg = new RuleProcessingMsg(ctx);
  243 + ActorRef ruleActorRef = ctx.getCurrentActor();
  244 + ruleActorRef.tell(msg, ActorRef.noSender());
  245 + } else {
  246 + context.self().tell(new RulesProcessedMsg(ctx), context.self());
  247 + }
  248 + }
  249 +
  250 + void processRpcResponses(ActorContext context, ToDeviceActorMsg msg) {
  251 + SessionId sessionId = msg.getSessionId();
  252 + FromDeviceMsg inMsg = msg.getPayload();
  253 + if (inMsg.getMsgType() == MsgType.TO_DEVICE_RPC_RESPONSE) {
  254 + logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId);
  255 + ToDeviceRpcResponseMsg responseMsg = (ToDeviceRpcResponseMsg) inMsg;
  256 + ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(responseMsg.getRequestId());
  257 + boolean success = requestMd != null;
  258 + if (success) {
  259 + ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), responseMsg.getData());
  260 + Optional<ServerAddress> pluginServerAddress = requestMd.getMsg().getServerAddress();
  261 + if (pluginServerAddress.isPresent()) {
  262 + systemContext.getRpcService().tell(pluginServerAddress.get(), responsePluginMsg);
  263 + logger.debug("[{}] Rpc command response sent to remote plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
  264 + } else {
  265 + context.parent().tell(responsePluginMsg, ActorRef.noSender());
  266 + logger.debug("[{}] Rpc command response sent to local plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
  267 + }
  268 + } else {
  269 + logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
  270 + }
  271 + if (msg.getSessionType() == SessionType.SYNC) {
  272 + BasicCommandAckResponse response = success
  273 + ? BasicCommandAckResponse.onSuccess(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId())
  274 + : BasicCommandAckResponse.onError(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException());
  275 + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(response, msg.getSessionId()), msg.getServerAddress());
  276 + }
  277 + }
  278 + }
  279 +
  280 + public void processClusterEventMsg(ClusterEventMsg msg) {
  281 + if (!msg.isAdded()) {
  282 + logger.debug("[{}] Clearing attributes/rpc subscription for server [{}]", deviceId, msg.getServerAddress());
  283 + Predicate<Map.Entry<SessionId, SessionInfo>> filter = e -> e.getValue().getServer()
  284 + .map(serverAddress -> serverAddress.equals(msg.getServerAddress())).orElse(false);
  285 + attributeSubscriptions.entrySet().removeIf(filter);
  286 + rpcSubscriptions.entrySet().removeIf(filter);
  287 + }
  288 + }
  289 +
  290 + private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data) {
  291 + return toPluginRpcResponseMsg(requestMsg, data, null);
  292 + }
  293 +
  294 + private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, RpcError error) {
  295 + return toPluginRpcResponseMsg(requestMsg, null, error);
  296 + }
  297 +
  298 + private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data, RpcError error) {
  299 + return new ToPluginRpcResponseDeviceMsg(
  300 + requestMsg.getPluginId(),
  301 + requestMsg.getPluginTenantId(),
  302 + new FromDeviceRpcResponse(requestMsg.getMsg().getId(),
  303 + data,
  304 + error
  305 + )
  306 + );
  307 + }
  308 +
  309 + void onRulesProcessedMsg(ActorContext context, RulesProcessedMsg msg) {
  310 + ChainProcessingContext ctx = msg.getCtx();
  311 + ToDeviceActorMsg inMsg = ctx.getInMsg();
  312 + SessionId sid = inMsg.getSessionId();
  313 + ToDeviceSessionActorMsg response;
  314 + if (ctx.getResponse() != null) {
  315 + response = new BasicToDeviceSessionActorMsg(ctx.getResponse(), sid);
  316 + } else {
  317 + response = new BasicToDeviceSessionActorMsg(ctx.getError(), sid);
  318 + }
  319 + sendMsgToSessionActor(response, inMsg.getServerAddress());
  320 + }
  321 +
  322 + private void processSubscriptionCommands(ActorContext context, ToDeviceActorMsg msg) {
  323 + SessionId sessionId = msg.getSessionId();
  324 + SessionType sessionType = msg.getSessionType();
  325 + FromDeviceMsg inMsg = msg.getPayload();
  326 + if (inMsg.getMsgType() == MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) {
  327 + logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId);
  328 + attributeSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress()));
  329 + } else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) {
  330 + logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId);
  331 + attributeSubscriptions.remove(sessionId);
  332 + } else if (inMsg.getMsgType() == MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) {
  333 + logger.debug("[{}] Registering rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType);
  334 + rpcSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress()));
  335 + sendPendingRequests(context, sessionId, sessionType, msg.getServerAddress());
  336 + } else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) {
  337 + logger.debug("[{}] Canceling rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType);
  338 + rpcSubscriptions.remove(sessionId);
  339 + }
  340 + }
  341 +
  342 + private void processSessionStateMsgs(ToDeviceActorMsg msg) {
  343 + SessionId sessionId = msg.getSessionId();
  344 + FromDeviceMsg inMsg = msg.getPayload();
  345 + if (inMsg instanceof SessionCloseMsg) {
  346 + logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId);
  347 + attributeSubscriptions.remove(sessionId);
  348 + rpcSubscriptions.remove(sessionId);
  349 + }
  350 + }
  351 +
  352 + private void sendMsgToSessionActor(ToDeviceSessionActorMsg response, Optional<ServerAddress> sessionAddress) {
  353 + if (sessionAddress.isPresent()) {
  354 + ServerAddress address = sessionAddress.get();
  355 + logger.debug("{} Forwarding msg: {}", address, response);
  356 + systemContext.getRpcService().tell(sessionAddress.get(), response);
  357 + } else {
  358 + systemContext.getSessionManagerActor().tell(response, ActorRef.noSender());
  359 + }
  360 + }
  361 +
  362 + private List<AttributeKvEntry> fetchAttributes(String attributeType) {
  363 + return systemContext.getAttributesService().findAll(this.deviceId, attributeType);
  364 + }
  365 +
  366 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.device;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.id.SessionId;
  20 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  21 +import org.thingsboard.server.common.msg.session.SessionType;
  22 +
  23 +import java.util.Optional;
  24 +
  25 +/**
  26 + * @author Andrew Shvayka
  27 + */
  28 +@Data
  29 +public class SessionInfo {
  30 + private final SessionType type;
  31 + private final Optional<ServerAddress> server;
  32 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.device;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
  20 +
  21 +/**
  22 + * @author Andrew Shvayka
  23 + */
  24 +@Data
  25 +public class ToDeviceRpcRequestMetadata {
  26 + private final ToDeviceRpcRequestPluginMsg msg;
  27 + private final boolean sent;
  28 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.plugin;
  17 +
  18 +import akka.actor.ActorContext;
  19 +import akka.actor.ActorRef;
  20 +import org.thingsboard.server.actors.ActorSystemContext;
  21 +import org.thingsboard.server.actors.service.ComponentActor;
  22 +import org.thingsboard.server.actors.service.ContextBasedCreator;
  23 +import org.thingsboard.server.actors.stats.StatsPersistTick;
  24 +import org.thingsboard.server.common.data.id.PluginId;
  25 +import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  27 +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
  28 +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
  29 +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
  30 +import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
  31 +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
  32 +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
  33 +import org.thingsboard.server.extensions.api.rules.RuleException;
  34 +
  35 +public class PluginActor extends ComponentActor<PluginId, PluginActorMessageProcessor> {
  36 +
  37 + private PluginActor(ActorSystemContext systemContext, TenantId tenantId, PluginId pluginId) {
  38 + super(systemContext, tenantId, pluginId);
  39 + setProcessor(new PluginActorMessageProcessor(tenantId, pluginId, systemContext,
  40 + logger, context().parent(), context().self()));
  41 + }
  42 +
  43 + @Override
  44 + public void onReceive(Object msg) throws Exception {
  45 + if (msg instanceof PluginWebsocketMsg) {
  46 + onWebsocketMsg((PluginWebsocketMsg<?>) msg);
  47 + } else if (msg instanceof PluginRestMsg) {
  48 + onRestMsg((PluginRestMsg) msg);
  49 + } else if (msg instanceof PluginCallbackMessage) {
  50 + onPluginCallback((PluginCallbackMessage) msg);
  51 + } else if (msg instanceof RuleToPluginMsgWrapper) {
  52 + onRuleToPluginMsg((RuleToPluginMsgWrapper) msg);
  53 + } else if (msg instanceof PluginRpcMsg) {
  54 + onRpcMsg((PluginRpcMsg) msg);
  55 + } else if (msg instanceof ClusterEventMsg) {
  56 + onClusterEventMsg((ClusterEventMsg) msg);
  57 + } else if (msg instanceof ComponentLifecycleMsg) {
  58 + onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
  59 + } else if (msg instanceof ToPluginRpcResponseDeviceMsg) {
  60 + onRpcResponse((ToPluginRpcResponseDeviceMsg) msg);
  61 + } else if (msg instanceof PluginTerminationMsg) {
  62 + logger.info("[{}][{}] Going to terminate plugin actor.", tenantId, id);
  63 + context().parent().tell(msg, ActorRef.noSender());
  64 + context().stop(self());
  65 + } else if (msg instanceof TimeoutMsg) {
  66 + onTimeoutMsg(context(), (TimeoutMsg) msg);
  67 + } else if (msg instanceof StatsPersistTick) {
  68 + onStatsPersistTick(id);
  69 + } else {
  70 + logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName());
  71 + }
  72 + }
  73 +
  74 + private void onPluginCallback(PluginCallbackMessage msg) {
  75 + try {
  76 + processor.onPluginCallbackMsg(msg);
  77 + } catch (Exception e) {
  78 + logAndPersist("onPluginCallbackMsg", e);
  79 + }
  80 + }
  81 +
  82 + private void onTimeoutMsg(ActorContext context, TimeoutMsg msg) {
  83 + processor.onTimeoutMsg(context, msg);
  84 + }
  85 +
  86 + private void onRpcResponse(ToPluginRpcResponseDeviceMsg msg) {
  87 + processor.onDeviceRpcMsg(msg.getResponse());
  88 + }
  89 +
  90 + private void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException {
  91 + logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg());
  92 + try {
  93 + processor.onRuleToPluginMsg(msg);
  94 + increaseMessagesProcessedCount();
  95 + } catch (Exception e) {
  96 + logAndPersist("onRuleMsg", e);
  97 + }
  98 + }
  99 +
  100 + private void onWebsocketMsg(PluginWebsocketMsg<?> msg) {
  101 + logger.debug("[{}] Going to process web socket msg: {}", id, msg);
  102 + try {
  103 + processor.onWebsocketMsg(msg);
  104 + increaseMessagesProcessedCount();
  105 + } catch (Exception e) {
  106 + logAndPersist("onWebsocketMsg", e);
  107 + }
  108 + }
  109 +
  110 + private void onRestMsg(PluginRestMsg msg) {
  111 + logger.debug("[{}] Going to process rest msg: {}", id, msg);
  112 + try {
  113 + processor.onRestMsg(msg);
  114 + increaseMessagesProcessedCount();
  115 + } catch (Exception e) {
  116 + logAndPersist("onRestMsg", e);
  117 + }
  118 + }
  119 +
  120 + private void onRpcMsg(PluginRpcMsg msg) {
  121 + try {
  122 + logger.debug("[{}] Going to process rpc msg: {}", id, msg);
  123 + processor.onRpcMsg(msg);
  124 + } catch (Exception e) {
  125 + logAndPersist("onRpcMsg", e);
  126 + }
  127 + }
  128 +
  129 + public static class ActorCreator extends ContextBasedCreator<PluginActor> {
  130 + private static final long serialVersionUID = 1L;
  131 +
  132 + private final TenantId tenantId;
  133 + private final PluginId pluginId;
  134 +
  135 + public ActorCreator(ActorSystemContext context, TenantId tenantId, PluginId pluginId) {
  136 + super(context);
  137 + this.tenantId = tenantId;
  138 + this.pluginId = pluginId;
  139 + }
  140 +
  141 + @Override
  142 + public PluginActor create() throws Exception {
  143 + return new PluginActor(context, tenantId, pluginId);
  144 + }
  145 + }
  146 +
  147 + @Override
  148 + protected long getErrorPersistFrequency() {
  149 + return systemContext.getPluginErrorPersistFrequency();
  150 + }
  151 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.plugin;
  17 +
  18 +import akka.actor.ActorContext;
  19 +import akka.actor.ActorRef;
  20 +import akka.event.LoggingAdapter;
  21 +import com.fasterxml.jackson.core.JsonProcessingException;
  22 +import org.thingsboard.server.actors.ActorSystemContext;
  23 +import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
  24 +import org.thingsboard.server.common.data.id.PluginId;
  25 +import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
  27 +import org.thingsboard.server.common.data.plugin.ComponentType;
  28 +import org.thingsboard.server.common.data.plugin.PluginMetaData;
  29 +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  30 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  31 +import org.thingsboard.server.extensions.api.plugins.Plugin;
  32 +import org.thingsboard.server.extensions.api.plugins.PluginInitializationException;
  33 +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
  34 +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
  35 +import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
  36 +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
  37 +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
  38 +import org.thingsboard.server.extensions.api.rules.RuleException;
  39 +
  40 +/**
  41 + * @author Andrew Shvayka
  42 + */
  43 +public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId> {
  44 +
  45 + private final SharedPluginProcessingContext pluginCtx;
  46 + private final PluginProcessingContext trustedCtx;
  47 + private PluginMetaData pluginMd;
  48 + private Plugin pluginImpl;
  49 + private ComponentLifecycleState state;
  50 +
  51 +
  52 + protected PluginActorMessageProcessor(TenantId tenantId, PluginId pluginId, ActorSystemContext systemContext
  53 + , LoggingAdapter logger, ActorRef parent, ActorRef self) {
  54 + super(systemContext, logger, tenantId, pluginId);
  55 + this.pluginCtx = new SharedPluginProcessingContext(systemContext, tenantId, pluginId, parent, self);
  56 + this.trustedCtx = new PluginProcessingContext(pluginCtx, null);
  57 + }
  58 +
  59 + @Override
  60 + public void start() throws Exception {
  61 + logger.info("[{}] Going to start plugin actor.", entityId);
  62 + pluginMd = systemContext.getPluginService().findPluginById(entityId);
  63 + if (pluginMd == null) {
  64 + throw new PluginInitializationException("Plugin not found!");
  65 + }
  66 + if (pluginMd.getConfiguration() == null) {
  67 + throw new PluginInitializationException("Plugin metadata is empty!");
  68 + }
  69 + state = pluginMd.getState();
  70 + if (state == ComponentLifecycleState.ACTIVE) {
  71 + logger.info("[{}] Plugin is active. Going to initialize plugin.", entityId);
  72 + initComponent();
  73 + } else {
  74 + logger.info("[{}] Plugin is suspended. Skipping plugin initialization.", entityId);
  75 + }
  76 + }
  77 +
  78 + @Override
  79 + public void stop() throws Exception {
  80 + onStop();
  81 + }
  82 +
  83 + private void initComponent() {
  84 + try {
  85 + pluginImpl = initComponent(pluginMd.getClazz(), ComponentType.PLUGIN, mapper.writeValueAsString(pluginMd.getConfiguration()));
  86 + } catch (InstantiationException e) {
  87 + throw new PluginInitializationException("No default constructor for plugin implementation!", e);
  88 + } catch (IllegalAccessException e) {
  89 + throw new PluginInitializationException("Illegal Access Exception during plugin initialization!", e);
  90 + } catch (ClassNotFoundException e) {
  91 + throw new PluginInitializationException("Plugin Class not found!", e);
  92 + } catch (JsonProcessingException e) {
  93 + throw new PluginInitializationException("Plugin Configuration is invalid!", e);
  94 + } catch (Exception e) {
  95 + throw new PluginInitializationException(e.getMessage(), e);
  96 + }
  97 + }
  98 +
  99 + public void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException {
  100 + if (state == ComponentLifecycleState.ACTIVE) {
  101 + pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg());
  102 + } else {
  103 + //TODO: reply with plugin suspended message
  104 + }
  105 + }
  106 +
  107 + public void onWebsocketMsg(PluginWebsocketMsg<?> msg) {
  108 + if (state == ComponentLifecycleState.ACTIVE) {
  109 + pluginImpl.process(new PluginProcessingContext(pluginCtx, msg.getSecurityCtx()), msg);
  110 + } else {
  111 + //TODO: reply with plugin suspended message
  112 + }
  113 + }
  114 +
  115 + public void onRestMsg(PluginRestMsg msg) {
  116 + if (state == ComponentLifecycleState.ACTIVE) {
  117 + pluginImpl.process(new PluginProcessingContext(pluginCtx, msg.getSecurityCtx()), msg);
  118 + }
  119 + }
  120 +
  121 + public void onRpcMsg(PluginRpcMsg msg) {
  122 + if (state == ComponentLifecycleState.ACTIVE) {
  123 + pluginImpl.process(trustedCtx, msg.getRpcMsg());
  124 + } else {
  125 + //TODO: reply with plugin suspended message
  126 + }
  127 + }
  128 +
  129 + public void onPluginCallbackMsg(PluginCallbackMessage msg) {
  130 + if (state == ComponentLifecycleState.ACTIVE) {
  131 + if (msg.isSuccess()) {
  132 + msg.getCallback().onSuccess(trustedCtx, msg.getV());
  133 + } else {
  134 + msg.getCallback().onFailure(trustedCtx, msg.getE());
  135 + }
  136 + } else {
  137 + //TODO: reply with plugin suspended message
  138 + }
  139 + }
  140 +
  141 +
  142 + public void onTimeoutMsg(ActorContext context, TimeoutMsg<?> msg) {
  143 + if (state == ComponentLifecycleState.ACTIVE) {
  144 + pluginImpl.process(trustedCtx, msg);
  145 + }
  146 + }
  147 +
  148 +
  149 + public void onDeviceRpcMsg(FromDeviceRpcResponse response) {
  150 + if (state == ComponentLifecycleState.ACTIVE) {
  151 + pluginImpl.process(trustedCtx, response);
  152 + }
  153 + }
  154 +
  155 + @Override
  156 + public void onClusterEventMsg(ClusterEventMsg msg) {
  157 + if (state == ComponentLifecycleState.ACTIVE) {
  158 + ServerAddress address = msg.getServerAddress();
  159 + if (msg.isAdded()) {
  160 + logger.debug("[{}] Going to process server add msg: {}", entityId, address);
  161 + pluginImpl.onServerAdded(trustedCtx, address);
  162 + } else {
  163 + logger.debug("[{}] Going to process server remove msg: {}", entityId, address);
  164 + pluginImpl.onServerRemoved(trustedCtx, address);
  165 + }
  166 + }
  167 + }
  168 +
  169 + @Override
  170 + public void onCreated(ActorContext context) {
  171 + logger.info("[{}] Going to process onCreated plugin.", entityId);
  172 + }
  173 +
  174 + @Override
  175 + public void onUpdate(ActorContext context) throws Exception {
  176 + PluginMetaData oldPluginMd = systemContext.getPluginService().findPluginById(entityId);
  177 + pluginMd = systemContext.getPluginService().findPluginById(entityId);
  178 + boolean requiresRestart = false;
  179 + logger.info("[{}] Plugin configuration was updated from {} to {}.", entityId, oldPluginMd, pluginMd);
  180 + if (!oldPluginMd.getClazz().equals(pluginMd.getClazz())) {
  181 + logger.info("[{}] Plugin requires restart due to clazz change from {} to {}.",
  182 + entityId, oldPluginMd.getClazz(), pluginMd.getClazz());
  183 + requiresRestart = true;
  184 + } else if (oldPluginMd.getConfiguration().equals(pluginMd.getConfiguration())) {
  185 + logger.info("[{}] Plugin requires restart due to configuration change from {} to {}.",
  186 + entityId, oldPluginMd.getConfiguration(), pluginMd.getConfiguration());
  187 + requiresRestart = true;
  188 + }
  189 + if (requiresRestart) {
  190 + this.state = ComponentLifecycleState.SUSPENDED;
  191 + if (pluginImpl != null) {
  192 + pluginImpl.stop(trustedCtx);
  193 + }
  194 + start();
  195 + }
  196 + }
  197 +
  198 + @Override
  199 + public void onStop(ActorContext context) {
  200 + onStop();
  201 + scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay());
  202 + }
  203 +
  204 + private void onStop() {
  205 + logger.info("[{}] Going to process onStop plugin.", entityId);
  206 + this.state = ComponentLifecycleState.SUSPENDED;
  207 + if (pluginImpl != null) {
  208 + pluginImpl.stop(trustedCtx);
  209 + }
  210 + }
  211 +
  212 + @Override
  213 + public void onActivate(ActorContext context) throws Exception {
  214 + logger.info("[{}] Going to process onActivate plugin.", entityId);
  215 + this.state = ComponentLifecycleState.ACTIVE;
  216 + if (pluginImpl != null) {
  217 + pluginImpl.resume(trustedCtx);
  218 + logger.info("[{}] Plugin resumed.", entityId);
  219 + } else {
  220 + start();
  221 + }
  222 + }
  223 +
  224 + @Override
  225 + public void onSuspend(ActorContext context) {
  226 + logger.info("[{}] Going to process onSuspend plugin.", entityId);
  227 + this.state = ComponentLifecycleState.SUSPENDED;
  228 + if (pluginImpl != null) {
  229 + pluginImpl.suspend(trustedCtx);
  230 + }
  231 + }
  232 +
  233 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.plugin;
  17 +
  18 +import lombok.Data;
  19 +import lombok.Getter;
  20 +import lombok.ToString;
  21 +import org.thingsboard.server.extensions.api.plugins.PluginCallback;
  22 +
  23 +import java.util.Optional;
  24 +
  25 +/**
  26 + * @author Andrew Shvayka
  27 + */
  28 +@ToString
  29 +public final class PluginCallbackMessage<V> {
  30 + @Getter
  31 + private final PluginCallback<V> callback;
  32 + @Getter
  33 + private final boolean success;
  34 + @Getter
  35 + private final V v;
  36 + @Getter
  37 + private final Exception e;
  38 +
  39 + public static <V> PluginCallbackMessage<V> onSuccess(PluginCallback<V> callback, V data) {
  40 + return new PluginCallbackMessage<V>(true, callback, data, null);
  41 + }
  42 +
  43 + public static <V> PluginCallbackMessage<V> onError(PluginCallback<V> callback, Exception e) {
  44 + return new PluginCallbackMessage<V>(false, callback, null, e);
  45 + }
  46 +
  47 + private PluginCallbackMessage(boolean success, PluginCallback<V> callback, V v, Exception e) {
  48 + this.success = success;
  49 + this.callback = callback;
  50 + this.v = v;
  51 + this.e = e;
  52 + }
  53 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.plugin;
  17 +
  18 +import java.io.IOException;
  19 +import java.util.*;
  20 +import java.util.concurrent.Executor;
  21 +import java.util.concurrent.Executors;
  22 +import java.util.stream.Collectors;
  23 +
  24 +import com.datastax.driver.core.ResultSet;
  25 +import com.datastax.driver.core.ResultSetFuture;
  26 +import com.datastax.driver.core.Row;
  27 +import com.google.common.base.Function;
  28 +import com.google.common.util.concurrent.FutureCallback;
  29 +import com.google.common.util.concurrent.Futures;
  30 +import com.google.common.util.concurrent.ListenableFuture;
  31 +import lombok.extern.slf4j.Slf4j;
  32 +import org.thingsboard.server.common.data.DataConstants;
  33 +import org.thingsboard.server.common.data.Device;
  34 +import org.thingsboard.server.common.data.id.CustomerId;
  35 +import org.thingsboard.server.common.data.id.DeviceId;
  36 +import org.thingsboard.server.common.data.id.PluginId;
  37 +import org.thingsboard.server.common.data.id.TenantId;
  38 +import org.thingsboard.server.common.data.kv.AttributeKey;
  39 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  40 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  41 +import org.thingsboard.server.common.data.kv.TsKvQuery;
  42 +import org.thingsboard.server.common.data.page.TextPageData;
  43 +import org.thingsboard.server.common.data.page.TextPageLink;
  44 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  45 +import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
  46 +import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
  47 +import org.thingsboard.server.extensions.api.plugins.PluginContext;
  48 +import org.thingsboard.server.extensions.api.plugins.PluginCallback;
  49 +import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
  50 +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
  51 +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
  52 +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
  53 +import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
  54 +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
  55 +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
  56 +
  57 +import akka.actor.ActorRef;
  58 +
  59 +import javax.annotation.Nullable;
  60 +
  61 +@Slf4j
  62 +public final class PluginProcessingContext implements PluginContext {
  63 +
  64 + private static final Executor executor = Executors.newSingleThreadExecutor();
  65 +
  66 + private final SharedPluginProcessingContext pluginCtx;
  67 + private final Optional<PluginApiCallSecurityContext> securityCtx;
  68 +
  69 + public PluginProcessingContext(SharedPluginProcessingContext pluginCtx, PluginApiCallSecurityContext securityCtx) {
  70 + super();
  71 + this.pluginCtx = pluginCtx;
  72 + this.securityCtx = Optional.ofNullable(securityCtx);
  73 + }
  74 +
  75 + @Override
  76 + public void sendPluginRpcMsg(RpcMsg msg) {
  77 + this.pluginCtx.rpcService.tell(new PluginRpcMsg(pluginCtx.tenantId, pluginCtx.pluginId, msg));
  78 + }
  79 +
  80 + @Override
  81 + public void send(PluginWebsocketMsg<?> wsMsg) throws IOException {
  82 + pluginCtx.msgEndpoint.send(wsMsg);
  83 + }
  84 +
  85 + @Override
  86 + public void close(PluginWebsocketSessionRef sessionRef) throws IOException {
  87 + pluginCtx.msgEndpoint.close(sessionRef);
  88 + }
  89 +
  90 + @Override
  91 + public void saveAttributes(DeviceId deviceId, String scope, List<AttributeKvEntry> attributes, PluginCallback<Void> callback) {
  92 + validate(deviceId);
  93 + Set<AttributeKey> keys = new HashSet<>();
  94 + for (AttributeKvEntry attribute : attributes) {
  95 + keys.add(new AttributeKey(scope, attribute.getKey()));
  96 + }
  97 +
  98 + ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(deviceId, scope, attributes);
  99 + Futures.addCallback(rsListFuture, getListCallback(callback, v -> {
  100 + onDeviceAttributesChanged(deviceId, keys);
  101 + return null;
  102 + }), executor);
  103 + }
  104 +
  105 + @Override
  106 + public Optional<AttributeKvEntry> loadAttribute(DeviceId deviceId, String attributeType, String attributeKey) {
  107 + validate(deviceId);
  108 + AttributeKvEntry attribute = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey);
  109 + return Optional.ofNullable(attribute);
  110 + }
  111 +
  112 + @Override
  113 + public List<AttributeKvEntry> loadAttributes(DeviceId deviceId, String attributeType, List<String> attributeKeys) {
  114 + validate(deviceId);
  115 + List<AttributeKvEntry> result = new ArrayList<>(attributeKeys.size());
  116 + for (String attributeKey : attributeKeys) {
  117 + AttributeKvEntry attribute = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey);
  118 + if (attribute != null) {
  119 + result.add(attribute);
  120 + }
  121 + }
  122 + return result;
  123 + }
  124 +
  125 + @Override
  126 + public List<AttributeKvEntry> loadAttributes(DeviceId deviceId, String attributeType) {
  127 + validate(deviceId);
  128 + return pluginCtx.attributesService.findAll(deviceId, attributeType);
  129 + }
  130 +
  131 + @Override
  132 + public void removeAttributes(DeviceId deviceId, String scope, List<String> keys) {
  133 + validate(deviceId);
  134 + pluginCtx.attributesService.removeAll(deviceId, scope, keys);
  135 + onDeviceAttributesDeleted(deviceId, keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet()));
  136 + }
  137 +
  138 + @Override
  139 + public void saveTsData(DeviceId deviceId, TsKvEntry entry, PluginCallback<Void> callback) {
  140 + validate(deviceId);
  141 + ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entry);
  142 + Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor);
  143 + }
  144 +
  145 + @Override
  146 + public void saveTsData(DeviceId deviceId, List<TsKvEntry> entries, PluginCallback<Void> callback) {
  147 + validate(deviceId);
  148 + ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entries);
  149 + Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor);
  150 + }
  151 +
  152 + @Override
  153 + public List<TsKvEntry> loadTimeseries(DeviceId deviceId, TsKvQuery query) {
  154 + validate(deviceId);
  155 + return pluginCtx.tsService.find(DataConstants.DEVICE, deviceId, query);
  156 + }
  157 +
  158 + @Override
  159 + public void loadLatestTimeseries(DeviceId deviceId, PluginCallback<List<TsKvEntry>> callback) {
  160 + validate(deviceId);
  161 + ResultSetFuture future = pluginCtx.tsService.findAllLatest(DataConstants.DEVICE, deviceId);
  162 + Futures.addCallback(future, getCallback(callback, pluginCtx.tsService::convertResultSetToTsKvEntryList), executor);
  163 + }
  164 +
  165 + @Override
  166 + public void loadLatestTimeseries(DeviceId deviceId, Collection<String> keys, PluginCallback<List<TsKvEntry>> callback) {
  167 + validate(deviceId);
  168 + ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.findLatest(DataConstants.DEVICE, deviceId, keys);
  169 + Futures.addCallback(rsListFuture, getListCallback(callback, rsList ->
  170 + {
  171 + List<TsKvEntry> result = new ArrayList<>();
  172 + for (ResultSet rs : rsList) {
  173 + Row row = rs.one();
  174 + if (row != null) {
  175 + result.add(pluginCtx.tsService.convertResultToTsKvEntry(row));
  176 + }
  177 + }
  178 + return result;
  179 + }), executor);
  180 + }
  181 +
  182 + @Override
  183 + public void reply(PluginToRuleMsg<?> msg) {
  184 + pluginCtx.parentActor.tell(msg, ActorRef.noSender());
  185 + }
  186 +
  187 + @Override
  188 + public boolean checkAccess(DeviceId deviceId) {
  189 + try {
  190 + return validate(deviceId);
  191 + } catch (IllegalStateException | IllegalArgumentException e) {
  192 + return false;
  193 + }
  194 + }
  195 +
  196 + @Override
  197 + public PluginId getPluginId() {
  198 + return pluginCtx.pluginId;
  199 + }
  200 +
  201 + @Override
  202 + public Optional<PluginApiCallSecurityContext> getSecurityCtx() {
  203 + return securityCtx;
  204 + }
  205 +
  206 + private void onDeviceAttributesChanged(DeviceId deviceId, AttributeKey key) {
  207 + onDeviceAttributesChanged(deviceId, Collections.singleton(key));
  208 + }
  209 +
  210 + private void onDeviceAttributesDeleted(DeviceId deviceId, Set<AttributeKey> keys) {
  211 + Device device = pluginCtx.deviceService.findDeviceById(deviceId);
  212 + pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), deviceId, keys));
  213 + }
  214 +
  215 + private void onDeviceAttributesChanged(DeviceId deviceId, Set<AttributeKey> keys) {
  216 + Device device = pluginCtx.deviceService.findDeviceById(deviceId);
  217 + pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onUpdate(device.getTenantId(), deviceId, keys));
  218 + }
  219 +
  220 + private <T> FutureCallback<List<ResultSet>> getListCallback(final PluginCallback<T> callback, Function<List<ResultSet>, T> transformer) {
  221 + return new FutureCallback<List<ResultSet>>() {
  222 + @Override
  223 + public void onSuccess(@Nullable List<ResultSet> result) {
  224 + pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender());
  225 + }
  226 +
  227 + @Override
  228 + public void onFailure(Throwable t) {
  229 + if (t instanceof Exception) {
  230 + pluginCtx.self().tell(PluginCallbackMessage.onError(callback, (Exception) t), ActorRef.noSender());
  231 + } else {
  232 + log.error("Critical error: {}", t.getMessage(), t);
  233 + }
  234 + }
  235 + };
  236 + }
  237 +
  238 + private <T> FutureCallback<ResultSet> getCallback(final PluginCallback<T> callback, Function<ResultSet, T> transformer) {
  239 + return new FutureCallback<ResultSet>() {
  240 + @Override
  241 + public void onSuccess(@Nullable ResultSet result) {
  242 + pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender());
  243 + }
  244 +
  245 + @Override
  246 + public void onFailure(Throwable t) {
  247 + if (t instanceof Exception) {
  248 + pluginCtx.self().tell(PluginCallbackMessage.onError(callback, (Exception) t), ActorRef.noSender());
  249 + } else {
  250 + log.error("Critical error: {}", t.getMessage(), t);
  251 + }
  252 + }
  253 + };
  254 + }
  255 +
  256 + // TODO: replace with our own exceptions
  257 + private boolean validate(DeviceId deviceId) {
  258 + if (securityCtx.isPresent()) {
  259 + PluginApiCallSecurityContext ctx = securityCtx.get();
  260 + if (ctx.isTenantAdmin() || ctx.isCustomerUser()) {
  261 + Device device = pluginCtx.deviceService.findDeviceById(deviceId);
  262 + if (device == null) {
  263 + throw new IllegalStateException("Device not found!");
  264 + } else {
  265 + if (!device.getTenantId().equals(ctx.getTenantId())) {
  266 + throw new IllegalArgumentException("Device belongs to different tenant!");
  267 + } else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) {
  268 + throw new IllegalArgumentException("Device belongs to different customer!");
  269 + }
  270 + }
  271 + } else {
  272 + return false;
  273 + }
  274 + }
  275 + return true;
  276 + }
  277 +
  278 + @Override
  279 + public Optional<ServerAddress> resolve(DeviceId deviceId) {
  280 + return pluginCtx.routingService.resolve(deviceId);
  281 + }
  282 +
  283 + @Override
  284 + public void getDevice(DeviceId deviceId, PluginCallback<Device> callback) {
  285 + //TODO: add caching here with async api.
  286 + Device device = pluginCtx.deviceService.findDeviceById(deviceId);
  287 + pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, device), ActorRef.noSender());
  288 + }
  289 +
  290 + @Override
  291 + public void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback<List<Device>> callback) {
  292 + //TODO: add caching here with async api.
  293 + List<Device> devices = pluginCtx.deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, new TextPageLink(limit)).getData();
  294 + pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, devices), ActorRef.noSender());
  295 + }
  296 +
  297 + @Override
  298 + public void sendRpcRequest(ToDeviceRpcRequest msg) {
  299 + pluginCtx.sendRpcRequest(msg);
  300 + }
  301 +
  302 + @Override
  303 + public void scheduleTimeoutMsg(TimeoutMsg msg) {
  304 + pluginCtx.scheduleTimeoutMsg(msg);
  305 + }
  306 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.plugin;
  17 +
  18 +import org.thingsboard.server.actors.shared.ActorTerminationMsg;
  19 +import org.thingsboard.server.common.data.id.PluginId;
  20 +import org.thingsboard.server.common.data.id.SessionId;
  21 +
  22 +/**
  23 + * @author Andrew Shvayka
  24 + */
  25 +public class PluginTerminationMsg extends ActorTerminationMsg<PluginId> {
  26 +
  27 + public PluginTerminationMsg(PluginId id) {
  28 + super(id);
  29 + }
  30 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.plugin;
  17 +
  18 +import org.thingsboard.server.common.data.id.PluginId;
  19 +import org.thingsboard.server.common.data.id.RuleId;
  20 +import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.common.msg.aware.RuleAwareMsg;
  22 +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
  23 +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
  24 +
  25 +public class RuleToPluginMsgWrapper implements ToPluginActorMsg, RuleAwareMsg {
  26 +
  27 + private final TenantId pluginTenantId;
  28 + private final PluginId pluginId;
  29 + private final TenantId ruleTenantId;
  30 + private final RuleId ruleId;
  31 + private final RuleToPluginMsg<?> msg;
  32 +
  33 + public RuleToPluginMsgWrapper(TenantId pluginTenantId, PluginId pluginId, TenantId ruleTenantId, RuleId ruleId, RuleToPluginMsg<?> msg) {
  34 + super();
  35 + this.pluginTenantId = pluginTenantId;
  36 + this.pluginId = pluginId;
  37 + this.ruleTenantId = ruleTenantId;
  38 + this.ruleId = ruleId;
  39 + this.msg = msg;
  40 + }
  41 +
  42 + @Override
  43 + public TenantId getPluginTenantId() {
  44 + return pluginTenantId;
  45 + }
  46 +
  47 + @Override
  48 + public PluginId getPluginId() {
  49 + return pluginId;
  50 + }
  51 +
  52 + public TenantId getRuleTenantId() {
  53 + return ruleTenantId;
  54 + }
  55 +
  56 + @Override
  57 + public RuleId getRuleId() {
  58 + return ruleId;
  59 + }
  60 +
  61 +
  62 + public RuleToPluginMsg<?> getMsg() {
  63 + return msg;
  64 + }
  65 +
  66 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.plugin;
  17 +
  18 +import akka.actor.ActorRef;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.thingsboard.server.actors.ActorSystemContext;
  21 +import org.thingsboard.server.common.data.Device;
  22 +import org.thingsboard.server.common.data.id.DeviceId;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  25 +import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
  26 +import org.thingsboard.server.common.data.id.PluginId;
  27 +import org.thingsboard.server.dao.attributes.AttributesService;
  28 +import org.thingsboard.server.dao.device.DeviceService;
  29 +import org.thingsboard.server.dao.timeseries.TimeseriesService;
  30 +import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
  31 +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
  32 +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
  33 +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
  34 +import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
  35 +import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
  36 +import scala.concurrent.duration.Duration;
  37 +
  38 +import java.util.Optional;
  39 +import java.util.concurrent.TimeUnit;
  40 +import java.util.function.BiConsumer;
  41 +
  42 +@Slf4j
  43 +public final class SharedPluginProcessingContext {
  44 + final ActorRef parentActor;
  45 + final ActorRef currentActor;
  46 + final ActorSystemContext systemContext;
  47 + final PluginWebSocketMsgEndpoint msgEndpoint;
  48 + final DeviceService deviceService;
  49 + final TimeseriesService tsService;
  50 + final AttributesService attributesService;
  51 + final ClusterRpcService rpcService;
  52 + final ClusterRoutingService routingService;
  53 + final PluginId pluginId;
  54 + final TenantId tenantId;
  55 +
  56 + public SharedPluginProcessingContext(ActorSystemContext sysContext, TenantId tenantId, PluginId pluginId,
  57 + ActorRef parentActor, ActorRef self) {
  58 + super();
  59 + this.tenantId = tenantId;
  60 + this.pluginId = pluginId;
  61 + this.parentActor = parentActor;
  62 + this.currentActor = self;
  63 + this.systemContext = sysContext;
  64 + this.msgEndpoint = sysContext.getWsMsgEndpoint();
  65 + this.tsService = sysContext.getTsService();
  66 + this.attributesService = sysContext.getAttributesService();
  67 + this.deviceService = sysContext.getDeviceService();
  68 + this.rpcService = sysContext.getRpcService();
  69 + this.routingService = sysContext.getRoutingService();
  70 + }
  71 +
  72 + public PluginId getPluginId() {
  73 + return pluginId;
  74 + }
  75 +
  76 + public void toDeviceActor(DeviceAttributesEventNotificationMsg msg) {
  77 + forward(msg.getDeviceId(), msg, rpcService::tell);
  78 + }
  79 +
  80 + public void sendRpcRequest(ToDeviceRpcRequest msg) {
  81 + log.trace("[{}] Forwarding msg {} to device actor!", pluginId, msg);
  82 + ToDeviceRpcRequestPluginMsg rpcMsg = new ToDeviceRpcRequestPluginMsg(pluginId, tenantId, msg);
  83 + forward(msg.getDeviceId(), rpcMsg, rpcService::tell);
  84 + }
  85 +
  86 + private <T> void forward(DeviceId deviceId, T msg, BiConsumer<ServerAddress, T> rpcFunction) {
  87 + Optional<ServerAddress> instance = routingService.resolve(deviceId);
  88 + if (instance.isPresent()) {
  89 + log.trace("[{}] Forwarding msg {} to remote device actor!", pluginId, msg);
  90 + rpcFunction.accept(instance.get(), msg);
  91 + } else {
  92 + log.trace("[{}] Forwarding msg {} to local device actor!", pluginId, msg);
  93 + parentActor.tell(msg, ActorRef.noSender());
  94 + }
  95 + }
  96 +
  97 + public void scheduleTimeoutMsg(TimeoutMsg msg) {
  98 + log.debug("Scheduling msg {} with delay {} ms", msg, msg.getTimeout());
  99 + systemContext.getScheduler().scheduleOnce(
  100 + Duration.create(msg.getTimeout(), TimeUnit.MILLISECONDS),
  101 + currentActor,
  102 + msg,
  103 + systemContext.getActorSystem().dispatcher(),
  104 + ActorRef.noSender());
  105 +
  106 + }
  107 +
  108 + public ActorRef self() {
  109 + return currentActor;
  110 + }
  111 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.plugin;
  17 +
  18 +import akka.actor.ActorRef;
  19 +
  20 +/**
  21 + * @author Andrew Shvayka
  22 + */
  23 +public interface TimeoutScheduler {
  24 +
  25 + void scheduleMsgWithDelay(Object msg, long delayInMs);
  26 +
  27 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rpc;
  17 +
  18 +import akka.actor.ActorRef;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.util.SerializationUtils;
  21 +import org.springframework.util.StringUtils;
  22 +import org.thingsboard.server.actors.ActorSystemContext;
  23 +import org.thingsboard.server.actors.service.ActorService;
  24 +import org.thingsboard.server.common.data.id.DeviceId;
  25 +import org.thingsboard.server.common.data.id.PluginId;
  26 +import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  28 +import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
  29 +import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
  30 +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  31 +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
  32 +import org.thingsboard.server.extensions.api.plugins.msg.*;
  33 +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
  34 +import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
  35 +import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
  36 +import org.thingsboard.server.service.cluster.rpc.GrpcSession;
  37 +import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener;
  38 +
  39 +import java.io.Serializable;
  40 +import java.util.UUID;
  41 +
  42 +/**
  43 + * @author Andrew Shvayka
  44 + */
  45 +@Slf4j
  46 +public class BasicRpcSessionListener implements GrpcSessionListener {
  47 +
  48 + private final ActorSystemContext context;
  49 + private final ActorService service;
  50 + private final ActorRef manager;
  51 + private final ActorRef self;
  52 +
  53 + public BasicRpcSessionListener(ActorSystemContext context, ActorRef manager, ActorRef self) {
  54 + this.context = context;
  55 + this.service = context.getActorService();
  56 + this.manager = manager;
  57 + this.self = self;
  58 + }
  59 +
  60 + @Override
  61 + public void onConnected(GrpcSession session) {
  62 + log.info("{} session started -> {}", getType(session), session.getRemoteServer());
  63 + if (!session.isClient()) {
  64 + manager.tell(new RpcSessionConnectedMsg(session.getRemoteServer(), session.getSessionId()), self);
  65 + }
  66 + }
  67 +
  68 + @Override
  69 + public void onDisconnected(GrpcSession session) {
  70 + log.info("{} session closed -> {}", getType(session), session.getRemoteServer());
  71 + manager.tell(new RpcSessionDisconnectedMsg(session.isClient(), session.getRemoteServer()), self);
  72 + }
  73 +
  74 + @Override
  75 + public void onToPluginRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcMessage msg) {
  76 + if (log.isTraceEnabled()) {
  77 + log.trace("{} session [{}] received plugin msg {}", getType(session), session.getRemoteServer(), msg);
  78 + }
  79 + service.onMsg(convert(session.getRemoteServer(), msg));
  80 + }
  81 +
  82 + @Override
  83 + public void onToDeviceActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorRpcMessage msg) {
  84 + log.trace("{} session [{}] received device actor msg {}", getType(session), session.getRemoteServer(), msg);
  85 + service.onMsg((ToDeviceActorMsg) deserialize(msg.getData().toByteArray()));
  86 + }
  87 +
  88 + @Override
  89 + public void onToDeviceActorNotificationRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorNotificationRpcMessage msg) {
  90 + log.trace("{} session [{}] received device actor notification msg {}", getType(session), session.getRemoteServer(), msg);
  91 + service.onMsg((ToDeviceActorNotificationMsg) deserialize(msg.getData().toByteArray()));
  92 + }
  93 +
  94 + @Override
  95 + public void onToDeviceSessionActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceSessionActorRpcMessage msg) {
  96 + log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg);
  97 + service.onMsg((ToDeviceSessionActorMsg) deserialize(msg.getData().toByteArray()));
  98 + }
  99 +
  100 + @Override
  101 + public void onToDeviceRpcRequestRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
  102 + log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg);
  103 + service.onMsg(deserialize(session.getRemoteServer(), msg));
  104 + }
  105 +
  106 + @Override
  107 + public void onFromDeviceRpcResponseRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) {
  108 + log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg);
  109 + service.onMsg(deserialize(session.getRemoteServer(), msg));
  110 + }
  111 +
  112 + @Override
  113 + public void onToAllNodesRpcMessage(GrpcSession session, ClusterAPIProtos.ToAllNodesRpcMessage msg) {
  114 + log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg);
  115 + service.onMsg((ToAllNodesMsg) deserialize(msg.getData().toByteArray()));
  116 + }
  117 +
  118 + @Override
  119 + public void onError(GrpcSession session, Throwable t) {
  120 + log.warn("{} session got error -> {}", getType(session), session.getRemoteServer(), t);
  121 + manager.tell(new RpcSessionClosedMsg(session.isClient(), session.getRemoteServer()), self);
  122 + session.close();
  123 + }
  124 +
  125 + private static String getType(GrpcSession session) {
  126 + return session.isClient() ? "Client" : "Server";
  127 + }
  128 +
  129 + private static PluginRpcMsg convert(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcMessage msg) {
  130 + ClusterAPIProtos.PluginAddress address = msg.getAddress();
  131 + TenantId tenantId = new TenantId(toUUID(address.getTenantId()));
  132 + PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
  133 + RpcMsg rpcMsg = new RpcMsg(serverAddress, msg.getClazz(), msg.getData().toByteArray());
  134 + return new PluginRpcMsg(tenantId, pluginId, rpcMsg);
  135 + }
  136 +
  137 + private static UUID toUUID(ClusterAPIProtos.Uid uid) {
  138 + return new UUID(uid.getPluginUuidMsb(), uid.getPluginUuidLsb());
  139 + }
  140 +
  141 + private static ToDeviceRpcRequestPluginMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
  142 + ClusterAPIProtos.PluginAddress address = msg.getAddress();
  143 + TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId()));
  144 + PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
  145 +
  146 + TenantId deviceTenantId = new TenantId(toUUID(msg.getDeviceTenantId()));
  147 + DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId()));
  148 +
  149 + ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams());
  150 + ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody);
  151 +
  152 + return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request);
  153 + }
  154 +
  155 + private static ToPluginRpcResponseDeviceMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) {
  156 + ClusterAPIProtos.PluginAddress address = msg.getAddress();
  157 + TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId()));
  158 + PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
  159 +
  160 + RpcError error = !StringUtils.isEmpty(msg.getError()) ? RpcError.valueOf(msg.getError()) : null;
  161 + FromDeviceRpcResponse response = new FromDeviceRpcResponse(toUUID(msg.getMsgId()), msg.getResponse(), error);
  162 + return new ToPluginRpcResponseDeviceMsg(pluginId, pluginTenantId, response);
  163 + }
  164 +
  165 + @SuppressWarnings("unchecked")
  166 + private static <T extends Serializable> T deserialize(byte[] data) {
  167 + return (T) SerializationUtils.deserialize(data);
  168 + }
  169 +
  170 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rpc;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
  20 +
  21 +/**
  22 + * @author Andrew Shvayka
  23 + */
  24 +@Data
  25 +public final class RpcBroadcastMsg {
  26 + private final ClusterAPIProtos.ToRpcServerMessage msg;
  27 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rpc;
  17 +
  18 +import akka.actor.ActorRef;
  19 +import akka.actor.Props;
  20 +import akka.event.Logging;
  21 +import akka.event.LoggingAdapter;
  22 +import org.thingsboard.server.actors.ActorSystemContext;
  23 +import org.thingsboard.server.actors.service.ContextAwareActor;
  24 +import org.thingsboard.server.actors.service.ContextBasedCreator;
  25 +import org.thingsboard.server.actors.service.DefaultActorService;
  26 +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  27 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  28 +import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
  29 +import org.thingsboard.server.service.cluster.discovery.ServerInstance;
  30 +
  31 +import java.util.*;
  32 +
  33 +/**
  34 + * @author Andrew Shvayka
  35 + */
  36 +public class RpcManagerActor extends ContextAwareActor {
  37 +
  38 + private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
  39 +
  40 + private final Map<ServerAddress, SessionActorInfo> sessionActors;
  41 +
  42 + private final Map<ServerAddress, Queue<ClusterAPIProtos.ToRpcServerMessage>> pendingMsgs;
  43 +
  44 + private final ServerAddress instance;
  45 +
  46 + public RpcManagerActor(ActorSystemContext systemContext) {
  47 + super(systemContext);
  48 + this.sessionActors = new HashMap<>();
  49 + this.pendingMsgs = new HashMap<>();
  50 + this.instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress();
  51 +
  52 + systemContext.getDiscoveryService().getOtherServers().stream()
  53 + .filter(otherServer -> otherServer.getServerAddress().compareTo(instance) > 0)
  54 + .forEach(otherServer -> onCreateSessionRequest(
  55 + new RpcSessionCreateRequestMsg(UUID.randomUUID(), otherServer.getServerAddress(), null)));
  56 +
  57 + }
  58 +
  59 + @Override
  60 + public void onReceive(Object msg) throws Exception {
  61 + if (msg instanceof RpcSessionTellMsg) {
  62 + onMsg((RpcSessionTellMsg) msg);
  63 + } else if (msg instanceof RpcBroadcastMsg) {
  64 + onMsg((RpcBroadcastMsg) msg);
  65 + } else if (msg instanceof RpcSessionCreateRequestMsg) {
  66 + onCreateSessionRequest((RpcSessionCreateRequestMsg) msg);
  67 + } else if (msg instanceof RpcSessionConnectedMsg) {
  68 + onSessionConnected((RpcSessionConnectedMsg) msg);
  69 + } else if (msg instanceof RpcSessionDisconnectedMsg) {
  70 + onSessionDisconnected((RpcSessionDisconnectedMsg) msg);
  71 + } else if (msg instanceof RpcSessionClosedMsg) {
  72 + onSessionClosed((RpcSessionClosedMsg) msg);
  73 + } else if (msg instanceof ClusterEventMsg) {
  74 + onClusterEvent((ClusterEventMsg) msg);
  75 + }
  76 + }
  77 +
  78 + private void onMsg(RpcBroadcastMsg msg) {
  79 + log.debug("Forwarding msg to session actors {}", msg);
  80 + sessionActors.keySet().forEach(address -> onMsg(new RpcSessionTellMsg(address, msg.getMsg())));
  81 + pendingMsgs.values().forEach(queue -> queue.add(msg.getMsg()));
  82 + }
  83 +
  84 + private void onMsg(RpcSessionTellMsg msg) {
  85 + ServerAddress address = msg.getServerAddress();
  86 + SessionActorInfo session = sessionActors.get(address);
  87 + if (session != null) {
  88 + log.debug("{} Forwarding msg to session actor", address);
  89 + session.actor.tell(msg, ActorRef.noSender());
  90 + } else {
  91 + log.debug("{} Storing msg to pending queue", address);
  92 + Queue<ClusterAPIProtos.ToRpcServerMessage> queue = pendingMsgs.get(address);
  93 + if (queue == null) {
  94 + queue = new LinkedList<>();
  95 + pendingMsgs.put(address, queue);
  96 + }
  97 + queue.add(msg.getMsg());
  98 + }
  99 + }
  100 +
  101 + @Override
  102 + public void postStop() {
  103 + sessionActors.clear();
  104 + pendingMsgs.clear();
  105 + }
  106 +
  107 + private void onClusterEvent(ClusterEventMsg msg) {
  108 + ServerAddress server = msg.getServerAddress();
  109 + if (server.compareTo(instance) > 0) {
  110 + if (msg.isAdded()) {
  111 + onCreateSessionRequest(new RpcSessionCreateRequestMsg(UUID.randomUUID(), server, null));
  112 + } else {
  113 + onSessionClose(false, server);
  114 + }
  115 + }
  116 + }
  117 +
  118 + private void onSessionConnected(RpcSessionConnectedMsg msg) {
  119 + register(msg.getRemoteAddress(), msg.getId(), context().sender());
  120 + }
  121 +
  122 + private void onSessionDisconnected(RpcSessionDisconnectedMsg msg) {
  123 + boolean reconnect = msg.isClient() && isRegistered(msg.getRemoteAddress());
  124 + onSessionClose(reconnect, msg.getRemoteAddress());
  125 + }
  126 +
  127 + private void onSessionClosed(RpcSessionClosedMsg msg) {
  128 + boolean reconnect = msg.isClient() && isRegistered(msg.getRemoteAddress());
  129 + onSessionClose(reconnect, msg.getRemoteAddress());
  130 + }
  131 +
  132 + private boolean isRegistered(ServerAddress address) {
  133 + for (ServerInstance server : systemContext.getDiscoveryService().getOtherServers()) {
  134 + if (server.getServerAddress().equals(address)) {
  135 + return true;
  136 + }
  137 + }
  138 + return false;
  139 + }
  140 +
  141 + private void onSessionClose(boolean reconnect, ServerAddress remoteAddress) {
  142 + log.debug("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect);
  143 + SessionActorInfo sessionRef = sessionActors.get(remoteAddress);
  144 + if (context().sender().equals(sessionRef.actor)) {
  145 + sessionActors.remove(remoteAddress);
  146 + pendingMsgs.remove(remoteAddress);
  147 + if (reconnect) {
  148 + onCreateSessionRequest(new RpcSessionCreateRequestMsg(sessionRef.sessionId, remoteAddress, null));
  149 + }
  150 + }
  151 + }
  152 +
  153 + private void onCreateSessionRequest(RpcSessionCreateRequestMsg msg) {
  154 + ActorRef actorRef = createSessionActor(msg);
  155 + if (msg.getRemoteAddress() != null) {
  156 + register(msg.getRemoteAddress(), msg.getMsgUid(), actorRef);
  157 + }
  158 + }
  159 +
  160 + private void register(ServerAddress remoteAddress, UUID uuid, ActorRef sender) {
  161 + sessionActors.put(remoteAddress, new SessionActorInfo(uuid, sender));
  162 + log.debug("[{}][{}] Registering session actor.", remoteAddress, uuid);
  163 + Queue<ClusterAPIProtos.ToRpcServerMessage> data = pendingMsgs.remove(remoteAddress);
  164 + if (data != null) {
  165 + log.debug("[{}][{}] Forwarding {} pending messages.", remoteAddress, uuid, data.size());
  166 + data.forEach(msg -> sender.tell(new RpcSessionTellMsg(remoteAddress, msg), ActorRef.noSender()));
  167 + } else {
  168 + log.debug("[{}][{}] No pending messages to forward.", remoteAddress, uuid);
  169 + }
  170 + }
  171 +
  172 + private ActorRef createSessionActor(RpcSessionCreateRequestMsg msg) {
  173 + log.debug("[{}] Creating session actor.", msg.getMsgUid());
  174 + ActorRef actor = context().actorOf(
  175 + Props.create(new RpcSessionActor.ActorCreator(systemContext, msg.getMsgUid())).withDispatcher(DefaultActorService.RPC_DISPATCHER_NAME));
  176 + actor.tell(msg, context().self());
  177 + return actor;
  178 + }
  179 +
  180 + public static class ActorCreator extends ContextBasedCreator<RpcManagerActor> {
  181 + private static final long serialVersionUID = 1L;
  182 +
  183 + public ActorCreator(ActorSystemContext context) {
  184 + super(context);
  185 + }
  186 +
  187 + @Override
  188 + public RpcManagerActor create() throws Exception {
  189 + return new RpcManagerActor(context);
  190 + }
  191 + }
  192 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rpc;
  17 +
  18 +import akka.event.Logging;
  19 +import akka.event.LoggingAdapter;
  20 +import io.grpc.Channel;
  21 +import io.grpc.ManagedChannelBuilder;
  22 +import io.grpc.stub.StreamObserver;
  23 +import org.thingsboard.server.actors.ActorSystemContext;
  24 +import org.thingsboard.server.actors.service.ContextAwareActor;
  25 +import org.thingsboard.server.actors.service.ContextBasedCreator;
  26 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  27 +import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
  28 +import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc;
  29 +import org.thingsboard.server.service.cluster.rpc.GrpcSession;
  30 +import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener;
  31 +
  32 +import java.util.UUID;
  33 +
  34 +/**
  35 + * @author Andrew Shvayka
  36 + */
  37 +public class RpcSessionActor extends ContextAwareActor {
  38 +
  39 + private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
  40 +
  41 + private final UUID sessionId;
  42 + private GrpcSession session;
  43 + private GrpcSessionListener listener;
  44 +
  45 + public RpcSessionActor(ActorSystemContext systemContext, UUID sessionId) {
  46 + super(systemContext);
  47 + this.sessionId = sessionId;
  48 + }
  49 +
  50 + @Override
  51 + public void onReceive(Object msg) throws Exception {
  52 + if (msg instanceof RpcSessionTellMsg) {
  53 + tell((RpcSessionTellMsg) msg);
  54 + } else if (msg instanceof RpcSessionCreateRequestMsg) {
  55 + initSession((RpcSessionCreateRequestMsg) msg);
  56 + }
  57 + }
  58 +
  59 + private void tell(RpcSessionTellMsg msg) {
  60 + session.sendMsg(msg.getMsg());
  61 + }
  62 +
  63 + @Override
  64 + public void postStop() {
  65 + log.info("Closing session -> {}", session.getRemoteServer());
  66 + session.close();
  67 + }
  68 +
  69 + private void initSession(RpcSessionCreateRequestMsg msg) {
  70 + log.info("[{}] Initializing session", context().self());
  71 + ServerAddress remoteServer = msg.getRemoteAddress();
  72 + listener = new BasicRpcSessionListener(systemContext, context().parent(), context().self());
  73 + if (msg.getRemoteAddress() == null) {
  74 + // Server session
  75 + session = new GrpcSession(listener);
  76 + session.setOutputStream(msg.getResponseObserver());
  77 + session.initInputStream();
  78 + session.initOutputStream();
  79 + systemContext.getRpcService().onSessionCreated(msg.getMsgUid(), session.getInputStream());
  80 + } else {
  81 + // Client session
  82 + Channel channel = ManagedChannelBuilder.forAddress(remoteServer.getHost(), remoteServer.getPort()).usePlaintext(true).build();
  83 + session = new GrpcSession(remoteServer, listener);
  84 + session.initInputStream();
  85 +
  86 + ClusterRpcServiceGrpc.ClusterRpcServiceStub stub = ClusterRpcServiceGrpc.newStub(channel);
  87 + StreamObserver<ClusterAPIProtos.ToRpcServerMessage> outputStream = stub.handlePluginMsgs(session.getInputStream());
  88 +
  89 + session.setOutputStream(outputStream);
  90 + session.initOutputStream();
  91 + outputStream.onNext(toConnectMsg());
  92 + }
  93 + }
  94 +
  95 + public static class ActorCreator extends ContextBasedCreator<RpcSessionActor> {
  96 + private static final long serialVersionUID = 1L;
  97 +
  98 + private final UUID sessionId;
  99 +
  100 + public ActorCreator(ActorSystemContext context, UUID sessionId) {
  101 + super(context);
  102 + this.sessionId = sessionId;
  103 + }
  104 +
  105 + @Override
  106 + public RpcSessionActor create() throws Exception {
  107 + return new RpcSessionActor(context, sessionId);
  108 + }
  109 + }
  110 +
  111 + private ClusterAPIProtos.ToRpcServerMessage toConnectMsg() {
  112 + ServerAddress instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress();
  113 + return ClusterAPIProtos.ToRpcServerMessage.newBuilder().setConnectMsg(
  114 + ClusterAPIProtos.ConnectRpcMessage.newBuilder().setServerAddress(
  115 + ClusterAPIProtos.ServerAddress.newBuilder().setHost(instance.getHost()).setPort(instance.getPort()).build()).build()).build();
  116 +
  117 + }
  118 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rpc;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  20 +
  21 +/**
  22 + * @author Andrew Shvayka
  23 + */
  24 +@Data
  25 +public final class RpcSessionClosedMsg {
  26 +
  27 + private final boolean client;
  28 + private final ServerAddress remoteAddress;
  29 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rpc;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +/**
  24 + * @author Andrew Shvayka
  25 + */
  26 +@Data
  27 +public final class RpcSessionConnectedMsg {
  28 +
  29 + private final ServerAddress remoteAddress;
  30 + private final UUID id;
  31 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rpc;
  17 +
  18 +import io.grpc.stub.StreamObserver;
  19 +import lombok.Data;
  20 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  21 +import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
  22 +
  23 +import java.util.UUID;
  24 +
  25 +/**
  26 + * @author Andrew Shvayka
  27 + */
  28 +@Data
  29 +public final class RpcSessionCreateRequestMsg {
  30 +
  31 + private final UUID msgUid;
  32 + private final ServerAddress remoteAddress;
  33 + private final StreamObserver<ClusterAPIProtos.ToRpcServerMessage> responseObserver;
  34 +
  35 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rpc;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  20 +
  21 +/**
  22 + * @author Andrew Shvayka
  23 + */
  24 +@Data
  25 +public final class RpcSessionDisconnectedMsg {
  26 +
  27 + private final boolean client;
  28 + private final ServerAddress remoteAddress;
  29 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rpc;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  20 +import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
  21 +
  22 +/**
  23 + * @author Andrew Shvayka
  24 + */
  25 +@Data
  26 +public final class RpcSessionTellMsg {
  27 + private final ServerAddress serverAddress;
  28 + private final ClusterAPIProtos.ToRpcServerMessage msg;
  29 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rpc;
  17 +
  18 +import akka.actor.ActorRef;
  19 +import lombok.Data;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +/**
  24 + * @author Andrew Shvayka
  25 + */
  26 +@Data
  27 +public final class SessionActorInfo {
  28 + protected final UUID sessionId;
  29 + protected final ActorRef actor;
  30 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +import akka.actor.ActorRef;
  19 +import org.thingsboard.server.common.msg.core.RuleEngineError;
  20 +import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg;
  21 +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  22 +import org.thingsboard.server.common.msg.session.ToDeviceMsg;
  23 +import org.thingsboard.server.extensions.api.device.DeviceAttributes;
  24 +
  25 +public class ChainProcessingContext {
  26 +
  27 + private final ChainProcessingMetaData md;
  28 + private final int index;
  29 + private final RuleEngineError error;
  30 + private ToDeviceMsg response;
  31 +
  32 +
  33 + public ChainProcessingContext(ChainProcessingMetaData md) {
  34 + super();
  35 + this.md = md;
  36 + this.index = 0;
  37 + this.error = RuleEngineError.NO_RULES;
  38 + }
  39 +
  40 + private ChainProcessingContext(ChainProcessingContext other, int indexOffset, RuleEngineError error) {
  41 + super();
  42 + this.md = other.md;
  43 + this.index = other.index + indexOffset;
  44 + this.error = error;
  45 + this.response = other.response;
  46 +
  47 + if (this.index < 0 || this.index >= this.md.chain.size()) {
  48 + throw new IllegalArgumentException("Can't apply offset " + indexOffset + " to the chain!");
  49 + }
  50 + }
  51 +
  52 + public ActorRef getDeviceActor() {
  53 + return md.originator;
  54 + }
  55 +
  56 + public ActorRef getCurrentActor() {
  57 + return md.chain.getRuleActorMd(index).getActorRef();
  58 + }
  59 +
  60 + public boolean hasNext() {
  61 + return (getChainLength() - 1) > index;
  62 + }
  63 +
  64 + public boolean isFailure() {
  65 + return (error != null && error.isCritical()) || (response != null && !response.isSuccess());
  66 + }
  67 +
  68 + public ChainProcessingContext getNext() {
  69 + return new ChainProcessingContext(this, 1, this.error);
  70 + }
  71 +
  72 + public ChainProcessingContext withError(RuleEngineError error) {
  73 + if (error != null && (this.error == null || this.error.getPriority() < error.getPriority())) {
  74 + return new ChainProcessingContext(this, 0, error);
  75 + } else {
  76 + return this;
  77 + }
  78 + }
  79 +
  80 + public int getChainLength() {
  81 + return md.chain.size();
  82 + }
  83 +
  84 + public ToDeviceActorMsg getInMsg() {
  85 + return md.inMsg;
  86 + }
  87 +
  88 + public DeviceAttributes getAttributes() {
  89 + return md.deviceAttributes;
  90 + }
  91 +
  92 + public ToDeviceMsg getResponse() {
  93 + return response;
  94 + }
  95 +
  96 + public void mergeResponse(ToDeviceMsg response) {
  97 + // TODO add merge logic
  98 + this.response = response;
  99 + }
  100 +
  101 + public RuleEngineErrorMsg getError() {
  102 + return new RuleEngineErrorMsg(md.inMsg.getPayload().getMsgType(), error);
  103 + }
  104 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +import org.thingsboard.server.extensions.api.device.DeviceAttributes;
  19 +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  20 +
  21 +import akka.actor.ActorRef;
  22 +
  23 +/**
  24 + * Immutable part of chain processing data;
  25 + *
  26 + * @author ashvayka
  27 + */
  28 +public final class ChainProcessingMetaData {
  29 +
  30 + final RuleActorChain chain;
  31 + final ToDeviceActorMsg inMsg;
  32 + final ActorRef originator;
  33 + final DeviceAttributes deviceAttributes;
  34 +
  35 + public ChainProcessingMetaData(RuleActorChain chain, ToDeviceActorMsg inMsg, DeviceAttributes deviceAttributes, ActorRef originator) {
  36 + super();
  37 + this.chain = chain;
  38 + this.inMsg = inMsg;
  39 + this.originator = originator;
  40 + this.deviceAttributes = deviceAttributes;
  41 + }
  42 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +public class ComplexRuleActorChain implements RuleActorChain {
  19 +
  20 + private final RuleActorChain systemChain;
  21 + private final RuleActorChain tenantChain;
  22 +
  23 + public ComplexRuleActorChain(RuleActorChain systemChain, RuleActorChain tenantChain) {
  24 + super();
  25 + this.systemChain = systemChain;
  26 + this.tenantChain = tenantChain;
  27 + }
  28 +
  29 + @Override
  30 + public int size() {
  31 + return systemChain.size() + tenantChain.size();
  32 + }
  33 +
  34 + @Override
  35 + public RuleActorMetaData getRuleActorMd(int index) {
  36 + if (index < systemChain.size()) {
  37 + return systemChain.getRuleActorMd(index);
  38 + } else {
  39 + return tenantChain.getRuleActorMd(index - systemChain.size());
  40 + }
  41 + }
  42 +
  43 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +public class CompoundRuleActorChain {
  19 +
  20 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +import org.thingsboard.server.actors.ActorSystemContext;
  19 +import org.thingsboard.server.actors.service.ComponentActor;
  20 +import org.thingsboard.server.actors.service.ContextBasedCreator;
  21 +import org.thingsboard.server.actors.stats.StatsPersistTick;
  22 +import org.thingsboard.server.common.data.id.RuleId;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  25 +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
  26 +import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
  27 +
  28 +public class RuleActor extends ComponentActor<RuleId, RuleActorMessageProcessor> {
  29 +
  30 + private RuleActor(ActorSystemContext systemContext, TenantId tenantId, RuleId ruleId) {
  31 + super(systemContext, tenantId, ruleId);
  32 + setProcessor(new RuleActorMessageProcessor(tenantId, ruleId, systemContext, logger));
  33 + }
  34 +
  35 + @Override
  36 + public void onReceive(Object msg) throws Exception {
  37 + logger.debug("[{}] Received message: {}", id, msg);
  38 + if (msg instanceof RuleProcessingMsg) {
  39 + try {
  40 + processor.onRuleProcessingMsg(context(), (RuleProcessingMsg) msg);
  41 + increaseMessagesProcessedCount();
  42 + } catch (Exception e) {
  43 + logAndPersist("onDeviceMsg", e);
  44 + }
  45 + } else if (msg instanceof PluginToRuleMsg<?>) {
  46 + try {
  47 + processor.onPluginMsg(context(), (PluginToRuleMsg<?>) msg);
  48 + } catch (Exception e) {
  49 + logAndPersist("onPluginMsg", e);
  50 + }
  51 + } else if (msg instanceof ComponentLifecycleMsg) {
  52 + onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
  53 + } else if (msg instanceof ClusterEventMsg) {
  54 + onClusterEventMsg((ClusterEventMsg) msg);
  55 + } else if (msg instanceof RuleToPluginTimeoutMsg) {
  56 + try {
  57 + processor.onTimeoutMsg(context(), (RuleToPluginTimeoutMsg) msg);
  58 + } catch (Exception e) {
  59 + logAndPersist("onTimeoutMsg", e);
  60 + }
  61 + } else if (msg instanceof StatsPersistTick) {
  62 + onStatsPersistTick(id);
  63 + } else {
  64 + logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName());
  65 + }
  66 + }
  67 +
  68 + public static class ActorCreator extends ContextBasedCreator<RuleActor> {
  69 + private static final long serialVersionUID = 1L;
  70 +
  71 + private final TenantId tenantId;
  72 + private final RuleId ruleId;
  73 +
  74 + public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleId ruleId) {
  75 + super(context);
  76 + this.tenantId = tenantId;
  77 + this.ruleId = ruleId;
  78 + }
  79 +
  80 + @Override
  81 + public RuleActor create() throws Exception {
  82 + return new RuleActor(context, tenantId, ruleId);
  83 + }
  84 + }
  85 +
  86 + @Override
  87 + protected long getErrorPersistFrequency() {
  88 + return systemContext.getRuleErrorPersistFrequency();
  89 + }
  90 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +public interface RuleActorChain {
  19 +
  20 + int size();
  21 +
  22 + RuleActorMetaData getRuleActorMd(int index);
  23 +
  24 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +import java.util.*;
  19 +
  20 +import com.fasterxml.jackson.core.JsonProcessingException;
  21 +import org.thingsboard.server.actors.ActorSystemContext;
  22 +import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper;
  23 +import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
  24 +import org.thingsboard.server.common.data.id.PluginId;
  25 +import org.thingsboard.server.common.data.id.RuleId;
  26 +import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
  28 +import org.thingsboard.server.common.data.plugin.PluginMetaData;
  29 +import org.thingsboard.server.common.data.rule.RuleMetaData;
  30 +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  31 +import org.thingsboard.server.common.msg.core.BasicRequest;
  32 +import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
  33 +import org.thingsboard.server.common.msg.core.RuleEngineError;
  34 +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  35 +import org.thingsboard.server.common.msg.session.MsgType;
  36 +import org.thingsboard.server.common.msg.session.ToDeviceMsg;
  37 +import org.thingsboard.server.common.msg.session.ex.ProcessingTimeoutException;
  38 +import org.thingsboard.server.extensions.api.rules.*;
  39 +import org.thingsboard.server.extensions.api.plugins.PluginAction;
  40 +import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
  41 +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
  42 +
  43 +import com.fasterxml.jackson.databind.JsonNode;
  44 +
  45 +import akka.actor.ActorContext;
  46 +import akka.actor.ActorRef;
  47 +import akka.event.LoggingAdapter;
  48 +
  49 +class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
  50 +
  51 + private final RuleProcessingContext ruleCtx;
  52 + private final Map<UUID, RuleProcessingMsg> pendingMsgMap;
  53 +
  54 + private RuleMetaData ruleMd;
  55 + private ComponentLifecycleState state;
  56 + private List<RuleFilter> filters;
  57 + private RuleProcessor processor;
  58 + private PluginAction action;
  59 +
  60 + private TenantId pluginTenantId;
  61 + private PluginId pluginId;
  62 +
  63 + protected RuleActorMessageProcessor(TenantId tenantId, RuleId ruleId, ActorSystemContext systemContext, LoggingAdapter logger) {
  64 + super(systemContext, logger, tenantId, ruleId);
  65 + this.pendingMsgMap = new HashMap<>();
  66 + this.ruleCtx = new RuleProcessingContext(systemContext, ruleId);
  67 + }
  68 +
  69 + @Override
  70 + public void start() throws Exception {
  71 + logger.info("[{}][{}] Starting rule actor.", entityId, tenantId);
  72 + ruleMd = systemContext.getRuleService().findRuleById(entityId);
  73 + if (ruleMd == null) {
  74 + throw new RuleInitializationException("Rule not found!");
  75 + }
  76 + state = ruleMd.getState();
  77 + if (state == ComponentLifecycleState.ACTIVE) {
  78 + logger.info("[{}] Rule is active. Going to initialize rule components.", entityId);
  79 + initComponent();
  80 + } else {
  81 + logger.info("[{}] Rule is suspended. Skipping rule components initialization.", entityId);
  82 + }
  83 +
  84 + logger.info("[{}][{}] Started rule actor.", entityId, tenantId);
  85 + }
  86 +
  87 + @Override
  88 + public void stop() throws Exception {
  89 + onStop();
  90 + }
  91 +
  92 +
  93 + private void initComponent() throws RuleException {
  94 + try {
  95 + if (!ruleMd.getFilters().isArray()) {
  96 + throw new RuntimeException("Filters are not array!");
  97 + }
  98 + fetchPluginInfo();
  99 + initFilters();
  100 + initProcessor();
  101 + initAction();
  102 + } catch (RuntimeException e) {
  103 + throw new RuleInitializationException("Unknown runtime exception!", e);
  104 + } catch (InstantiationException e) {
  105 + throw new RuleInitializationException("No default constructor for rule implementation!", e);
  106 + } catch (IllegalAccessException e) {
  107 + throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e);
  108 + } catch (ClassNotFoundException e) {
  109 + throw new RuleInitializationException("Rule Class not found!", e);
  110 + } catch (Exception e) {
  111 + throw new RuleException(e.getMessage(), e);
  112 + }
  113 + }
  114 +
  115 + private void initAction() throws Exception {
  116 + JsonNode actionMd = ruleMd.getAction();
  117 + action = initComponent(actionMd);
  118 + }
  119 +
  120 + private void initProcessor() throws Exception {
  121 + if (ruleMd.getProcessor() != null && !ruleMd.getProcessor().isNull()) {
  122 + processor = initComponent(ruleMd.getProcessor());
  123 + }
  124 + }
  125 +
  126 + private void initFilters() throws Exception {
  127 + filters = new ArrayList<>(ruleMd.getFilters().size());
  128 + for (int i = 0; i < ruleMd.getFilters().size(); i++) {
  129 + filters.add(initComponent(ruleMd.getFilters().get(i)));
  130 + }
  131 + }
  132 +
  133 + private void fetchPluginInfo() {
  134 + PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken());
  135 + pluginTenantId = pluginMd.getTenantId();
  136 + pluginId = pluginMd.getId();
  137 + }
  138 +
  139 + protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException {
  140 + if (state != ComponentLifecycleState.ACTIVE) {
  141 + pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_ACTIVE_RULES);
  142 + return;
  143 + }
  144 + ChainProcessingContext chainCtx = msg.getCtx();
  145 + ToDeviceActorMsg inMsg = chainCtx.getInMsg();
  146 +
  147 + ruleCtx.update(inMsg, chainCtx.getAttributes());
  148 +
  149 + logger.debug("[{}] Going to filter in msg: {}", entityId, inMsg);
  150 + for (RuleFilter filter : filters) {
  151 + if (!filter.filter(ruleCtx, inMsg)) {
  152 + logger.debug("[{}] In msg is NOT valid for processing by current rule: {}", entityId, inMsg);
  153 + pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_FILTERS_MATCHED);
  154 + return;
  155 + }
  156 + }
  157 + RuleProcessingMetaData inMsgMd;
  158 + if (processor != null) {
  159 + logger.debug("[{}] Going to process in msg: {}", entityId, inMsg);
  160 + inMsgMd = processor.process(ruleCtx, inMsg);
  161 + } else {
  162 + inMsgMd = new RuleProcessingMetaData();
  163 + }
  164 + logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg);
  165 + Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd);
  166 + if (ruleToPluginMsgOptional.isPresent()) {
  167 + RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get();
  168 + logger.debug("[{}] Device msg is converter to: {}", entityId, ruleToPluginMsg);
  169 + context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self());
  170 + if (action.isOneWayAction()) {
  171 + pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
  172 + } else {
  173 + pendingMsgMap.put(ruleToPluginMsg.getUid(), msg);
  174 + scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout());
  175 + }
  176 + } else {
  177 + logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId);
  178 + pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_REQUEST_FROM_ACTIONS);
  179 + return;
  180 + }
  181 + }
  182 +
  183 + public void onPluginMsg(ActorContext context, PluginToRuleMsg<?> msg) {
  184 + RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid());
  185 + if (pendingMsg != null) {
  186 + ChainProcessingContext ctx = pendingMsg.getCtx();
  187 + Optional<ToDeviceMsg> ruleResponseOptional = action.convert(msg);
  188 + if (ruleResponseOptional.isPresent()) {
  189 + ctx.mergeResponse(ruleResponseOptional.get());
  190 + pushToNextRule(context, ctx, null);
  191 + } else {
  192 + pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS);
  193 + }
  194 + } else {
  195 + logger.warning("[{}] Processing timeout detected: [{}]", entityId, msg.getUid());
  196 + }
  197 + }
  198 +
  199 + public void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) {
  200 + RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId());
  201 + if (pendingMsg != null) {
  202 + logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg);
  203 + ChainProcessingContext ctx = pendingMsg.getCtx();
  204 + pushToNextRule(context, ctx, RuleEngineError.PLUGIN_TIMEOUT);
  205 + }
  206 + }
  207 +
  208 + private void pushToNextRule(ActorContext context, ChainProcessingContext ctx, RuleEngineError error) {
  209 + if (error != null) {
  210 + ctx = ctx.withError(error);
  211 + }
  212 + if (ctx.isFailure()) {
  213 + logger.debug("[{}] Forwarding processing chain to device actor due to failure.", ctx.getInMsg().getDeviceId());
  214 + ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
  215 + } else if (!ctx.hasNext()) {
  216 + logger.debug("[{}] Forwarding processing chain to device actor due to end of chain.", ctx.getInMsg().getDeviceId());
  217 + ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
  218 + } else {
  219 + logger.debug("[{}] Forwarding processing chain to next rule actor.", ctx.getInMsg().getDeviceId());
  220 + ChainProcessingContext nextTask = ctx.getNext();
  221 + nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self());
  222 + }
  223 + }
  224 +
  225 + @Override
  226 + public void onCreated(ActorContext context) {
  227 + logger.info("[{}] Going to process onCreated rule.", entityId);
  228 + }
  229 +
  230 + @Override
  231 + public void onUpdate(ActorContext context) throws RuleException {
  232 + RuleMetaData oldRuleMd = ruleMd;
  233 + ruleMd = systemContext.getRuleService().findRuleById(entityId);
  234 + logger.info("[{}] Rule configuration was updated from {} to {}.", entityId, oldRuleMd, ruleMd);
  235 + try {
  236 + fetchPluginInfo();
  237 + if (!Objects.equals(oldRuleMd.getFilters(), ruleMd.getFilters())) {
  238 + logger.info("[{}] Rule filters require restart due to json change from {} to {}.",
  239 + entityId, mapper.writeValueAsString(oldRuleMd.getFilters()), mapper.writeValueAsString(ruleMd.getFilters()));
  240 + stopFilters();
  241 + initFilters();
  242 + }
  243 + if (!Objects.equals(oldRuleMd.getProcessor(), ruleMd.getProcessor())) {
  244 + logger.info("[{}] Rule processor require restart due to configuration change.", entityId);
  245 + stopProcessor();
  246 + initProcessor();
  247 + }
  248 + if (!Objects.equals(oldRuleMd.getAction(), ruleMd.getAction())) {
  249 + logger.info("[{}] Rule action require restart due to configuration change.", entityId);
  250 + stopAction();
  251 + initAction();
  252 + }
  253 + } catch (RuntimeException e) {
  254 + throw new RuleInitializationException("Unknown runtime exception!", e);
  255 + } catch (InstantiationException e) {
  256 + throw new RuleInitializationException("No default constructor for rule implementation!", e);
  257 + } catch (IllegalAccessException e) {
  258 + throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e);
  259 + } catch (ClassNotFoundException e) {
  260 + throw new RuleInitializationException("Rule Class not found!", e);
  261 + } catch (JsonProcessingException e) {
  262 + throw new RuleInitializationException("Rule configuration is invalid!", e);
  263 + } catch (Exception e) {
  264 + throw new RuleInitializationException(e.getMessage(), e);
  265 + }
  266 + }
  267 +
  268 + @Override
  269 + public void onActivate(ActorContext context) throws Exception {
  270 + logger.info("[{}] Going to process onActivate rule.", entityId);
  271 + this.state = ComponentLifecycleState.ACTIVE;
  272 + if (action != null) {
  273 + if (filters != null) {
  274 + filters.forEach(f -> f.resume());
  275 + }
  276 + if (processor != null) {
  277 + processor.resume();
  278 + }
  279 + if (action != null) {
  280 + action.resume();
  281 + }
  282 + logger.info("[{}] Rule resumed.", entityId);
  283 + } else {
  284 + start();
  285 + }
  286 + }
  287 +
  288 + @Override
  289 + public void onSuspend(ActorContext context) {
  290 + logger.info("[{}] Going to process onSuspend rule.", entityId);
  291 + this.state = ComponentLifecycleState.SUSPENDED;
  292 + if (filters != null) {
  293 + filters.forEach(f -> f.suspend());
  294 + }
  295 + if (processor != null) {
  296 + processor.suspend();
  297 + }
  298 + if (action != null) {
  299 + action.suspend();
  300 + }
  301 + }
  302 +
  303 + @Override
  304 + public void onStop(ActorContext context) {
  305 + logger.info("[{}] Going to process onStop rule.", entityId);
  306 + onStop();
  307 + scheduleMsgWithDelay(context, new RuleTerminationMsg(entityId), systemContext.getRuleActorTerminationDelay());
  308 + }
  309 +
  310 + private void onStop() {
  311 + this.state = ComponentLifecycleState.SUSPENDED;
  312 + stopFilters();
  313 + stopProcessor();
  314 + stopAction();
  315 + }
  316 +
  317 + @Override
  318 + public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
  319 +
  320 + }
  321 +
  322 + private void stopAction() {
  323 + if (action != null) {
  324 + action.stop();
  325 + }
  326 + }
  327 +
  328 + private void stopProcessor() {
  329 + if (processor != null) {
  330 + processor.stop();
  331 + }
  332 + }
  333 +
  334 + private void stopFilters() {
  335 + if (filters != null) {
  336 + filters.forEach(f -> f.stop());
  337 + }
  338 + }
  339 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +import java.util.Comparator;
  19 +
  20 +import org.thingsboard.server.common.data.id.RuleId;
  21 +
  22 +import akka.actor.ActorRef;
  23 +
  24 +public class RuleActorMetaData {
  25 +
  26 + private final RuleId ruleId;
  27 + private final boolean systemRule;
  28 + private final int weight;
  29 + private final ActorRef actorRef;
  30 +
  31 + public static final Comparator<RuleActorMetaData> RULE_ACTOR_MD_COMPARATOR = new Comparator<RuleActorMetaData>() {
  32 +
  33 + @Override
  34 + public int compare(RuleActorMetaData r1, RuleActorMetaData r2) {
  35 + if (r1.isSystemRule() && !r2.isSystemRule()) {
  36 + return 1;
  37 + } else if (!r1.isSystemRule() && r2.isSystemRule()) {
  38 + return -1;
  39 + } else {
  40 + return Integer.compare(r2.getWeight(), r1.getWeight());
  41 + }
  42 + }
  43 + };
  44 +
  45 + public static RuleActorMetaData systemRule(RuleId ruleId, int weight, ActorRef actorRef) {
  46 + return new RuleActorMetaData(ruleId, true, weight, actorRef);
  47 + }
  48 +
  49 + public static RuleActorMetaData tenantRule(RuleId ruleId, int weight, ActorRef actorRef) {
  50 + return new RuleActorMetaData(ruleId, false, weight, actorRef);
  51 + }
  52 +
  53 + private RuleActorMetaData(RuleId ruleId, boolean systemRule, int weight, ActorRef actorRef) {
  54 + super();
  55 + this.ruleId = ruleId;
  56 + this.systemRule = systemRule;
  57 + this.weight = weight;
  58 + this.actorRef = actorRef;
  59 + }
  60 +
  61 + public RuleId getRuleId() {
  62 + return ruleId;
  63 + }
  64 +
  65 + public boolean isSystemRule() {
  66 + return systemRule;
  67 + }
  68 +
  69 + public int getWeight() {
  70 + return weight;
  71 + }
  72 +
  73 + public ActorRef getActorRef() {
  74 + return actorRef;
  75 + }
  76 +
  77 + @Override
  78 + public int hashCode() {
  79 + final int prime = 31;
  80 + int result = 1;
  81 + result = prime * result + ((ruleId == null) ? 0 : ruleId.hashCode());
  82 + return result;
  83 + }
  84 +
  85 + @Override
  86 + public boolean equals(Object obj) {
  87 + if (this == obj)
  88 + return true;
  89 + if (obj == null)
  90 + return false;
  91 + if (getClass() != obj.getClass())
  92 + return false;
  93 + RuleActorMetaData other = (RuleActorMetaData) obj;
  94 + if (ruleId == null) {
  95 + if (other.ruleId != null)
  96 + return false;
  97 + } else if (!ruleId.equals(other.ruleId))
  98 + return false;
  99 + return true;
  100 + }
  101 +
  102 + @Override
  103 + public String toString() {
  104 + return "RuleActorMetaData [ruleId=" + ruleId + ", systemRule=" + systemRule + ", weight=" + weight + ", actorRef=" + actorRef + "]";
  105 + }
  106 +
  107 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +import org.thingsboard.server.actors.ActorSystemContext;
  19 +import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
  20 +import org.thingsboard.server.common.data.id.RuleId;
  21 +
  22 +import akka.event.LoggingAdapter;
  23 +
  24 +public class RuleContextAwareMsgProcessor extends AbstractContextAwareMsgProcessor {
  25 +
  26 + private final RuleId ruleId;
  27 +
  28 + protected RuleContextAwareMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, RuleId ruleId) {
  29 + super(systemContext, logger);
  30 + this.ruleId = ruleId;
  31 + }
  32 +
  33 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +import org.thingsboard.server.actors.ActorSystemContext;
  19 +import org.thingsboard.server.common.data.Event;
  20 +import org.thingsboard.server.common.data.id.*;
  21 +import org.thingsboard.server.dao.event.EventService;
  22 +import org.thingsboard.server.dao.timeseries.TimeseriesService;
  23 +import org.thingsboard.server.extensions.api.device.DeviceAttributes;
  24 +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  25 +import org.thingsboard.server.extensions.api.rules.RuleContext;
  26 +
  27 +import java.util.Optional;
  28 +
  29 +public class RuleProcessingContext implements RuleContext {
  30 +
  31 + private final TimeseriesService tsService;
  32 + private final EventService eventService;
  33 + private final RuleId ruleId;
  34 + private TenantId tenantId;
  35 + private CustomerId customerId;
  36 + private DeviceId deviceId;
  37 + private DeviceAttributes deviceAttributes;
  38 +
  39 + RuleProcessingContext(ActorSystemContext systemContext, RuleId ruleId) {
  40 + this.tsService = systemContext.getTsService();
  41 + this.eventService = systemContext.getEventService();
  42 + this.ruleId = ruleId;
  43 + }
  44 +
  45 + void update(ToDeviceActorMsg toDeviceActorMsg, DeviceAttributes attributes) {
  46 + this.tenantId = toDeviceActorMsg.getTenantId();
  47 + this.customerId = toDeviceActorMsg.getCustomerId();
  48 + this.deviceId = toDeviceActorMsg.getDeviceId();
  49 + this.deviceAttributes = attributes;
  50 + }
  51 +
  52 + @Override
  53 + public RuleId getRuleId() {
  54 + return ruleId;
  55 + }
  56 +
  57 + @Override
  58 + public DeviceAttributes getDeviceAttributes() {
  59 + return deviceAttributes;
  60 + }
  61 +
  62 + @Override
  63 + public Event save(Event event) {
  64 + checkEvent(event);
  65 + return eventService.save(event);
  66 + }
  67 +
  68 + @Override
  69 + public Optional<Event> saveIfNotExists(Event event) {
  70 + checkEvent(event);
  71 + return eventService.saveIfNotExists(event);
  72 + }
  73 +
  74 + @Override
  75 + public Optional<Event> findEvent(String eventType, String eventUid) {
  76 + return eventService.findEvent(tenantId, deviceId, eventType, eventUid);
  77 + }
  78 +
  79 + private void checkEvent(Event event) {
  80 + if (event.getTenantId() == null) {
  81 + event.setTenantId(tenantId);
  82 + } else if (!tenantId.equals(event.getTenantId())) {
  83 + throw new IllegalArgumentException("Invalid Tenant id!");
  84 + }
  85 + if (event.getEntityId() == null) {
  86 + event.setEntityId(deviceId);
  87 + }
  88 + }
  89 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +public class RuleProcessingMsg {
  19 +
  20 + private final ChainProcessingContext ctx;
  21 +
  22 + public RuleProcessingMsg(ChainProcessingContext ctx) {
  23 + super();
  24 + this.ctx = ctx;
  25 + }
  26 +
  27 + public ChainProcessingContext getCtx() {
  28 + return ctx;
  29 + }
  30 +
  31 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +import org.thingsboard.server.actors.shared.ActorTerminationMsg;
  19 +import org.thingsboard.server.common.data.id.PluginId;
  20 +import org.thingsboard.server.common.data.id.RuleId;
  21 +
  22 +/**
  23 + * @author Andrew Shvayka
  24 + */
  25 +public class RuleTerminationMsg extends ActorTerminationMsg<RuleId> {
  26 +
  27 + public RuleTerminationMsg(RuleId id) {
  28 + super(id);
  29 + }
  30 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +import java.io.Serializable;
  19 +import java.util.UUID;
  20 +
  21 +public class RuleToPluginTimeoutMsg implements Serializable {
  22 +
  23 + private static final long serialVersionUID = 1L;
  24 +
  25 + private final UUID msgId;
  26 +
  27 + public RuleToPluginTimeoutMsg(UUID msgId) {
  28 + super();
  29 + this.msgId = msgId;
  30 + }
  31 +
  32 + public UUID getMsgId() {
  33 + return msgId;
  34 + }
  35 +
  36 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +public class RulesProcessedMsg {
  19 + private final ChainProcessingContext ctx;
  20 +
  21 + public RulesProcessedMsg(ChainProcessingContext ctx) {
  22 + super();
  23 + this.ctx = ctx;
  24 + }
  25 +
  26 + public ChainProcessingContext getCtx() {
  27 + return ctx;
  28 + }
  29 +
  30 + @Override
  31 + public String toString() {
  32 + return "RulesProcessedMsg [ctx=" + ctx + "]";
  33 + }
  34 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.rule;
  17 +
  18 +import java.util.ArrayList;
  19 +import java.util.Collections;
  20 +import java.util.List;
  21 +import java.util.Set;
  22 +
  23 +public class SimpleRuleActorChain implements RuleActorChain {
  24 +
  25 + private final List<RuleActorMetaData> rules;
  26 +
  27 + public SimpleRuleActorChain(Set<RuleActorMetaData> ruleSet) {
  28 + rules = new ArrayList<>(ruleSet);
  29 + Collections.sort(rules, RuleActorMetaData.RULE_ACTOR_MD_COMPARATOR);
  30 + }
  31 +
  32 + public int size() {
  33 + return rules.size();
  34 + }
  35 +
  36 + public RuleActorMetaData getRuleActorMd(int index) {
  37 + return rules.get(index);
  38 + }
  39 +
  40 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.service;
  17 +
  18 +import org.thingsboard.server.common.data.id.PluginId;
  19 +import org.thingsboard.server.common.data.id.RuleId;
  20 +import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  22 +import org.thingsboard.server.common.transport.SessionMsgProcessor;
  23 +import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener;
  24 +import org.thingsboard.server.service.cluster.rpc.RpcMsgListener;
  25 +
  26 +public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor, RestMsgProcessor, RpcMsgListener, DiscoveryServiceListener {
  27 +
  28 + void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state);
  29 +
  30 + void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state);
  31 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.service;
  17 +
  18 +import akka.actor.ActorRef;
  19 +import akka.event.Logging;
  20 +import akka.event.LoggingAdapter;
  21 +import org.thingsboard.server.actors.ActorSystemContext;
  22 +import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
  23 +import org.thingsboard.server.actors.stats.StatsPersistMsg;
  24 +import org.thingsboard.server.common.data.id.EntityId;
  25 +import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  27 +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  28 +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
  29 +
  30 +/**
  31 + * @author Andrew Shvayka
  32 + */
  33 +public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgProcessor<T>> extends ContextAwareActor {
  34 +
  35 + protected final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
  36 +
  37 + private long lastPersistedErrorTs = 0L;
  38 + protected final TenantId tenantId;
  39 + protected final T id;
  40 + protected P processor;
  41 + private long messagesProcessed;
  42 + private long errorsOccurred;
  43 +
  44 + public ComponentActor(ActorSystemContext systemContext, TenantId tenantId, T id) {
  45 + super(systemContext);
  46 + this.tenantId = tenantId;
  47 + this.id = id;
  48 + }
  49 +
  50 + protected void setProcessor(P processor) {
  51 + this.processor = processor;
  52 + }
  53 +
  54 + @Override
  55 + public void preStart() {
  56 + try {
  57 + processor.start();
  58 + logLifecycleEvent(ComponentLifecycleEvent.STARTED);
  59 + if (systemContext.isStatisticsEnabled()) {
  60 + scheduleStatsPersistTick();
  61 + }
  62 + } catch (Exception e) {
  63 + logger.warning("[{}][{}] Failed to start {} processor: {}", tenantId, id, id.getEntityType(), e);
  64 + logAndPersist("OnStart", e, true);
  65 + logLifecycleEvent(ComponentLifecycleEvent.STARTED, e);
  66 + }
  67 + }
  68 +
  69 + private void scheduleStatsPersistTick() {
  70 + try {
  71 + processor.scheduleStatsPersistTick(context(), systemContext.getStatisticsPersistFrequency());
  72 + } catch (Exception e) {
  73 + logger.error("[{}][{}] Failed to schedule statistics store message. No statistics is going to be stored: {}", tenantId, id, e.getMessage());
  74 + logAndPersist("onScheduleStatsPersistMsg", e);
  75 + }
  76 + }
  77 +
  78 + @Override
  79 + public void postStop() {
  80 + try {
  81 + processor.stop();
  82 + logLifecycleEvent(ComponentLifecycleEvent.STOPPED);
  83 + } catch (Exception e) {
  84 + logger.warning("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage());
  85 + logAndPersist("OnStop", e, true);
  86 + logLifecycleEvent(ComponentLifecycleEvent.STOPPED, e);
  87 + }
  88 + }
  89 +
  90 + protected void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
  91 + try {
  92 + switch (msg.getEvent()) {
  93 + case CREATED:
  94 + processor.onCreated(context());
  95 + break;
  96 + case UPDATED:
  97 + processor.onUpdate(context());
  98 + break;
  99 + case ACTIVATED:
  100 + processor.onActivate(context());
  101 + break;
  102 + case SUSPENDED:
  103 + processor.onSuspend(context());
  104 + break;
  105 + case DELETED:
  106 + processor.onStop(context());
  107 + }
  108 + logLifecycleEvent(msg.getEvent());
  109 + } catch (Exception e) {
  110 + logAndPersist("onLifecycleMsg", e, true);
  111 + logLifecycleEvent(msg.getEvent(), e);
  112 + }
  113 + }
  114 +
  115 + protected void onClusterEventMsg(ClusterEventMsg msg) {
  116 + try {
  117 + processor.onClusterEventMsg(msg);
  118 + } catch (Exception e) {
  119 + logAndPersist("onClusterEventMsg", e);
  120 + }
  121 + }
  122 +
  123 + protected void onStatsPersistTick(EntityId entityId) {
  124 + try {
  125 + systemContext.getStatsActor().tell(new StatsPersistMsg(messagesProcessed, errorsOccurred, tenantId, entityId), ActorRef.noSender());
  126 + resetStatsCounters();
  127 + } catch (Exception e) {
  128 + logAndPersist("onStatsPersistTick", e);
  129 + }
  130 + }
  131 +
  132 + private void resetStatsCounters() {
  133 + messagesProcessed = 0;
  134 + errorsOccurred = 0;
  135 + }
  136 +
  137 + protected void increaseMessagesProcessedCount() {
  138 + messagesProcessed++;
  139 + }
  140 +
  141 +
  142 + protected void logAndPersist(String method, Exception e) {
  143 + logAndPersist(method, e, false);
  144 + }
  145 +
  146 + private void logAndPersist(String method, Exception e, boolean critical) {
  147 + errorsOccurred++;
  148 + if (critical) {
  149 + logger.warning("[{}][{}] Failed to process {} msg: {}", id, tenantId, method, e);
  150 + } else {
  151 + logger.debug("[{}][{}] Failed to process {} msg: {}", id, tenantId, method, e);
  152 + }
  153 + long ts = System.currentTimeMillis();
  154 + if (ts - lastPersistedErrorTs > getErrorPersistFrequency()) {
  155 + systemContext.persistError(tenantId, id, method, e);
  156 + lastPersistedErrorTs = ts;
  157 + }
  158 + }
  159 +
  160 + protected void logLifecycleEvent(ComponentLifecycleEvent event) {
  161 + logLifecycleEvent(event, null);
  162 + }
  163 +
  164 + protected void logLifecycleEvent(ComponentLifecycleEvent event, Exception e) {
  165 + systemContext.persistLifecycleEvent(tenantId, id, event, e);
  166 + }
  167 +
  168 + protected abstract long getErrorPersistFrequency();
  169 +}
... ...
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.service;
  17 +
  18 +import akka.actor.UntypedActor;
  19 +import org.thingsboard.server.actors.ActorSystemContext;
  20 +
  21 +public abstract class ContextAwareActor extends UntypedActor {
  22 +
  23 + public static final int ENTITY_PACK_LIMIT = 1024;
  24 +
  25 + protected final ActorSystemContext systemContext;
  26 +
  27 + public ContextAwareActor(ActorSystemContext systemContext) {
  28 + super();
  29 + this.systemContext = systemContext;
  30 + }
  31 +}
... ...