commit
c22bf33def
466 changed files with 33748 additions and 0 deletions
@ -0,0 +1,32 @@ |
|||
output/** |
|||
*.class |
|||
*~ |
|||
*.iml |
|||
*/.idea/** |
|||
.idea/** |
|||
.idea |
|||
*.log |
|||
*.log.[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] |
|||
*/.classpath |
|||
.classpath |
|||
*/.project |
|||
.project |
|||
.cache/** |
|||
target/ |
|||
build/ |
|||
tmp_deb_control/ |
|||
tmp_rpm_control/ |
|||
tmp_sh/ |
|||
.gwt/ |
|||
.settings/ |
|||
/bin |
|||
bin/ |
|||
**/dependency-reduced-pom.xml |
|||
pom.xml.versionsBackup |
|||
.DS_Store |
|||
**/.gradle |
|||
**/local.properties |
|||
**/build |
|||
**/target |
|||
**/Californium.properties |
|||
**/.env |
|||
@ -0,0 +1,201 @@ |
|||
Apache License |
|||
Version 2.0, January 2004 |
|||
http://www.apache.org/licenses/ |
|||
|
|||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
|||
|
|||
1. Definitions. |
|||
|
|||
"License" shall mean the terms and conditions for use, reproduction, |
|||
and distribution as defined by Sections 1 through 9 of this document. |
|||
|
|||
"Licensor" shall mean the copyright owner or entity authorized by |
|||
the copyright owner that is granting the License. |
|||
|
|||
"Legal Entity" shall mean the union of the acting entity and all |
|||
other entities that control, are controlled by, or are under common |
|||
control with that entity. For the purposes of this definition, |
|||
"control" means (i) the power, direct or indirect, to cause the |
|||
direction or management of such entity, whether by contract or |
|||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
|||
outstanding shares, or (iii) beneficial ownership of such entity. |
|||
|
|||
"You" (or "Your") shall mean an individual or Legal Entity |
|||
exercising permissions granted by this License. |
|||
|
|||
"Source" form shall mean the preferred form for making modifications, |
|||
including but not limited to software source code, documentation |
|||
source, and configuration files. |
|||
|
|||
"Object" form shall mean any form resulting from mechanical |
|||
transformation or translation of a Source form, including but |
|||
not limited to compiled object code, generated documentation, |
|||
and conversions to other media types. |
|||
|
|||
"Work" shall mean the work of authorship, whether in Source or |
|||
Object form, made available under the License, as indicated by a |
|||
copyright notice that is included in or attached to the work |
|||
(an example is provided in the Appendix below). |
|||
|
|||
"Derivative Works" shall mean any work, whether in Source or Object |
|||
form, that is based on (or derived from) the Work and for which the |
|||
editorial revisions, annotations, elaborations, or other modifications |
|||
represent, as a whole, an original work of authorship. For the purposes |
|||
of this License, Derivative Works shall not include works that remain |
|||
separable from, or merely link (or bind by name) to the interfaces of, |
|||
the Work and Derivative Works thereof. |
|||
|
|||
"Contribution" shall mean any work of authorship, including |
|||
the original version of the Work and any modifications or additions |
|||
to that Work or Derivative Works thereof, that is intentionally |
|||
submitted to Licensor for inclusion in the Work by the copyright owner |
|||
or by an individual or Legal Entity authorized to submit on behalf of |
|||
the copyright owner. For the purposes of this definition, "submitted" |
|||
means any form of electronic, verbal, or written communication sent |
|||
to the Licensor or its representatives, including but not limited to |
|||
communication on electronic mailing lists, source code control systems, |
|||
and issue tracking systems that are managed by, or on behalf of, the |
|||
Licensor for the purpose of discussing and improving the Work, but |
|||
excluding communication that is conspicuously marked or otherwise |
|||
designated in writing by the copyright owner as "Not a Contribution." |
|||
|
|||
"Contributor" shall mean Licensor and any individual or Legal Entity |
|||
on behalf of whom a Contribution has been received by Licensor and |
|||
subsequently incorporated within the Work. |
|||
|
|||
2. Grant of Copyright License. Subject to the terms and conditions of |
|||
this License, each Contributor hereby grants to You a perpetual, |
|||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
|||
copyright license to reproduce, prepare Derivative Works of, |
|||
publicly display, publicly perform, sublicense, and distribute the |
|||
Work and such Derivative Works in Source or Object form. |
|||
|
|||
3. Grant of Patent License. Subject to the terms and conditions of |
|||
this License, each Contributor hereby grants to You a perpetual, |
|||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
|||
(except as stated in this section) patent license to make, have made, |
|||
use, offer to sell, sell, import, and otherwise transfer the Work, |
|||
where such license applies only to those patent claims licensable |
|||
by such Contributor that are necessarily infringed by their |
|||
Contribution(s) alone or by combination of their Contribution(s) |
|||
with the Work to which such Contribution(s) was submitted. If You |
|||
institute patent litigation against any entity (including a |
|||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
|||
or a Contribution incorporated within the Work constitutes direct |
|||
or contributory patent infringement, then any patent licenses |
|||
granted to You under this License for that Work shall terminate |
|||
as of the date such litigation is filed. |
|||
|
|||
4. Redistribution. You may reproduce and distribute copies of the |
|||
Work or Derivative Works thereof in any medium, with or without |
|||
modifications, and in Source or Object form, provided that You |
|||
meet the following conditions: |
|||
|
|||
(a) You must give any other recipients of the Work or |
|||
Derivative Works a copy of this License; and |
|||
|
|||
(b) You must cause any modified files to carry prominent notices |
|||
stating that You changed the files; and |
|||
|
|||
(c) You must retain, in the Source form of any Derivative Works |
|||
that You distribute, all copyright, patent, trademark, and |
|||
attribution notices from the Source form of the Work, |
|||
excluding those notices that do not pertain to any part of |
|||
the Derivative Works; and |
|||
|
|||
(d) If the Work includes a "NOTICE" text file as part of its |
|||
distribution, then any Derivative Works that You distribute must |
|||
include a readable copy of the attribution notices contained |
|||
within such NOTICE file, excluding those notices that do not |
|||
pertain to any part of the Derivative Works, in at least one |
|||
of the following places: within a NOTICE text file distributed |
|||
as part of the Derivative Works; within the Source form or |
|||
documentation, if provided along with the Derivative Works; or, |
|||
within a display generated by the Derivative Works, if and |
|||
wherever such third-party notices normally appear. The contents |
|||
of the NOTICE file are for informational purposes only and |
|||
do not modify the License. You may add Your own attribution |
|||
notices within Derivative Works that You distribute, alongside |
|||
or as an addendum to the NOTICE text from the Work, provided |
|||
that such additional attribution notices cannot be construed |
|||
as modifying the License. |
|||
|
|||
You may add Your own copyright statement to Your modifications and |
|||
may provide additional or different license terms and conditions |
|||
for use, reproduction, or distribution of Your modifications, or |
|||
for any such Derivative Works as a whole, provided Your use, |
|||
reproduction, and distribution of the Work otherwise complies with |
|||
the conditions stated in this License. |
|||
|
|||
5. Submission of Contributions. Unless You explicitly state otherwise, |
|||
any Contribution intentionally submitted for inclusion in the Work |
|||
by You to the Licensor shall be under the terms and conditions of |
|||
this License, without any additional terms or conditions. |
|||
Notwithstanding the above, nothing herein shall supersede or modify |
|||
the terms of any separate license agreement you may have executed |
|||
with Licensor regarding such Contributions. |
|||
|
|||
6. Trademarks. This License does not grant permission to use the trade |
|||
names, trademarks, service marks, or product names of the Licensor, |
|||
except as required for reasonable and customary use in describing the |
|||
origin of the Work and reproducing the content of the NOTICE file. |
|||
|
|||
7. Disclaimer of Warranty. Unless required by applicable law or |
|||
agreed to in writing, Licensor provides the Work (and each |
|||
Contributor provides its Contributions) on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
|||
implied, including, without limitation, any warranties or conditions |
|||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
|||
PARTICULAR PURPOSE. You are solely responsible for determining the |
|||
appropriateness of using or redistributing the Work and assume any |
|||
risks associated with Your exercise of permissions under this License. |
|||
|
|||
8. Limitation of Liability. In no event and under no legal theory, |
|||
whether in tort (including negligence), contract, or otherwise, |
|||
unless required by applicable law (such as deliberate and grossly |
|||
negligent acts) or agreed to in writing, shall any Contributor be |
|||
liable to You for damages, including any direct, indirect, special, |
|||
incidental, or consequential damages of any character arising as a |
|||
result of this License or out of the use or inability to use the |
|||
Work (including but not limited to damages for loss of goodwill, |
|||
work stoppage, computer failure or malfunction, or any and all |
|||
other commercial damages or losses), even if such Contributor |
|||
has been advised of the possibility of such damages. |
|||
|
|||
9. Accepting Warranty or Additional Liability. While redistributing |
|||
the Work or Derivative Works thereof, You may choose to offer, |
|||
and charge a fee for, acceptance of support, warranty, indemnity, |
|||
or other liability obligations and/or rights consistent with this |
|||
License. However, in accepting such obligations, You may act only |
|||
on Your own behalf and on Your sole responsibility, not on behalf |
|||
of any other Contributor, and only if You agree to indemnify, |
|||
defend, and hold each Contributor harmless for any liability |
|||
incurred by, or claims asserted against, such Contributor by reason |
|||
of your accepting any such warranty or additional liability. |
|||
|
|||
END OF TERMS AND CONDITIONS |
|||
|
|||
APPENDIX: How to apply the Apache License to your work. |
|||
|
|||
To apply the Apache License to your work, attach the following |
|||
boilerplate notice, with the fields enclosed by brackets "{}" |
|||
replaced with your own identifying information. (Don't include |
|||
the brackets!) The text should be enclosed in the appropriate |
|||
comment syntax for the file format. We also recommend that a |
|||
file or class name and description of purpose be included on the |
|||
same "printed page" as the copyright notice for easier |
|||
identification within third-party archives. |
|||
|
|||
Copyright 2016 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
@ -0,0 +1,29 @@ |
|||
# iotrules |
|||
IoT Rules Engine |
|||
|
|||
**Docker usage** |
|||
|
|||
**start platfrom using docker:** |
|||
- install docker |
|||
- cd to 'docker' folder |
|||
- create folder for cassandra data directory on your local env (host) |
|||
- `mkdir /home/user/data_dir` |
|||
- modify .env file to point to the directory created in previous step |
|||
- start ./deploy.sh script to run all the services |
|||
|
|||
|
|||
**start-up for local development** |
|||
|
|||
cassandra with thingsboard schema (9042 and 9061 ports are exposed). |
|||
zookeper services (2181 port is exposed). |
|||
9042, 9061 and 2181 ports must be free so 'Thingsboard' server that is running outside docker container is able to connect to services. |
|||
you can change these ports in docker-compose.static.yml file to some others, but 'Thingsbaord' application.yml file must be updated accordingly. |
|||
if you would like to change cassandra port, change it to "9999:9042" for example and update cassandra.node_list entry in application.yml file to localhost:9999. |
|||
|
|||
- install docker |
|||
- cd to 'docker' folder |
|||
- create folder for cassandra data directory on your local env (host) |
|||
- `mkdir /home/user/data_dir` |
|||
- modify .env file to point to the directory created in previous step |
|||
- start ./deploy_cassandra_zookeeper.sh script to run cassandra with thingsboard schema and zookeper services |
|||
- Start boot class: _org.thingsboard.server.ThingsboardServerApplication_ |
|||
@ -0,0 +1 @@ |
|||
!bin/ |
|||
@ -0,0 +1,134 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
buildscript { |
|||
ext { |
|||
osPackageVersion = "3.8.0" |
|||
} |
|||
repositories { |
|||
jcenter() |
|||
} |
|||
dependencies { |
|||
classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}") |
|||
} |
|||
} |
|||
|
|||
apply plugin: "nebula.ospackage" |
|||
|
|||
buildDir = projectBuildDir |
|||
version = projectVersion |
|||
distsDirName = "./" |
|||
|
|||
// OS Package plugin configuration |
|||
ospackage { |
|||
packageName = pkgName |
|||
version = "${project.version}" |
|||
release = 1 |
|||
os = LINUX |
|||
type = BINARY |
|||
|
|||
into pkgInstallFolder |
|||
|
|||
user pkgName |
|||
permissionGroup pkgName |
|||
|
|||
// Copy the actual .jar file |
|||
from(mainJar) { |
|||
// Strip the version from the jar filename |
|||
rename { String fileName -> |
|||
fileName.replace("-${project.version}", "") |
|||
} |
|||
fileMode 0500 |
|||
into "bin" |
|||
} |
|||
|
|||
// Copy the config files |
|||
from("target/conf") { |
|||
fileType CONFIG | NOREPLACE |
|||
fileMode 0754 |
|||
into "conf" |
|||
} |
|||
|
|||
// Copy the data files |
|||
from("target/data") { |
|||
fileType CONFIG | NOREPLACE |
|||
fileMode 0754 |
|||
into "data" |
|||
} |
|||
|
|||
// Copy the extensions files |
|||
from("target/extensions") { |
|||
into "extensions" |
|||
} |
|||
} |
|||
|
|||
// Configure our RPM build task |
|||
buildRpm { |
|||
|
|||
arch = NOARCH |
|||
|
|||
version = projectVersion.replace('-', '') |
|||
archiveName = "${pkgName}.rpm" |
|||
|
|||
requires("java-1.8.0") |
|||
|
|||
preInstall file("${buildDir}/control/rpm/preinst") |
|||
postInstall file("${buildDir}/control/rpm/postinst") |
|||
preUninstall file("${buildDir}/control/rpm/prerm") |
|||
postUninstall file("${buildDir}/control/rpm/postrm") |
|||
|
|||
user pkgName |
|||
permissionGroup pkgName |
|||
|
|||
// Copy the system unit files |
|||
from("${buildDir}/control/${pkgName}.service") { |
|||
addParentDirs = false |
|||
fileMode 0644 |
|||
into "/usr/lib/systemd/system" |
|||
} |
|||
|
|||
directory(pkgLogFolder, 0755) |
|||
link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") |
|||
link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") |
|||
} |
|||
|
|||
// Same as the buildRpm task |
|||
buildDeb { |
|||
|
|||
arch = "all" |
|||
|
|||
archiveName = "${pkgName}.deb" |
|||
|
|||
requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer") |
|||
|
|||
configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf") |
|||
configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml") |
|||
configurationFile("${pkgInstallFolder}/conf/logback.xml") |
|||
configurationFile("${pkgInstallFolder}/conf/actor-system.conf") |
|||
|
|||
preInstall file("${buildDir}/control/deb/preinst") |
|||
postInstall file("${buildDir}/control/deb/postinst") |
|||
preUninstall file("${buildDir}/control/deb/prerm") |
|||
postUninstall file("${buildDir}/control/deb/postrm") |
|||
|
|||
user pkgName |
|||
permissionGroup pkgName |
|||
|
|||
directory(pkgLogFolder, 0755) |
|||
link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar") |
|||
link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") |
|||
link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") |
|||
} |
|||
@ -0,0 +1,417 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
|||
<modelVersion>4.0.0</modelVersion> |
|||
<parent> |
|||
<groupId>org.thingsboard</groupId> |
|||
<version>0.0.1-SNAPSHOT</version> |
|||
<artifactId>server</artifactId> |
|||
</parent> |
|||
<groupId>org.thingsboard.server</groupId> |
|||
<artifactId>application</artifactId> |
|||
<packaging>jar</packaging> |
|||
|
|||
<name>Thingsboard Server Application</name> |
|||
<url>http://thingsboard.org</url> |
|||
|
|||
<properties> |
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
|||
<main.dir>${basedir}/..</main.dir> |
|||
<pkg.name>thingsboard</pkg.name> |
|||
<pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder> |
|||
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder> |
|||
</properties> |
|||
|
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>io.netty</groupId> |
|||
<artifactId>netty-transport-native-epoll</artifactId> |
|||
<version>${netty.version}</version> |
|||
<!-- Explicitly bring in the linux classifier, test may fail on 32-bit linux --> |
|||
<classifier>linux-x86_64</classifier> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.thingsboard.server</groupId> |
|||
<artifactId>extensions-api</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.thingsboard.server</groupId> |
|||
<artifactId>extensions-core</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.thingsboard.server.common</groupId> |
|||
<artifactId>transport</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.thingsboard.server.transport</groupId> |
|||
<artifactId>http</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.thingsboard.server.transport</groupId> |
|||
<artifactId>coap</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.thingsboard.server.transport</groupId> |
|||
<artifactId>mqtt</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.thingsboard.server</groupId> |
|||
<artifactId>dao</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.thingsboard.server</groupId> |
|||
<artifactId>dao</artifactId> |
|||
<type>test-jar</type> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>io.takari.junit</groupId> |
|||
<artifactId>takari-cpsuite</artifactId> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.cassandraunit</groupId> |
|||
<artifactId>cassandra-unit</artifactId> |
|||
<exclusions> |
|||
<exclusion> |
|||
<groupId>org.slf4j</groupId> |
|||
<artifactId>slf4j-log4j12</artifactId> |
|||
</exclusion> |
|||
</exclusions> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.thingsboard.server</groupId> |
|||
<artifactId>ui</artifactId> |
|||
<version>${project.version}</version> |
|||
<scope>runtime</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-security</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-web</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-websocket</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>io.jsonwebtoken</groupId> |
|||
<artifactId>jjwt</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>joda-time</groupId> |
|||
<artifactId>joda-time</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.apache.velocity</groupId> |
|||
<artifactId>velocity</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.apache.velocity</groupId> |
|||
<artifactId>velocity-tools</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.springframework</groupId> |
|||
<artifactId>spring-context-support</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-test</artifactId> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.springframework.security</groupId> |
|||
<artifactId>spring-security-test</artifactId> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>com.jayway.jsonpath</groupId> |
|||
<artifactId>json-path</artifactId> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>com.jayway.jsonpath</groupId> |
|||
<artifactId>json-path-assert</artifactId> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>com.typesafe.akka</groupId> |
|||
<artifactId>akka-actor_${scala.version}</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>com.typesafe.akka</groupId> |
|||
<artifactId>akka-slf4j_${scala.version}</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.slf4j</groupId> |
|||
<artifactId>slf4j-api</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.slf4j</groupId> |
|||
<artifactId>log4j-over-slf4j</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>ch.qos.logback</groupId> |
|||
<artifactId>logback-core</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>ch.qos.logback</groupId> |
|||
<artifactId>logback-classic</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>junit</groupId> |
|||
<artifactId>junit</artifactId> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.mockito</groupId> |
|||
<artifactId>mockito-all</artifactId> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>javax.mail</groupId> |
|||
<artifactId>mail</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.apache.curator</groupId> |
|||
<artifactId>curator-recipes</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>com.google.protobuf</groupId> |
|||
<artifactId>protobuf-java</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>io.grpc</groupId> |
|||
<artifactId>grpc-netty</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>io.grpc</groupId> |
|||
<artifactId>grpc-protobuf</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>io.grpc</groupId> |
|||
<artifactId>grpc-stub</artifactId> |
|||
</dependency> |
|||
</dependencies> |
|||
|
|||
<build> |
|||
<finalName>${pkg.name}-${project.version}</finalName> |
|||
<resources> |
|||
<resource> |
|||
<directory>${project.basedir}/src/main/resources</directory> |
|||
</resource> |
|||
</resources> |
|||
<plugins> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-surefire-plugin</artifactId> |
|||
<version>${surfire.version}</version> |
|||
<configuration> |
|||
<systemPropertyVariables> |
|||
<spring.config.name>thingsboard</spring.config.name> |
|||
</systemPropertyVariables> |
|||
<includes> |
|||
<include>**/*TestSuite.java</include> |
|||
</includes> |
|||
</configuration> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-resources-plugin</artifactId> |
|||
<executions> |
|||
<execution> |
|||
<id>copy-conf</id> |
|||
<phase>process-resources</phase> |
|||
<goals> |
|||
<goal>copy-resources</goal> |
|||
</goals> |
|||
<configuration> |
|||
<outputDirectory>${project.build.directory}/conf</outputDirectory> |
|||
<resources> |
|||
<resource> |
|||
<directory>src/main/resources</directory> |
|||
<excludes> |
|||
<exclude>logback.xml</exclude> |
|||
</excludes> |
|||
<filtering>false</filtering> |
|||
</resource> |
|||
</resources> |
|||
</configuration> |
|||
</execution> |
|||
<execution> |
|||
<id>copy-service-conf</id> |
|||
<phase>process-resources</phase> |
|||
<goals> |
|||
<goal>copy-resources</goal> |
|||
</goals> |
|||
<configuration> |
|||
<outputDirectory>${project.build.directory}/conf</outputDirectory> |
|||
<resources> |
|||
<resource> |
|||
<directory>src/main/conf</directory> |
|||
<filtering>true</filtering> |
|||
</resource> |
|||
</resources> |
|||
</configuration> |
|||
</execution> |
|||
<execution> |
|||
<id>copy-control</id> |
|||
<phase>process-resources</phase> |
|||
<goals> |
|||
<goal>copy-resources</goal> |
|||
</goals> |
|||
<configuration> |
|||
<outputDirectory>${project.build.directory}/control</outputDirectory> |
|||
<resources> |
|||
<resource> |
|||
<directory>src/main/scripts/control</directory> |
|||
<filtering>true</filtering> |
|||
</resource> |
|||
</resources> |
|||
</configuration> |
|||
</execution> |
|||
<execution> |
|||
<id>copy-data-cql</id> |
|||
<phase>process-resources</phase> |
|||
<goals> |
|||
<goal>copy-resources</goal> |
|||
</goals> |
|||
<configuration> |
|||
<outputDirectory>${project.build.directory}/data</outputDirectory> |
|||
<resources> |
|||
<resource> |
|||
<directory>../dao/src/main/resources</directory> |
|||
<includes> |
|||
<include>**/*.cql</include> |
|||
</includes> |
|||
<filtering>false</filtering> |
|||
</resource> |
|||
</resources> |
|||
</configuration> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-dependency-plugin</artifactId> |
|||
<executions> |
|||
<execution> |
|||
<id>copy-extensions</id> |
|||
<phase>package</phase> |
|||
<goals> |
|||
<goal>copy</goal> |
|||
</goals> |
|||
<configuration> |
|||
<outputDirectory>${project.build.directory}/extensions</outputDirectory> |
|||
<artifactItems> |
|||
<artifactItem> |
|||
<groupId>org.thingsboard.server.extensions</groupId> |
|||
<artifactId>extension-rabbitmq</artifactId> |
|||
<classifier>extension</classifier> |
|||
</artifactItem> |
|||
<artifactItem> |
|||
<groupId>org.thingsboard.server.extensions</groupId> |
|||
<artifactId>extension-rest-api-call</artifactId> |
|||
<classifier>extension</classifier> |
|||
</artifactItem> |
|||
<artifactItem> |
|||
<groupId>org.thingsboard.server.extensions</groupId> |
|||
<artifactId>extension-kafka</artifactId> |
|||
<classifier>extension</classifier> |
|||
</artifactItem> |
|||
</artifactItems> |
|||
</configuration> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-jar-plugin</artifactId> |
|||
<configuration> |
|||
<archive> |
|||
<manifestEntries> |
|||
<Implementation-Title>Thingsboard</Implementation-Title> |
|||
<Implementation-Version>${project.version}</Implementation-Version> |
|||
</manifestEntries> |
|||
</archive> |
|||
</configuration> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-maven-plugin</artifactId> |
|||
<configuration> |
|||
<layout>ZIP</layout> |
|||
<executable>true</executable> |
|||
<excludeDevtools>true</excludeDevtools> |
|||
<embeddedLaunchScriptProperties> |
|||
<confFolder>${pkg.installFolder}/conf</confFolder> |
|||
<logFolder>${pkg.logFolder}</logFolder> |
|||
<logFilename>${pkg.name}.out</logFilename> |
|||
</embeddedLaunchScriptProperties> |
|||
</configuration> |
|||
<executions> |
|||
<execution> |
|||
<goals> |
|||
<goal>repackage</goal> |
|||
</goals> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.fortasoft</groupId> |
|||
<artifactId>gradle-maven-plugin</artifactId> |
|||
<configuration> |
|||
<tasks> |
|||
<task>build</task> |
|||
<task>buildDeb</task> |
|||
<task>buildRpm</task> |
|||
</tasks> |
|||
<args> |
|||
<arg>-PprojectBuildDir=${project.build.directory}</arg> |
|||
<arg>-PprojectVersion=${project.version}</arg> |
|||
<arg>-PmainJar=${project.build.directory}/${project.build.finalName}.${project.packaging}</arg> |
|||
<arg>-PpkgName=${pkg.name}</arg> |
|||
<arg>-PpkgInstallFolder=${pkg.installFolder}</arg> |
|||
<arg>-PpkgLogFolder=${pkg.logFolder}</arg> |
|||
</args> |
|||
</configuration> |
|||
<executions> |
|||
<execution> |
|||
<phase>package</phase> |
|||
<goals> |
|||
<goal>invoke</goal> |
|||
</goals> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.xolstice.maven.plugins</groupId> |
|||
<artifactId>protobuf-maven-plugin</artifactId> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.codehaus.mojo</groupId> |
|||
<artifactId>build-helper-maven-plugin</artifactId> |
|||
</plugin> |
|||
</plugins> |
|||
</build> |
|||
</project> |
|||
@ -0,0 +1,44 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<!-- |
|||
|
|||
Copyright © 2016 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<!DOCTYPE configuration> |
|||
<configuration> |
|||
|
|||
<appender name="fileLogAppender" |
|||
class="ch.qos.logback.core.rolling.RollingFileAppender"> |
|||
<file>${pkg.logFolder}/${pkg.name}.log</file> |
|||
<rollingPolicy |
|||
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
|||
<fileNamePattern>${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
|||
<maxFileSize>100MB</maxFileSize> |
|||
<maxHistory>30</maxHistory> |
|||
<totalSizeCap>3GB</totalSizeCap> |
|||
</rollingPolicy> |
|||
<encoder> |
|||
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> |
|||
</encoder> |
|||
</appender> |
|||
|
|||
<logger name="org.thingsboard.server" level="INFO" /> |
|||
<logger name="akka" level="INFO" /> |
|||
|
|||
<root level="INFO"> |
|||
<appender-ref ref="fileLogAppender"/> |
|||
</root> |
|||
|
|||
</configuration> |
|||
@ -0,0 +1,19 @@ |
|||
# |
|||
# Copyright © 2016 The Thingsboard Authors |
|||
# |
|||
# Licensed under the Apache License, Version 2.0 (the "License"); |
|||
# you may not use this file except in compliance with the License. |
|||
# You may obtain a copy of the License at |
|||
# |
|||
# http://www.apache.org/licenses/LICENSE-2.0 |
|||
# |
|||
# Unless required by applicable law or agreed to in writing, software |
|||
# distributed under the License is distributed on an "AS IS" BASIS, |
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
# See the License for the specific language governing permissions and |
|||
# limitations under the License. |
|||
# |
|||
|
|||
export JAVA_OPTS="$JAVA_OPTS" |
|||
export LOG_FILENAME=${pkg.name}.out |
|||
export LOADER_PATH=${pkg.installFolder}/conf,${pkg.installFolder}/extensions |
|||
@ -0,0 +1,46 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server; |
|||
|
|||
import org.springframework.boot.SpringApplication; |
|||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
|||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
|||
import org.springframework.context.annotation.ComponentScan; |
|||
|
|||
import java.util.Arrays; |
|||
|
|||
@EnableAutoConfiguration |
|||
@SpringBootApplication |
|||
@ComponentScan({"org.thingsboard.server"}) |
|||
public class ThingsboardServerApplication { |
|||
|
|||
private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; |
|||
private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "thingsboard"; |
|||
|
|||
public static void main(String[] args) { |
|||
SpringApplication.run(ThingsboardServerApplication.class, updateArguments(args)); |
|||
} |
|||
|
|||
private static String[] updateArguments(String[] args) { |
|||
if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) { |
|||
String[] modifiedArgs = new String[args.length + 1]; |
|||
System.arraycopy(args, 0, modifiedArgs, 0, args.length); |
|||
modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM; |
|||
return modifiedArgs; |
|||
} |
|||
return args; |
|||
} |
|||
} |
|||
@ -0,0 +1,191 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import akka.actor.ActorSystem; |
|||
import akka.actor.Scheduler; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import com.typesafe.config.Config; |
|||
import com.typesafe.config.ConfigFactory; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.server.actors.service.ActorService; |
|||
import org.thingsboard.server.common.data.DataConstants; |
|||
import org.thingsboard.server.common.data.Event; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
|||
import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.customer.CustomerService; |
|||
import org.thingsboard.server.dao.device.DeviceService; |
|||
import org.thingsboard.server.dao.event.EventService; |
|||
import org.thingsboard.server.dao.plugin.PluginService; |
|||
import org.thingsboard.server.dao.rule.RuleService; |
|||
import org.thingsboard.server.dao.tenant.TenantService; |
|||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|||
import org.thingsboard.server.service.cluster.discovery.DiscoveryService; |
|||
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; |
|||
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; |
|||
import org.thingsboard.server.service.component.ComponentDiscoveryService; |
|||
|
|||
import java.io.PrintWriter; |
|||
import java.io.StringWriter; |
|||
import java.util.Optional; |
|||
|
|||
@Component |
|||
public class ActorSystemContext { |
|||
private static final String AKKA_CONF_FILE_NAME = "actor-system.conf"; |
|||
|
|||
protected final ObjectMapper mapper = new ObjectMapper(); |
|||
|
|||
@Getter @Setter private ActorService actorService; |
|||
|
|||
@Autowired |
|||
@Getter private DiscoveryService discoveryService; |
|||
|
|||
@Autowired |
|||
@Getter @Setter private ComponentDiscoveryService componentService; |
|||
|
|||
@Autowired |
|||
@Getter private ClusterRoutingService routingService; |
|||
|
|||
@Autowired |
|||
@Getter private ClusterRpcService rpcService; |
|||
|
|||
@Autowired |
|||
@Getter private DeviceAuthService deviceAuthService; |
|||
|
|||
@Autowired |
|||
@Getter private DeviceService deviceService; |
|||
|
|||
@Autowired |
|||
@Getter private TenantService tenantService; |
|||
|
|||
@Autowired |
|||
@Getter private CustomerService customerService; |
|||
|
|||
@Autowired |
|||
@Getter private RuleService ruleService; |
|||
|
|||
@Autowired |
|||
@Getter private PluginService pluginService; |
|||
|
|||
@Autowired |
|||
@Getter private TimeseriesService tsService; |
|||
|
|||
@Autowired |
|||
@Getter private AttributesService attributesService; |
|||
|
|||
@Autowired |
|||
@Getter private EventService eventService; |
|||
|
|||
@Autowired |
|||
@Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint; |
|||
|
|||
@Value("${actors.session.sync.timeout}") |
|||
@Getter private long syncSessionTimeout; |
|||
|
|||
@Value("${actors.plugin.termination.delay}") |
|||
@Getter private long pluginActorTerminationDelay; |
|||
|
|||
@Value("${actors.plugin.processing.timeout}") |
|||
@Getter private long pluginProcessingTimeout; |
|||
|
|||
@Value("${actors.plugin.error_persist_frequency}") |
|||
@Getter private long pluginErrorPersistFrequency; |
|||
|
|||
@Value("${actors.rule.termination.delay}") |
|||
@Getter private long ruleActorTerminationDelay; |
|||
|
|||
@Value("${actors.rule.error_persist_frequency}") |
|||
@Getter private long ruleErrorPersistFrequency; |
|||
|
|||
@Value("${actors.statistics.enabled}") |
|||
@Getter private boolean statisticsEnabled; |
|||
|
|||
@Value("${actors.statistics.persist_frequency}") |
|||
@Getter private long statisticsPersistFrequency; |
|||
|
|||
@Getter @Setter private ActorSystem actorSystem; |
|||
|
|||
@Getter @Setter private ActorRef appActor; |
|||
|
|||
@Getter @Setter private ActorRef sessionManagerActor; |
|||
|
|||
@Getter @Setter private ActorRef statsActor; |
|||
|
|||
@Getter private final Config config; |
|||
|
|||
public ActorSystemContext() { |
|||
config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load()); |
|||
} |
|||
|
|||
public Scheduler getScheduler() { |
|||
return actorSystem.scheduler(); |
|||
} |
|||
|
|||
public void persistError(TenantId tenantId, EntityId entityId, String method, Exception e) { |
|||
Event event = new Event(); |
|||
event.setTenantId(tenantId); |
|||
event.setEntityId(entityId); |
|||
event.setType(DataConstants.ERROR); |
|||
event.setBody(toBodyJson(discoveryService.getCurrentServer().getServerAddress(), method, toString(e))); |
|||
persistEvent(event); |
|||
} |
|||
|
|||
public void persistLifecycleEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent lcEvent, Exception e) { |
|||
Event event = new Event(); |
|||
event.setTenantId(tenantId); |
|||
event.setEntityId(entityId); |
|||
event.setType(DataConstants.LC_EVENT); |
|||
event.setBody(toBodyJson(discoveryService.getCurrentServer().getServerAddress(), lcEvent, Optional.ofNullable(e))); |
|||
persistEvent(event); |
|||
} |
|||
|
|||
private void persistEvent(Event event) { |
|||
eventService.save(event); |
|||
} |
|||
|
|||
private String toString(Exception e) { |
|||
StringWriter sw = new StringWriter(); |
|||
e.printStackTrace(new PrintWriter(sw)); |
|||
return sw.toString(); |
|||
} |
|||
|
|||
private JsonNode toBodyJson(ServerAddress server, ComponentLifecycleEvent event, Optional<Exception> e) { |
|||
ObjectNode node = mapper.createObjectNode().put("server", server.toString()).put("event", event.name()); |
|||
if (e.isPresent()) { |
|||
node = node.put("success", false); |
|||
node = node.put("error", toString(e.get())); |
|||
} else { |
|||
node = node.put("success", true); |
|||
} |
|||
return node; |
|||
} |
|||
|
|||
private JsonNode toBodyJson(ServerAddress server, String method, String body) { |
|||
return mapper.createObjectNode().put("server", server.toString()).put("method", method).put("error", body); |
|||
} |
|||
} |
|||
@ -0,0 +1,226 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.app; |
|||
|
|||
import akka.actor.*; |
|||
import akka.actor.SupervisorStrategy.Directive; |
|||
import akka.event.Logging; |
|||
import akka.event.LoggingAdapter; |
|||
import akka.japi.Function; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.plugin.PluginTerminationMsg; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|||
import org.thingsboard.server.actors.service.DefaultActorService; |
|||
import org.thingsboard.server.actors.shared.plugin.PluginManager; |
|||
import org.thingsboard.server.actors.shared.plugin.SystemPluginManager; |
|||
import org.thingsboard.server.actors.shared.rule.RuleManager; |
|||
import org.thingsboard.server.actors.shared.rule.SystemRuleManager; |
|||
import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg; |
|||
import org.thingsboard.server.actors.tenant.TenantActor; |
|||
import org.thingsboard.server.common.data.Tenant; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
import org.thingsboard.server.dao.tenant.TenantService; |
|||
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; |
|||
import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg; |
|||
import scala.concurrent.duration.Duration; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
|
|||
public class AppActor extends ContextAwareActor { |
|||
|
|||
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); |
|||
|
|||
public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); |
|||
private final RuleManager ruleManager; |
|||
private final PluginManager pluginManager; |
|||
private final TenantService tenantService; |
|||
private final Map<TenantId, ActorRef> tenantActors; |
|||
|
|||
private AppActor(ActorSystemContext systemContext) { |
|||
super(systemContext); |
|||
this.ruleManager = new SystemRuleManager(systemContext); |
|||
this.pluginManager = new SystemPluginManager(systemContext); |
|||
this.tenantService = systemContext.getTenantService(); |
|||
this.tenantActors = new HashMap<>(); |
|||
} |
|||
|
|||
@Override |
|||
public SupervisorStrategy supervisorStrategy() { |
|||
return strategy; |
|||
} |
|||
|
|||
@Override |
|||
public void preStart() { |
|||
logger.info("Starting main system actor."); |
|||
try { |
|||
ruleManager.init(this.context()); |
|||
pluginManager.init(this.context()); |
|||
|
|||
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(link -> tenantService.findTenants(link), ENTITY_PACK_LIMIT); |
|||
for (Tenant tenant : tenantIterator) { |
|||
logger.debug("[{}] Creating tenant actor", tenant.getId()); |
|||
getOrCreateTenantActor(tenant.getId()); |
|||
logger.debug("Tenant actor created."); |
|||
} |
|||
|
|||
logger.info("Main system actor started."); |
|||
} catch (Exception e) { |
|||
logger.error(e, "Unknown failure"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Object msg) throws Exception { |
|||
logger.debug("Received message: {}", msg); |
|||
if (msg instanceof ToDeviceActorMsg) { |
|||
processDeviceMsg((ToDeviceActorMsg) msg); |
|||
} else if (msg instanceof ToPluginActorMsg) { |
|||
onToPluginMsg((ToPluginActorMsg) msg); |
|||
} else if (msg instanceof ToRuleActorMsg) { |
|||
onToRuleMsg((ToRuleActorMsg) msg); |
|||
} else if (msg instanceof ToDeviceActorNotificationMsg) { |
|||
onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg); |
|||
} else if (msg instanceof Terminated) { |
|||
processTermination((Terminated) msg); |
|||
} else if (msg instanceof ClusterEventMsg) { |
|||
broadcast(msg); |
|||
} else if (msg instanceof ComponentLifecycleMsg) { |
|||
onComponentLifecycleMsg((ComponentLifecycleMsg) msg); |
|||
} else if (msg instanceof PluginTerminationMsg) { |
|||
onPluginTerminated((PluginTerminationMsg) msg); |
|||
} else { |
|||
logger.warning("Unknown message: {}!", msg); |
|||
} |
|||
} |
|||
|
|||
private void onPluginTerminated(PluginTerminationMsg msg) { |
|||
pluginManager.remove(msg.getId()); |
|||
} |
|||
|
|||
private void broadcast(Object msg) { |
|||
pluginManager.broadcast(msg); |
|||
tenantActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); |
|||
} |
|||
|
|||
private void onToRuleMsg(ToRuleActorMsg msg) { |
|||
ActorRef target; |
|||
if (SYSTEM_TENANT.equals(msg.getTenantId())) { |
|||
target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId()); |
|||
} else { |
|||
target = getOrCreateTenantActor(msg.getTenantId()); |
|||
} |
|||
target.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
private void onToPluginMsg(ToPluginActorMsg msg) { |
|||
ActorRef target; |
|||
if (SYSTEM_TENANT.equals(msg.getPluginTenantId())) { |
|||
target = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId()); |
|||
} else { |
|||
target = getOrCreateTenantActor(msg.getPluginTenantId()); |
|||
} |
|||
target.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { |
|||
ActorRef target = null; |
|||
if (SYSTEM_TENANT.equals(msg.getTenantId())) { |
|||
if (msg.getPluginId().isPresent()) { |
|||
target = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId().get()); |
|||
} else if (msg.getRuleId().isPresent()) { |
|||
Optional<ActorRef> ref = ruleManager.update(this.context(), msg.getRuleId().get(), msg.getEvent()); |
|||
if (ref.isPresent()) { |
|||
target = ref.get(); |
|||
} else { |
|||
logger.debug("Failed to find actor for rule: [{}]", msg.getRuleId()); |
|||
return; |
|||
} |
|||
} |
|||
} else { |
|||
target = getOrCreateTenantActor(msg.getTenantId()); |
|||
} |
|||
if (target != null) { |
|||
target.tell(msg, ActorRef.noSender()); |
|||
} |
|||
} |
|||
|
|||
private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) { |
|||
getOrCreateTenantActor(msg.getTenantId()).tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
private void processDeviceMsg(ToDeviceActorMsg toDeviceActorMsg) { |
|||
TenantId tenantId = toDeviceActorMsg.getTenantId(); |
|||
ActorRef tenantActor = getOrCreateTenantActor(tenantId); |
|||
if (toDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) { |
|||
tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain()), context().self()); |
|||
} else { |
|||
tenantActor.tell(toDeviceActorMsg, context().self()); |
|||
} |
|||
} |
|||
|
|||
private ActorRef getOrCreateTenantActor(TenantId tenantId) { |
|||
ActorRef tenantActor = tenantActors.get(tenantId); |
|||
if (tenantActor == null) { |
|||
tenantActor = context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId)) |
|||
.withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString()); |
|||
tenantActors.put(tenantId, tenantActor); |
|||
} |
|||
return tenantActor; |
|||
} |
|||
|
|||
private void processTermination(Terminated message) { |
|||
ActorRef terminated = message.actor(); |
|||
if (terminated instanceof LocalActorRef) { |
|||
logger.debug("Removed actor: {}", terminated); |
|||
} else { |
|||
throw new IllegalStateException("Remote actors are not supported!"); |
|||
} |
|||
} |
|||
|
|||
public static class ActorCreator extends ContextBasedCreator<AppActor> { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public ActorCreator(ActorSystemContext context) { |
|||
super(context); |
|||
} |
|||
|
|||
@Override |
|||
public AppActor create() throws Exception { |
|||
return new AppActor(context); |
|||
} |
|||
} |
|||
|
|||
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, Directive>() { |
|||
@Override |
|||
public Directive apply(Throwable t) { |
|||
logger.error(t, "Unknown failure"); |
|||
if (t instanceof RuntimeException) { |
|||
return SupervisorStrategy.restart(); |
|||
} else { |
|||
return SupervisorStrategy.stop(); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.device; |
|||
|
|||
import akka.event.Logging; |
|||
import akka.event.LoggingAdapter; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.rule.RulesProcessedMsg; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|||
import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; |
|||
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.*; |
|||
|
|||
public class DeviceActor extends ContextAwareActor { |
|||
|
|||
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); |
|||
|
|||
private final TenantId tenantId; |
|||
private final DeviceId deviceId; |
|||
private final DeviceActorMessageProcessor processor; |
|||
|
|||
private DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) { |
|||
super(systemContext); |
|||
this.tenantId = tenantId; |
|||
this.deviceId = deviceId; |
|||
this.processor = new DeviceActorMessageProcessor(systemContext, logger, deviceId); |
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Object msg) throws Exception { |
|||
if (msg instanceof RuleChainDeviceMsg) { |
|||
processor.process(context(), (RuleChainDeviceMsg) msg); |
|||
} else if (msg instanceof RulesProcessedMsg) { |
|||
processor.onRulesProcessedMsg(context(), (RulesProcessedMsg) msg); |
|||
} else if (msg instanceof ToDeviceActorMsg) { |
|||
processor.process(context(), (ToDeviceActorMsg) msg); |
|||
} else if (msg instanceof ToDeviceActorNotificationMsg) { |
|||
if (msg instanceof DeviceAttributesEventNotificationMsg) { |
|||
processor.processAttributesUpdate(context(), (DeviceAttributesEventNotificationMsg) msg); |
|||
} else if (msg instanceof ToDeviceRpcRequestPluginMsg) { |
|||
processor.processRpcRequest(context(), (ToDeviceRpcRequestPluginMsg) msg); |
|||
} |
|||
} else if (msg instanceof TimeoutMsg) { |
|||
processor.processTimeout(context(), (TimeoutMsg) msg); |
|||
} else if (msg instanceof ClusterEventMsg) { |
|||
processor.processClusterEventMsg((ClusterEventMsg) msg); |
|||
} else { |
|||
logger.debug("[{}][{}] Unknown msg type.", tenantId, deviceId, msg.getClass().getName()); |
|||
} |
|||
} |
|||
|
|||
public static class ActorCreator extends ContextBasedCreator<DeviceActor> { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private final TenantId tenantId; |
|||
private final DeviceId deviceId; |
|||
|
|||
public ActorCreator(ActorSystemContext context, TenantId tenantId, DeviceId deviceId) { |
|||
super(context); |
|||
this.tenantId = tenantId; |
|||
this.deviceId = deviceId; |
|||
} |
|||
|
|||
@Override |
|||
public DeviceActor create() throws Exception { |
|||
return new DeviceActor(context, tenantId, deviceId); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,366 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.device; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.actor.ActorRef; |
|||
import akka.event.LoggingAdapter; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.rule.ChainProcessingContext; |
|||
import org.thingsboard.server.actors.rule.ChainProcessingMetaData; |
|||
import org.thingsboard.server.actors.rule.RuleProcessingMsg; |
|||
import org.thingsboard.server.actors.rule.RulesProcessedMsg; |
|||
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; |
|||
import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg; |
|||
import org.thingsboard.server.common.data.DataConstants; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.SessionId; |
|||
import org.thingsboard.server.common.data.kv.AttributeKey; |
|||
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.core.AttributesUpdateNotification; |
|||
import org.thingsboard.server.common.msg.core.BasicCommandAckResponse; |
|||
import org.thingsboard.server.common.msg.core.BasicToDeviceSessionActorMsg; |
|||
import org.thingsboard.server.common.msg.core.SessionCloseMsg; |
|||
import org.thingsboard.server.common.msg.core.ToDeviceRpcRequestMsg; |
|||
import org.thingsboard.server.common.msg.core.ToDeviceRpcResponseMsg; |
|||
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.common.msg.kv.BasicAttributeKVMsg; |
|||
import org.thingsboard.server.common.msg.session.FromDeviceMsg; |
|||
import org.thingsboard.server.common.msg.session.MsgType; |
|||
import org.thingsboard.server.common.msg.session.SessionType; |
|||
import org.thingsboard.server.common.msg.session.ToDeviceMsg; |
|||
import org.thingsboard.server.extensions.api.device.DeviceAttributes; |
|||
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.RpcError; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutIntMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.TimeoutException; |
|||
import java.util.function.Consumer; |
|||
import java.util.function.Predicate; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { |
|||
|
|||
private final DeviceId deviceId; |
|||
private final Map<SessionId, SessionInfo> attributeSubscriptions; |
|||
private final Map<SessionId, SessionInfo> rpcSubscriptions; |
|||
|
|||
private final Map<Integer, ToDeviceRpcRequestMetadata> rpcPendingMap; |
|||
|
|||
private int rpcSeq = 0; |
|||
private DeviceAttributes deviceAttributes; |
|||
|
|||
public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, DeviceId deviceId) { |
|||
super(systemContext, logger); |
|||
this.deviceId = deviceId; |
|||
this.attributeSubscriptions = new HashMap<>(); |
|||
this.rpcSubscriptions = new HashMap<>(); |
|||
this.rpcPendingMap = new HashMap<>(); |
|||
refreshAttributes(); |
|||
} |
|||
|
|||
private void refreshAttributes() { |
|||
this.deviceAttributes = new DeviceAttributes(fetchAttributes(DataConstants.CLIENT_SCOPE), |
|||
fetchAttributes(DataConstants.SERVER_SCOPE), fetchAttributes(DataConstants.SHARED_SCOPE)); |
|||
} |
|||
|
|||
void processRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg) { |
|||
ToDeviceRpcRequest request = msg.getMsg(); |
|||
ToDeviceRpcRequestBody body = request.getBody(); |
|||
ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg( |
|||
rpcSeq++, |
|||
body.getMethod(), |
|||
body.getParams() |
|||
); |
|||
|
|||
long timeout = request.getExpirationTime() - System.currentTimeMillis(); |
|||
if (timeout <= 0) { |
|||
logger.debug("[{}][{}] Ignoring message due to exp time reached", deviceId, request.getId(), request.getExpirationTime()); |
|||
return; |
|||
} |
|||
|
|||
boolean sent = rpcSubscriptions.size() > 0; |
|||
Set<SessionId> syncSessionSet = new HashSet<>(); |
|||
rpcSubscriptions.entrySet().forEach(sub -> { |
|||
ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(rpcRequest, sub.getKey()); |
|||
sendMsgToSessionActor(response, sub.getValue().getServer()); |
|||
if (SessionType.SYNC == sub.getValue().getType()) { |
|||
syncSessionSet.add(sub.getKey()); |
|||
} |
|||
}); |
|||
syncSessionSet.forEach(rpcSubscriptions::remove); |
|||
|
|||
if (request.isOneway() && sent) { |
|||
ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(msg, (String) null); |
|||
context.parent().tell(responsePluginMsg, ActorRef.noSender()); |
|||
logger.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId()); |
|||
} else { |
|||
registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout); |
|||
} |
|||
if (sent) { |
|||
logger.debug("[{}] RPC request {} is sent!", deviceId, request.getId()); |
|||
} else { |
|||
logger.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId()); |
|||
} |
|||
|
|||
} |
|||
|
|||
private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) { |
|||
rpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent)); |
|||
TimeoutIntMsg timeoutMsg = new TimeoutIntMsg(rpcRequest.getRequestId(), timeout); |
|||
scheduleMsgWithDelay(context, timeoutMsg, timeoutMsg.getTimeout()); |
|||
} |
|||
|
|||
public void processTimeout(ActorContext context, TimeoutMsg msg) { |
|||
ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(msg.getId()); |
|||
if (requestMd != null) { |
|||
logger.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId()); |
|||
ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION); |
|||
context.parent().tell(responsePluginMsg, ActorRef.noSender()); |
|||
} |
|||
} |
|||
|
|||
private void sendPendingRequests(ActorContext context, SessionId sessionId, SessionType type, Optional<ServerAddress> server) { |
|||
if (!rpcPendingMap.isEmpty()) { |
|||
logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, rpcPendingMap.size(), sessionId); |
|||
if (type == SessionType.SYNC) { |
|||
logger.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId); |
|||
rpcSubscriptions.remove(sessionId); |
|||
} |
|||
} else { |
|||
logger.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId); |
|||
} |
|||
Set<UUID> sentOneWayIds = new HashSet<>(); |
|||
if (type == SessionType.ASYNC) { |
|||
rpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds)); |
|||
} else { |
|||
rpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds)); |
|||
} |
|||
|
|||
sentOneWayIds.forEach(rpcPendingMap::remove); |
|||
} |
|||
|
|||
private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(ActorContext context, SessionId sessionId, Optional<ServerAddress> server, Set<UUID> sentOneWayIds) { |
|||
return entry -> { |
|||
ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg(); |
|||
ToDeviceRpcRequestBody body = request.getBody(); |
|||
if (request.isOneway()) { |
|||
sentOneWayIds.add(request.getId()); |
|||
ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(entry.getValue().getMsg(), (String) null); |
|||
context.parent().tell(responsePluginMsg, ActorRef.noSender()); |
|||
} |
|||
ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg( |
|||
entry.getKey(), |
|||
body.getMethod(), |
|||
body.getParams() |
|||
); |
|||
ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(rpcRequest, sessionId); |
|||
sendMsgToSessionActor(response, server); |
|||
}; |
|||
} |
|||
|
|||
void process(ActorContext context, ToDeviceActorMsg msg) { |
|||
processSubscriptionCommands(context, msg); |
|||
processRpcResponses(context, msg); |
|||
processSessionStateMsgs(msg); |
|||
} |
|||
|
|||
void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) { |
|||
//TODO: improve this procedure to fetch only changed attributes.
|
|||
refreshAttributes(); |
|||
//TODO: support attributes deletion
|
|||
Set<AttributeKey> keys = msg.getKeys(); |
|||
if (attributeSubscriptions.size() > 0) { |
|||
ToDeviceMsg notification = null; |
|||
if (msg.isDeleted()) { |
|||
List<AttributeKey> sharedKeys = keys.stream() |
|||
.filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope())) |
|||
.collect(Collectors.toList()); |
|||
notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromDeleted(sharedKeys)); |
|||
} else { |
|||
List<AttributeKvEntry> attributes = keys.stream() |
|||
.filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope())) |
|||
.map(key -> deviceAttributes.getServerPublicAttribute(key.getAttributeKey())) |
|||
.filter(Optional::isPresent) |
|||
.map(Optional::get) |
|||
.collect(Collectors.toList()); |
|||
if (attributes.size() > 0) { |
|||
notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromShared(attributes)); |
|||
} else { |
|||
logger.debug("[{}] No public server side attributes changed!", deviceId); |
|||
} |
|||
} |
|||
if (notification != null) { |
|||
ToDeviceMsg finalNotification = notification; |
|||
attributeSubscriptions.entrySet().forEach(sub -> { |
|||
ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(finalNotification, sub.getKey()); |
|||
sendMsgToSessionActor(response, sub.getValue().getServer()); |
|||
}); |
|||
} |
|||
} else { |
|||
logger.debug("[{}] No registered attributes subscriptions to process!", deviceId); |
|||
} |
|||
} |
|||
|
|||
void process(ActorContext context, RuleChainDeviceMsg srcMsg) { |
|||
ChainProcessingMetaData md = new ChainProcessingMetaData(srcMsg.getRuleChain(), |
|||
srcMsg.getToDeviceActorMsg(), deviceAttributes, context.self()); |
|||
ChainProcessingContext ctx = new ChainProcessingContext(md); |
|||
if (ctx.getChainLength() > 0) { |
|||
RuleProcessingMsg msg = new RuleProcessingMsg(ctx); |
|||
ActorRef ruleActorRef = ctx.getCurrentActor(); |
|||
ruleActorRef.tell(msg, ActorRef.noSender()); |
|||
} else { |
|||
context.self().tell(new RulesProcessedMsg(ctx), context.self()); |
|||
} |
|||
} |
|||
|
|||
void processRpcResponses(ActorContext context, ToDeviceActorMsg msg) { |
|||
SessionId sessionId = msg.getSessionId(); |
|||
FromDeviceMsg inMsg = msg.getPayload(); |
|||
if (inMsg.getMsgType() == MsgType.TO_DEVICE_RPC_RESPONSE) { |
|||
logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId); |
|||
ToDeviceRpcResponseMsg responseMsg = (ToDeviceRpcResponseMsg) inMsg; |
|||
ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(responseMsg.getRequestId()); |
|||
boolean success = requestMd != null; |
|||
if (success) { |
|||
ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), responseMsg.getData()); |
|||
Optional<ServerAddress> pluginServerAddress = requestMd.getMsg().getServerAddress(); |
|||
if (pluginServerAddress.isPresent()) { |
|||
systemContext.getRpcService().tell(pluginServerAddress.get(), responsePluginMsg); |
|||
logger.debug("[{}] Rpc command response sent to remote plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId()); |
|||
} else { |
|||
context.parent().tell(responsePluginMsg, ActorRef.noSender()); |
|||
logger.debug("[{}] Rpc command response sent to local plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId()); |
|||
} |
|||
} else { |
|||
logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); |
|||
} |
|||
if (msg.getSessionType() == SessionType.SYNC) { |
|||
BasicCommandAckResponse response = success |
|||
? BasicCommandAckResponse.onSuccess(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId()) |
|||
: BasicCommandAckResponse.onError(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException()); |
|||
sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(response, msg.getSessionId()), msg.getServerAddress()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void processClusterEventMsg(ClusterEventMsg msg) { |
|||
if (!msg.isAdded()) { |
|||
logger.debug("[{}] Clearing attributes/rpc subscription for server [{}]", deviceId, msg.getServerAddress()); |
|||
Predicate<Map.Entry<SessionId, SessionInfo>> filter = e -> e.getValue().getServer() |
|||
.map(serverAddress -> serverAddress.equals(msg.getServerAddress())).orElse(false); |
|||
attributeSubscriptions.entrySet().removeIf(filter); |
|||
rpcSubscriptions.entrySet().removeIf(filter); |
|||
} |
|||
} |
|||
|
|||
private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data) { |
|||
return toPluginRpcResponseMsg(requestMsg, data, null); |
|||
} |
|||
|
|||
private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, RpcError error) { |
|||
return toPluginRpcResponseMsg(requestMsg, null, error); |
|||
} |
|||
|
|||
private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data, RpcError error) { |
|||
return new ToPluginRpcResponseDeviceMsg( |
|||
requestMsg.getPluginId(), |
|||
requestMsg.getPluginTenantId(), |
|||
new FromDeviceRpcResponse(requestMsg.getMsg().getId(), |
|||
data, |
|||
error |
|||
) |
|||
); |
|||
} |
|||
|
|||
void onRulesProcessedMsg(ActorContext context, RulesProcessedMsg msg) { |
|||
ChainProcessingContext ctx = msg.getCtx(); |
|||
ToDeviceActorMsg inMsg = ctx.getInMsg(); |
|||
SessionId sid = inMsg.getSessionId(); |
|||
ToDeviceSessionActorMsg response; |
|||
if (ctx.getResponse() != null) { |
|||
response = new BasicToDeviceSessionActorMsg(ctx.getResponse(), sid); |
|||
} else { |
|||
response = new BasicToDeviceSessionActorMsg(ctx.getError(), sid); |
|||
} |
|||
sendMsgToSessionActor(response, inMsg.getServerAddress()); |
|||
} |
|||
|
|||
private void processSubscriptionCommands(ActorContext context, ToDeviceActorMsg msg) { |
|||
SessionId sessionId = msg.getSessionId(); |
|||
SessionType sessionType = msg.getSessionType(); |
|||
FromDeviceMsg inMsg = msg.getPayload(); |
|||
if (inMsg.getMsgType() == MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) { |
|||
logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); |
|||
attributeSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress())); |
|||
} else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) { |
|||
logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId); |
|||
attributeSubscriptions.remove(sessionId); |
|||
} else if (inMsg.getMsgType() == MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) { |
|||
logger.debug("[{}] Registering rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType); |
|||
rpcSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress())); |
|||
sendPendingRequests(context, sessionId, sessionType, msg.getServerAddress()); |
|||
} else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) { |
|||
logger.debug("[{}] Canceling rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType); |
|||
rpcSubscriptions.remove(sessionId); |
|||
} |
|||
} |
|||
|
|||
private void processSessionStateMsgs(ToDeviceActorMsg msg) { |
|||
SessionId sessionId = msg.getSessionId(); |
|||
FromDeviceMsg inMsg = msg.getPayload(); |
|||
if (inMsg instanceof SessionCloseMsg) { |
|||
logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); |
|||
attributeSubscriptions.remove(sessionId); |
|||
rpcSubscriptions.remove(sessionId); |
|||
} |
|||
} |
|||
|
|||
private void sendMsgToSessionActor(ToDeviceSessionActorMsg response, Optional<ServerAddress> sessionAddress) { |
|||
if (sessionAddress.isPresent()) { |
|||
ServerAddress address = sessionAddress.get(); |
|||
logger.debug("{} Forwarding msg: {}", address, response); |
|||
systemContext.getRpcService().tell(sessionAddress.get(), response); |
|||
} else { |
|||
systemContext.getSessionManagerActor().tell(response, ActorRef.noSender()); |
|||
} |
|||
} |
|||
|
|||
private List<AttributeKvEntry> fetchAttributes(String attributeType) { |
|||
return systemContext.getAttributesService().findAll(this.deviceId, attributeType); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.device; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.SessionId; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.session.SessionType; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
public class SessionInfo { |
|||
private final SessionType type; |
|||
private final Optional<ServerAddress> server; |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.device; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
public class ToDeviceRpcRequestMetadata { |
|||
private final ToDeviceRpcRequestPluginMsg msg; |
|||
private final boolean sent; |
|||
} |
|||
@ -0,0 +1,151 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.plugin; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.actor.ActorRef; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ComponentActor; |
|||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|||
import org.thingsboard.server.actors.stats.StatsPersistTick; |
|||
import org.thingsboard.server.common.data.id.PluginId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; |
|||
import org.thingsboard.server.extensions.api.rules.RuleException; |
|||
|
|||
public class PluginActor extends ComponentActor<PluginId, PluginActorMessageProcessor> { |
|||
|
|||
private PluginActor(ActorSystemContext systemContext, TenantId tenantId, PluginId pluginId) { |
|||
super(systemContext, tenantId, pluginId); |
|||
setProcessor(new PluginActorMessageProcessor(tenantId, pluginId, systemContext, |
|||
logger, context().parent(), context().self())); |
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Object msg) throws Exception { |
|||
if (msg instanceof PluginWebsocketMsg) { |
|||
onWebsocketMsg((PluginWebsocketMsg<?>) msg); |
|||
} else if (msg instanceof PluginRestMsg) { |
|||
onRestMsg((PluginRestMsg) msg); |
|||
} else if (msg instanceof PluginCallbackMessage) { |
|||
onPluginCallback((PluginCallbackMessage) msg); |
|||
} else if (msg instanceof RuleToPluginMsgWrapper) { |
|||
onRuleToPluginMsg((RuleToPluginMsgWrapper) msg); |
|||
} else if (msg instanceof PluginRpcMsg) { |
|||
onRpcMsg((PluginRpcMsg) msg); |
|||
} else if (msg instanceof ClusterEventMsg) { |
|||
onClusterEventMsg((ClusterEventMsg) msg); |
|||
} else if (msg instanceof ComponentLifecycleMsg) { |
|||
onComponentLifecycleMsg((ComponentLifecycleMsg) msg); |
|||
} else if (msg instanceof ToPluginRpcResponseDeviceMsg) { |
|||
onRpcResponse((ToPluginRpcResponseDeviceMsg) msg); |
|||
} else if (msg instanceof PluginTerminationMsg) { |
|||
logger.info("[{}][{}] Going to terminate plugin actor.", tenantId, id); |
|||
context().parent().tell(msg, ActorRef.noSender()); |
|||
context().stop(self()); |
|||
} else if (msg instanceof TimeoutMsg) { |
|||
onTimeoutMsg(context(), (TimeoutMsg) msg); |
|||
} else if (msg instanceof StatsPersistTick) { |
|||
onStatsPersistTick(id); |
|||
} else { |
|||
logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName()); |
|||
} |
|||
} |
|||
|
|||
private void onPluginCallback(PluginCallbackMessage msg) { |
|||
try { |
|||
processor.onPluginCallbackMsg(msg); |
|||
} catch (Exception e) { |
|||
logAndPersist("onPluginCallbackMsg", e); |
|||
} |
|||
} |
|||
|
|||
private void onTimeoutMsg(ActorContext context, TimeoutMsg msg) { |
|||
processor.onTimeoutMsg(context, msg); |
|||
} |
|||
|
|||
private void onRpcResponse(ToPluginRpcResponseDeviceMsg msg) { |
|||
processor.onDeviceRpcMsg(msg.getResponse()); |
|||
} |
|||
|
|||
private void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException { |
|||
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg()); |
|||
try { |
|||
processor.onRuleToPluginMsg(msg); |
|||
increaseMessagesProcessedCount(); |
|||
} catch (Exception e) { |
|||
logAndPersist("onRuleMsg", e); |
|||
} |
|||
} |
|||
|
|||
private void onWebsocketMsg(PluginWebsocketMsg<?> msg) { |
|||
logger.debug("[{}] Going to process web socket msg: {}", id, msg); |
|||
try { |
|||
processor.onWebsocketMsg(msg); |
|||
increaseMessagesProcessedCount(); |
|||
} catch (Exception e) { |
|||
logAndPersist("onWebsocketMsg", e); |
|||
} |
|||
} |
|||
|
|||
private void onRestMsg(PluginRestMsg msg) { |
|||
logger.debug("[{}] Going to process rest msg: {}", id, msg); |
|||
try { |
|||
processor.onRestMsg(msg); |
|||
increaseMessagesProcessedCount(); |
|||
} catch (Exception e) { |
|||
logAndPersist("onRestMsg", e); |
|||
} |
|||
} |
|||
|
|||
private void onRpcMsg(PluginRpcMsg msg) { |
|||
try { |
|||
logger.debug("[{}] Going to process rpc msg: {}", id, msg); |
|||
processor.onRpcMsg(msg); |
|||
} catch (Exception e) { |
|||
logAndPersist("onRpcMsg", e); |
|||
} |
|||
} |
|||
|
|||
public static class ActorCreator extends ContextBasedCreator<PluginActor> { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private final TenantId tenantId; |
|||
private final PluginId pluginId; |
|||
|
|||
public ActorCreator(ActorSystemContext context, TenantId tenantId, PluginId pluginId) { |
|||
super(context); |
|||
this.tenantId = tenantId; |
|||
this.pluginId = pluginId; |
|||
} |
|||
|
|||
@Override |
|||
public PluginActor create() throws Exception { |
|||
return new PluginActor(context, tenantId, pluginId); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected long getErrorPersistFrequency() { |
|||
return systemContext.getPluginErrorPersistFrequency(); |
|||
} |
|||
} |
|||
@ -0,0 +1,233 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.plugin; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.actor.ActorRef; |
|||
import akka.event.LoggingAdapter; |
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.shared.ComponentMsgProcessor; |
|||
import org.thingsboard.server.common.data.id.PluginId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; |
|||
import org.thingsboard.server.common.data.plugin.ComponentType; |
|||
import org.thingsboard.server.common.data.plugin.PluginMetaData; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.extensions.api.plugins.Plugin; |
|||
import org.thingsboard.server.extensions.api.plugins.PluginInitializationException; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; |
|||
import org.thingsboard.server.extensions.api.rules.RuleException; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId> { |
|||
|
|||
private final SharedPluginProcessingContext pluginCtx; |
|||
private final PluginProcessingContext trustedCtx; |
|||
private PluginMetaData pluginMd; |
|||
private Plugin pluginImpl; |
|||
private ComponentLifecycleState state; |
|||
|
|||
|
|||
protected PluginActorMessageProcessor(TenantId tenantId, PluginId pluginId, ActorSystemContext systemContext |
|||
, LoggingAdapter logger, ActorRef parent, ActorRef self) { |
|||
super(systemContext, logger, tenantId, pluginId); |
|||
this.pluginCtx = new SharedPluginProcessingContext(systemContext, tenantId, pluginId, parent, self); |
|||
this.trustedCtx = new PluginProcessingContext(pluginCtx, null); |
|||
} |
|||
|
|||
@Override |
|||
public void start() throws Exception { |
|||
logger.info("[{}] Going to start plugin actor.", entityId); |
|||
pluginMd = systemContext.getPluginService().findPluginById(entityId); |
|||
if (pluginMd == null) { |
|||
throw new PluginInitializationException("Plugin not found!"); |
|||
} |
|||
if (pluginMd.getConfiguration() == null) { |
|||
throw new PluginInitializationException("Plugin metadata is empty!"); |
|||
} |
|||
state = pluginMd.getState(); |
|||
if (state == ComponentLifecycleState.ACTIVE) { |
|||
logger.info("[{}] Plugin is active. Going to initialize plugin.", entityId); |
|||
initComponent(); |
|||
} else { |
|||
logger.info("[{}] Plugin is suspended. Skipping plugin initialization.", entityId); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void stop() throws Exception { |
|||
onStop(); |
|||
} |
|||
|
|||
private void initComponent() { |
|||
try { |
|||
pluginImpl = initComponent(pluginMd.getClazz(), ComponentType.PLUGIN, mapper.writeValueAsString(pluginMd.getConfiguration())); |
|||
} catch (InstantiationException e) { |
|||
throw new PluginInitializationException("No default constructor for plugin implementation!", e); |
|||
} catch (IllegalAccessException e) { |
|||
throw new PluginInitializationException("Illegal Access Exception during plugin initialization!", e); |
|||
} catch (ClassNotFoundException e) { |
|||
throw new PluginInitializationException("Plugin Class not found!", e); |
|||
} catch (JsonProcessingException e) { |
|||
throw new PluginInitializationException("Plugin Configuration is invalid!", e); |
|||
} catch (Exception e) { |
|||
throw new PluginInitializationException(e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
public void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException { |
|||
if (state == ComponentLifecycleState.ACTIVE) { |
|||
pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg()); |
|||
} else { |
|||
//TODO: reply with plugin suspended message
|
|||
} |
|||
} |
|||
|
|||
public void onWebsocketMsg(PluginWebsocketMsg<?> msg) { |
|||
if (state == ComponentLifecycleState.ACTIVE) { |
|||
pluginImpl.process(new PluginProcessingContext(pluginCtx, msg.getSecurityCtx()), msg); |
|||
} else { |
|||
//TODO: reply with plugin suspended message
|
|||
} |
|||
} |
|||
|
|||
public void onRestMsg(PluginRestMsg msg) { |
|||
if (state == ComponentLifecycleState.ACTIVE) { |
|||
pluginImpl.process(new PluginProcessingContext(pluginCtx, msg.getSecurityCtx()), msg); |
|||
} |
|||
} |
|||
|
|||
public void onRpcMsg(PluginRpcMsg msg) { |
|||
if (state == ComponentLifecycleState.ACTIVE) { |
|||
pluginImpl.process(trustedCtx, msg.getRpcMsg()); |
|||
} else { |
|||
//TODO: reply with plugin suspended message
|
|||
} |
|||
} |
|||
|
|||
public void onPluginCallbackMsg(PluginCallbackMessage msg) { |
|||
if (state == ComponentLifecycleState.ACTIVE) { |
|||
if (msg.isSuccess()) { |
|||
msg.getCallback().onSuccess(trustedCtx, msg.getV()); |
|||
} else { |
|||
msg.getCallback().onFailure(trustedCtx, msg.getE()); |
|||
} |
|||
} else { |
|||
//TODO: reply with plugin suspended message
|
|||
} |
|||
} |
|||
|
|||
|
|||
public void onTimeoutMsg(ActorContext context, TimeoutMsg<?> msg) { |
|||
if (state == ComponentLifecycleState.ACTIVE) { |
|||
pluginImpl.process(trustedCtx, msg); |
|||
} |
|||
} |
|||
|
|||
|
|||
public void onDeviceRpcMsg(FromDeviceRpcResponse response) { |
|||
if (state == ComponentLifecycleState.ACTIVE) { |
|||
pluginImpl.process(trustedCtx, response); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onClusterEventMsg(ClusterEventMsg msg) { |
|||
if (state == ComponentLifecycleState.ACTIVE) { |
|||
ServerAddress address = msg.getServerAddress(); |
|||
if (msg.isAdded()) { |
|||
logger.debug("[{}] Going to process server add msg: {}", entityId, address); |
|||
pluginImpl.onServerAdded(trustedCtx, address); |
|||
} else { |
|||
logger.debug("[{}] Going to process server remove msg: {}", entityId, address); |
|||
pluginImpl.onServerRemoved(trustedCtx, address); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onCreated(ActorContext context) { |
|||
logger.info("[{}] Going to process onCreated plugin.", entityId); |
|||
} |
|||
|
|||
@Override |
|||
public void onUpdate(ActorContext context) throws Exception { |
|||
PluginMetaData oldPluginMd = systemContext.getPluginService().findPluginById(entityId); |
|||
pluginMd = systemContext.getPluginService().findPluginById(entityId); |
|||
boolean requiresRestart = false; |
|||
logger.info("[{}] Plugin configuration was updated from {} to {}.", entityId, oldPluginMd, pluginMd); |
|||
if (!oldPluginMd.getClazz().equals(pluginMd.getClazz())) { |
|||
logger.info("[{}] Plugin requires restart due to clazz change from {} to {}.", |
|||
entityId, oldPluginMd.getClazz(), pluginMd.getClazz()); |
|||
requiresRestart = true; |
|||
} else if (oldPluginMd.getConfiguration().equals(pluginMd.getConfiguration())) { |
|||
logger.info("[{}] Plugin requires restart due to configuration change from {} to {}.", |
|||
entityId, oldPluginMd.getConfiguration(), pluginMd.getConfiguration()); |
|||
requiresRestart = true; |
|||
} |
|||
if (requiresRestart) { |
|||
this.state = ComponentLifecycleState.SUSPENDED; |
|||
if (pluginImpl != null) { |
|||
pluginImpl.stop(trustedCtx); |
|||
} |
|||
start(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onStop(ActorContext context) { |
|||
onStop(); |
|||
scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay()); |
|||
} |
|||
|
|||
private void onStop() { |
|||
logger.info("[{}] Going to process onStop plugin.", entityId); |
|||
this.state = ComponentLifecycleState.SUSPENDED; |
|||
if (pluginImpl != null) { |
|||
pluginImpl.stop(trustedCtx); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onActivate(ActorContext context) throws Exception { |
|||
logger.info("[{}] Going to process onActivate plugin.", entityId); |
|||
this.state = ComponentLifecycleState.ACTIVE; |
|||
if (pluginImpl != null) { |
|||
pluginImpl.resume(trustedCtx); |
|||
logger.info("[{}] Plugin resumed.", entityId); |
|||
} else { |
|||
start(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onSuspend(ActorContext context) { |
|||
logger.info("[{}] Going to process onSuspend plugin.", entityId); |
|||
this.state = ComponentLifecycleState.SUSPENDED; |
|||
if (pluginImpl != null) { |
|||
pluginImpl.suspend(trustedCtx); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.plugin; |
|||
|
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.ToString; |
|||
import org.thingsboard.server.extensions.api.plugins.PluginCallback; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@ToString |
|||
public final class PluginCallbackMessage<V> { |
|||
@Getter |
|||
private final PluginCallback<V> callback; |
|||
@Getter |
|||
private final boolean success; |
|||
@Getter |
|||
private final V v; |
|||
@Getter |
|||
private final Exception e; |
|||
|
|||
public static <V> PluginCallbackMessage<V> onSuccess(PluginCallback<V> callback, V data) { |
|||
return new PluginCallbackMessage<V>(true, callback, data, null); |
|||
} |
|||
|
|||
public static <V> PluginCallbackMessage<V> onError(PluginCallback<V> callback, Exception e) { |
|||
return new PluginCallbackMessage<V>(false, callback, null, e); |
|||
} |
|||
|
|||
private PluginCallbackMessage(boolean success, PluginCallback<V> callback, V v, Exception e) { |
|||
this.success = success; |
|||
this.callback = callback; |
|||
this.v = v; |
|||
this.e = e; |
|||
} |
|||
} |
|||
@ -0,0 +1,306 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.plugin; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.*; |
|||
import java.util.concurrent.Executor; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import com.datastax.driver.core.ResultSet; |
|||
import com.datastax.driver.core.ResultSetFuture; |
|||
import com.datastax.driver.core.Row; |
|||
import com.google.common.base.Function; |
|||
import com.google.common.util.concurrent.FutureCallback; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.DataConstants; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.PluginId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.kv.AttributeKey; |
|||
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|||
import org.thingsboard.server.common.data.kv.TsKvQuery; |
|||
import org.thingsboard.server.common.data.page.TextPageData; |
|||
import org.thingsboard.server.common.data.page.TextPageLink; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; |
|||
import org.thingsboard.server.extensions.api.plugins.PluginContext; |
|||
import org.thingsboard.server.extensions.api.plugins.PluginCallback; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; |
|||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; |
|||
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; |
|||
|
|||
import akka.actor.ActorRef; |
|||
|
|||
import javax.annotation.Nullable; |
|||
|
|||
@Slf4j |
|||
public final class PluginProcessingContext implements PluginContext { |
|||
|
|||
private static final Executor executor = Executors.newSingleThreadExecutor(); |
|||
|
|||
private final SharedPluginProcessingContext pluginCtx; |
|||
private final Optional<PluginApiCallSecurityContext> securityCtx; |
|||
|
|||
public PluginProcessingContext(SharedPluginProcessingContext pluginCtx, PluginApiCallSecurityContext securityCtx) { |
|||
super(); |
|||
this.pluginCtx = pluginCtx; |
|||
this.securityCtx = Optional.ofNullable(securityCtx); |
|||
} |
|||
|
|||
@Override |
|||
public void sendPluginRpcMsg(RpcMsg msg) { |
|||
this.pluginCtx.rpcService.tell(new PluginRpcMsg(pluginCtx.tenantId, pluginCtx.pluginId, msg)); |
|||
} |
|||
|
|||
@Override |
|||
public void send(PluginWebsocketMsg<?> wsMsg) throws IOException { |
|||
pluginCtx.msgEndpoint.send(wsMsg); |
|||
} |
|||
|
|||
@Override |
|||
public void close(PluginWebsocketSessionRef sessionRef) throws IOException { |
|||
pluginCtx.msgEndpoint.close(sessionRef); |
|||
} |
|||
|
|||
@Override |
|||
public void saveAttributes(DeviceId deviceId, String scope, List<AttributeKvEntry> attributes, PluginCallback<Void> callback) { |
|||
validate(deviceId); |
|||
Set<AttributeKey> keys = new HashSet<>(); |
|||
for (AttributeKvEntry attribute : attributes) { |
|||
keys.add(new AttributeKey(scope, attribute.getKey())); |
|||
} |
|||
|
|||
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(deviceId, scope, attributes); |
|||
Futures.addCallback(rsListFuture, getListCallback(callback, v -> { |
|||
onDeviceAttributesChanged(deviceId, keys); |
|||
return null; |
|||
}), executor); |
|||
} |
|||
|
|||
@Override |
|||
public Optional<AttributeKvEntry> loadAttribute(DeviceId deviceId, String attributeType, String attributeKey) { |
|||
validate(deviceId); |
|||
AttributeKvEntry attribute = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey); |
|||
return Optional.ofNullable(attribute); |
|||
} |
|||
|
|||
@Override |
|||
public List<AttributeKvEntry> loadAttributes(DeviceId deviceId, String attributeType, List<String> attributeKeys) { |
|||
validate(deviceId); |
|||
List<AttributeKvEntry> result = new ArrayList<>(attributeKeys.size()); |
|||
for (String attributeKey : attributeKeys) { |
|||
AttributeKvEntry attribute = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey); |
|||
if (attribute != null) { |
|||
result.add(attribute); |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
public List<AttributeKvEntry> loadAttributes(DeviceId deviceId, String attributeType) { |
|||
validate(deviceId); |
|||
return pluginCtx.attributesService.findAll(deviceId, attributeType); |
|||
} |
|||
|
|||
@Override |
|||
public void removeAttributes(DeviceId deviceId, String scope, List<String> keys) { |
|||
validate(deviceId); |
|||
pluginCtx.attributesService.removeAll(deviceId, scope, keys); |
|||
onDeviceAttributesDeleted(deviceId, keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet())); |
|||
} |
|||
|
|||
@Override |
|||
public void saveTsData(DeviceId deviceId, TsKvEntry entry, PluginCallback<Void> callback) { |
|||
validate(deviceId); |
|||
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entry); |
|||
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor); |
|||
} |
|||
|
|||
@Override |
|||
public void saveTsData(DeviceId deviceId, List<TsKvEntry> entries, PluginCallback<Void> callback) { |
|||
validate(deviceId); |
|||
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entries); |
|||
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor); |
|||
} |
|||
|
|||
@Override |
|||
public List<TsKvEntry> loadTimeseries(DeviceId deviceId, TsKvQuery query) { |
|||
validate(deviceId); |
|||
return pluginCtx.tsService.find(DataConstants.DEVICE, deviceId, query); |
|||
} |
|||
|
|||
@Override |
|||
public void loadLatestTimeseries(DeviceId deviceId, PluginCallback<List<TsKvEntry>> callback) { |
|||
validate(deviceId); |
|||
ResultSetFuture future = pluginCtx.tsService.findAllLatest(DataConstants.DEVICE, deviceId); |
|||
Futures.addCallback(future, getCallback(callback, pluginCtx.tsService::convertResultSetToTsKvEntryList), executor); |
|||
} |
|||
|
|||
@Override |
|||
public void loadLatestTimeseries(DeviceId deviceId, Collection<String> keys, PluginCallback<List<TsKvEntry>> callback) { |
|||
validate(deviceId); |
|||
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.findLatest(DataConstants.DEVICE, deviceId, keys); |
|||
Futures.addCallback(rsListFuture, getListCallback(callback, rsList -> |
|||
{ |
|||
List<TsKvEntry> result = new ArrayList<>(); |
|||
for (ResultSet rs : rsList) { |
|||
Row row = rs.one(); |
|||
if (row != null) { |
|||
result.add(pluginCtx.tsService.convertResultToTsKvEntry(row)); |
|||
} |
|||
} |
|||
return result; |
|||
}), executor); |
|||
} |
|||
|
|||
@Override |
|||
public void reply(PluginToRuleMsg<?> msg) { |
|||
pluginCtx.parentActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public boolean checkAccess(DeviceId deviceId) { |
|||
try { |
|||
return validate(deviceId); |
|||
} catch (IllegalStateException | IllegalArgumentException e) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public PluginId getPluginId() { |
|||
return pluginCtx.pluginId; |
|||
} |
|||
|
|||
@Override |
|||
public Optional<PluginApiCallSecurityContext> getSecurityCtx() { |
|||
return securityCtx; |
|||
} |
|||
|
|||
private void onDeviceAttributesChanged(DeviceId deviceId, AttributeKey key) { |
|||
onDeviceAttributesChanged(deviceId, Collections.singleton(key)); |
|||
} |
|||
|
|||
private void onDeviceAttributesDeleted(DeviceId deviceId, Set<AttributeKey> keys) { |
|||
Device device = pluginCtx.deviceService.findDeviceById(deviceId); |
|||
pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), deviceId, keys)); |
|||
} |
|||
|
|||
private void onDeviceAttributesChanged(DeviceId deviceId, Set<AttributeKey> keys) { |
|||
Device device = pluginCtx.deviceService.findDeviceById(deviceId); |
|||
pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onUpdate(device.getTenantId(), deviceId, keys)); |
|||
} |
|||
|
|||
private <T> FutureCallback<List<ResultSet>> getListCallback(final PluginCallback<T> callback, Function<List<ResultSet>, T> transformer) { |
|||
return new FutureCallback<List<ResultSet>>() { |
|||
@Override |
|||
public void onSuccess(@Nullable List<ResultSet> result) { |
|||
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
if (t instanceof Exception) { |
|||
pluginCtx.self().tell(PluginCallbackMessage.onError(callback, (Exception) t), ActorRef.noSender()); |
|||
} else { |
|||
log.error("Critical error: {}", t.getMessage(), t); |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
private <T> FutureCallback<ResultSet> getCallback(final PluginCallback<T> callback, Function<ResultSet, T> transformer) { |
|||
return new FutureCallback<ResultSet>() { |
|||
@Override |
|||
public void onSuccess(@Nullable ResultSet result) { |
|||
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
if (t instanceof Exception) { |
|||
pluginCtx.self().tell(PluginCallbackMessage.onError(callback, (Exception) t), ActorRef.noSender()); |
|||
} else { |
|||
log.error("Critical error: {}", t.getMessage(), t); |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
// TODO: replace with our own exceptions
|
|||
private boolean validate(DeviceId deviceId) { |
|||
if (securityCtx.isPresent()) { |
|||
PluginApiCallSecurityContext ctx = securityCtx.get(); |
|||
if (ctx.isTenantAdmin() || ctx.isCustomerUser()) { |
|||
Device device = pluginCtx.deviceService.findDeviceById(deviceId); |
|||
if (device == null) { |
|||
throw new IllegalStateException("Device not found!"); |
|||
} else { |
|||
if (!device.getTenantId().equals(ctx.getTenantId())) { |
|||
throw new IllegalArgumentException("Device belongs to different tenant!"); |
|||
} else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) { |
|||
throw new IllegalArgumentException("Device belongs to different customer!"); |
|||
} |
|||
} |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
public Optional<ServerAddress> resolve(DeviceId deviceId) { |
|||
return pluginCtx.routingService.resolve(deviceId); |
|||
} |
|||
|
|||
@Override |
|||
public void getDevice(DeviceId deviceId, PluginCallback<Device> callback) { |
|||
//TODO: add caching here with async api.
|
|||
Device device = pluginCtx.deviceService.findDeviceById(deviceId); |
|||
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, device), ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback<List<Device>> callback) { |
|||
//TODO: add caching here with async api.
|
|||
List<Device> devices = pluginCtx.deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, new TextPageLink(limit)).getData(); |
|||
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, devices), ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void sendRpcRequest(ToDeviceRpcRequest msg) { |
|||
pluginCtx.sendRpcRequest(msg); |
|||
} |
|||
|
|||
@Override |
|||
public void scheduleTimeoutMsg(TimeoutMsg msg) { |
|||
pluginCtx.scheduleTimeoutMsg(msg); |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.plugin; |
|||
|
|||
import org.thingsboard.server.actors.shared.ActorTerminationMsg; |
|||
import org.thingsboard.server.common.data.id.PluginId; |
|||
import org.thingsboard.server.common.data.id.SessionId; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public class PluginTerminationMsg extends ActorTerminationMsg<PluginId> { |
|||
|
|||
public PluginTerminationMsg(PluginId id) { |
|||
super(id); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.plugin; |
|||
|
|||
import org.thingsboard.server.common.data.id.PluginId; |
|||
import org.thingsboard.server.common.data.id.RuleId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.aware.RuleAwareMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; |
|||
|
|||
public class RuleToPluginMsgWrapper implements ToPluginActorMsg, RuleAwareMsg { |
|||
|
|||
private final TenantId pluginTenantId; |
|||
private final PluginId pluginId; |
|||
private final TenantId ruleTenantId; |
|||
private final RuleId ruleId; |
|||
private final RuleToPluginMsg<?> msg; |
|||
|
|||
public RuleToPluginMsgWrapper(TenantId pluginTenantId, PluginId pluginId, TenantId ruleTenantId, RuleId ruleId, RuleToPluginMsg<?> msg) { |
|||
super(); |
|||
this.pluginTenantId = pluginTenantId; |
|||
this.pluginId = pluginId; |
|||
this.ruleTenantId = ruleTenantId; |
|||
this.ruleId = ruleId; |
|||
this.msg = msg; |
|||
} |
|||
|
|||
@Override |
|||
public TenantId getPluginTenantId() { |
|||
return pluginTenantId; |
|||
} |
|||
|
|||
@Override |
|||
public PluginId getPluginId() { |
|||
return pluginId; |
|||
} |
|||
|
|||
public TenantId getRuleTenantId() { |
|||
return ruleTenantId; |
|||
} |
|||
|
|||
@Override |
|||
public RuleId getRuleId() { |
|||
return ruleId; |
|||
} |
|||
|
|||
|
|||
public RuleToPluginMsg<?> getMsg() { |
|||
return msg; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.plugin; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; |
|||
import org.thingsboard.server.common.data.id.PluginId; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.device.DeviceService; |
|||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|||
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; |
|||
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; |
|||
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; |
|||
import scala.concurrent.duration.Duration; |
|||
|
|||
import java.util.Optional; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.function.BiConsumer; |
|||
|
|||
@Slf4j |
|||
public final class SharedPluginProcessingContext { |
|||
final ActorRef parentActor; |
|||
final ActorRef currentActor; |
|||
final ActorSystemContext systemContext; |
|||
final PluginWebSocketMsgEndpoint msgEndpoint; |
|||
final DeviceService deviceService; |
|||
final TimeseriesService tsService; |
|||
final AttributesService attributesService; |
|||
final ClusterRpcService rpcService; |
|||
final ClusterRoutingService routingService; |
|||
final PluginId pluginId; |
|||
final TenantId tenantId; |
|||
|
|||
public SharedPluginProcessingContext(ActorSystemContext sysContext, TenantId tenantId, PluginId pluginId, |
|||
ActorRef parentActor, ActorRef self) { |
|||
super(); |
|||
this.tenantId = tenantId; |
|||
this.pluginId = pluginId; |
|||
this.parentActor = parentActor; |
|||
this.currentActor = self; |
|||
this.systemContext = sysContext; |
|||
this.msgEndpoint = sysContext.getWsMsgEndpoint(); |
|||
this.tsService = sysContext.getTsService(); |
|||
this.attributesService = sysContext.getAttributesService(); |
|||
this.deviceService = sysContext.getDeviceService(); |
|||
this.rpcService = sysContext.getRpcService(); |
|||
this.routingService = sysContext.getRoutingService(); |
|||
} |
|||
|
|||
public PluginId getPluginId() { |
|||
return pluginId; |
|||
} |
|||
|
|||
public void toDeviceActor(DeviceAttributesEventNotificationMsg msg) { |
|||
forward(msg.getDeviceId(), msg, rpcService::tell); |
|||
} |
|||
|
|||
public void sendRpcRequest(ToDeviceRpcRequest msg) { |
|||
log.trace("[{}] Forwarding msg {} to device actor!", pluginId, msg); |
|||
ToDeviceRpcRequestPluginMsg rpcMsg = new ToDeviceRpcRequestPluginMsg(pluginId, tenantId, msg); |
|||
forward(msg.getDeviceId(), rpcMsg, rpcService::tell); |
|||
} |
|||
|
|||
private <T> void forward(DeviceId deviceId, T msg, BiConsumer<ServerAddress, T> rpcFunction) { |
|||
Optional<ServerAddress> instance = routingService.resolve(deviceId); |
|||
if (instance.isPresent()) { |
|||
log.trace("[{}] Forwarding msg {} to remote device actor!", pluginId, msg); |
|||
rpcFunction.accept(instance.get(), msg); |
|||
} else { |
|||
log.trace("[{}] Forwarding msg {} to local device actor!", pluginId, msg); |
|||
parentActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
} |
|||
|
|||
public void scheduleTimeoutMsg(TimeoutMsg msg) { |
|||
log.debug("Scheduling msg {} with delay {} ms", msg, msg.getTimeout()); |
|||
systemContext.getScheduler().scheduleOnce( |
|||
Duration.create(msg.getTimeout(), TimeUnit.MILLISECONDS), |
|||
currentActor, |
|||
msg, |
|||
systemContext.getActorSystem().dispatcher(), |
|||
ActorRef.noSender()); |
|||
|
|||
} |
|||
|
|||
public ActorRef self() { |
|||
return currentActor; |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.plugin; |
|||
|
|||
import akka.actor.ActorRef; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface TimeoutScheduler { |
|||
|
|||
void scheduleMsgWithDelay(Object msg, long delayInMs); |
|||
|
|||
} |
|||
@ -0,0 +1,170 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.util.SerializationUtils; |
|||
import org.springframework.util.StringUtils; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ActorService; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.PluginId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; |
|||
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.*; |
|||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
import org.thingsboard.server.service.cluster.rpc.GrpcSession; |
|||
import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; |
|||
|
|||
import java.io.Serializable; |
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Slf4j |
|||
public class BasicRpcSessionListener implements GrpcSessionListener { |
|||
|
|||
private final ActorSystemContext context; |
|||
private final ActorService service; |
|||
private final ActorRef manager; |
|||
private final ActorRef self; |
|||
|
|||
public BasicRpcSessionListener(ActorSystemContext context, ActorRef manager, ActorRef self) { |
|||
this.context = context; |
|||
this.service = context.getActorService(); |
|||
this.manager = manager; |
|||
this.self = self; |
|||
} |
|||
|
|||
@Override |
|||
public void onConnected(GrpcSession session) { |
|||
log.info("{} session started -> {}", getType(session), session.getRemoteServer()); |
|||
if (!session.isClient()) { |
|||
manager.tell(new RpcSessionConnectedMsg(session.getRemoteServer(), session.getSessionId()), self); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onDisconnected(GrpcSession session) { |
|||
log.info("{} session closed -> {}", getType(session), session.getRemoteServer()); |
|||
manager.tell(new RpcSessionDisconnectedMsg(session.isClient(), session.getRemoteServer()), self); |
|||
} |
|||
|
|||
@Override |
|||
public void onToPluginRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcMessage msg) { |
|||
if (log.isTraceEnabled()) { |
|||
log.trace("{} session [{}] received plugin msg {}", getType(session), session.getRemoteServer(), msg); |
|||
} |
|||
service.onMsg(convert(session.getRemoteServer(), msg)); |
|||
} |
|||
|
|||
@Override |
|||
public void onToDeviceActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorRpcMessage msg) { |
|||
log.trace("{} session [{}] received device actor msg {}", getType(session), session.getRemoteServer(), msg); |
|||
service.onMsg((ToDeviceActorMsg) deserialize(msg.getData().toByteArray())); |
|||
} |
|||
|
|||
@Override |
|||
public void onToDeviceActorNotificationRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorNotificationRpcMessage msg) { |
|||
log.trace("{} session [{}] received device actor notification msg {}", getType(session), session.getRemoteServer(), msg); |
|||
service.onMsg((ToDeviceActorNotificationMsg) deserialize(msg.getData().toByteArray())); |
|||
} |
|||
|
|||
@Override |
|||
public void onToDeviceSessionActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceSessionActorRpcMessage msg) { |
|||
log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg); |
|||
service.onMsg((ToDeviceSessionActorMsg) deserialize(msg.getData().toByteArray())); |
|||
} |
|||
|
|||
@Override |
|||
public void onToDeviceRpcRequestRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) { |
|||
log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg); |
|||
service.onMsg(deserialize(session.getRemoteServer(), msg)); |
|||
} |
|||
|
|||
@Override |
|||
public void onFromDeviceRpcResponseRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) { |
|||
log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg); |
|||
service.onMsg(deserialize(session.getRemoteServer(), msg)); |
|||
} |
|||
|
|||
@Override |
|||
public void onToAllNodesRpcMessage(GrpcSession session, ClusterAPIProtos.ToAllNodesRpcMessage msg) { |
|||
log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg); |
|||
service.onMsg((ToAllNodesMsg) deserialize(msg.getData().toByteArray())); |
|||
} |
|||
|
|||
@Override |
|||
public void onError(GrpcSession session, Throwable t) { |
|||
log.warn("{} session got error -> {}", getType(session), session.getRemoteServer(), t); |
|||
manager.tell(new RpcSessionClosedMsg(session.isClient(), session.getRemoteServer()), self); |
|||
session.close(); |
|||
} |
|||
|
|||
private static String getType(GrpcSession session) { |
|||
return session.isClient() ? "Client" : "Server"; |
|||
} |
|||
|
|||
private static PluginRpcMsg convert(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcMessage msg) { |
|||
ClusterAPIProtos.PluginAddress address = msg.getAddress(); |
|||
TenantId tenantId = new TenantId(toUUID(address.getTenantId())); |
|||
PluginId pluginId = new PluginId(toUUID(address.getPluginId())); |
|||
RpcMsg rpcMsg = new RpcMsg(serverAddress, msg.getClazz(), msg.getData().toByteArray()); |
|||
return new PluginRpcMsg(tenantId, pluginId, rpcMsg); |
|||
} |
|||
|
|||
private static UUID toUUID(ClusterAPIProtos.Uid uid) { |
|||
return new UUID(uid.getPluginUuidMsb(), uid.getPluginUuidLsb()); |
|||
} |
|||
|
|||
private static ToDeviceRpcRequestPluginMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) { |
|||
ClusterAPIProtos.PluginAddress address = msg.getAddress(); |
|||
TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId())); |
|||
PluginId pluginId = new PluginId(toUUID(address.getPluginId())); |
|||
|
|||
TenantId deviceTenantId = new TenantId(toUUID(msg.getDeviceTenantId())); |
|||
DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId())); |
|||
|
|||
ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams()); |
|||
ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody); |
|||
|
|||
return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request); |
|||
} |
|||
|
|||
private static ToPluginRpcResponseDeviceMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) { |
|||
ClusterAPIProtos.PluginAddress address = msg.getAddress(); |
|||
TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId())); |
|||
PluginId pluginId = new PluginId(toUUID(address.getPluginId())); |
|||
|
|||
RpcError error = !StringUtils.isEmpty(msg.getError()) ? RpcError.valueOf(msg.getError()) : null; |
|||
FromDeviceRpcResponse response = new FromDeviceRpcResponse(toUUID(msg.getMsgId()), msg.getResponse(), error); |
|||
return new ToPluginRpcResponseDeviceMsg(pluginId, pluginTenantId, response); |
|||
} |
|||
|
|||
@SuppressWarnings("unchecked") |
|||
private static <T extends Serializable> T deserialize(byte[] data) { |
|||
return (T) SerializationUtils.deserialize(data); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
public final class RpcBroadcastMsg { |
|||
private final ClusterAPIProtos.ToRpcServerMessage msg; |
|||
} |
|||
@ -0,0 +1,192 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import akka.actor.Props; |
|||
import akka.event.Logging; |
|||
import akka.event.LoggingAdapter; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|||
import org.thingsboard.server.actors.service.DefaultActorService; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
import org.thingsboard.server.service.cluster.discovery.ServerInstance; |
|||
|
|||
import java.util.*; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public class RpcManagerActor extends ContextAwareActor { |
|||
|
|||
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); |
|||
|
|||
private final Map<ServerAddress, SessionActorInfo> sessionActors; |
|||
|
|||
private final Map<ServerAddress, Queue<ClusterAPIProtos.ToRpcServerMessage>> pendingMsgs; |
|||
|
|||
private final ServerAddress instance; |
|||
|
|||
public RpcManagerActor(ActorSystemContext systemContext) { |
|||
super(systemContext); |
|||
this.sessionActors = new HashMap<>(); |
|||
this.pendingMsgs = new HashMap<>(); |
|||
this.instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress(); |
|||
|
|||
systemContext.getDiscoveryService().getOtherServers().stream() |
|||
.filter(otherServer -> otherServer.getServerAddress().compareTo(instance) > 0) |
|||
.forEach(otherServer -> onCreateSessionRequest( |
|||
new RpcSessionCreateRequestMsg(UUID.randomUUID(), otherServer.getServerAddress(), null))); |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Object msg) throws Exception { |
|||
if (msg instanceof RpcSessionTellMsg) { |
|||
onMsg((RpcSessionTellMsg) msg); |
|||
} else if (msg instanceof RpcBroadcastMsg) { |
|||
onMsg((RpcBroadcastMsg) msg); |
|||
} else if (msg instanceof RpcSessionCreateRequestMsg) { |
|||
onCreateSessionRequest((RpcSessionCreateRequestMsg) msg); |
|||
} else if (msg instanceof RpcSessionConnectedMsg) { |
|||
onSessionConnected((RpcSessionConnectedMsg) msg); |
|||
} else if (msg instanceof RpcSessionDisconnectedMsg) { |
|||
onSessionDisconnected((RpcSessionDisconnectedMsg) msg); |
|||
} else if (msg instanceof RpcSessionClosedMsg) { |
|||
onSessionClosed((RpcSessionClosedMsg) msg); |
|||
} else if (msg instanceof ClusterEventMsg) { |
|||
onClusterEvent((ClusterEventMsg) msg); |
|||
} |
|||
} |
|||
|
|||
private void onMsg(RpcBroadcastMsg msg) { |
|||
log.debug("Forwarding msg to session actors {}", msg); |
|||
sessionActors.keySet().forEach(address -> onMsg(new RpcSessionTellMsg(address, msg.getMsg()))); |
|||
pendingMsgs.values().forEach(queue -> queue.add(msg.getMsg())); |
|||
} |
|||
|
|||
private void onMsg(RpcSessionTellMsg msg) { |
|||
ServerAddress address = msg.getServerAddress(); |
|||
SessionActorInfo session = sessionActors.get(address); |
|||
if (session != null) { |
|||
log.debug("{} Forwarding msg to session actor", address); |
|||
session.actor.tell(msg, ActorRef.noSender()); |
|||
} else { |
|||
log.debug("{} Storing msg to pending queue", address); |
|||
Queue<ClusterAPIProtos.ToRpcServerMessage> queue = pendingMsgs.get(address); |
|||
if (queue == null) { |
|||
queue = new LinkedList<>(); |
|||
pendingMsgs.put(address, queue); |
|||
} |
|||
queue.add(msg.getMsg()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void postStop() { |
|||
sessionActors.clear(); |
|||
pendingMsgs.clear(); |
|||
} |
|||
|
|||
private void onClusterEvent(ClusterEventMsg msg) { |
|||
ServerAddress server = msg.getServerAddress(); |
|||
if (server.compareTo(instance) > 0) { |
|||
if (msg.isAdded()) { |
|||
onCreateSessionRequest(new RpcSessionCreateRequestMsg(UUID.randomUUID(), server, null)); |
|||
} else { |
|||
onSessionClose(false, server); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void onSessionConnected(RpcSessionConnectedMsg msg) { |
|||
register(msg.getRemoteAddress(), msg.getId(), context().sender()); |
|||
} |
|||
|
|||
private void onSessionDisconnected(RpcSessionDisconnectedMsg msg) { |
|||
boolean reconnect = msg.isClient() && isRegistered(msg.getRemoteAddress()); |
|||
onSessionClose(reconnect, msg.getRemoteAddress()); |
|||
} |
|||
|
|||
private void onSessionClosed(RpcSessionClosedMsg msg) { |
|||
boolean reconnect = msg.isClient() && isRegistered(msg.getRemoteAddress()); |
|||
onSessionClose(reconnect, msg.getRemoteAddress()); |
|||
} |
|||
|
|||
private boolean isRegistered(ServerAddress address) { |
|||
for (ServerInstance server : systemContext.getDiscoveryService().getOtherServers()) { |
|||
if (server.getServerAddress().equals(address)) { |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private void onSessionClose(boolean reconnect, ServerAddress remoteAddress) { |
|||
log.debug("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect); |
|||
SessionActorInfo sessionRef = sessionActors.get(remoteAddress); |
|||
if (context().sender().equals(sessionRef.actor)) { |
|||
sessionActors.remove(remoteAddress); |
|||
pendingMsgs.remove(remoteAddress); |
|||
if (reconnect) { |
|||
onCreateSessionRequest(new RpcSessionCreateRequestMsg(sessionRef.sessionId, remoteAddress, null)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void onCreateSessionRequest(RpcSessionCreateRequestMsg msg) { |
|||
ActorRef actorRef = createSessionActor(msg); |
|||
if (msg.getRemoteAddress() != null) { |
|||
register(msg.getRemoteAddress(), msg.getMsgUid(), actorRef); |
|||
} |
|||
} |
|||
|
|||
private void register(ServerAddress remoteAddress, UUID uuid, ActorRef sender) { |
|||
sessionActors.put(remoteAddress, new SessionActorInfo(uuid, sender)); |
|||
log.debug("[{}][{}] Registering session actor.", remoteAddress, uuid); |
|||
Queue<ClusterAPIProtos.ToRpcServerMessage> data = pendingMsgs.remove(remoteAddress); |
|||
if (data != null) { |
|||
log.debug("[{}][{}] Forwarding {} pending messages.", remoteAddress, uuid, data.size()); |
|||
data.forEach(msg -> sender.tell(new RpcSessionTellMsg(remoteAddress, msg), ActorRef.noSender())); |
|||
} else { |
|||
log.debug("[{}][{}] No pending messages to forward.", remoteAddress, uuid); |
|||
} |
|||
} |
|||
|
|||
private ActorRef createSessionActor(RpcSessionCreateRequestMsg msg) { |
|||
log.debug("[{}] Creating session actor.", msg.getMsgUid()); |
|||
ActorRef actor = context().actorOf( |
|||
Props.create(new RpcSessionActor.ActorCreator(systemContext, msg.getMsgUid())).withDispatcher(DefaultActorService.RPC_DISPATCHER_NAME)); |
|||
actor.tell(msg, context().self()); |
|||
return actor; |
|||
} |
|||
|
|||
public static class ActorCreator extends ContextBasedCreator<RpcManagerActor> { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public ActorCreator(ActorSystemContext context) { |
|||
super(context); |
|||
} |
|||
|
|||
@Override |
|||
public RpcManagerActor create() throws Exception { |
|||
return new RpcManagerActor(context); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import akka.event.Logging; |
|||
import akka.event.LoggingAdapter; |
|||
import io.grpc.Channel; |
|||
import io.grpc.ManagedChannelBuilder; |
|||
import io.grpc.stub.StreamObserver; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc; |
|||
import org.thingsboard.server.service.cluster.rpc.GrpcSession; |
|||
import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public class RpcSessionActor extends ContextAwareActor { |
|||
|
|||
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); |
|||
|
|||
private final UUID sessionId; |
|||
private GrpcSession session; |
|||
private GrpcSessionListener listener; |
|||
|
|||
public RpcSessionActor(ActorSystemContext systemContext, UUID sessionId) { |
|||
super(systemContext); |
|||
this.sessionId = sessionId; |
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Object msg) throws Exception { |
|||
if (msg instanceof RpcSessionTellMsg) { |
|||
tell((RpcSessionTellMsg) msg); |
|||
} else if (msg instanceof RpcSessionCreateRequestMsg) { |
|||
initSession((RpcSessionCreateRequestMsg) msg); |
|||
} |
|||
} |
|||
|
|||
private void tell(RpcSessionTellMsg msg) { |
|||
session.sendMsg(msg.getMsg()); |
|||
} |
|||
|
|||
@Override |
|||
public void postStop() { |
|||
log.info("Closing session -> {}", session.getRemoteServer()); |
|||
session.close(); |
|||
} |
|||
|
|||
private void initSession(RpcSessionCreateRequestMsg msg) { |
|||
log.info("[{}] Initializing session", context().self()); |
|||
ServerAddress remoteServer = msg.getRemoteAddress(); |
|||
listener = new BasicRpcSessionListener(systemContext, context().parent(), context().self()); |
|||
if (msg.getRemoteAddress() == null) { |
|||
// Server session
|
|||
session = new GrpcSession(listener); |
|||
session.setOutputStream(msg.getResponseObserver()); |
|||
session.initInputStream(); |
|||
session.initOutputStream(); |
|||
systemContext.getRpcService().onSessionCreated(msg.getMsgUid(), session.getInputStream()); |
|||
} else { |
|||
// Client session
|
|||
Channel channel = ManagedChannelBuilder.forAddress(remoteServer.getHost(), remoteServer.getPort()).usePlaintext(true).build(); |
|||
session = new GrpcSession(remoteServer, listener); |
|||
session.initInputStream(); |
|||
|
|||
ClusterRpcServiceGrpc.ClusterRpcServiceStub stub = ClusterRpcServiceGrpc.newStub(channel); |
|||
StreamObserver<ClusterAPIProtos.ToRpcServerMessage> outputStream = stub.handlePluginMsgs(session.getInputStream()); |
|||
|
|||
session.setOutputStream(outputStream); |
|||
session.initOutputStream(); |
|||
outputStream.onNext(toConnectMsg()); |
|||
} |
|||
} |
|||
|
|||
public static class ActorCreator extends ContextBasedCreator<RpcSessionActor> { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private final UUID sessionId; |
|||
|
|||
public ActorCreator(ActorSystemContext context, UUID sessionId) { |
|||
super(context); |
|||
this.sessionId = sessionId; |
|||
} |
|||
|
|||
@Override |
|||
public RpcSessionActor create() throws Exception { |
|||
return new RpcSessionActor(context, sessionId); |
|||
} |
|||
} |
|||
|
|||
private ClusterAPIProtos.ToRpcServerMessage toConnectMsg() { |
|||
ServerAddress instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress(); |
|||
return ClusterAPIProtos.ToRpcServerMessage.newBuilder().setConnectMsg( |
|||
ClusterAPIProtos.ConnectRpcMessage.newBuilder().setServerAddress( |
|||
ClusterAPIProtos.ServerAddress.newBuilder().setHost(instance.getHost()).setPort(instance.getPort()).build()).build()).build(); |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
public final class RpcSessionClosedMsg { |
|||
|
|||
private final boolean client; |
|||
private final ServerAddress remoteAddress; |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
public final class RpcSessionConnectedMsg { |
|||
|
|||
private final ServerAddress remoteAddress; |
|||
private final UUID id; |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import io.grpc.stub.StreamObserver; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
public final class RpcSessionCreateRequestMsg { |
|||
|
|||
private final UUID msgUid; |
|||
private final ServerAddress remoteAddress; |
|||
private final StreamObserver<ClusterAPIProtos.ToRpcServerMessage> responseObserver; |
|||
|
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
public final class RpcSessionDisconnectedMsg { |
|||
|
|||
private final boolean client; |
|||
private final ServerAddress remoteAddress; |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
public final class RpcSessionTellMsg { |
|||
private final ServerAddress serverAddress; |
|||
private final ClusterAPIProtos.ToRpcServerMessage msg; |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import lombok.Data; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
public final class SessionActorInfo { |
|||
protected final UUID sessionId; |
|||
protected final ActorRef actor; |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import org.thingsboard.server.common.msg.core.RuleEngineError; |
|||
import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.common.msg.session.ToDeviceMsg; |
|||
import org.thingsboard.server.extensions.api.device.DeviceAttributes; |
|||
|
|||
public class ChainProcessingContext { |
|||
|
|||
private final ChainProcessingMetaData md; |
|||
private final int index; |
|||
private final RuleEngineError error; |
|||
private ToDeviceMsg response; |
|||
|
|||
|
|||
public ChainProcessingContext(ChainProcessingMetaData md) { |
|||
super(); |
|||
this.md = md; |
|||
this.index = 0; |
|||
this.error = RuleEngineError.NO_RULES; |
|||
} |
|||
|
|||
private ChainProcessingContext(ChainProcessingContext other, int indexOffset, RuleEngineError error) { |
|||
super(); |
|||
this.md = other.md; |
|||
this.index = other.index + indexOffset; |
|||
this.error = error; |
|||
this.response = other.response; |
|||
|
|||
if (this.index < 0 || this.index >= this.md.chain.size()) { |
|||
throw new IllegalArgumentException("Can't apply offset " + indexOffset + " to the chain!"); |
|||
} |
|||
} |
|||
|
|||
public ActorRef getDeviceActor() { |
|||
return md.originator; |
|||
} |
|||
|
|||
public ActorRef getCurrentActor() { |
|||
return md.chain.getRuleActorMd(index).getActorRef(); |
|||
} |
|||
|
|||
public boolean hasNext() { |
|||
return (getChainLength() - 1) > index; |
|||
} |
|||
|
|||
public boolean isFailure() { |
|||
return (error != null && error.isCritical()) || (response != null && !response.isSuccess()); |
|||
} |
|||
|
|||
public ChainProcessingContext getNext() { |
|||
return new ChainProcessingContext(this, 1, this.error); |
|||
} |
|||
|
|||
public ChainProcessingContext withError(RuleEngineError error) { |
|||
if (error != null && (this.error == null || this.error.getPriority() < error.getPriority())) { |
|||
return new ChainProcessingContext(this, 0, error); |
|||
} else { |
|||
return this; |
|||
} |
|||
} |
|||
|
|||
public int getChainLength() { |
|||
return md.chain.size(); |
|||
} |
|||
|
|||
public ToDeviceActorMsg getInMsg() { |
|||
return md.inMsg; |
|||
} |
|||
|
|||
public DeviceAttributes getAttributes() { |
|||
return md.deviceAttributes; |
|||
} |
|||
|
|||
public ToDeviceMsg getResponse() { |
|||
return response; |
|||
} |
|||
|
|||
public void mergeResponse(ToDeviceMsg response) { |
|||
// TODO add merge logic
|
|||
this.response = response; |
|||
} |
|||
|
|||
public RuleEngineErrorMsg getError() { |
|||
return new RuleEngineErrorMsg(md.inMsg.getPayload().getMsgType(), error); |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
import org.thingsboard.server.extensions.api.device.DeviceAttributes; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
|
|||
import akka.actor.ActorRef; |
|||
|
|||
/** |
|||
* Immutable part of chain processing data; |
|||
* |
|||
* @author ashvayka |
|||
*/ |
|||
public final class ChainProcessingMetaData { |
|||
|
|||
final RuleActorChain chain; |
|||
final ToDeviceActorMsg inMsg; |
|||
final ActorRef originator; |
|||
final DeviceAttributes deviceAttributes; |
|||
|
|||
public ChainProcessingMetaData(RuleActorChain chain, ToDeviceActorMsg inMsg, DeviceAttributes deviceAttributes, ActorRef originator) { |
|||
super(); |
|||
this.chain = chain; |
|||
this.inMsg = inMsg; |
|||
this.originator = originator; |
|||
this.deviceAttributes = deviceAttributes; |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
public class ComplexRuleActorChain implements RuleActorChain { |
|||
|
|||
private final RuleActorChain systemChain; |
|||
private final RuleActorChain tenantChain; |
|||
|
|||
public ComplexRuleActorChain(RuleActorChain systemChain, RuleActorChain tenantChain) { |
|||
super(); |
|||
this.systemChain = systemChain; |
|||
this.tenantChain = tenantChain; |
|||
} |
|||
|
|||
@Override |
|||
public int size() { |
|||
return systemChain.size() + tenantChain.size(); |
|||
} |
|||
|
|||
@Override |
|||
public RuleActorMetaData getRuleActorMd(int index) { |
|||
if (index < systemChain.size()) { |
|||
return systemChain.getRuleActorMd(index); |
|||
} else { |
|||
return tenantChain.getRuleActorMd(index - systemChain.size()); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
public class CompoundRuleActorChain { |
|||
|
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ComponentActor; |
|||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|||
import org.thingsboard.server.actors.stats.StatsPersistTick; |
|||
import org.thingsboard.server.common.data.id.RuleId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; |
|||
|
|||
public class RuleActor extends ComponentActor<RuleId, RuleActorMessageProcessor> { |
|||
|
|||
private RuleActor(ActorSystemContext systemContext, TenantId tenantId, RuleId ruleId) { |
|||
super(systemContext, tenantId, ruleId); |
|||
setProcessor(new RuleActorMessageProcessor(tenantId, ruleId, systemContext, logger)); |
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Object msg) throws Exception { |
|||
logger.debug("[{}] Received message: {}", id, msg); |
|||
if (msg instanceof RuleProcessingMsg) { |
|||
try { |
|||
processor.onRuleProcessingMsg(context(), (RuleProcessingMsg) msg); |
|||
increaseMessagesProcessedCount(); |
|||
} catch (Exception e) { |
|||
logAndPersist("onDeviceMsg", e); |
|||
} |
|||
} else if (msg instanceof PluginToRuleMsg<?>) { |
|||
try { |
|||
processor.onPluginMsg(context(), (PluginToRuleMsg<?>) msg); |
|||
} catch (Exception e) { |
|||
logAndPersist("onPluginMsg", e); |
|||
} |
|||
} else if (msg instanceof ComponentLifecycleMsg) { |
|||
onComponentLifecycleMsg((ComponentLifecycleMsg) msg); |
|||
} else if (msg instanceof ClusterEventMsg) { |
|||
onClusterEventMsg((ClusterEventMsg) msg); |
|||
} else if (msg instanceof RuleToPluginTimeoutMsg) { |
|||
try { |
|||
processor.onTimeoutMsg(context(), (RuleToPluginTimeoutMsg) msg); |
|||
} catch (Exception e) { |
|||
logAndPersist("onTimeoutMsg", e); |
|||
} |
|||
} else if (msg instanceof StatsPersistTick) { |
|||
onStatsPersistTick(id); |
|||
} else { |
|||
logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName()); |
|||
} |
|||
} |
|||
|
|||
public static class ActorCreator extends ContextBasedCreator<RuleActor> { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private final TenantId tenantId; |
|||
private final RuleId ruleId; |
|||
|
|||
public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleId ruleId) { |
|||
super(context); |
|||
this.tenantId = tenantId; |
|||
this.ruleId = ruleId; |
|||
} |
|||
|
|||
@Override |
|||
public RuleActor create() throws Exception { |
|||
return new RuleActor(context, tenantId, ruleId); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected long getErrorPersistFrequency() { |
|||
return systemContext.getRuleErrorPersistFrequency(); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
public interface RuleActorChain { |
|||
|
|||
int size(); |
|||
|
|||
RuleActorMetaData getRuleActorMd(int index); |
|||
|
|||
} |
|||
@ -0,0 +1,339 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
import java.util.*; |
|||
|
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper; |
|||
import org.thingsboard.server.actors.shared.ComponentMsgProcessor; |
|||
import org.thingsboard.server.common.data.id.PluginId; |
|||
import org.thingsboard.server.common.data.id.RuleId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; |
|||
import org.thingsboard.server.common.data.plugin.PluginMetaData; |
|||
import org.thingsboard.server.common.data.rule.RuleMetaData; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.core.BasicRequest; |
|||
import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; |
|||
import org.thingsboard.server.common.msg.core.RuleEngineError; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.common.msg.session.MsgType; |
|||
import org.thingsboard.server.common.msg.session.ToDeviceMsg; |
|||
import org.thingsboard.server.common.msg.session.ex.ProcessingTimeoutException; |
|||
import org.thingsboard.server.extensions.api.rules.*; |
|||
import org.thingsboard.server.extensions.api.plugins.PluginAction; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.actor.ActorRef; |
|||
import akka.event.LoggingAdapter; |
|||
|
|||
class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> { |
|||
|
|||
private final RuleProcessingContext ruleCtx; |
|||
private final Map<UUID, RuleProcessingMsg> pendingMsgMap; |
|||
|
|||
private RuleMetaData ruleMd; |
|||
private ComponentLifecycleState state; |
|||
private List<RuleFilter> filters; |
|||
private RuleProcessor processor; |
|||
private PluginAction action; |
|||
|
|||
private TenantId pluginTenantId; |
|||
private PluginId pluginId; |
|||
|
|||
protected RuleActorMessageProcessor(TenantId tenantId, RuleId ruleId, ActorSystemContext systemContext, LoggingAdapter logger) { |
|||
super(systemContext, logger, tenantId, ruleId); |
|||
this.pendingMsgMap = new HashMap<>(); |
|||
this.ruleCtx = new RuleProcessingContext(systemContext, ruleId); |
|||
} |
|||
|
|||
@Override |
|||
public void start() throws Exception { |
|||
logger.info("[{}][{}] Starting rule actor.", entityId, tenantId); |
|||
ruleMd = systemContext.getRuleService().findRuleById(entityId); |
|||
if (ruleMd == null) { |
|||
throw new RuleInitializationException("Rule not found!"); |
|||
} |
|||
state = ruleMd.getState(); |
|||
if (state == ComponentLifecycleState.ACTIVE) { |
|||
logger.info("[{}] Rule is active. Going to initialize rule components.", entityId); |
|||
initComponent(); |
|||
} else { |
|||
logger.info("[{}] Rule is suspended. Skipping rule components initialization.", entityId); |
|||
} |
|||
|
|||
logger.info("[{}][{}] Started rule actor.", entityId, tenantId); |
|||
} |
|||
|
|||
@Override |
|||
public void stop() throws Exception { |
|||
onStop(); |
|||
} |
|||
|
|||
|
|||
private void initComponent() throws RuleException { |
|||
try { |
|||
if (!ruleMd.getFilters().isArray()) { |
|||
throw new RuntimeException("Filters are not array!"); |
|||
} |
|||
fetchPluginInfo(); |
|||
initFilters(); |
|||
initProcessor(); |
|||
initAction(); |
|||
} catch (RuntimeException e) { |
|||
throw new RuleInitializationException("Unknown runtime exception!", e); |
|||
} catch (InstantiationException e) { |
|||
throw new RuleInitializationException("No default constructor for rule implementation!", e); |
|||
} catch (IllegalAccessException e) { |
|||
throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e); |
|||
} catch (ClassNotFoundException e) { |
|||
throw new RuleInitializationException("Rule Class not found!", e); |
|||
} catch (Exception e) { |
|||
throw new RuleException(e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
private void initAction() throws Exception { |
|||
JsonNode actionMd = ruleMd.getAction(); |
|||
action = initComponent(actionMd); |
|||
} |
|||
|
|||
private void initProcessor() throws Exception { |
|||
if (ruleMd.getProcessor() != null && !ruleMd.getProcessor().isNull()) { |
|||
processor = initComponent(ruleMd.getProcessor()); |
|||
} |
|||
} |
|||
|
|||
private void initFilters() throws Exception { |
|||
filters = new ArrayList<>(ruleMd.getFilters().size()); |
|||
for (int i = 0; i < ruleMd.getFilters().size(); i++) { |
|||
filters.add(initComponent(ruleMd.getFilters().get(i))); |
|||
} |
|||
} |
|||
|
|||
private void fetchPluginInfo() { |
|||
PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken()); |
|||
pluginTenantId = pluginMd.getTenantId(); |
|||
pluginId = pluginMd.getId(); |
|||
} |
|||
|
|||
protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException { |
|||
if (state != ComponentLifecycleState.ACTIVE) { |
|||
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_ACTIVE_RULES); |
|||
return; |
|||
} |
|||
ChainProcessingContext chainCtx = msg.getCtx(); |
|||
ToDeviceActorMsg inMsg = chainCtx.getInMsg(); |
|||
|
|||
ruleCtx.update(inMsg, chainCtx.getAttributes()); |
|||
|
|||
logger.debug("[{}] Going to filter in msg: {}", entityId, inMsg); |
|||
for (RuleFilter filter : filters) { |
|||
if (!filter.filter(ruleCtx, inMsg)) { |
|||
logger.debug("[{}] In msg is NOT valid for processing by current rule: {}", entityId, inMsg); |
|||
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_FILTERS_MATCHED); |
|||
return; |
|||
} |
|||
} |
|||
RuleProcessingMetaData inMsgMd; |
|||
if (processor != null) { |
|||
logger.debug("[{}] Going to process in msg: {}", entityId, inMsg); |
|||
inMsgMd = processor.process(ruleCtx, inMsg); |
|||
} else { |
|||
inMsgMd = new RuleProcessingMetaData(); |
|||
} |
|||
logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg); |
|||
Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd); |
|||
if (ruleToPluginMsgOptional.isPresent()) { |
|||
RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get(); |
|||
logger.debug("[{}] Device msg is converter to: {}", entityId, ruleToPluginMsg); |
|||
context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self()); |
|||
if (action.isOneWayAction()) { |
|||
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS); |
|||
} else { |
|||
pendingMsgMap.put(ruleToPluginMsg.getUid(), msg); |
|||
scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout()); |
|||
} |
|||
} else { |
|||
logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId); |
|||
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_REQUEST_FROM_ACTIONS); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
public void onPluginMsg(ActorContext context, PluginToRuleMsg<?> msg) { |
|||
RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid()); |
|||
if (pendingMsg != null) { |
|||
ChainProcessingContext ctx = pendingMsg.getCtx(); |
|||
Optional<ToDeviceMsg> ruleResponseOptional = action.convert(msg); |
|||
if (ruleResponseOptional.isPresent()) { |
|||
ctx.mergeResponse(ruleResponseOptional.get()); |
|||
pushToNextRule(context, ctx, null); |
|||
} else { |
|||
pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS); |
|||
} |
|||
} else { |
|||
logger.warning("[{}] Processing timeout detected: [{}]", entityId, msg.getUid()); |
|||
} |
|||
} |
|||
|
|||
public void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) { |
|||
RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId()); |
|||
if (pendingMsg != null) { |
|||
logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg); |
|||
ChainProcessingContext ctx = pendingMsg.getCtx(); |
|||
pushToNextRule(context, ctx, RuleEngineError.PLUGIN_TIMEOUT); |
|||
} |
|||
} |
|||
|
|||
private void pushToNextRule(ActorContext context, ChainProcessingContext ctx, RuleEngineError error) { |
|||
if (error != null) { |
|||
ctx = ctx.withError(error); |
|||
} |
|||
if (ctx.isFailure()) { |
|||
logger.debug("[{}] Forwarding processing chain to device actor due to failure.", ctx.getInMsg().getDeviceId()); |
|||
ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender()); |
|||
} else if (!ctx.hasNext()) { |
|||
logger.debug("[{}] Forwarding processing chain to device actor due to end of chain.", ctx.getInMsg().getDeviceId()); |
|||
ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender()); |
|||
} else { |
|||
logger.debug("[{}] Forwarding processing chain to next rule actor.", ctx.getInMsg().getDeviceId()); |
|||
ChainProcessingContext nextTask = ctx.getNext(); |
|||
nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onCreated(ActorContext context) { |
|||
logger.info("[{}] Going to process onCreated rule.", entityId); |
|||
} |
|||
|
|||
@Override |
|||
public void onUpdate(ActorContext context) throws RuleException { |
|||
RuleMetaData oldRuleMd = ruleMd; |
|||
ruleMd = systemContext.getRuleService().findRuleById(entityId); |
|||
logger.info("[{}] Rule configuration was updated from {} to {}.", entityId, oldRuleMd, ruleMd); |
|||
try { |
|||
fetchPluginInfo(); |
|||
if (!Objects.equals(oldRuleMd.getFilters(), ruleMd.getFilters())) { |
|||
logger.info("[{}] Rule filters require restart due to json change from {} to {}.", |
|||
entityId, mapper.writeValueAsString(oldRuleMd.getFilters()), mapper.writeValueAsString(ruleMd.getFilters())); |
|||
stopFilters(); |
|||
initFilters(); |
|||
} |
|||
if (!Objects.equals(oldRuleMd.getProcessor(), ruleMd.getProcessor())) { |
|||
logger.info("[{}] Rule processor require restart due to configuration change.", entityId); |
|||
stopProcessor(); |
|||
initProcessor(); |
|||
} |
|||
if (!Objects.equals(oldRuleMd.getAction(), ruleMd.getAction())) { |
|||
logger.info("[{}] Rule action require restart due to configuration change.", entityId); |
|||
stopAction(); |
|||
initAction(); |
|||
} |
|||
} catch (RuntimeException e) { |
|||
throw new RuleInitializationException("Unknown runtime exception!", e); |
|||
} catch (InstantiationException e) { |
|||
throw new RuleInitializationException("No default constructor for rule implementation!", e); |
|||
} catch (IllegalAccessException e) { |
|||
throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e); |
|||
} catch (ClassNotFoundException e) { |
|||
throw new RuleInitializationException("Rule Class not found!", e); |
|||
} catch (JsonProcessingException e) { |
|||
throw new RuleInitializationException("Rule configuration is invalid!", e); |
|||
} catch (Exception e) { |
|||
throw new RuleInitializationException(e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onActivate(ActorContext context) throws Exception { |
|||
logger.info("[{}] Going to process onActivate rule.", entityId); |
|||
this.state = ComponentLifecycleState.ACTIVE; |
|||
if (action != null) { |
|||
if (filters != null) { |
|||
filters.forEach(f -> f.resume()); |
|||
} |
|||
if (processor != null) { |
|||
processor.resume(); |
|||
} |
|||
if (action != null) { |
|||
action.resume(); |
|||
} |
|||
logger.info("[{}] Rule resumed.", entityId); |
|||
} else { |
|||
start(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onSuspend(ActorContext context) { |
|||
logger.info("[{}] Going to process onSuspend rule.", entityId); |
|||
this.state = ComponentLifecycleState.SUSPENDED; |
|||
if (filters != null) { |
|||
filters.forEach(f -> f.suspend()); |
|||
} |
|||
if (processor != null) { |
|||
processor.suspend(); |
|||
} |
|||
if (action != null) { |
|||
action.suspend(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onStop(ActorContext context) { |
|||
logger.info("[{}] Going to process onStop rule.", entityId); |
|||
onStop(); |
|||
scheduleMsgWithDelay(context, new RuleTerminationMsg(entityId), systemContext.getRuleActorTerminationDelay()); |
|||
} |
|||
|
|||
private void onStop() { |
|||
this.state = ComponentLifecycleState.SUSPENDED; |
|||
stopFilters(); |
|||
stopProcessor(); |
|||
stopAction(); |
|||
} |
|||
|
|||
@Override |
|||
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { |
|||
|
|||
} |
|||
|
|||
private void stopAction() { |
|||
if (action != null) { |
|||
action.stop(); |
|||
} |
|||
} |
|||
|
|||
private void stopProcessor() { |
|||
if (processor != null) { |
|||
processor.stop(); |
|||
} |
|||
} |
|||
|
|||
private void stopFilters() { |
|||
if (filters != null) { |
|||
filters.forEach(f -> f.stop()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
import java.util.Comparator; |
|||
|
|||
import org.thingsboard.server.common.data.id.RuleId; |
|||
|
|||
import akka.actor.ActorRef; |
|||
|
|||
public class RuleActorMetaData { |
|||
|
|||
private final RuleId ruleId; |
|||
private final boolean systemRule; |
|||
private final int weight; |
|||
private final ActorRef actorRef; |
|||
|
|||
public static final Comparator<RuleActorMetaData> RULE_ACTOR_MD_COMPARATOR = new Comparator<RuleActorMetaData>() { |
|||
|
|||
@Override |
|||
public int compare(RuleActorMetaData r1, RuleActorMetaData r2) { |
|||
if (r1.isSystemRule() && !r2.isSystemRule()) { |
|||
return 1; |
|||
} else if (!r1.isSystemRule() && r2.isSystemRule()) { |
|||
return -1; |
|||
} else { |
|||
return Integer.compare(r2.getWeight(), r1.getWeight()); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
public static RuleActorMetaData systemRule(RuleId ruleId, int weight, ActorRef actorRef) { |
|||
return new RuleActorMetaData(ruleId, true, weight, actorRef); |
|||
} |
|||
|
|||
public static RuleActorMetaData tenantRule(RuleId ruleId, int weight, ActorRef actorRef) { |
|||
return new RuleActorMetaData(ruleId, false, weight, actorRef); |
|||
} |
|||
|
|||
private RuleActorMetaData(RuleId ruleId, boolean systemRule, int weight, ActorRef actorRef) { |
|||
super(); |
|||
this.ruleId = ruleId; |
|||
this.systemRule = systemRule; |
|||
this.weight = weight; |
|||
this.actorRef = actorRef; |
|||
} |
|||
|
|||
public RuleId getRuleId() { |
|||
return ruleId; |
|||
} |
|||
|
|||
public boolean isSystemRule() { |
|||
return systemRule; |
|||
} |
|||
|
|||
public int getWeight() { |
|||
return weight; |
|||
} |
|||
|
|||
public ActorRef getActorRef() { |
|||
return actorRef; |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
final int prime = 31; |
|||
int result = 1; |
|||
result = prime * result + ((ruleId == null) ? 0 : ruleId.hashCode()); |
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(Object obj) { |
|||
if (this == obj) |
|||
return true; |
|||
if (obj == null) |
|||
return false; |
|||
if (getClass() != obj.getClass()) |
|||
return false; |
|||
RuleActorMetaData other = (RuleActorMetaData) obj; |
|||
if (ruleId == null) { |
|||
if (other.ruleId != null) |
|||
return false; |
|||
} else if (!ruleId.equals(other.ruleId)) |
|||
return false; |
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "RuleActorMetaData [ruleId=" + ruleId + ", systemRule=" + systemRule + ", weight=" + weight + ", actorRef=" + actorRef + "]"; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; |
|||
import org.thingsboard.server.common.data.id.RuleId; |
|||
|
|||
import akka.event.LoggingAdapter; |
|||
|
|||
public class RuleContextAwareMsgProcessor extends AbstractContextAwareMsgProcessor { |
|||
|
|||
private final RuleId ruleId; |
|||
|
|||
protected RuleContextAwareMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, RuleId ruleId) { |
|||
super(systemContext, logger); |
|||
this.ruleId = ruleId; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.data.Event; |
|||
import org.thingsboard.server.common.data.id.*; |
|||
import org.thingsboard.server.dao.event.EventService; |
|||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|||
import org.thingsboard.server.extensions.api.device.DeviceAttributes; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.extensions.api.rules.RuleContext; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
public class RuleProcessingContext implements RuleContext { |
|||
|
|||
private final TimeseriesService tsService; |
|||
private final EventService eventService; |
|||
private final RuleId ruleId; |
|||
private TenantId tenantId; |
|||
private CustomerId customerId; |
|||
private DeviceId deviceId; |
|||
private DeviceAttributes deviceAttributes; |
|||
|
|||
RuleProcessingContext(ActorSystemContext systemContext, RuleId ruleId) { |
|||
this.tsService = systemContext.getTsService(); |
|||
this.eventService = systemContext.getEventService(); |
|||
this.ruleId = ruleId; |
|||
} |
|||
|
|||
void update(ToDeviceActorMsg toDeviceActorMsg, DeviceAttributes attributes) { |
|||
this.tenantId = toDeviceActorMsg.getTenantId(); |
|||
this.customerId = toDeviceActorMsg.getCustomerId(); |
|||
this.deviceId = toDeviceActorMsg.getDeviceId(); |
|||
this.deviceAttributes = attributes; |
|||
} |
|||
|
|||
@Override |
|||
public RuleId getRuleId() { |
|||
return ruleId; |
|||
} |
|||
|
|||
@Override |
|||
public DeviceAttributes getDeviceAttributes() { |
|||
return deviceAttributes; |
|||
} |
|||
|
|||
@Override |
|||
public Event save(Event event) { |
|||
checkEvent(event); |
|||
return eventService.save(event); |
|||
} |
|||
|
|||
@Override |
|||
public Optional<Event> saveIfNotExists(Event event) { |
|||
checkEvent(event); |
|||
return eventService.saveIfNotExists(event); |
|||
} |
|||
|
|||
@Override |
|||
public Optional<Event> findEvent(String eventType, String eventUid) { |
|||
return eventService.findEvent(tenantId, deviceId, eventType, eventUid); |
|||
} |
|||
|
|||
private void checkEvent(Event event) { |
|||
if (event.getTenantId() == null) { |
|||
event.setTenantId(tenantId); |
|||
} else if (!tenantId.equals(event.getTenantId())) { |
|||
throw new IllegalArgumentException("Invalid Tenant id!"); |
|||
} |
|||
if (event.getEntityId() == null) { |
|||
event.setEntityId(deviceId); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
public class RuleProcessingMsg { |
|||
|
|||
private final ChainProcessingContext ctx; |
|||
|
|||
public RuleProcessingMsg(ChainProcessingContext ctx) { |
|||
super(); |
|||
this.ctx = ctx; |
|||
} |
|||
|
|||
public ChainProcessingContext getCtx() { |
|||
return ctx; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
import org.thingsboard.server.actors.shared.ActorTerminationMsg; |
|||
import org.thingsboard.server.common.data.id.PluginId; |
|||
import org.thingsboard.server.common.data.id.RuleId; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public class RuleTerminationMsg extends ActorTerminationMsg<RuleId> { |
|||
|
|||
public RuleTerminationMsg(RuleId id) { |
|||
super(id); |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
import java.io.Serializable; |
|||
import java.util.UUID; |
|||
|
|||
public class RuleToPluginTimeoutMsg implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private final UUID msgId; |
|||
|
|||
public RuleToPluginTimeoutMsg(UUID msgId) { |
|||
super(); |
|||
this.msgId = msgId; |
|||
} |
|||
|
|||
public UUID getMsgId() { |
|||
return msgId; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
public class RulesProcessedMsg { |
|||
private final ChainProcessingContext ctx; |
|||
|
|||
public RulesProcessedMsg(ChainProcessingContext ctx) { |
|||
super(); |
|||
this.ctx = ctx; |
|||
} |
|||
|
|||
public ChainProcessingContext getCtx() { |
|||
return ctx; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "RulesProcessedMsg [ctx=" + ctx + "]"; |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rule; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.Set; |
|||
|
|||
public class SimpleRuleActorChain implements RuleActorChain { |
|||
|
|||
private final List<RuleActorMetaData> rules; |
|||
|
|||
public SimpleRuleActorChain(Set<RuleActorMetaData> ruleSet) { |
|||
rules = new ArrayList<>(ruleSet); |
|||
Collections.sort(rules, RuleActorMetaData.RULE_ACTOR_MD_COMPARATOR); |
|||
} |
|||
|
|||
public int size() { |
|||
return rules.size(); |
|||
} |
|||
|
|||
public RuleActorMetaData getRuleActorMd(int index) { |
|||
return rules.get(index); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.service; |
|||
|
|||
import org.thingsboard.server.common.data.id.PluginId; |
|||
import org.thingsboard.server.common.data.id.RuleId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
import org.thingsboard.server.common.transport.SessionMsgProcessor; |
|||
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener; |
|||
import org.thingsboard.server.service.cluster.rpc.RpcMsgListener; |
|||
|
|||
public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor, RestMsgProcessor, RpcMsgListener, DiscoveryServiceListener { |
|||
|
|||
void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state); |
|||
|
|||
void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state); |
|||
} |
|||
@ -0,0 +1,169 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.service; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import akka.event.Logging; |
|||
import akka.event.LoggingAdapter; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.shared.ComponentMsgProcessor; |
|||
import org.thingsboard.server.actors.stats.StatsPersistMsg; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgProcessor<T>> extends ContextAwareActor { |
|||
|
|||
protected final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); |
|||
|
|||
private long lastPersistedErrorTs = 0L; |
|||
protected final TenantId tenantId; |
|||
protected final T id; |
|||
protected P processor; |
|||
private long messagesProcessed; |
|||
private long errorsOccurred; |
|||
|
|||
public ComponentActor(ActorSystemContext systemContext, TenantId tenantId, T id) { |
|||
super(systemContext); |
|||
this.tenantId = tenantId; |
|||
this.id = id; |
|||
} |
|||
|
|||
protected void setProcessor(P processor) { |
|||
this.processor = processor; |
|||
} |
|||
|
|||
@Override |
|||
public void preStart() { |
|||
try { |
|||
processor.start(); |
|||
logLifecycleEvent(ComponentLifecycleEvent.STARTED); |
|||
if (systemContext.isStatisticsEnabled()) { |
|||
scheduleStatsPersistTick(); |
|||
} |
|||
} catch (Exception e) { |
|||
logger.warning("[{}][{}] Failed to start {} processor: {}", tenantId, id, id.getEntityType(), e); |
|||
logAndPersist("OnStart", e, true); |
|||
logLifecycleEvent(ComponentLifecycleEvent.STARTED, e); |
|||
} |
|||
} |
|||
|
|||
private void scheduleStatsPersistTick() { |
|||
try { |
|||
processor.scheduleStatsPersistTick(context(), systemContext.getStatisticsPersistFrequency()); |
|||
} catch (Exception e) { |
|||
logger.error("[{}][{}] Failed to schedule statistics store message. No statistics is going to be stored: {}", tenantId, id, e.getMessage()); |
|||
logAndPersist("onScheduleStatsPersistMsg", e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void postStop() { |
|||
try { |
|||
processor.stop(); |
|||
logLifecycleEvent(ComponentLifecycleEvent.STOPPED); |
|||
} catch (Exception e) { |
|||
logger.warning("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage()); |
|||
logAndPersist("OnStop", e, true); |
|||
logLifecycleEvent(ComponentLifecycleEvent.STOPPED, e); |
|||
} |
|||
} |
|||
|
|||
protected void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { |
|||
try { |
|||
switch (msg.getEvent()) { |
|||
case CREATED: |
|||
processor.onCreated(context()); |
|||
break; |
|||
case UPDATED: |
|||
processor.onUpdate(context()); |
|||
break; |
|||
case ACTIVATED: |
|||
processor.onActivate(context()); |
|||
break; |
|||
case SUSPENDED: |
|||
processor.onSuspend(context()); |
|||
break; |
|||
case DELETED: |
|||
processor.onStop(context()); |
|||
} |
|||
logLifecycleEvent(msg.getEvent()); |
|||
} catch (Exception e) { |
|||
logAndPersist("onLifecycleMsg", e, true); |
|||
logLifecycleEvent(msg.getEvent(), e); |
|||
} |
|||
} |
|||
|
|||
protected void onClusterEventMsg(ClusterEventMsg msg) { |
|||
try { |
|||
processor.onClusterEventMsg(msg); |
|||
} catch (Exception e) { |
|||
logAndPersist("onClusterEventMsg", e); |
|||
} |
|||
} |
|||
|
|||
protected void onStatsPersistTick(EntityId entityId) { |
|||
try { |
|||
systemContext.getStatsActor().tell(new StatsPersistMsg(messagesProcessed, errorsOccurred, tenantId, entityId), ActorRef.noSender()); |
|||
resetStatsCounters(); |
|||
} catch (Exception e) { |
|||
logAndPersist("onStatsPersistTick", e); |
|||
} |
|||
} |
|||
|
|||
private void resetStatsCounters() { |
|||
messagesProcessed = 0; |
|||
errorsOccurred = 0; |
|||
} |
|||
|
|||
protected void increaseMessagesProcessedCount() { |
|||
messagesProcessed++; |
|||
} |
|||
|
|||
|
|||
protected void logAndPersist(String method, Exception e) { |
|||
logAndPersist(method, e, false); |
|||
} |
|||
|
|||
private void logAndPersist(String method, Exception e, boolean critical) { |
|||
errorsOccurred++; |
|||
if (critical) { |
|||
logger.warning("[{}][{}] Failed to process {} msg: {}", id, tenantId, method, e); |
|||
} else { |
|||
logger.debug("[{}][{}] Failed to process {} msg: {}", id, tenantId, method, e); |
|||
} |
|||
long ts = System.currentTimeMillis(); |
|||
if (ts - lastPersistedErrorTs > getErrorPersistFrequency()) { |
|||
systemContext.persistError(tenantId, id, method, e); |
|||
lastPersistedErrorTs = ts; |
|||
} |
|||
} |
|||
|
|||
protected void logLifecycleEvent(ComponentLifecycleEvent event) { |
|||
logLifecycleEvent(event, null); |
|||
} |
|||
|
|||
protected void logLifecycleEvent(ComponentLifecycleEvent event, Exception e) { |
|||
systemContext.persistLifecycleEvent(tenantId, id, event, e); |
|||
} |
|||
|
|||
protected abstract long getErrorPersistFrequency(); |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.service; |
|||
|
|||
import akka.actor.UntypedActor; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
|
|||
public abstract class ContextAwareActor extends UntypedActor { |
|||
|
|||
public static final int ENTITY_PACK_LIMIT = 1024; |
|||
|
|||
protected final ActorSystemContext systemContext; |
|||
|
|||
public ContextAwareActor(ActorSystemContext systemContext) { |
|||
super(); |
|||
this.systemContext = systemContext; |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.service; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
|
|||
import akka.japi.Creator; |
|||
|
|||
public abstract class ContextBasedCreator<T> implements Creator<T> { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
protected final ActorSystemContext context; |
|||
|
|||
public ContextBasedCreator(ActorSystemContext context) { |
|||
super(); |
|||
this.context = context; |
|||
} |
|||
} |
|||
@ -0,0 +1,234 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.service; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import akka.actor.ActorSystem; |
|||
import akka.actor.Props; |
|||
import akka.actor.Terminated; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.app.AppActor; |
|||
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; |
|||
import org.thingsboard.server.actors.rpc.RpcManagerActor; |
|||
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; |
|||
import org.thingsboard.server.actors.rpc.RpcSessionTellMsg; |
|||
import org.thingsboard.server.actors.session.SessionManagerActor; |
|||
import org.thingsboard.server.actors.stats.StatsActor; |
|||
import org.thingsboard.server.common.data.id.PluginId; |
|||
import org.thingsboard.server.common.data.id.RuleId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
import org.thingsboard.server.common.msg.aware.SessionAwareMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; |
|||
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
|||
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; |
|||
import org.thingsboard.server.service.cluster.discovery.DiscoveryService; |
|||
import org.thingsboard.server.service.cluster.discovery.ServerInstance; |
|||
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; |
|||
import scala.concurrent.Await; |
|||
import scala.concurrent.Future; |
|||
import scala.concurrent.duration.Duration; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class DefaultActorService implements ActorService { |
|||
|
|||
private static final String ACTOR_SYSTEM_NAME = "Akka"; |
|||
|
|||
public static final String APP_DISPATCHER_NAME = "app-dispatcher"; |
|||
public static final String CORE_DISPATCHER_NAME = "core-dispatcher"; |
|||
public static final String RULE_DISPATCHER_NAME = "rule-dispatcher"; |
|||
public static final String PLUGIN_DISPATCHER_NAME = "plugin-dispatcher"; |
|||
public static final String SESSION_DISPATCHER_NAME = "session-dispatcher"; |
|||
public static final String RPC_DISPATCHER_NAME = "rpc-dispatcher"; |
|||
|
|||
@Autowired |
|||
private ActorSystemContext actorContext; |
|||
|
|||
@Autowired |
|||
private ClusterRpcService rpcService; |
|||
|
|||
@Autowired |
|||
private DiscoveryService discoveryService; |
|||
|
|||
private ActorSystem system; |
|||
|
|||
private ActorRef appActor; |
|||
|
|||
private ActorRef sessionManagerActor; |
|||
|
|||
private ActorRef rpcManagerActor; |
|||
|
|||
@PostConstruct |
|||
public void initActorSystem() { |
|||
log.info("Initializing Actor system. {}", actorContext.getRuleService()); |
|||
actorContext.setActorService(this); |
|||
system = ActorSystem.create(ACTOR_SYSTEM_NAME, actorContext.getConfig()); |
|||
actorContext.setActorSystem(system); |
|||
|
|||
appActor = system.actorOf(Props.create(new AppActor.ActorCreator(actorContext)).withDispatcher(APP_DISPATCHER_NAME), "appActor"); |
|||
actorContext.setAppActor(appActor); |
|||
|
|||
sessionManagerActor = system.actorOf(Props.create(new SessionManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME), |
|||
"sessionManagerActor"); |
|||
actorContext.setSessionManagerActor(sessionManagerActor); |
|||
|
|||
rpcManagerActor = system.actorOf(Props.create(new RpcManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME), |
|||
"rpcManagerActor"); |
|||
|
|||
ActorRef statsActor = system.actorOf(Props.create(new StatsActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME), "statsActor"); |
|||
actorContext.setStatsActor(statsActor); |
|||
|
|||
rpcService.init(this); |
|||
|
|||
discoveryService.addListener(this); |
|||
log.info("Actor system initialized."); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void stopActorSystem() { |
|||
Future<Terminated> status = system.terminate(); |
|||
try { |
|||
Terminated terminated = Await.result(status, Duration.Inf()); |
|||
log.info("Actor system terminated: {}", terminated); |
|||
} catch (Exception e) { |
|||
log.error("Failed to terminate actor system.", e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void process(SessionAwareMsg msg) { |
|||
if (msg instanceof SessionAwareMsg) { |
|||
log.debug("Processing session aware msg: {}", msg); |
|||
sessionManagerActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void process(PluginWebsocketMsg<?> msg) { |
|||
log.debug("Processing websocket msg: {}", msg); |
|||
appActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void process(PluginRestMsg msg) { |
|||
log.debug("Processing rest msg: {}", msg); |
|||
appActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void onMsg(ToPluginActorMsg msg) { |
|||
log.trace("Processing plugin rpc msg: {}", msg); |
|||
appActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void onMsg(ToDeviceActorMsg msg) { |
|||
log.trace("Processing device rpc msg: {}", msg); |
|||
appActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void onMsg(ToDeviceActorNotificationMsg msg) { |
|||
log.trace("Processing notification rpc msg: {}", msg); |
|||
appActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void onMsg(ToDeviceSessionActorMsg msg) { |
|||
log.trace("Processing session rpc msg: {}", msg); |
|||
sessionManagerActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void onMsg(ToAllNodesMsg msg) { |
|||
log.trace("Processing broadcast rpc msg: {}", msg); |
|||
appActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void onMsg(RpcSessionCreateRequestMsg msg) { |
|||
log.trace("Processing session create msg: {}", msg); |
|||
rpcManagerActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void onMsg(RpcSessionTellMsg msg) { |
|||
log.trace("Processing session rpc msg: {}", msg); |
|||
rpcManagerActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void onMsg(RpcBroadcastMsg msg) { |
|||
log.trace("Processing broadcast rpc msg: {}", msg); |
|||
rpcManagerActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Override |
|||
public void onServerAdded(ServerInstance server) { |
|||
log.trace("Processing onServerAdded msg: {}", server); |
|||
broadcast(new ClusterEventMsg(server.getServerAddress(), true)); |
|||
} |
|||
|
|||
@Override |
|||
public void onServerUpdated(ServerInstance server) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void onServerRemoved(ServerInstance server) { |
|||
log.trace("Processing onServerRemoved msg: {}", server); |
|||
broadcast(new ClusterEventMsg(server.getServerAddress(), false)); |
|||
} |
|||
|
|||
@Override |
|||
public void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state) { |
|||
log.trace("[{}] Processing onPluginStateChange event: {}", pluginId, state); |
|||
broadcast(ComponentLifecycleMsg.forPlugin(tenantId, pluginId, state)); |
|||
} |
|||
|
|||
@Override |
|||
public void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state) { |
|||
log.trace("[{}] Processing onRuleStateChange event: {}", ruleId, state); |
|||
broadcast(ComponentLifecycleMsg.forRule(tenantId, ruleId, state)); |
|||
} |
|||
|
|||
public void broadcast(ToAllNodesMsg msg) { |
|||
rpcService.broadcast(msg); |
|||
appActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
private void broadcast(ClusterEventMsg msg) { |
|||
this.appActor.tell(msg, ActorRef.noSender()); |
|||
this.sessionManagerActor.tell(msg, ActorRef.noSender()); |
|||
this.rpcManagerActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.service; |
|||
|
|||
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; |
|||
|
|||
public interface RestMsgProcessor { |
|||
|
|||
void process(PluginRestMsg msg); |
|||
|
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.service; |
|||
|
|||
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; |
|||
|
|||
public interface WebSocketMsgProcessor { |
|||
|
|||
void process(PluginWebsocketMsg<?> msg); |
|||
|
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.session; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.shared.SessionTimeoutMsg; |
|||
import org.thingsboard.server.common.data.id.SessionId; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.core.AttributesSubscribeMsg; |
|||
import org.thingsboard.server.common.msg.core.ResponseMsg; |
|||
import org.thingsboard.server.common.msg.core.RpcSubscribeMsg; |
|||
import org.thingsboard.server.common.msg.core.SessionCloseMsg; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.common.msg.session.*; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.event.LoggingAdapter; |
|||
import org.thingsboard.server.common.msg.session.ex.SessionException; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
|
|||
class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor { |
|||
|
|||
Map<Integer, ToDeviceActorMsg> pendingMap = new HashMap<>(); |
|||
private Optional<ServerAddress> currentTargetServer; |
|||
private boolean subscribedToAttributeUpdates; |
|||
private boolean subscribedToRpcCommands; |
|||
|
|||
public ASyncMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { |
|||
super(ctx, logger, sessionId); |
|||
} |
|||
|
|||
@Override |
|||
protected void processToDeviceActorMsg(ActorContext ctx, ToDeviceActorSessionMsg msg) { |
|||
updateSessionCtx(msg, SessionType.ASYNC); |
|||
ToDeviceActorMsg pendingMsg = toDeviceMsg(msg); |
|||
FromDeviceMsg fromDeviceMsg = pendingMsg.getPayload(); |
|||
switch (fromDeviceMsg.getMsgType()) { |
|||
case POST_TELEMETRY_REQUEST: |
|||
case POST_ATTRIBUTES_REQUEST: |
|||
FromDeviceRequestMsg requestMsg = (FromDeviceRequestMsg) fromDeviceMsg; |
|||
if (requestMsg.getRequestId() >= 0) { |
|||
logger.debug("[{}] Pending request {} registered", requestMsg.getRequestId(), requestMsg.getMsgType()); |
|||
//TODO: handle duplicates.
|
|||
pendingMap.put(requestMsg.getRequestId(), pendingMsg); |
|||
} |
|||
break; |
|||
case SUBSCRIBE_ATTRIBUTES_REQUEST: |
|||
subscribedToAttributeUpdates = true; |
|||
break; |
|||
case UNSUBSCRIBE_ATTRIBUTES_REQUEST: |
|||
subscribedToAttributeUpdates = false; |
|||
break; |
|||
case SUBSCRIBE_RPC_COMMANDS_REQUEST: |
|||
subscribedToRpcCommands = true; |
|||
break; |
|||
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: |
|||
subscribedToRpcCommands = false; |
|||
break; |
|||
} |
|||
currentTargetServer = forwardToAppActor(ctx, pendingMsg); |
|||
} |
|||
|
|||
@Override |
|||
public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) { |
|||
try { |
|||
switch (msg.getMsgType()) { |
|||
case STATUS_CODE_RESPONSE: |
|||
case GET_ATTRIBUTES_RESPONSE: |
|||
ResponseMsg responseMsg = (ResponseMsg) msg; |
|||
if (responseMsg.getRequestId() >= 0) { |
|||
logger.debug("[{}] Pending request processed: {}", responseMsg.getRequestId(), responseMsg); |
|||
pendingMap.remove(responseMsg.getRequestId()); |
|||
} |
|||
break; |
|||
} |
|||
sessionCtx.onMsg(new BasicSessionActorToAdaptorMsg(this.sessionCtx, msg)); |
|||
} catch (SessionException e) { |
|||
logger.warning("Failed to push session response msg", e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg) { |
|||
// TODO Auto-generated method stub
|
|||
} |
|||
|
|||
protected void cleanupSession(ActorContext ctx) { |
|||
toDeviceMsg(new SessionCloseMsg()).ifPresent(msg -> forwardToAppActor(ctx, msg)); |
|||
} |
|||
|
|||
@Override |
|||
public void processClusterEvent(ActorContext context, ClusterEventMsg msg) { |
|||
if (pendingMap.size() > 0 || subscribedToAttributeUpdates || subscribedToRpcCommands) { |
|||
Optional<ServerAddress> newTargetServer = systemContext.getRoutingService().resolve(getDeviceId()); |
|||
if (!newTargetServer.equals(currentTargetServer)) { |
|||
currentTargetServer = newTargetServer; |
|||
pendingMap.values().stream().forEach(v -> { |
|||
forwardToAppActor(context, v, currentTargetServer); |
|||
if (currentTargetServer.isPresent()) { |
|||
logger.debug("[{}] Forwarded msg to new server: {}", sessionId, currentTargetServer.get()); |
|||
} else { |
|||
logger.debug("[{}] Forwarded msg to local server.", sessionId); |
|||
} |
|||
}); |
|||
if (subscribedToAttributeUpdates) { |
|||
toDeviceMsg(new AttributesSubscribeMsg()).ifPresent(m -> forwardToAppActor(context, m, currentTargetServer)); |
|||
logger.debug("[{}] Forwarded attributes subscription.", sessionId); |
|||
} |
|||
if (subscribedToRpcCommands) { |
|||
toDeviceMsg(new RpcSubscribeMsg()).ifPresent(m -> forwardToAppActor(context, m, currentTargetServer)); |
|||
logger.debug("[{}] Forwarded rpc commands subscription.", sessionId); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.session; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; |
|||
import org.thingsboard.server.actors.shared.SessionTimeoutMsg; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.SessionId; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.device.BasicToDeviceActorMsg; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.common.msg.session.*; |
|||
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.actor.ActorRef; |
|||
import akka.event.LoggingAdapter; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgProcessor { |
|||
|
|||
protected final SessionId sessionId; |
|||
protected SessionContext sessionCtx; |
|||
protected ToDeviceActorMsg toDeviceActorMsgPrototype; |
|||
|
|||
protected AbstractSessionActorMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { |
|||
super(ctx, logger); |
|||
this.sessionId = sessionId; |
|||
} |
|||
|
|||
protected abstract void processToDeviceActorMsg(ActorContext ctx, ToDeviceActorSessionMsg msg); |
|||
|
|||
protected abstract void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg); |
|||
|
|||
protected abstract void processToDeviceMsg(ActorContext context, ToDeviceMsg msg); |
|||
|
|||
public abstract void processClusterEvent(ActorContext context, ClusterEventMsg msg); |
|||
|
|||
protected void processSessionCtrlMsg(ActorContext ctx, SessionCtrlMsg msg) { |
|||
if (msg instanceof SessionCloseMsg) { |
|||
cleanupSession(ctx); |
|||
terminateSession(ctx, sessionId); |
|||
} |
|||
} |
|||
|
|||
protected void cleanupSession(ActorContext ctx) { |
|||
} |
|||
|
|||
protected void updateSessionCtx(ToDeviceActorSessionMsg msg, SessionType type) { |
|||
sessionCtx = msg.getSessionMsg().getSessionContext(); |
|||
toDeviceActorMsgPrototype = new BasicToDeviceActorMsg(msg, type); |
|||
} |
|||
|
|||
protected ToDeviceActorMsg toDeviceMsg(ToDeviceActorSessionMsg msg) { |
|||
AdaptorToSessionActorMsg adaptorMsg = msg.getSessionMsg(); |
|||
return new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, adaptorMsg.getMsg()); |
|||
} |
|||
|
|||
protected Optional<ToDeviceActorMsg> toDeviceMsg(FromDeviceMsg msg) { |
|||
if (toDeviceActorMsgPrototype != null) { |
|||
return Optional.of(new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, msg)); |
|||
} else { |
|||
return Optional.empty(); |
|||
} |
|||
} |
|||
|
|||
protected Optional<ServerAddress> forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward) { |
|||
Optional<ServerAddress> address = systemContext.getRoutingService().resolve(toForward.getDeviceId()); |
|||
forwardToAppActor(ctx, toForward, address); |
|||
return address; |
|||
} |
|||
|
|||
protected Optional<ServerAddress> forwardToAppActorIfAdressChanged(ActorContext ctx, ToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) { |
|||
Optional<ServerAddress> newAddress = systemContext.getRoutingService().resolve(toForward.getDeviceId()); |
|||
if (!newAddress.equals(oldAddress)) { |
|||
if (newAddress.isPresent()) { |
|||
systemContext.getRpcService().tell(newAddress.get(), |
|||
toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer())); |
|||
} else { |
|||
getAppActor().tell(toForward, ctx.self()); |
|||
} |
|||
} |
|||
return newAddress; |
|||
} |
|||
|
|||
protected void forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward, Optional<ServerAddress> address) { |
|||
if (address.isPresent()) { |
|||
systemContext.getRpcService().tell(address.get(), |
|||
toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer())); |
|||
} else { |
|||
getAppActor().tell(toForward, ctx.self()); |
|||
} |
|||
} |
|||
|
|||
public static void terminateSession(ActorContext ctx, SessionId sessionId) { |
|||
ctx.parent().tell(new SessionTerminationMsg(sessionId), ActorRef.noSender()); |
|||
ctx.stop(ctx.self()); |
|||
} |
|||
|
|||
public DeviceId getDeviceId() { |
|||
return toDeviceActorMsgPrototype.getDeviceId(); |
|||
} |
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.session; |
|||
|
|||
import akka.actor.OneForOneStrategy; |
|||
import akka.actor.SupervisorStrategy; |
|||
import akka.japi.Function; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|||
import org.thingsboard.server.actors.shared.SessionTimeoutMsg; |
|||
import org.thingsboard.server.common.data.id.SessionId; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; |
|||
import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg; |
|||
import org.thingsboard.server.common.msg.session.SessionCtrlMsg; |
|||
import org.thingsboard.server.common.msg.session.SessionMsg; |
|||
import org.thingsboard.server.common.msg.session.SessionType; |
|||
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; |
|||
|
|||
import akka.event.Logging; |
|||
import akka.event.LoggingAdapter; |
|||
import scala.concurrent.duration.Duration; |
|||
|
|||
public class SessionActor extends ContextAwareActor { |
|||
|
|||
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); |
|||
|
|||
private final SessionId sessionId; |
|||
private AbstractSessionActorMsgProcessor processor; |
|||
|
|||
private SessionActor(ActorSystemContext systemContext, SessionId sessionId) { |
|||
super(systemContext); |
|||
this.sessionId = sessionId; |
|||
} |
|||
|
|||
@Override |
|||
public SupervisorStrategy supervisorStrategy() { |
|||
return new OneForOneStrategy(-1, Duration.Inf(), |
|||
throwable -> { |
|||
logger.error(throwable, "Unknown session error"); |
|||
if (throwable instanceof Error) { |
|||
return OneForOneStrategy.escalate(); |
|||
} else { |
|||
return OneForOneStrategy.resume(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Object msg) throws Exception { |
|||
logger.debug("[{}] Processing: {}.", sessionId, msg); |
|||
if (msg instanceof ToDeviceActorSessionMsg) { |
|||
processDeviceMsg((ToDeviceActorSessionMsg) msg); |
|||
} else if (msg instanceof ToDeviceSessionActorMsg) { |
|||
processToDeviceMsg((ToDeviceSessionActorMsg) msg); |
|||
} else if (msg instanceof SessionTimeoutMsg) { |
|||
processTimeoutMsg((SessionTimeoutMsg) msg); |
|||
} else if (msg instanceof SessionCtrlMsg) { |
|||
processSessionCtrlMsg((SessionCtrlMsg) msg); |
|||
} else if (msg instanceof ClusterEventMsg) { |
|||
processClusterEvent((ClusterEventMsg) msg); |
|||
} else { |
|||
logger.warning("[{}] Unknown msg: {}", sessionId, msg); |
|||
} |
|||
} |
|||
|
|||
private void processClusterEvent(ClusterEventMsg msg) { |
|||
processor.processClusterEvent(context(), msg); |
|||
} |
|||
|
|||
private void processDeviceMsg(ToDeviceActorSessionMsg msg) { |
|||
initProcessor(msg); |
|||
processor.processToDeviceActorMsg(context(), msg); |
|||
} |
|||
|
|||
private void processToDeviceMsg(ToDeviceSessionActorMsg msg) { |
|||
processor.processToDeviceMsg(context(), msg.getMsg()); |
|||
} |
|||
|
|||
private void processTimeoutMsg(SessionTimeoutMsg msg) { |
|||
if (processor != null) { |
|||
processor.processTimeoutMsg(context(), msg); |
|||
} else { |
|||
logger.warning("[{}] Can't process timeout msg: {} without processor", sessionId, msg); |
|||
} |
|||
} |
|||
|
|||
private void processSessionCtrlMsg(SessionCtrlMsg msg) { |
|||
if (processor != null) { |
|||
processor.processSessionCtrlMsg(context(), msg); |
|||
} else if (msg instanceof SessionCloseMsg) { |
|||
AbstractSessionActorMsgProcessor.terminateSession(context(), sessionId); |
|||
} else { |
|||
logger.warning("[{}] Can't process session ctrl msg: {} without processor", sessionId, msg); |
|||
} |
|||
} |
|||
|
|||
private void initProcessor(ToDeviceActorSessionMsg msg) { |
|||
if (processor == null) { |
|||
SessionMsg sessionMsg = (SessionMsg) msg.getSessionMsg(); |
|||
if (sessionMsg.getSessionContext().getSessionType() == SessionType.SYNC) { |
|||
processor = new SyncMsgProcessor(systemContext, logger, sessionId); |
|||
} else { |
|||
processor = new ASyncMsgProcessor(systemContext, logger, sessionId); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static class ActorCreator extends ContextBasedCreator<SessionActor> { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private final SessionId sessionId; |
|||
|
|||
public ActorCreator(ActorSystemContext context, SessionId sessionId) { |
|||
super(context); |
|||
this.sessionId = sessionId; |
|||
} |
|||
|
|||
@Override |
|||
public SessionActor create() throws Exception { |
|||
return new SessionActor(context, sessionId); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,154 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.session; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
|
|||
import akka.actor.*; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|||
import org.thingsboard.server.actors.service.DefaultActorService; |
|||
import org.thingsboard.server.actors.shared.SessionTimeoutMsg; |
|||
import org.thingsboard.server.common.data.id.SessionId; |
|||
import org.thingsboard.server.common.msg.aware.SessionAwareMsg; |
|||
|
|||
import akka.event.Logging; |
|||
import akka.event.LoggingAdapter; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.core.SessionCloseMsg; |
|||
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; |
|||
import org.thingsboard.server.common.msg.session.SessionCtrlMsg; |
|||
|
|||
public class SessionManagerActor extends ContextAwareActor { |
|||
|
|||
private static final int INITIAL_SESSION_MAP_SIZE = 1024; |
|||
|
|||
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); |
|||
|
|||
private final Map<String, ActorRef> sessionActors; |
|||
|
|||
public SessionManagerActor(ActorSystemContext systemContext) { |
|||
super(systemContext); |
|||
this.sessionActors = new HashMap<>(INITIAL_SESSION_MAP_SIZE); |
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Object msg) throws Exception { |
|||
if (msg instanceof SessionAwareMsg) { |
|||
forwardToSessionActor((SessionAwareMsg) msg); |
|||
} else if (msg instanceof SessionTerminationMsg) { |
|||
onSessionTermination((SessionTerminationMsg) msg); |
|||
} else if (msg instanceof Terminated) { |
|||
onTermination((Terminated) msg); |
|||
} else if (msg instanceof SessionTimeoutMsg) { |
|||
onSessionTimeout((SessionTimeoutMsg) msg); |
|||
} else if (msg instanceof SessionCtrlMsg) { |
|||
onSessionCtrlMsg((SessionCtrlMsg) msg); |
|||
} else if (msg instanceof ClusterEventMsg) { |
|||
broadcast(msg); |
|||
} |
|||
} |
|||
|
|||
private void broadcast(Object msg) { |
|||
sessionActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); |
|||
} |
|||
|
|||
private void onSessionTimeout(SessionTimeoutMsg msg) { |
|||
String sessionIdStr = msg.getSessionId().toUidStr(); |
|||
ActorRef sessionActor = sessionActors.get(sessionIdStr); |
|||
if (sessionActor != null) { |
|||
sessionActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
} |
|||
|
|||
private void onSessionCtrlMsg(SessionCtrlMsg msg) { |
|||
String sessionIdStr = msg.getSessionId().toUidStr(); |
|||
ActorRef sessionActor = sessionActors.get(sessionIdStr); |
|||
if (sessionActor != null) { |
|||
sessionActor.tell(msg, ActorRef.noSender()); |
|||
} |
|||
} |
|||
|
|||
private void onSessionTermination(SessionTerminationMsg msg) { |
|||
String sessionIdStr = msg.getId().toUidStr(); |
|||
ActorRef sessionActor = sessionActors.remove(sessionIdStr); |
|||
if (sessionActor != null) { |
|||
log.debug("[{}] Removed session actor.", sessionIdStr); |
|||
//TODO: onSubscriptionUpdate device actor about session close;
|
|||
} else { |
|||
log.debug("[{}] Session actor was already removed.", sessionIdStr); |
|||
} |
|||
} |
|||
|
|||
private void forwardToSessionActor(SessionAwareMsg msg) { |
|||
if (msg instanceof ToDeviceSessionActorMsg || msg instanceof SessionCloseMsg) { |
|||
String sessionIdStr = msg.getSessionId().toUidStr(); |
|||
ActorRef sessionActor = sessionActors.get(sessionIdStr); |
|||
if (sessionActor != null) { |
|||
sessionActor.tell(msg, ActorRef.noSender()); |
|||
} else { |
|||
log.debug("[{}] Session actor was already removed.", sessionIdStr); |
|||
} |
|||
} else { |
|||
try { |
|||
getOrCreateSessionActor(msg.getSessionId()).tell(msg, self()); |
|||
} catch (InvalidActorNameException e) { |
|||
log.info("Invalid msg : {}", msg); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private ActorRef getOrCreateSessionActor(SessionId sessionId) { |
|||
String sessionIdStr = sessionId.toUidStr(); |
|||
ActorRef sessionActor = sessionActors.get(sessionIdStr); |
|||
if (sessionActor == null) { |
|||
log.debug("[{}] Creating session actor.", sessionIdStr); |
|||
sessionActor = context().actorOf( |
|||
Props.create(new SessionActor.ActorCreator(systemContext, sessionId)).withDispatcher(DefaultActorService.SESSION_DISPATCHER_NAME), |
|||
sessionIdStr); |
|||
sessionActors.put(sessionIdStr, sessionActor); |
|||
log.debug("[{}] Created session actor.", sessionIdStr); |
|||
} |
|||
return sessionActor; |
|||
} |
|||
|
|||
private void onTermination(Terminated message) { |
|||
ActorRef terminated = message.actor(); |
|||
if (terminated instanceof LocalActorRef) { |
|||
log.info("Removed actor: {}.", terminated); |
|||
//TODO: cleanup session actors map
|
|||
} else { |
|||
throw new IllegalStateException("Remote actors are not supported!"); |
|||
} |
|||
} |
|||
|
|||
public static class ActorCreator extends ContextBasedCreator<SessionManagerActor> { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public ActorCreator(ActorSystemContext context) { |
|||
super(context); |
|||
} |
|||
|
|||
@Override |
|||
public SessionManagerActor create() throws Exception { |
|||
return new SessionManagerActor(context); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.session; |
|||
|
|||
import org.thingsboard.server.actors.shared.ActorTerminationMsg; |
|||
import org.thingsboard.server.common.data.id.SessionId; |
|||
|
|||
public class SessionTerminationMsg extends ActorTerminationMsg<SessionId> { |
|||
|
|||
public SessionTerminationMsg(SessionId id) { |
|||
super(id); |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.session; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.shared.SessionTimeoutMsg; |
|||
import org.thingsboard.server.common.data.id.SessionId; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.common.msg.session.*; |
|||
import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg; |
|||
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; |
|||
import org.thingsboard.server.common.msg.session.ex.SessionException; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.event.LoggingAdapter; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
class SyncMsgProcessor extends AbstractSessionActorMsgProcessor { |
|||
private ToDeviceActorMsg pendingMsg; |
|||
private Optional<ServerAddress> currentTargetServer; |
|||
private boolean pendingResponse; |
|||
|
|||
public SyncMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { |
|||
super(ctx, logger, sessionId); |
|||
} |
|||
|
|||
@Override |
|||
protected void processToDeviceActorMsg(ActorContext ctx, ToDeviceActorSessionMsg msg) { |
|||
updateSessionCtx(msg, SessionType.SYNC); |
|||
pendingMsg = toDeviceMsg(msg); |
|||
pendingResponse = true; |
|||
currentTargetServer = forwardToAppActor(ctx, pendingMsg); |
|||
scheduleMsgWithDelay(ctx, new SessionTimeoutMsg(sessionId), getTimeout(systemContext, msg.getSessionMsg().getSessionContext()), ctx.parent()); |
|||
} |
|||
|
|||
public void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg) { |
|||
if (pendingResponse) { |
|||
try { |
|||
sessionCtx.onMsg(new SessionCloseMsg(sessionId, true)); |
|||
} catch (SessionException e) { |
|||
logger.warning("Failed to push session close msg", e); |
|||
} |
|||
terminateSession(context, this.sessionId); |
|||
} |
|||
} |
|||
|
|||
public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) { |
|||
try { |
|||
sessionCtx.onMsg(new BasicSessionActorToAdaptorMsg(this.sessionCtx, msg)); |
|||
pendingResponse = false; |
|||
} catch (SessionException e) { |
|||
logger.warning("Failed to push session response msg", e); |
|||
} |
|||
terminateSession(context, this.sessionId); |
|||
} |
|||
|
|||
@Override |
|||
public void processClusterEvent(ActorContext context, ClusterEventMsg msg) { |
|||
if (pendingResponse) { |
|||
Optional<ServerAddress> newTargetServer = forwardToAppActorIfAdressChanged(context, pendingMsg, currentTargetServer); |
|||
if (logger.isDebugEnabled()) { |
|||
if (!newTargetServer.equals(currentTargetServer)) { |
|||
if (newTargetServer.isPresent()) { |
|||
logger.debug("[{}] Forwarded msg to new server: {}", sessionId, newTargetServer.get()); |
|||
} else { |
|||
logger.debug("[{}] Forwarded msg to local server.", sessionId); |
|||
} |
|||
} |
|||
} |
|||
currentTargetServer = newTargetServer; |
|||
} |
|||
} |
|||
|
|||
private long getTimeout(ActorSystemContext ctx, SessionContext sessionCtx) { |
|||
return sessionCtx.getTimeout() > 0 ? sessionCtx.getTimeout() : ctx.getSyncSessionTimeout(); |
|||
} |
|||
} |
|||
@ -0,0 +1,135 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.actor.ActorRef; |
|||
import akka.actor.Scheduler; |
|||
import akka.event.LoggingAdapter; |
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.data.plugin.ComponentDescriptor; |
|||
import org.thingsboard.server.common.data.plugin.ComponentType; |
|||
import org.thingsboard.server.extensions.api.component.*; |
|||
import scala.concurrent.ExecutionContextExecutor; |
|||
import scala.concurrent.duration.Duration; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
public abstract class AbstractContextAwareMsgProcessor { |
|||
|
|||
protected final ActorSystemContext systemContext; |
|||
protected final LoggingAdapter logger; |
|||
protected final ObjectMapper mapper = new ObjectMapper(); |
|||
|
|||
protected AbstractContextAwareMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger) { |
|||
super(); |
|||
this.systemContext = systemContext; |
|||
this.logger = logger; |
|||
} |
|||
|
|||
protected ActorRef getAppActor() { |
|||
return systemContext.getAppActor(); |
|||
} |
|||
|
|||
protected Scheduler getScheduler() { |
|||
return systemContext.getScheduler(); |
|||
} |
|||
|
|||
protected ExecutionContextExecutor getSystemDispatcher() { |
|||
return systemContext.getActorSystem().dispatcher(); |
|||
} |
|||
|
|||
protected void schedulePeriodicMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, long periodInMs) { |
|||
schedulePeriodicMsgWithDelay(ctx, msg, delayInMs, periodInMs, ctx.self()); |
|||
} |
|||
|
|||
protected void schedulePeriodicMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, long periodInMs, ActorRef target) { |
|||
logger.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs); |
|||
getScheduler().schedule(Duration.create(delayInMs, TimeUnit.MILLISECONDS), Duration.create(periodInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null); |
|||
} |
|||
|
|||
|
|||
protected void scheduleMsgWithDelay(ActorContext ctx, Object msg, long delayInMs) { |
|||
scheduleMsgWithDelay(ctx, msg, delayInMs, ctx.self()); |
|||
} |
|||
|
|||
protected void scheduleMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, ActorRef target) { |
|||
logger.debug("Scheduling msg {} with delay {} ms", msg, delayInMs); |
|||
getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null); |
|||
} |
|||
|
|||
protected <T extends ConfigurableComponent> T initComponent(JsonNode componentNode) throws Exception { |
|||
ComponentConfiguration configuration = new ComponentConfiguration( |
|||
componentNode.get("clazz").asText(), |
|||
componentNode.get("name").asText(), |
|||
mapper.writeValueAsString(componentNode.get("configuration")) |
|||
); |
|||
logger.info("Initializing [{}][{}] component", configuration.getName(), configuration.getClazz()); |
|||
ComponentDescriptor componentDescriptor = systemContext.getComponentService().getComponent(configuration.getClazz()) |
|||
.orElseThrow(() -> new InstantiationException("Component Not found!")); |
|||
return initComponent(componentDescriptor, configuration); |
|||
} |
|||
|
|||
protected <T extends ConfigurableComponent> T initComponent(ComponentDescriptor componentDefinition, ComponentConfiguration configuration) |
|||
throws Exception { |
|||
return initComponent(componentDefinition.getClazz(), componentDefinition.getType(), configuration.getConfiguration()); |
|||
} |
|||
|
|||
protected <T extends ConfigurableComponent> T initComponent(String clazz, ComponentType type, String configuration) |
|||
throws Exception { |
|||
Class<?> componentClazz = Class.forName(clazz); |
|||
T component = (T) (componentClazz.newInstance()); |
|||
Class<?> configurationClazz; |
|||
switch (type) { |
|||
case FILTER: |
|||
configurationClazz = ((Filter) componentClazz.getAnnotation(Filter.class)).configuration(); |
|||
break; |
|||
case PROCESSOR: |
|||
configurationClazz = ((Processor) componentClazz.getAnnotation(Processor.class)).configuration(); |
|||
break; |
|||
case ACTION: |
|||
configurationClazz = ((Action) componentClazz.getAnnotation(Action.class)).configuration(); |
|||
break; |
|||
case PLUGIN: |
|||
configurationClazz = ((Plugin) componentClazz.getAnnotation(Plugin.class)).configuration(); |
|||
break; |
|||
default: |
|||
throw new IllegalStateException("Component with type: " + type + " is not supported!"); |
|||
} |
|||
component.init(decode(configuration, configurationClazz)); |
|||
return component; |
|||
} |
|||
|
|||
public <C> C decode(String configuration, Class<C> configurationClazz) throws IOException, RuntimeException { |
|||
logger.info("Initializing using configuration: {}", configuration); |
|||
return mapper.readValue(configuration, configurationClazz); |
|||
} |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
private static class ComponentConfiguration { |
|||
private final String clazz; |
|||
private final String name; |
|||
private final String configuration; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared; |
|||
|
|||
public abstract class ActorTerminationMsg<T> { |
|||
|
|||
private final T id; |
|||
|
|||
public ActorTerminationMsg(T id) { |
|||
super(); |
|||
this.id = id; |
|||
} |
|||
|
|||
public T getId() { |
|||
return id; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.event.LoggingAdapter; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.stats.StatsPersistTick; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
|
|||
public abstract class ComponentMsgProcessor<T> extends AbstractContextAwareMsgProcessor { |
|||
|
|||
protected final TenantId tenantId; |
|||
protected final T entityId; |
|||
|
|||
protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) { |
|||
super(systemContext, logger); |
|||
this.tenantId = tenantId; |
|||
this.entityId = id; |
|||
} |
|||
|
|||
public abstract void start() throws Exception; |
|||
|
|||
public abstract void stop() throws Exception; |
|||
|
|||
public abstract void onCreated(ActorContext context) throws Exception; |
|||
|
|||
public abstract void onUpdate(ActorContext context) throws Exception; |
|||
|
|||
public abstract void onActivate(ActorContext context) throws Exception; |
|||
|
|||
public abstract void onSuspend(ActorContext context) throws Exception; |
|||
|
|||
public abstract void onStop(ActorContext context) throws Exception; |
|||
|
|||
public abstract void onClusterEventMsg(ClusterEventMsg msg) throws Exception; |
|||
|
|||
public void scheduleStatsPersistTick(ActorContext context, long statsPersistFrequency) { |
|||
schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.SessionId; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
@Data |
|||
public class SessionTimeoutMsg implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private final SessionId sessionId; |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared.plugin; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.plugin.PluginActor; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.actors.service.DefaultActorService; |
|||
import org.thingsboard.server.common.data.id.PluginId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|||
import org.thingsboard.server.common.data.plugin.PluginMetaData; |
|||
import org.thingsboard.server.dao.plugin.PluginService; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.actor.ActorRef; |
|||
import akka.actor.Props; |
|||
|
|||
@Slf4j |
|||
public abstract class PluginManager { |
|||
|
|||
protected final ActorSystemContext systemContext; |
|||
protected final PluginService pluginService; |
|||
protected final Map<PluginId, ActorRef> pluginActors; |
|||
|
|||
public PluginManager(ActorSystemContext systemContext) { |
|||
this.systemContext = systemContext; |
|||
this.pluginService = systemContext.getPluginService(); |
|||
this.pluginActors = new HashMap<>(); |
|||
} |
|||
|
|||
public void init(ActorContext context) { |
|||
PageDataIterable<PluginMetaData> pluginIterator = new PageDataIterable<>(getFetchPluginsFunction(), |
|||
ContextAwareActor.ENTITY_PACK_LIMIT); |
|||
for (PluginMetaData plugin : pluginIterator) { |
|||
log.debug("[{}] Creating plugin actor", plugin.getId()); |
|||
getOrCreatePluginActor(context, plugin.getId()); |
|||
log.debug("Plugin actor created."); |
|||
} |
|||
} |
|||
|
|||
abstract FetchFunction<PluginMetaData> getFetchPluginsFunction(); |
|||
|
|||
abstract TenantId getTenantId(); |
|||
|
|||
public ActorRef getOrCreatePluginActor(ActorContext context, PluginId pluginId) { |
|||
ActorRef pluginActor = pluginActors.get(pluginId); |
|||
if (pluginActor == null) { |
|||
pluginActor = context.actorOf(Props.create(new PluginActor.ActorCreator(systemContext, getTenantId(), pluginId)) |
|||
.withDispatcher(DefaultActorService.PLUGIN_DISPATCHER_NAME), pluginId.toString()); |
|||
pluginActors.put(pluginId, pluginActor); |
|||
} |
|||
return pluginActor; |
|||
} |
|||
|
|||
public void broadcast(Object msg) { |
|||
pluginActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); |
|||
} |
|||
|
|||
public void remove(PluginId id) { |
|||
pluginActors.remove(id); |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared.plugin; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|||
import org.thingsboard.server.common.data.plugin.PluginMetaData; |
|||
import org.thingsboard.server.dao.plugin.BasePluginService; |
|||
import org.thingsboard.server.dao.plugin.PluginService; |
|||
|
|||
public class SystemPluginManager extends PluginManager { |
|||
|
|||
public SystemPluginManager(ActorSystemContext systemContext) { |
|||
super(systemContext); |
|||
} |
|||
|
|||
@Override |
|||
FetchFunction<PluginMetaData> getFetchPluginsFunction() { |
|||
return link -> pluginService.findSystemPlugins(link); |
|||
} |
|||
|
|||
@Override |
|||
TenantId getTenantId() { |
|||
return BasePluginService.SYSTEM_TENANT; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared.plugin; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|||
import org.thingsboard.server.common.data.plugin.PluginMetaData; |
|||
|
|||
public class TenantPluginManager extends PluginManager { |
|||
|
|||
private final TenantId tenantId; |
|||
|
|||
public TenantPluginManager(ActorSystemContext systemContext, TenantId tenantId) { |
|||
super(systemContext); |
|||
this.tenantId = tenantId; |
|||
} |
|||
|
|||
@Override |
|||
FetchFunction<PluginMetaData> getFetchPluginsFunction() { |
|||
return link -> pluginService.findTenantPlugins(tenantId, link); |
|||
} |
|||
|
|||
@Override |
|||
TenantId getTenantId() { |
|||
return tenantId; |
|||
} |
|||
} |
|||
@ -0,0 +1,126 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared.rule; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.actor.ActorRef; |
|||
import akka.actor.Props; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.rule.RuleActor; |
|||
import org.thingsboard.server.actors.rule.RuleActorChain; |
|||
import org.thingsboard.server.actors.rule.RuleActorMetaData; |
|||
import org.thingsboard.server.actors.rule.SimpleRuleActorChain; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.actors.service.DefaultActorService; |
|||
import org.thingsboard.server.common.data.id.RuleId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; |
|||
import org.thingsboard.server.common.data.rule.RuleMetaData; |
|||
import org.thingsboard.server.dao.rule.RuleService; |
|||
|
|||
import java.util.*; |
|||
|
|||
public abstract class RuleManager { |
|||
|
|||
protected static final Logger logger = LoggerFactory.getLogger(RuleManager.class); |
|||
|
|||
protected final ActorSystemContext systemContext; |
|||
protected final RuleService ruleService; |
|||
protected final Map<RuleId, ActorRef> ruleActors; |
|||
protected final TenantId tenantId; |
|||
|
|||
Map<RuleMetaData, RuleActorMetaData> ruleMap = new HashMap<>(); |
|||
private RuleActorChain ruleChain; |
|||
|
|||
public RuleManager(ActorSystemContext systemContext, TenantId tenantId) { |
|||
this.systemContext = systemContext; |
|||
this.ruleService = systemContext.getRuleService(); |
|||
this.ruleActors = new HashMap<>(); |
|||
this.tenantId = tenantId; |
|||
} |
|||
|
|||
public void init(ActorContext context) { |
|||
PageDataIterable<RuleMetaData> ruleIterator = new PageDataIterable<>(getFetchRulesFunction(), |
|||
ContextAwareActor.ENTITY_PACK_LIMIT); |
|||
ruleMap = new HashMap<>(); |
|||
|
|||
for (RuleMetaData rule : ruleIterator) { |
|||
logger.debug("[{}] Creating rule actor {}", rule.getId(), rule); |
|||
ActorRef ref = getOrCreateRuleActor(context, rule.getId()); |
|||
RuleActorMetaData actorMd = RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref); |
|||
ruleMap.put(rule, actorMd); |
|||
logger.debug("[{}] Rule actor created.", rule.getId()); |
|||
} |
|||
|
|||
refreshRuleChain(); |
|||
} |
|||
|
|||
public Optional<ActorRef> update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) { |
|||
RuleMetaData rule = null; |
|||
if (event != ComponentLifecycleEvent.DELETED) { |
|||
rule = systemContext.getRuleService().findRuleById(ruleId); |
|||
} |
|||
if (rule == null) { |
|||
rule = ruleMap.keySet().stream().filter(r -> r.getId().equals(ruleId)).findFirst().orElse(null); |
|||
rule.setState(ComponentLifecycleState.SUSPENDED); |
|||
} |
|||
if (rule != null) { |
|||
RuleActorMetaData actorMd = ruleMap.get(rule); |
|||
if (actorMd == null) { |
|||
ActorRef ref = getOrCreateRuleActor(context, rule.getId()); |
|||
actorMd = RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref); |
|||
ruleMap.put(rule, actorMd); |
|||
} |
|||
refreshRuleChain(); |
|||
return Optional.of(actorMd.getActorRef()); |
|||
} else { |
|||
logger.warn("[{}] Can't process unknown rule!", rule.getId()); |
|||
return Optional.empty(); |
|||
} |
|||
} |
|||
|
|||
abstract FetchFunction<RuleMetaData> getFetchRulesFunction(); |
|||
|
|||
public ActorRef getOrCreateRuleActor(ActorContext context, RuleId ruleId) { |
|||
ActorRef ruleActor = ruleActors.get(ruleId); |
|||
if (ruleActor == null) { |
|||
ruleActor = context.actorOf(Props.create(new RuleActor.ActorCreator(systemContext, tenantId, ruleId)) |
|||
.withDispatcher(DefaultActorService.RULE_DISPATCHER_NAME), ruleId.toString()); |
|||
ruleActors.put(ruleId, ruleActor); |
|||
} |
|||
return ruleActor; |
|||
} |
|||
|
|||
public RuleActorChain getRuleChain() { |
|||
return ruleChain; |
|||
} |
|||
|
|||
|
|||
private void refreshRuleChain() { |
|||
Set<RuleActorMetaData> activeRuleSet = new HashSet<>(); |
|||
for (Map.Entry<RuleMetaData, RuleActorMetaData> rule : ruleMap.entrySet()) { |
|||
if (rule.getKey().getState() == ComponentLifecycleState.ACTIVE) { |
|||
activeRuleSet.add(rule.getValue()); |
|||
} |
|||
} |
|||
ruleChain = new SimpleRuleActorChain(activeRuleSet); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared.rule; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|||
import org.thingsboard.server.common.data.rule.RuleMetaData; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
|
|||
public class SystemRuleManager extends RuleManager { |
|||
|
|||
public SystemRuleManager(ActorSystemContext systemContext) { |
|||
super(systemContext, new TenantId(ModelConstants.NULL_UUID)); |
|||
} |
|||
|
|||
@Override |
|||
FetchFunction<RuleMetaData> getFetchRulesFunction() { |
|||
return link -> ruleService.findSystemRules(link); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared.rule; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|||
import org.thingsboard.server.common.data.rule.RuleMetaData; |
|||
|
|||
public class TenantRuleManager extends RuleManager { |
|||
|
|||
public TenantRuleManager(ActorSystemContext systemContext, TenantId tenantId) { |
|||
super(systemContext, tenantId); |
|||
} |
|||
|
|||
@Override |
|||
FetchFunction<RuleMetaData> getFetchRulesFunction() { |
|||
return link -> ruleService.findTenantRules(tenantId, link); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.stats; |
|||
|
|||
import akka.event.Logging; |
|||
import akka.event.LoggingAdapter; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|||
import org.thingsboard.server.common.data.DataConstants; |
|||
import org.thingsboard.server.common.data.Event; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
|
|||
public class StatsActor extends ContextAwareActor { |
|||
|
|||
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); |
|||
private final ObjectMapper mapper = new ObjectMapper(); |
|||
|
|||
public StatsActor(ActorSystemContext context) { |
|||
super(context); |
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Object msg) throws Exception { |
|||
logger.debug("Received message: {}", msg); |
|||
if (msg instanceof StatsPersistMsg) { |
|||
try { |
|||
onStatsPersistMsg((StatsPersistMsg) msg); |
|||
} catch (Exception e) { |
|||
logger.warning("Failed to persist statistics: {}", msg, e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void onStatsPersistMsg(StatsPersistMsg msg) throws Exception { |
|||
Event event = new Event(); |
|||
event.setEntityId(msg.getEntityId()); |
|||
event.setTenantId(msg.getTenantId()); |
|||
event.setType(DataConstants.STATS); |
|||
event.setBody(toBodyJson(systemContext.getDiscoveryService().getCurrentServer().getServerAddress(), msg.getMessagesProcessed(), msg.getErrorsOccurred())); |
|||
systemContext.getEventService().save(event); |
|||
} |
|||
|
|||
private JsonNode toBodyJson(ServerAddress server, long messagesProcessed, long errorsOccurred) { |
|||
return mapper.createObjectNode().put("server", server.toString()).put("messagesProcessed", messagesProcessed).put("errorsOccurred", errorsOccurred); |
|||
} |
|||
|
|||
public static class ActorCreator extends ContextBasedCreator<StatsActor> { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public ActorCreator(ActorSystemContext context) { |
|||
super(context); |
|||
} |
|||
|
|||
@Override |
|||
public StatsActor create() throws Exception { |
|||
return new StatsActor(context); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.stats; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Getter; |
|||
import lombok.ToString; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
@AllArgsConstructor |
|||
@Getter |
|||
@ToString |
|||
public final class StatsPersistMsg { |
|||
private long messagesProcessed; |
|||
private long errorsOccurred; |
|||
private TenantId tenantId; |
|||
private EntityId entityId; |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.stats; |
|||
|
|||
public final class StatsPersistTick {} |
|||
@ -0,0 +1,40 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.tenant; |
|||
|
|||
import org.thingsboard.server.actors.rule.RuleActorChain; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
|
|||
public class RuleChainDeviceMsg { |
|||
|
|||
private final ToDeviceActorMsg toDeviceActorMsg; |
|||
private final RuleActorChain ruleChain; |
|||
|
|||
public RuleChainDeviceMsg(ToDeviceActorMsg toDeviceActorMsg, RuleActorChain ruleChain) { |
|||
super(); |
|||
this.toDeviceActorMsg = toDeviceActorMsg; |
|||
this.ruleChain = ruleChain; |
|||
} |
|||
|
|||
public ToDeviceActorMsg getToDeviceActorMsg() { |
|||
return toDeviceActorMsg; |
|||
} |
|||
|
|||
public RuleActorChain getRuleChain() { |
|||
return ruleChain; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,184 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.tenant; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.device.DeviceActor; |
|||
import org.thingsboard.server.actors.plugin.PluginTerminationMsg; |
|||
import org.thingsboard.server.actors.rule.ComplexRuleActorChain; |
|||
import org.thingsboard.server.actors.rule.RuleActorChain; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|||
import org.thingsboard.server.actors.service.DefaultActorService; |
|||
import org.thingsboard.server.actors.shared.plugin.PluginManager; |
|||
import org.thingsboard.server.actors.shared.plugin.TenantPluginManager; |
|||
import org.thingsboard.server.actors.shared.rule.RuleManager; |
|||
import org.thingsboard.server.actors.shared.rule.TenantRuleManager; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import akka.actor.Props; |
|||
import akka.event.Logging; |
|||
import akka.event.LoggingAdapter; |
|||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
|||
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; |
|||
import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg; |
|||
|
|||
public class TenantActor extends ContextAwareActor { |
|||
|
|||
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); |
|||
|
|||
private final TenantId tenantId; |
|||
private final RuleManager ruleManager; |
|||
private final PluginManager pluginManager; |
|||
private final Map<DeviceId, ActorRef> deviceActors; |
|||
|
|||
private TenantActor(ActorSystemContext systemContext, TenantId tenantId) { |
|||
super(systemContext); |
|||
this.tenantId = tenantId; |
|||
this.ruleManager = new TenantRuleManager(systemContext, tenantId); |
|||
this.pluginManager = new TenantPluginManager(systemContext, tenantId); |
|||
this.deviceActors = new HashMap<>(); |
|||
} |
|||
|
|||
@Override |
|||
public void preStart() { |
|||
logger.info("[{}] Starting tenant actor.", tenantId); |
|||
try { |
|||
ruleManager.init(this.context()); |
|||
pluginManager.init(this.context()); |
|||
logger.info("[{}] Tenant actor started.", tenantId); |
|||
} catch (Exception e) { |
|||
logger.error(e, "[{}] Unknown failure", tenantId); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Object msg) throws Exception { |
|||
logger.debug("[{}] Received message: {}", tenantId, msg); |
|||
if (msg instanceof RuleChainDeviceMsg) { |
|||
process((RuleChainDeviceMsg) msg); |
|||
} else if (msg instanceof ToDeviceActorMsg) { |
|||
onToDeviceActorMsg((ToDeviceActorMsg) msg); |
|||
} else if (msg instanceof ToPluginActorMsg) { |
|||
onToPluginMsg((ToPluginActorMsg) msg); |
|||
} else if (msg instanceof ToRuleActorMsg) { |
|||
onToRuleMsg((ToRuleActorMsg) msg); |
|||
} else if (msg instanceof ToDeviceActorNotificationMsg) { |
|||
onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg); |
|||
} else if (msg instanceof ClusterEventMsg) { |
|||
broadcast(msg); |
|||
} else if (msg instanceof ComponentLifecycleMsg) { |
|||
onComponentLifecycleMsg((ComponentLifecycleMsg) msg); |
|||
} else if (msg instanceof PluginTerminationMsg) { |
|||
onPluginTerminated((PluginTerminationMsg) msg); |
|||
} else { |
|||
logger.warning("[{}] Unknown message: {}!", tenantId, msg); |
|||
} |
|||
} |
|||
|
|||
private void broadcast(Object msg) { |
|||
pluginManager.broadcast(msg); |
|||
deviceActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); |
|||
} |
|||
|
|||
private void onToDeviceActorMsg(ToDeviceActorMsg msg) { |
|||
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) { |
|||
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
private void onToRuleMsg(ToRuleActorMsg msg) { |
|||
ActorRef target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId()); |
|||
target.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
private void onToPluginMsg(ToPluginActorMsg msg) { |
|||
if (msg.getPluginTenantId().equals(tenantId)) { |
|||
ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId()); |
|||
pluginActor.tell(msg, ActorRef.noSender()); |
|||
} else { |
|||
context().parent().tell(msg, ActorRef.noSender()); |
|||
} |
|||
} |
|||
|
|||
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { |
|||
if (msg.getPluginId().isPresent()) { |
|||
ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId().get()); |
|||
pluginActor.tell(msg, ActorRef.noSender()); |
|||
} else if (msg.getRuleId().isPresent()) { |
|||
ActorRef target; |
|||
Optional<ActorRef> ref = ruleManager.update(this.context(), msg.getRuleId().get(), msg.getEvent()); |
|||
if (ref.isPresent()) { |
|||
target = ref.get(); |
|||
} else { |
|||
logger.debug("Failed to find actor for rule: [{}]", msg.getRuleId()); |
|||
return; |
|||
} |
|||
target.tell(msg, ActorRef.noSender()); |
|||
} else { |
|||
logger.debug("[{}] Invalid component lifecycle msg.", tenantId); |
|||
} |
|||
} |
|||
|
|||
private void onPluginTerminated(PluginTerminationMsg msg) { |
|||
pluginManager.remove(msg.getId()); |
|||
} |
|||
|
|||
private void process(RuleChainDeviceMsg msg) { |
|||
ToDeviceActorMsg toDeviceActorMsg = msg.getToDeviceActorMsg(); |
|||
ActorRef deviceActor = getOrCreateDeviceActor(toDeviceActorMsg.getDeviceId()); |
|||
RuleActorChain chain = new ComplexRuleActorChain(msg.getRuleChain(), ruleManager.getRuleChain()); |
|||
deviceActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, chain), context().self()); |
|||
} |
|||
|
|||
private ActorRef getOrCreateDeviceActor(DeviceId deviceId) { |
|||
ActorRef deviceActor = deviceActors.get(deviceId); |
|||
if (deviceActor == null) { |
|||
deviceActor = context().actorOf(Props.create(new DeviceActor.ActorCreator(systemContext, tenantId, deviceId)) |
|||
.withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), deviceId.toString()); |
|||
deviceActors.put(deviceId, deviceActor); |
|||
} |
|||
return deviceActor; |
|||
} |
|||
|
|||
public static class ActorCreator extends ContextBasedCreator<TenantActor> { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private final TenantId tenantId; |
|||
|
|||
public ActorCreator(ActorSystemContext context, TenantId tenantId) { |
|||
super(context); |
|||
this.tenantId = tenantId; |
|||
} |
|||
|
|||
@Override |
|||
public TenantActor create() throws Exception { |
|||
return new TenantActor(context, tenantId); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.discovery; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.util.Assert; |
|||
import org.thingsboard.server.gen.discovery.ServerInstanceProtos; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
|
|||
import static org.thingsboard.server.utils.MiscUtils.missingProperty; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Service |
|||
@Slf4j |
|||
public class CurrentServerInstanceService implements ServerInstanceService { |
|||
|
|||
@Value("${rpc.bind_host}") |
|||
private String rpcHost; |
|||
@Value("${rpc.bind_port}") |
|||
private Integer rpcPort; |
|||
|
|||
private ServerInstance self; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
Assert.hasLength(rpcHost, missingProperty("rpc.bind_host")); |
|||
Assert.notNull(rpcPort, missingProperty("rpc.bind_port")); |
|||
|
|||
self = new ServerInstance(ServerInstanceProtos.ServerInfo.newBuilder().setHost(rpcHost).setPort(rpcPort).setTs(System.currentTimeMillis()).build()); |
|||
log.info("Current server instance: [{};{}]", self.getHost(), self.getPort()); |
|||
} |
|||
|
|||
@Override |
|||
public ServerInstance getSelf() { |
|||
return self; |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.discovery; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface DiscoveryService { |
|||
|
|||
void publishCurrentServer(); |
|||
|
|||
void unpublishCurrentServer(); |
|||
|
|||
ServerInstance getCurrentServer(); |
|||
|
|||
List<ServerInstance> getOtherServers(); |
|||
|
|||
boolean addListener(DiscoveryServiceListener listener); |
|||
|
|||
boolean removeListener(DiscoveryServiceListener listener); |
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.discovery; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface DiscoveryServiceListener { |
|||
|
|||
void onServerAdded(ServerInstance server); |
|||
|
|||
void onServerUpdated(ServerInstance server); |
|||
|
|||
void onServerRemoved(ServerInstance server); |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.discovery; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.context.annotation.DependsOn; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.service.environment.EnvironmentLogService; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Service |
|||
@ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "false", matchIfMissing = true) |
|||
@Slf4j |
|||
@DependsOn("environmentLogService") |
|||
public class DummyDiscoveryService implements DiscoveryService { |
|||
|
|||
@Autowired |
|||
private ServerInstanceService serverInstance; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
log.info("Initializing..."); |
|||
} |
|||
|
|||
@Override |
|||
public void publishCurrentServer() { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void unpublishCurrentServer() { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public ServerInstance getCurrentServer() { |
|||
return serverInstance.getSelf(); |
|||
} |
|||
|
|||
@Override |
|||
public List<ServerInstance> getOtherServers() { |
|||
return Collections.emptyList(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean addListener(DiscoveryServiceListener listener) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean removeListener(DiscoveryServiceListener listener) { |
|||
return false; |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.discovery; |
|||
|
|||
import lombok.AccessLevel; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.Getter; |
|||
import lombok.ToString; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.gen.discovery.ServerInstanceProtos.ServerInfo; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@ToString |
|||
@EqualsAndHashCode(exclude = {"serverInfo", "serverAddress"}) |
|||
public final class ServerInstance implements Comparable<ServerInstance> { |
|||
|
|||
@Getter(AccessLevel.PACKAGE) |
|||
private final ServerInfo serverInfo; |
|||
@Getter |
|||
private final String host; |
|||
@Getter |
|||
private final int port; |
|||
@Getter |
|||
private final ServerAddress serverAddress; |
|||
|
|||
public ServerInstance(ServerInfo serverInfo) { |
|||
this.serverInfo = serverInfo; |
|||
this.host = serverInfo.getHost(); |
|||
this.port = serverInfo.getPort(); |
|||
this.serverAddress = new ServerAddress(host, port); |
|||
} |
|||
|
|||
@Override |
|||
public int compareTo(ServerInstance o) { |
|||
return this.serverAddress.compareTo(o.serverAddress); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.discovery; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface ServerInstanceService { |
|||
|
|||
ServerInstance getSelf(); |
|||
} |
|||
@ -0,0 +1,207 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.discovery; |
|||
|
|||
import com.google.protobuf.InvalidProtocolBufferException; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.curator.framework.CuratorFramework; |
|||
import org.apache.curator.framework.CuratorFrameworkFactory; |
|||
import org.apache.curator.framework.recipes.cache.ChildData; |
|||
import org.apache.curator.framework.recipes.cache.PathChildrenCache; |
|||
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; |
|||
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; |
|||
import org.apache.curator.retry.RetryForever; |
|||
import org.apache.curator.utils.CloseableUtils; |
|||
import org.apache.zookeeper.CreateMode; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.boot.context.event.ApplicationReadyEvent; |
|||
import org.springframework.context.ApplicationListener; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.util.Assert; |
|||
import org.thingsboard.server.gen.discovery.ServerInstanceProtos.ServerInfo; |
|||
import org.thingsboard.server.utils.MiscUtils; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.io.IOException; |
|||
import java.util.List; |
|||
import java.util.concurrent.CopyOnWriteArrayList; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Service |
|||
@ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "true", matchIfMissing = false) |
|||
@Slf4j |
|||
public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheListener, ApplicationListener<ApplicationReadyEvent> { |
|||
|
|||
@Value("${zk.url}") |
|||
private String zkUrl; |
|||
@Value("${zk.retry_interval_ms}") |
|||
private Integer zkRetryInterval; |
|||
@Value("${zk.connection_timeout_ms}") |
|||
private Integer zkConnectionTimeout; |
|||
@Value("${zk.session_timeout_ms}") |
|||
private Integer zkSessionTimeout; |
|||
@Value("${zk.zk_dir}") |
|||
private String zkDir; |
|||
|
|||
private String zkNodesDir; |
|||
|
|||
@Autowired |
|||
private ServerInstanceService serverInstance; |
|||
|
|||
private final List<DiscoveryServiceListener> listeners = new CopyOnWriteArrayList<>(); |
|||
|
|||
private CuratorFramework client; |
|||
private PathChildrenCache cache; |
|||
private String nodePath; |
|||
|
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
log.info("Initializing..."); |
|||
Assert.hasLength(zkUrl, MiscUtils.missingProperty("zk.url")); |
|||
Assert.notNull(zkRetryInterval, MiscUtils.missingProperty("zk.retry_interval_ms")); |
|||
Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms")); |
|||
Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms")); |
|||
|
|||
log.info("Initializing discovery service using ZK connect string: {}", zkUrl); |
|||
|
|||
zkNodesDir = zkDir + "/nodes"; |
|||
try { |
|||
client = CuratorFrameworkFactory.newClient(zkUrl, zkSessionTimeout, zkConnectionTimeout, new RetryForever(zkRetryInterval)); |
|||
client.start(); |
|||
client.blockUntilConnected(); |
|||
cache = new PathChildrenCache(client, zkNodesDir, true); |
|||
cache.getListenable().addListener(this); |
|||
cache.start(); |
|||
} catch (Exception e) { |
|||
log.error("Failed to connect to ZK: {}", e.getMessage(), e); |
|||
CloseableUtils.closeQuietly(client); |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void destroy() { |
|||
unpublishCurrentServer(); |
|||
CloseableUtils.closeQuietly(client); |
|||
log.info("Stopped discovery service"); |
|||
} |
|||
|
|||
@Override |
|||
public void publishCurrentServer() { |
|||
try { |
|||
ServerInstance self = this.serverInstance.getSelf(); |
|||
log.info("[{}:{}] Creating ZK node for current instance", self.getHost(), self.getPort()); |
|||
nodePath = client.create() |
|||
.creatingParentsIfNeeded() |
|||
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", self.getServerInfo().toByteArray()); |
|||
log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath); |
|||
} catch (Exception e) { |
|||
log.error("Failed to create ZK node", e); |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void unpublishCurrentServer() { |
|||
try { |
|||
if (nodePath != null) { |
|||
client.delete().forPath(nodePath); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("Failed to delete ZK node {}", nodePath, e); |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public ServerInstance getCurrentServer() { |
|||
return serverInstance.getSelf(); |
|||
} |
|||
|
|||
@Override |
|||
public List<ServerInstance> getOtherServers() { |
|||
return cache.getCurrentData().stream() |
|||
.filter(cd -> !cd.getPath().equals(nodePath)) |
|||
.map(cd -> { |
|||
try { |
|||
return new ServerInstance(ServerInfo.parseFrom(cd.getData())); |
|||
} catch (InvalidProtocolBufferException e) { |
|||
log.error("Failed to decode ZK node", e); |
|||
throw new RuntimeException(e); |
|||
} |
|||
}) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
@Override |
|||
public boolean addListener(DiscoveryServiceListener listener) { |
|||
return listeners.add(listener); |
|||
} |
|||
|
|||
@Override |
|||
public boolean removeListener(DiscoveryServiceListener listener) { |
|||
return listeners.remove(listener); |
|||
} |
|||
|
|||
@Override |
|||
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { |
|||
publishCurrentServer(); |
|||
getOtherServers().stream().forEach( |
|||
server -> log.info("Found active server: [{}:{}]", server.getHost(), server.getPort()) |
|||
); |
|||
} |
|||
|
|||
@Override |
|||
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception { |
|||
ChildData data = pathChildrenCacheEvent.getData(); |
|||
if (data == null) { |
|||
log.debug("Ignoring {} due to empty child data", pathChildrenCacheEvent); |
|||
return; |
|||
} else if (data.getData() == null) { |
|||
log.debug("Ignoring {} due to empty child's data", pathChildrenCacheEvent); |
|||
return; |
|||
} else if (nodePath != null && nodePath.equals(data.getPath())) { |
|||
log.debug("Ignoring event about current server {}", pathChildrenCacheEvent); |
|||
return; |
|||
} |
|||
ServerInstance instance; |
|||
try { |
|||
instance = new ServerInstance(ServerInfo.parseFrom(data.getData())); |
|||
} catch (IOException e) { |
|||
log.error("Failed to decode server instance for node {}", data.getPath(), e); |
|||
throw e; |
|||
} |
|||
log.info("Processing [{}] event for [{}:{}]", pathChildrenCacheEvent.getType(), instance.getHost(), instance.getPort()); |
|||
switch (pathChildrenCacheEvent.getType()) { |
|||
case CHILD_ADDED: |
|||
listeners.stream().forEach(listener -> listener.onServerAdded(instance)); |
|||
break; |
|||
case CHILD_UPDATED: |
|||
listeners.stream().forEach(listener -> listener.onServerUpdated(instance)); |
|||
break; |
|||
case CHILD_REMOVED: |
|||
listeners.stream().forEach(listener -> listener.onServerRemoved(instance)); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.routing; |
|||
|
|||
import org.thingsboard.server.common.data.id.UUIDBased; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.service.cluster.discovery.ServerInstance; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface ClusterRoutingService { |
|||
|
|||
ServerAddress getCurrentServer(); |
|||
|
|||
Optional<ServerAddress> resolve(UUIDBased entityId); |
|||
|
|||
} |
|||
@ -0,0 +1,142 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.routing; |
|||
|
|||
import com.google.common.hash.HashCode; |
|||
import com.google.common.hash.HashFunction; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.util.Assert; |
|||
import org.thingsboard.server.common.data.id.UUIDBased; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.service.cluster.discovery.DiscoveryService; |
|||
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener; |
|||
import org.thingsboard.server.service.cluster.discovery.ServerInstance; |
|||
import org.thingsboard.server.utils.MiscUtils; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.util.Optional; |
|||
import java.util.concurrent.ConcurrentNavigableMap; |
|||
import java.util.concurrent.ConcurrentSkipListMap; |
|||
|
|||
/** |
|||
* Cluster service implementation based on consistent hash ring |
|||
*/ |
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class ConsistentClusterRoutingService implements ClusterRoutingService, DiscoveryServiceListener { |
|||
|
|||
@Autowired |
|||
private DiscoveryService discoveryService; |
|||
|
|||
@Value("${cluster.hash_function_name}") |
|||
private String hashFunctionName; |
|||
@Value("${cluster.vitrual_nodes_size}") |
|||
private Integer virtualNodesSize; |
|||
|
|||
private ServerInstance currentServer; |
|||
|
|||
private HashFunction hashFunction; |
|||
|
|||
private final ConcurrentNavigableMap<Long, ServerInstance> circle = |
|||
new ConcurrentSkipListMap<>(); |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
log.info("Initializing Cluster routing service!"); |
|||
hashFunction = MiscUtils.forName(hashFunctionName); |
|||
discoveryService.addListener(this); |
|||
this.currentServer = discoveryService.getCurrentServer(); |
|||
addNode(discoveryService.getCurrentServer()); |
|||
for (ServerInstance instance : discoveryService.getOtherServers()) { |
|||
addNode(instance); |
|||
} |
|||
logCircle(); |
|||
log.info("Cluster routing service initialized!"); |
|||
} |
|||
|
|||
@Override |
|||
public ServerAddress getCurrentServer() { |
|||
return discoveryService.getCurrentServer().getServerAddress(); |
|||
} |
|||
|
|||
@Override |
|||
public Optional<ServerAddress> resolve(UUIDBased entityId) { |
|||
Assert.notNull(entityId); |
|||
if (circle.isEmpty()) { |
|||
return Optional.empty(); |
|||
} |
|||
Long hash = hashFunction.newHasher().putLong(entityId.getId().getMostSignificantBits()) |
|||
.putLong(entityId.getId().getLeastSignificantBits()).hash().asLong(); |
|||
if (!circle.containsKey(hash)) { |
|||
ConcurrentNavigableMap<Long, ServerInstance> tailMap = |
|||
circle.tailMap(hash); |
|||
hash = tailMap.isEmpty() ? |
|||
circle.firstKey() : tailMap.firstKey(); |
|||
} |
|||
ServerInstance result = circle.get(hash); |
|||
if (!currentServer.equals(result)) { |
|||
return Optional.of(result.getServerAddress()); |
|||
} else { |
|||
return Optional.empty(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onServerAdded(ServerInstance server) { |
|||
log.debug("On server added event: {}", server); |
|||
addNode(server); |
|||
logCircle(); |
|||
} |
|||
|
|||
@Override |
|||
public void onServerUpdated(ServerInstance server) { |
|||
log.debug("Ignoring server onUpdate event: {}", server); |
|||
} |
|||
|
|||
@Override |
|||
public void onServerRemoved(ServerInstance server) { |
|||
log.debug("On server removed event: {}", server); |
|||
removeNode(server); |
|||
logCircle(); |
|||
} |
|||
|
|||
private void addNode(ServerInstance instance) { |
|||
for (int i = 0; i < virtualNodesSize; i++) { |
|||
circle.put(hash(instance, i).asLong(), instance); |
|||
} |
|||
} |
|||
|
|||
private void removeNode(ServerInstance instance) { |
|||
for (int i = 0; i < virtualNodesSize; i++) { |
|||
circle.remove(hash(instance, i).asLong()); |
|||
} |
|||
} |
|||
|
|||
private HashCode hash(ServerInstance instance, int i) { |
|||
return hashFunction.newHasher().putString(instance.getHost(), MiscUtils.UTF8).putInt(instance.getPort()).putInt(i).hash(); |
|||
} |
|||
|
|||
private void logCircle() { |
|||
log.trace("Consistent Hash Circle Start"); |
|||
circle.entrySet().stream().forEach((e) -> log.debug("{} -> {}", e.getKey(), e.getValue().getServerAddress())); |
|||
log.trace("Consistent Hash Circle End"); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,273 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.rpc; |
|||
|
|||
import com.google.protobuf.ByteString; |
|||
import io.grpc.Server; |
|||
import io.grpc.ServerBuilder; |
|||
import io.grpc.stub.StreamObserver; |
|||
import lombok.Setter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.util.SerializationUtils; |
|||
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; |
|||
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; |
|||
import org.thingsboard.server.actors.rpc.RpcSessionTellMsg; |
|||
import org.thingsboard.server.actors.service.ActorService; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; |
|||
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc; |
|||
import org.thingsboard.server.service.cluster.discovery.DiscoveryService; |
|||
import org.thingsboard.server.service.cluster.discovery.ServerInstance; |
|||
import org.thingsboard.server.service.cluster.discovery.ServerInstanceService; |
|||
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.io.IOException; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Service |
|||
@Slf4j |
|||
public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceImplBase implements ClusterRpcService { |
|||
|
|||
@Autowired |
|||
private ServerInstanceService instanceService; |
|||
|
|||
private RpcMsgListener listener; |
|||
|
|||
private Server server; |
|||
|
|||
private ServerInstance instance; |
|||
|
|||
private ConcurrentMap<UUID, RpcSessionCreationFuture> pendingSessionMap = new ConcurrentHashMap<>(); |
|||
|
|||
public void init(RpcMsgListener listener) { |
|||
this.listener = listener; |
|||
log.info("Initializing RPC service!"); |
|||
instance = instanceService.getSelf(); |
|||
server = ServerBuilder.forPort(instance.getPort()).addService(this).build(); |
|||
log.info("Going to start RPC server using port: {}", instance.getPort()); |
|||
try { |
|||
server.start(); |
|||
} catch (IOException e) { |
|||
log.error("Failed to start RPC server!", e); |
|||
throw new RuntimeException("Failed to start RPC server!"); |
|||
} |
|||
log.info("RPC service initialized!"); |
|||
} |
|||
|
|||
@Override |
|||
public void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ToRpcServerMessage> msg) { |
|||
RpcSessionCreationFuture future = pendingSessionMap.remove(msgUid); |
|||
if (future != null) { |
|||
try { |
|||
future.onMsg(msg); |
|||
} catch (InterruptedException e) { |
|||
log.warn("Failed to report created session!"); |
|||
} |
|||
} else { |
|||
log.warn("Failed to lookup pending session!"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public StreamObserver<ClusterAPIProtos.ToRpcServerMessage> handlePluginMsgs(StreamObserver<ClusterAPIProtos.ToRpcServerMessage> responseObserver) { |
|||
log.info("Processing new session."); |
|||
return createSession(new RpcSessionCreateRequestMsg(UUID.randomUUID(), null, responseObserver)); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void stop() { |
|||
if (server != null) { |
|||
log.info("Going to onStop RPC server"); |
|||
server.shutdownNow(); |
|||
try { |
|||
server.awaitTermination(); |
|||
log.info("RPC server stopped!"); |
|||
} catch (InterruptedException e) { |
|||
log.warn("Failed to onStop RPC server!"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward) { |
|||
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() |
|||
.setToDeviceActorRpcMsg(toProtoMsg(toForward)).build(); |
|||
tell(serverAddress, msg); |
|||
} |
|||
|
|||
@Override |
|||
public void tell(ServerAddress serverAddress, ToDeviceActorNotificationMsg toForward) { |
|||
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() |
|||
.setToDeviceActorNotificationRpcMsg(toProtoMsg(toForward)).build(); |
|||
tell(serverAddress, msg); |
|||
} |
|||
|
|||
@Override |
|||
public void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward) { |
|||
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() |
|||
.setToDeviceRpcRequestRpcMsg(toProtoMsg(toForward)).build(); |
|||
tell(serverAddress, msg); |
|||
} |
|||
|
|||
@Override |
|||
public void tell(ServerAddress serverAddress, ToPluginRpcResponseDeviceMsg toForward) { |
|||
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() |
|||
.setToPluginRpcResponseRpcMsg(toProtoMsg(toForward)).build(); |
|||
tell(serverAddress, msg); |
|||
} |
|||
|
|||
@Override |
|||
public void tell(ServerAddress serverAddress, ToDeviceSessionActorMsg toForward) { |
|||
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() |
|||
.setToDeviceSessionActorRpcMsg(toProtoMsg(toForward)).build(); |
|||
tell(serverAddress, msg); |
|||
} |
|||
|
|||
@Override |
|||
public void tell(PluginRpcMsg toForward) { |
|||
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() |
|||
.setToPluginRpcMsg(toProtoMsg(toForward)).build(); |
|||
tell(toForward.getRpcMsg().getServerAddress(), msg); |
|||
} |
|||
|
|||
@Override |
|||
public void broadcast(ToAllNodesMsg toForward) { |
|||
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() |
|||
.setToAllNodesRpcMsg(toProtoMsg(toForward)).build(); |
|||
listener.onMsg(new RpcBroadcastMsg(msg)); |
|||
} |
|||
|
|||
private void tell(ServerAddress serverAddress, ClusterAPIProtos.ToRpcServerMessage msg) { |
|||
listener.onMsg(new RpcSessionTellMsg(serverAddress, msg)); |
|||
} |
|||
|
|||
private StreamObserver<ClusterAPIProtos.ToRpcServerMessage> createSession(RpcSessionCreateRequestMsg msg) { |
|||
RpcSessionCreationFuture future = new RpcSessionCreationFuture(); |
|||
pendingSessionMap.put(msg.getMsgUid(), future); |
|||
listener.onMsg(msg); |
|||
try { |
|||
StreamObserver<ClusterAPIProtos.ToRpcServerMessage> observer = future.get(); |
|||
log.info("Processed new session."); |
|||
return observer; |
|||
} catch (Exception e) { |
|||
log.info("Failed to process session.", e); |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
private static ClusterAPIProtos.ToDeviceActorRpcMessage toProtoMsg(ToDeviceActorMsg msg) { |
|||
return ClusterAPIProtos.ToDeviceActorRpcMessage.newBuilder().setData( |
|||
ByteString.copyFrom(SerializationUtils.serialize(msg)) |
|||
).build(); |
|||
} |
|||
|
|||
private static ClusterAPIProtos.ToDeviceActorNotificationRpcMessage toProtoMsg(ToDeviceActorNotificationMsg msg) { |
|||
return ClusterAPIProtos.ToDeviceActorNotificationRpcMessage.newBuilder().setData( |
|||
ByteString.copyFrom(SerializationUtils.serialize(msg)) |
|||
).build(); |
|||
} |
|||
|
|||
private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestPluginMsg msg) { |
|||
ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.Builder builder = ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.newBuilder(); |
|||
ToDeviceRpcRequest request = msg.getMsg(); |
|||
|
|||
builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder() |
|||
.setTenantId(toUid(msg.getPluginTenantId().getId())) |
|||
.setPluginId(toUid(msg.getPluginId().getId())) |
|||
.build()); |
|||
|
|||
builder.setDeviceTenantId(toUid(msg.getTenantId())); |
|||
builder.setDeviceId(toUid(msg.getDeviceId())); |
|||
|
|||
builder.setMsgId(toUid(request.getId())); |
|||
builder.setOneway(request.isOneway()); |
|||
builder.setExpTime(request.getExpirationTime()); |
|||
builder.setMethod(request.getBody().getMethod()); |
|||
builder.setParams(request.getBody().getParams()); |
|||
|
|||
return builder.build(); |
|||
} |
|||
|
|||
private static ClusterAPIProtos.ToPluginRpcResponseRpcMessage toProtoMsg(ToPluginRpcResponseDeviceMsg msg) { |
|||
ClusterAPIProtos.ToPluginRpcResponseRpcMessage.Builder builder = ClusterAPIProtos.ToPluginRpcResponseRpcMessage.newBuilder(); |
|||
FromDeviceRpcResponse request = msg.getResponse(); |
|||
|
|||
builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder() |
|||
.setTenantId(toUid(msg.getPluginTenantId().getId())) |
|||
.setPluginId(toUid(msg.getPluginId().getId())) |
|||
.build()); |
|||
|
|||
builder.setMsgId(toUid(request.getId())); |
|||
request.getResponse().ifPresent(builder::setResponse); |
|||
request.getError().ifPresent(e -> builder.setError(e.name())); |
|||
|
|||
return builder.build(); |
|||
} |
|||
|
|||
private ClusterAPIProtos.ToAllNodesRpcMessage toProtoMsg(ToAllNodesMsg msg) { |
|||
return ClusterAPIProtos.ToAllNodesRpcMessage.newBuilder().setData( |
|||
ByteString.copyFrom(SerializationUtils.serialize(msg)) |
|||
).build(); |
|||
} |
|||
|
|||
|
|||
private ClusterAPIProtos.ToPluginRpcMessage toProtoMsg(PluginRpcMsg msg) { |
|||
return ClusterAPIProtos.ToPluginRpcMessage.newBuilder() |
|||
.setClazz(msg.getRpcMsg().getMsgClazz()) |
|||
.setData(ByteString.copyFrom(msg.getRpcMsg().getMsgData())) |
|||
.setAddress(ClusterAPIProtos.PluginAddress.newBuilder() |
|||
.setTenantId(toUid(msg.getPluginTenantId().getId())) |
|||
.setPluginId(toUid(msg.getPluginId().getId())) |
|||
.build() |
|||
).build(); |
|||
} |
|||
|
|||
private static ClusterAPIProtos.Uid toUid(EntityId uuid) { |
|||
return toUid(uuid.getId()); |
|||
} |
|||
|
|||
private static ClusterAPIProtos.Uid toUid(UUID uuid) { |
|||
return ClusterAPIProtos.Uid.newBuilder().setPluginUuidMsb(uuid.getMostSignificantBits()).setPluginUuidLsb( |
|||
uuid.getLeastSignificantBits()).build(); |
|||
} |
|||
|
|||
private static ClusterAPIProtos.ToDeviceSessionActorRpcMessage toProtoMsg(ToDeviceSessionActorMsg msg) { |
|||
return ClusterAPIProtos.ToDeviceSessionActorRpcMessage.newBuilder().setData( |
|||
ByteString.copyFrom(SerializationUtils.serialize(msg)) |
|||
).build(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.rpc; |
|||
|
|||
import io.grpc.stub.StreamObserver; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; |
|||
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface ClusterRpcService { |
|||
|
|||
void init(RpcMsgListener listener); |
|||
|
|||
void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward); |
|||
|
|||
void tell(ServerAddress serverAddress, ToDeviceSessionActorMsg toForward); |
|||
|
|||
void tell(ServerAddress serverAddress, ToDeviceActorNotificationMsg toForward); |
|||
|
|||
void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward); |
|||
|
|||
void tell(ServerAddress serverAddress, ToPluginRpcResponseDeviceMsg toForward); |
|||
|
|||
void tell(PluginRpcMsg toForward); |
|||
|
|||
void broadcast(ToAllNodesMsg msg); |
|||
|
|||
void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ToRpcServerMessage> inputStream); |
|||
} |
|||
@ -0,0 +1,130 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.rpc; |
|||
|
|||
import io.grpc.stub.StreamObserver; |
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
import java.io.Closeable; |
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
@Slf4j |
|||
final public class GrpcSession implements Closeable { |
|||
private final UUID sessionId; |
|||
private final boolean client; |
|||
private final GrpcSessionListener listener; |
|||
private StreamObserver<ClusterAPIProtos.ToRpcServerMessage> inputStream; |
|||
private StreamObserver<ClusterAPIProtos.ToRpcServerMessage> outputStream; |
|||
|
|||
private boolean connected; |
|||
private ServerAddress remoteServer; |
|||
|
|||
public GrpcSession(GrpcSessionListener listener) { |
|||
this(null, listener); |
|||
} |
|||
|
|||
public GrpcSession(ServerAddress remoteServer, GrpcSessionListener listener) { |
|||
this.sessionId = UUID.randomUUID(); |
|||
this.listener = listener; |
|||
if (remoteServer != null) { |
|||
this.client = true; |
|||
this.connected = true; |
|||
this.remoteServer = remoteServer; |
|||
} else { |
|||
this.client = false; |
|||
} |
|||
} |
|||
|
|||
public void initInputStream() { |
|||
this.inputStream = new StreamObserver<ClusterAPIProtos.ToRpcServerMessage>() { |
|||
@Override |
|||
public void onNext(ClusterAPIProtos.ToRpcServerMessage msg) { |
|||
if (!connected) { |
|||
if (msg.hasConnectMsg()) { |
|||
connected = true; |
|||
ClusterAPIProtos.ServerAddress rpcAddress = msg.getConnectMsg().getServerAddress(); |
|||
remoteServer = new ServerAddress(rpcAddress.getHost(), rpcAddress.getPort()); |
|||
listener.onConnected(GrpcSession.this); |
|||
} |
|||
} |
|||
if (connected) { |
|||
if (msg.hasToPluginRpcMsg()) { |
|||
listener.onToPluginRpcMsg(GrpcSession.this, msg.getToPluginRpcMsg()); |
|||
} |
|||
if (msg.hasToDeviceActorRpcMsg()) { |
|||
listener.onToDeviceActorRpcMsg(GrpcSession.this, msg.getToDeviceActorRpcMsg()); |
|||
} |
|||
if (msg.hasToDeviceSessionActorRpcMsg()) { |
|||
listener.onToDeviceSessionActorRpcMsg(GrpcSession.this, msg.getToDeviceSessionActorRpcMsg()); |
|||
} |
|||
if (msg.hasToDeviceActorNotificationRpcMsg()) { |
|||
listener.onToDeviceActorNotificationRpcMsg(GrpcSession.this, msg.getToDeviceActorNotificationRpcMsg()); |
|||
} |
|||
if (msg.hasToDeviceRpcRequestRpcMsg()) { |
|||
listener.onToDeviceRpcRequestRpcMsg(GrpcSession.this, msg.getToDeviceRpcRequestRpcMsg()); |
|||
} |
|||
if (msg.hasToPluginRpcResponseRpcMsg()) { |
|||
listener.onFromDeviceRpcResponseRpcMsg(GrpcSession.this, msg.getToPluginRpcResponseRpcMsg()); |
|||
} |
|||
if (msg.hasToAllNodesRpcMsg()) { |
|||
listener.onToAllNodesRpcMessage(GrpcSession.this, msg.getToAllNodesRpcMsg()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onError(Throwable t) { |
|||
listener.onError(GrpcSession.this, t); |
|||
} |
|||
|
|||
@Override |
|||
public void onCompleted() { |
|||
outputStream.onCompleted(); |
|||
listener.onDisconnected(GrpcSession.this); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
public void initOutputStream() { |
|||
if (client) { |
|||
listener.onConnected(GrpcSession.this); |
|||
} |
|||
} |
|||
|
|||
public void sendMsg(ClusterAPIProtos.ToRpcServerMessage msg) { |
|||
outputStream.onNext(msg); |
|||
} |
|||
|
|||
public void onError(Throwable t) { |
|||
outputStream.onError(t); |
|||
} |
|||
|
|||
@Override |
|||
public void close() { |
|||
try { |
|||
outputStream.onCompleted(); |
|||
} catch (IllegalStateException e) { |
|||
log.debug("[{}] Failed to close output stream: {}", sessionId, e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.rpc; |
|||
|
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface GrpcSessionListener { |
|||
|
|||
void onConnected(GrpcSession session); |
|||
|
|||
void onDisconnected(GrpcSession session); |
|||
|
|||
void onToPluginRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcMessage msg); |
|||
|
|||
void onToDeviceActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorRpcMessage msg); |
|||
|
|||
void onToDeviceActorNotificationRpcMsg(GrpcSession grpcSession, ClusterAPIProtos.ToDeviceActorNotificationRpcMessage msg); |
|||
|
|||
void onToDeviceSessionActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceSessionActorRpcMessage msg); |
|||
|
|||
void onToAllNodesRpcMessage(GrpcSession grpcSession, ClusterAPIProtos.ToAllNodesRpcMessage toAllNodesRpcMessage); |
|||
|
|||
void onToDeviceRpcRequestRpcMsg(GrpcSession grpcSession, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toDeviceRpcRequestRpcMsg); |
|||
|
|||
void onFromDeviceRpcResponseRpcMsg(GrpcSession grpcSession, ClusterAPIProtos.ToPluginRpcResponseRpcMessage toPluginRpcResponseRpcMsg); |
|||
|
|||
void onError(GrpcSession session, Throwable t); |
|||
|
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.rpc; |
|||
|
|||
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; |
|||
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; |
|||
import org.thingsboard.server.actors.rpc.RpcSessionTellMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; |
|||
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; |
|||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|||
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; |
|||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface RpcMsgListener { |
|||
|
|||
void onMsg(ToDeviceActorMsg msg); |
|||
|
|||
void onMsg(ToDeviceActorNotificationMsg msg); |
|||
|
|||
void onMsg(ToDeviceSessionActorMsg msg); |
|||
|
|||
void onMsg(ToAllNodesMsg nodeMsg); |
|||
|
|||
void onMsg(ToPluginActorMsg msg); |
|||
|
|||
void onMsg(RpcSessionCreateRequestMsg msg); |
|||
|
|||
void onMsg(RpcSessionTellMsg rpcSessionTellMsg); |
|||
|
|||
void onMsg(RpcBroadcastMsg rpcBroadcastMsg); |
|||
|
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.rpc; |
|||
|
|||
import io.grpc.stub.StreamObserver; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
import java.util.concurrent.*; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public class RpcSessionCreationFuture implements Future<StreamObserver<ClusterAPIProtos.ToRpcServerMessage>> { |
|||
|
|||
private final BlockingQueue<StreamObserver<ClusterAPIProtos.ToRpcServerMessage>> queue = new ArrayBlockingQueue<>(1); |
|||
|
|||
public void onMsg(StreamObserver<ClusterAPIProtos.ToRpcServerMessage> result) throws InterruptedException { |
|||
queue.put(result); |
|||
} |
|||
|
|||
@Override |
|||
public boolean cancel(boolean mayInterruptIfRunning) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean isCancelled() { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean isDone() { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public StreamObserver<ClusterAPIProtos.ToRpcServerMessage> get() throws InterruptedException, ExecutionException { |
|||
return this.queue.take(); |
|||
} |
|||
|
|||
@Override |
|||
public StreamObserver<ClusterAPIProtos.ToRpcServerMessage> get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { |
|||
StreamObserver<ClusterAPIProtos.ToRpcServerMessage> result = this.queue.poll(timeout, unit); |
|||
if (result == null) { |
|||
throw new TimeoutException(); |
|||
} else { |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,190 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.component; |
|||
|
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.google.common.base.Charsets; |
|||
import com.google.common.io.Resources; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.beans.factory.config.BeanDefinition; |
|||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; |
|||
import org.springframework.core.type.filter.AnnotationTypeFilter; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.plugin.ComponentDescriptor; |
|||
import org.thingsboard.server.common.data.plugin.ComponentType; |
|||
import org.thingsboard.server.dao.component.ComponentDescriptorService; |
|||
import org.thingsboard.server.extensions.api.component.*; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.io.IOException; |
|||
import java.lang.annotation.Annotation; |
|||
import java.util.*; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class AnnotationComponentDiscoveryService implements ComponentDiscoveryService { |
|||
|
|||
@Value("${plugins.scan_packages}") |
|||
private String[] scanPackages; |
|||
|
|||
@Autowired |
|||
private ComponentDescriptorService componentDescriptorService; |
|||
|
|||
private Map<String, ComponentDescriptor> components = new HashMap<>(); |
|||
|
|||
private Map<ComponentType, List<ComponentDescriptor>> componentsMap = new HashMap<>(); |
|||
|
|||
private ObjectMapper mapper = new ObjectMapper(); |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
registerComponents(ComponentType.FILTER, Filter.class); |
|||
|
|||
registerComponents(ComponentType.PROCESSOR, Processor.class); |
|||
|
|||
registerComponents(ComponentType.ACTION, Action.class); |
|||
|
|||
registerComponents(ComponentType.PLUGIN, Plugin.class); |
|||
|
|||
log.info("Found following definitions: {}", components.values()); |
|||
} |
|||
|
|||
private void registerComponents(ComponentType type, Class<? extends Annotation> annotation) { |
|||
List<ComponentDescriptor> components = persist(getBeanDefinitions(annotation), type); |
|||
componentsMap.put(type, components); |
|||
registerComponents(components); |
|||
} |
|||
|
|||
private void registerComponents(Collection<ComponentDescriptor> comps) { |
|||
comps.stream().forEach(c -> components.put(c.getClazz(), c)); |
|||
} |
|||
|
|||
private List<ComponentDescriptor> persist(Set<BeanDefinition> filterDefs, ComponentType type) { |
|||
List<ComponentDescriptor> result = new ArrayList<>(); |
|||
for (BeanDefinition def : filterDefs) { |
|||
ComponentDescriptor scannedComponent = new ComponentDescriptor(); |
|||
String clazzName = def.getBeanClassName(); |
|||
try { |
|||
scannedComponent.setType(type); |
|||
Class<?> clazz = Class.forName(clazzName); |
|||
String descriptorResourceName; |
|||
switch (type) { |
|||
case FILTER: |
|||
Filter filterAnnotation = clazz.getAnnotation(Filter.class); |
|||
scannedComponent.setName(filterAnnotation.name()); |
|||
scannedComponent.setScope(filterAnnotation.scope()); |
|||
descriptorResourceName = filterAnnotation.descriptor(); |
|||
break; |
|||
case PROCESSOR: |
|||
Processor processorAnnotation = clazz.getAnnotation(Processor.class); |
|||
scannedComponent.setName(processorAnnotation.name()); |
|||
scannedComponent.setScope(processorAnnotation.scope()); |
|||
descriptorResourceName = processorAnnotation.descriptor(); |
|||
break; |
|||
case ACTION: |
|||
Action actionAnnotation = clazz.getAnnotation(Action.class); |
|||
scannedComponent.setName(actionAnnotation.name()); |
|||
scannedComponent.setScope(actionAnnotation.scope()); |
|||
descriptorResourceName = actionAnnotation.descriptor(); |
|||
break; |
|||
case PLUGIN: |
|||
Plugin pluginAnnotation = clazz.getAnnotation(Plugin.class); |
|||
scannedComponent.setName(pluginAnnotation.name()); |
|||
scannedComponent.setScope(pluginAnnotation.scope()); |
|||
descriptorResourceName = pluginAnnotation.descriptor(); |
|||
for (Class<?> actionClazz : pluginAnnotation.actions()) { |
|||
ComponentDescriptor actionComponent = getComponent(actionClazz.getName()) |
|||
.orElseThrow(() -> { |
|||
log.error("Can't initialize plugin {}, due to missing action {}!", def.getBeanClassName(), actionClazz.getName()); |
|||
return new ClassNotFoundException("Action: " + actionClazz.getName() + "is missing!"); |
|||
}); |
|||
if (actionComponent.getType() != ComponentType.ACTION) { |
|||
log.error("Plugin {} action {} has wrong component type!", def.getBeanClassName(), actionClazz.getName(), actionComponent.getType()); |
|||
throw new RuntimeException("Plugin " + def.getBeanClassName() + "action " + actionClazz.getName() + " has wrong component type!"); |
|||
} |
|||
} |
|||
scannedComponent.setActions(Arrays.asList(pluginAnnotation.actions()).stream().map(action -> action.getName()).collect(Collectors.joining(","))); |
|||
break; |
|||
default: |
|||
throw new RuntimeException(type + " is not supported yet!"); |
|||
} |
|||
scannedComponent.setConfigurationDescriptor(mapper.readTree( |
|||
Resources.toString(Resources.getResource(descriptorResourceName), Charsets.UTF_8))); |
|||
scannedComponent.setClazz(clazzName); |
|||
log.info("Processing scanned component: {}", scannedComponent); |
|||
} catch (Exception e) { |
|||
log.error("Can't initialize component {}, due to {}", def.getBeanClassName(), e.getMessage(), e); |
|||
throw new RuntimeException(e); |
|||
} |
|||
ComponentDescriptor persistedComponent = componentDescriptorService.findByClazz(clazzName); |
|||
if (persistedComponent == null) { |
|||
log.info("Persisting new component: {}", scannedComponent); |
|||
scannedComponent = componentDescriptorService.saveComponent(scannedComponent); |
|||
} else if (scannedComponent.equals(persistedComponent)) { |
|||
log.info("Component is already persisted: {}", persistedComponent); |
|||
scannedComponent = persistedComponent; |
|||
} else { |
|||
log.info("Component {} will be updated to {}", persistedComponent, scannedComponent); |
|||
componentDescriptorService.deleteByClazz(persistedComponent.getClazz()); |
|||
scannedComponent.setId(persistedComponent.getId()); |
|||
scannedComponent = componentDescriptorService.saveComponent(scannedComponent); |
|||
} |
|||
result.add(scannedComponent); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
private Set<BeanDefinition> getBeanDefinitions(Class<? extends Annotation> componentType) { |
|||
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); |
|||
scanner.addIncludeFilter(new AnnotationTypeFilter(componentType)); |
|||
Set<BeanDefinition> defs = new HashSet<>(); |
|||
for (String scanPackage : scanPackages) { |
|||
defs.addAll(scanner.findCandidateComponents(scanPackage)); |
|||
} |
|||
return defs; |
|||
} |
|||
|
|||
@Override |
|||
public List<ComponentDescriptor> getComponents(ComponentType type) { |
|||
return Collections.unmodifiableList(componentsMap.get(type)); |
|||
} |
|||
|
|||
@Override |
|||
public Optional<ComponentDescriptor> getComponent(String clazz) { |
|||
return Optional.ofNullable(components.get(clazz)); |
|||
} |
|||
|
|||
@Override |
|||
public List<ComponentDescriptor> getPluginActions(String pluginClazz) { |
|||
Optional<ComponentDescriptor> pluginOpt = getComponent(pluginClazz); |
|||
if (pluginOpt.isPresent()) { |
|||
ComponentDescriptor plugin = pluginOpt.get(); |
|||
if (ComponentType.PLUGIN != plugin.getType()) { |
|||
throw new IllegalArgumentException(pluginClazz + " is not a plugin!"); |
|||
} |
|||
List<ComponentDescriptor> result = new ArrayList<>(); |
|||
for (String action : plugin.getActions().split(",")) { |
|||
getComponent(action).ifPresent(v -> result.add(v)); |
|||
} |
|||
return result; |
|||
} else { |
|||
throw new IllegalArgumentException(pluginClazz + " is not a component!"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.component; |
|||
|
|||
import org.thingsboard.server.common.data.plugin.ComponentDescriptor; |
|||
import org.thingsboard.server.common.data.plugin.ComponentType; |
|||
|
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface ComponentDiscoveryService { |
|||
|
|||
List<ComponentDescriptor> getComponents(ComponentType type); |
|||
|
|||
Optional<ComponentDescriptor> getComponent(String clazz); |
|||
|
|||
List<ComponentDescriptor> getPluginActions(String pluginClazz); |
|||
|
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.utils; |
|||
|
|||
import com.google.common.hash.HashFunction; |
|||
import com.google.common.hash.Hashing; |
|||
|
|||
import java.nio.charset.Charset; |
|||
|
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public class MiscUtils { |
|||
|
|||
public static final Charset UTF8 = Charset.forName("UTF-8"); |
|||
|
|||
public static String missingProperty(String propertyName) { |
|||
return "The " + propertyName + " property need to be set!"; |
|||
} |
|||
|
|||
public static HashFunction forName(String name) { |
|||
switch (name) { |
|||
case "murmur3_32": |
|||
return Hashing.murmur3_32(); |
|||
case "murmur3_128": |
|||
return Hashing.murmur3_128(); |
|||
case "crc32": |
|||
return Hashing.crc32(); |
|||
case "md5": |
|||
return Hashing.md5(); |
|||
default: |
|||
throw new IllegalArgumentException("Can't find hash function with name " + name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
syntax = "proto3"; |
|||
package cluster; |
|||
|
|||
option java_package = "org.thingsboard.server.gen.cluster"; |
|||
option java_outer_classname = "ClusterAPIProtos"; |
|||
|
|||
message ServerAddress { |
|||
string host = 1; |
|||
int32 port = 2; |
|||
} |
|||
|
|||
message Uid { |
|||
sint64 pluginUuidMsb = 1; |
|||
sint64 pluginUuidLsb = 2; |
|||
} |
|||
|
|||
message PluginAddress { |
|||
Uid pluginId = 1; |
|||
Uid tenantId = 2; |
|||
} |
|||
|
|||
message ToPluginRpcMessage { |
|||
PluginAddress address = 1; |
|||
int32 clazz = 2; |
|||
bytes data = 3; |
|||
} |
|||
|
|||
message ToDeviceActorRpcMessage { |
|||
bytes data = 1; |
|||
} |
|||
|
|||
message ToDeviceSessionActorRpcMessage { |
|||
bytes data = 1; |
|||
} |
|||
|
|||
message ToDeviceActorNotificationRpcMessage { |
|||
bytes data = 1; |
|||
} |
|||
|
|||
message ToAllNodesRpcMessage { |
|||
bytes data = 1; |
|||
} |
|||
|
|||
message ConnectRpcMessage { |
|||
ServerAddress serverAddress = 1; |
|||
} |
|||
|
|||
message ToDeviceRpcRequestRpcMessage { |
|||
PluginAddress address = 1; |
|||
Uid deviceTenantId = 2; |
|||
Uid deviceId = 3; |
|||
|
|||
Uid msgId = 4; |
|||
bool oneway = 5; |
|||
int64 expTime = 6; |
|||
string method = 7; |
|||
string params = 8; |
|||
} |
|||
|
|||
message ToPluginRpcResponseRpcMessage { |
|||
PluginAddress address = 1; |
|||
|
|||
Uid msgId = 2; |
|||
string response = 3; |
|||
string error = 4; |
|||
} |
|||
|
|||
message ToRpcServerMessage { |
|||
ConnectRpcMessage connectMsg = 1; |
|||
ToPluginRpcMessage toPluginRpcMsg = 2; |
|||
ToDeviceActorRpcMessage toDeviceActorRpcMsg = 3; |
|||
ToDeviceSessionActorRpcMessage toDeviceSessionActorRpcMsg = 4; |
|||
ToDeviceActorNotificationRpcMessage toDeviceActorNotificationRpcMsg = 5; |
|||
ToAllNodesRpcMessage toAllNodesRpcMsg = 6; |
|||
ToDeviceRpcRequestRpcMessage toDeviceRpcRequestRpcMsg = 7; |
|||
ToPluginRpcResponseRpcMessage toPluginRpcResponseRpcMsg = 8; |
|||
} |
|||
|
|||
service ClusterRpcService { |
|||
rpc handlePluginMsgs(stream ToRpcServerMessage) returns (stream ToRpcServerMessage) {} |
|||
} |
|||
|
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
syntax = "proto3"; |
|||
package discovery; |
|||
|
|||
option java_package = "org.thingsboard.server.gen.discovery"; |
|||
option java_outer_classname = "ServerInstanceProtos"; |
|||
|
|||
message ServerInfo { |
|||
string host = 1; |
|||
int32 port = 2; |
|||
int64 ts = 3; |
|||
} |
|||
@ -0,0 +1,163 @@ |
|||
# |
|||
# Copyright © 2016 The Thingsboard Authors |
|||
# |
|||
# Licensed under the Apache License, Version 2.0 (the "License"); |
|||
# you may not use this file except in compliance with the License. |
|||
# You may obtain a copy of the License at |
|||
# |
|||
# http://www.apache.org/licenses/LICENSE-2.0 |
|||
# |
|||
# Unless required by applicable law or agreed to in writing, software |
|||
# distributed under the License is distributed on an "AS IS" BASIS, |
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
# See the License for the specific language governing permissions and |
|||
# limitations under the License. |
|||
# |
|||
|
|||
|
|||
akka { |
|||
# JVM shutdown, System.exit(-1), in case of a fatal error, |
|||
# such as OutOfMemoryError |
|||
jvm-exit-on-fatal-error = off |
|||
loglevel = "INFO" |
|||
loggers = ["akka.event.slf4j.Slf4jLogger"] |
|||
} |
|||
|
|||
# This dispatcher is used for app |
|||
app-dispatcher { |
|||
type = Dispatcher |
|||
executor = "fork-join-executor" |
|||
fork-join-executor { |
|||
# Min number of threads to cap factor-based parallelism number to |
|||
parallelism-min = 2 |
|||
# Max number of threads to cap factor-based parallelism number to |
|||
parallelism-max = 12 |
|||
|
|||
# The parallelism factor is used to determine thread pool size using the |
|||
# following formula: ceil(available processors * factor). Resulting size |
|||
# is then bounded by the parallelism-min and parallelism-max values. |
|||
parallelism-factor = 1.0 |
|||
} |
|||
# How long time the dispatcher will wait for new actors until it shuts down |
|||
shutdown-timeout = 1s |
|||
|
|||
# Throughput defines the number of messages that are processed in a batch |
|||
# before the thread is returned to the pool. Set to 1 for as fair as possible. |
|||
throughput = 5 |
|||
} |
|||
|
|||
# This dispatcher is used for rpc actors |
|||
rpc-dispatcher { |
|||
type = Dispatcher |
|||
executor = "fork-join-executor" |
|||
fork-join-executor { |
|||
# Min number of threads to cap factor-based parallelism number to |
|||
parallelism-min = 2 |
|||
# Max number of threads to cap factor-based parallelism number to |
|||
parallelism-max = 12 |
|||
|
|||
# The parallelism factor is used to determine thread pool size using the |
|||
# following formula: ceil(available processors * factor). Resulting size |
|||
# is then bounded by the parallelism-min and parallelism-max values. |
|||
parallelism-factor = 0.5 |
|||
} |
|||
# How long time the dispatcher will wait for new actors until it shuts down |
|||
shutdown-timeout = 1s |
|||
|
|||
# Throughput defines the number of messages that are processed in a batch |
|||
# before the thread is returned to the pool. Set to 1 for as fair as possible. |
|||
throughput = 5 |
|||
} |
|||
|
|||
# This dispatcher is used for auth |
|||
core-dispatcher { |
|||
type = Dispatcher |
|||
executor = "fork-join-executor" |
|||
fork-join-executor { |
|||
# Min number of threads to cap factor-based parallelism number to |
|||
parallelism-min = 2 |
|||
# Max number of threads to cap factor-based parallelism number to |
|||
parallelism-max = 12 |
|||
|
|||
# The parallelism factor is used to determine thread pool size using the |
|||
# following formula: ceil(available processors * factor). Resulting size |
|||
# is then bounded by the parallelism-min and parallelism-max values. |
|||
parallelism-factor = 1.0 |
|||
} |
|||
# How long time the dispatcher will wait for new actors until it shuts down |
|||
shutdown-timeout = 1s |
|||
|
|||
# Throughput defines the number of messages that are processed in a batch |
|||
# before the thread is returned to the pool. Set to 1 for as fair as possible. |
|||
throughput = 5 |
|||
} |
|||
|
|||
# This dispatcher is used for rule actors |
|||
rule-dispatcher { |
|||
type = Dispatcher |
|||
executor = "fork-join-executor" |
|||
fork-join-executor { |
|||
# Min number of threads to cap factor-based parallelism number to |
|||
parallelism-min = 2 |
|||
# Max number of threads to cap factor-based parallelism number to |
|||
parallelism-max = 12 |
|||
|
|||
# The parallelism factor is used to determine thread pool size using the |
|||
# following formula: ceil(available processors * factor). Resulting size |
|||
# is then bounded by the parallelism-min and parallelism-max values. |
|||
parallelism-factor = 1.0 |
|||
} |
|||
# How long time the dispatcher will wait for new actors until it shuts down |
|||
shutdown-timeout = 1s |
|||
|
|||
# Throughput defines the number of messages that are processed in a batch |
|||
# before the thread is returned to the pool. Set to 1 for as fair as possible. |
|||
throughput = 5 |
|||
} |
|||
|
|||
# This dispatcher is used for rule actors |
|||
plugin-dispatcher { |
|||
type = Dispatcher |
|||
executor = "fork-join-executor" |
|||
fork-join-executor { |
|||
# Min number of threads to cap factor-based parallelism number to |
|||
parallelism-min = 2 |
|||
# Max number of threads to cap factor-based parallelism number to |
|||
parallelism-max = 12 |
|||
|
|||
# The parallelism factor is used to determine thread pool size using the |
|||
# following formula: ceil(available processors * factor). Resulting size |
|||
# is then bounded by the parallelism-min and parallelism-max values. |
|||
parallelism-factor = 1.0 |
|||
} |
|||
# How long time the dispatcher will wait for new actors until it shuts down |
|||
shutdown-timeout = 1s |
|||
|
|||
# Throughput defines the number of messages that are processed in a batch |
|||
# before the thread is returned to the pool. Set to 1 for as fair as possible. |
|||
throughput = 5 |
|||
} |
|||
|
|||
|
|||
# This dispatcher is used for rule actors |
|||
session-dispatcher { |
|||
type = Dispatcher |
|||
executor = "fork-join-executor" |
|||
fork-join-executor { |
|||
# Min number of threads to cap factor-based parallelism number to |
|||
parallelism-min = 2 |
|||
# Max number of threads to cap factor-based parallelism number to |
|||
parallelism-max = 12 |
|||
|
|||
# The parallelism factor is used to determine thread pool size using the |
|||
# following formula: ceil(available processors * factor). Resulting size |
|||
# is then bounded by the parallelism-min and parallelism-max values. |
|||
parallelism-factor = 1.0 |
|||
} |
|||
# How long time the dispatcher will wait for new actors until it shuts down |
|||
shutdown-timeout = 1s |
|||
|
|||
# Throughput defines the number of messages that are processed in a batch |
|||
# before the thread is returned to the pool. Set to 1 for as fair as possible. |
|||
throughput = 5 |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
=================================================== |
|||
:: ${application.title} :: ${application.formatted-version} |
|||
=================================================== |
|||
@ -0,0 +1,5 @@ |
|||
test.message.subject=Test message from Thingsboard |
|||
activation.subject=Your account activation on Thingsboard |
|||
account.activated.subject=Thingsboard - your account has been activated |
|||
reset.password.subject=Thingsboard - Password reset has been requested |
|||
password.was.reset.subject=Thingsboard - your account password has been reset |
|||
@ -0,0 +1,35 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<!-- |
|||
|
|||
Copyright © 2016 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<!DOCTYPE configuration> |
|||
<configuration> |
|||
|
|||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> |
|||
<encoder> |
|||
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> |
|||
</encoder> |
|||
</appender> |
|||
|
|||
<logger name="org.thingsboard.server" level="TRACE" /> |
|||
<logger name="akka" level="INFO" /> |
|||
|
|||
<root level="INFO"> |
|||
<appender-ref ref="STDOUT"/> |
|||
</root> |
|||
|
|||
</configuration> |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue