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.
.gitignore
0 → 100644
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 |
LICENSE
0 → 100644
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. |
README.md
0 → 100644
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_ |
application/.gitignore
0 → 100644
1 | +!bin/ |
application/build.gradle
0 → 100644
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 | +} |
application/pom.xml
0 → 100644
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> |
application/src/main/conf/logback.xml
0 → 100644
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> |
application/src/main/conf/thingsboard.conf
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/actors/plugin/PluginCallbackMessage.java
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/actors/plugin/RuleToPluginMsgWrapper.java
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionCreateRequestMsg.java
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionDisconnectedMsg.java
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java
0 → 100644
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 | +} |
application/src/main/java/org/thingsboard/server/actors/rule/RuleContextAwareMsgProcessor.java
0 → 100644
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 | +} |