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/ | |
\ No newline at end of file | ... | ... |
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 | +} | ... | ... |