Browse Source

Merge with 2.5.1

pull/2436/head
deaflynx 6 years ago
parent
commit
3b1e1416fb
  1. 298
      application/pom.xml
  2. 248
      application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql
  3. 113
      application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql
  4. 5
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  5. 9
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  6. 2
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  7. 7
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  8. 61
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  9. 20
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  10. 3
      application/src/main/java/org/thingsboard/server/controller/CustomerController.java
  11. 21
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  12. 45
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  13. 12
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  14. 45
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  15. 13
      application/src/main/java/org/thingsboard/server/controller/RpcController.java
  16. 23
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  17. 4
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  18. 12
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  19. 9
      application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
  20. 8
      application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java
  21. 9
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  22. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  23. 20
      application/src/main/java/org/thingsboard/server/service/edge/rpc/init/DefaultInitEdgeService.java
  24. 3
      application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java
  25. 12
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
  26. 2
      application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java
  27. 1
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  28. 4
      application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseSchemaService.java
  29. 114
      application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java
  30. 9
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  31. 87
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java
  32. 15
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  33. 45
      application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java
  34. 59
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  35. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java
  36. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java
  37. 24
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  38. 9
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  39. 18
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  40. 2
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  41. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscriptionScope.java
  42. 4
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  43. 25
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  44. 23
      application/src/main/resources/thingsboard.yml
  45. 9
      application/src/main/scripts/control/rpm/postinst
  46. 24
      application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
  47. 12
      application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java
  48. 304
      application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java
  49. 14
      application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
  50. 11
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
  51. 10
      application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
  52. 480
      application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
  53. 14
      application/src/test/java/org/thingsboard/server/controller/BaseWidgetTypeControllerTest.java
  54. 11
      application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java
  55. 50
      application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java
  56. 2
      common/dao-api/pom.xml
  57. 8
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
  58. 11
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  59. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java
  60. 10
      common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
  61. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2User.java
  62. 2
      common/data/pom.xml
  63. 13
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  64. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
  65. 15
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  66. 3
      common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java
  67. 2
      common/edge-api/pom.xml
  68. 2
      common/message/pom.xml
  69. 30
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  70. 1
      common/message/src/main/proto/tbmsg.proto
  71. 2
      common/pom.xml
  72. 2
      common/queue/pom.xml
  73. 2
      common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusConsumerTemplate.java
  74. 7
      common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java
  75. 9
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  76. 8
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java
  77. 2
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java
  78. 2
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java
  79. 2
      common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubConsumerTemplate.java
  80. 2
      common/transport/coap/pom.xml
  81. 2
      common/transport/http/pom.xml
  82. 2
      common/transport/mqtt/pom.xml
  83. 2
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  84. 2
      common/transport/pom.xml
  85. 6
      common/transport/transport-api/pom.xml
  86. 2
      common/util/pom.xml
  87. 2
      dao/pom.xml
  88. 16
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  89. 16
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
  90. 65
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  91. 44
      dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java
  92. 10
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  93. 42
      dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java
  94. 14
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
  95. 64
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  96. 12
      dao/src/main/java/org/thingsboard/server/dao/edge/CassandraEdgeDao.java
  97. 10
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java
  98. 101
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
  99. 12
      dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
  100. 44
      dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java

298
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>application</artifactId> <artifactId>application</artifactId>
@ -34,10 +34,15 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/..</main.dir> <main.dir>${basedir}/..</main.dir>
<pkg.type>java</pkg.type>
<pkg.disabled>false</pkg.disabled>
<pkg.process-resources.phase>process-resources</pkg.process-resources.phase>
<pkg.package.phase>package</pkg.package.phase>
<pkg.name>thingsboard</pkg.name> <pkg.name>thingsboard</pkg.name>
<pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<pkg.win.dist>${project.build.directory}/windows</pkg.win.dist> <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist>
<pkg.copyInstallScripts>true</pkg.copyInstallScripts>
<pkg.implementationTitle>ThingsBoard</pkg.implementationTitle>
<pkg.mainClass>org.thingsboard.server.ThingsboardServerApplication</pkg.mainClass>
</properties> </properties>
<dependencies> <dependencies>
@ -311,6 +316,10 @@
</resource> </resource>
</resources> </resources>
<plugins> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
@ -327,313 +336,30 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId> <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>
<filters>
<filter>src/main/filters/unix.properties</filter>
</filters>
</configuration>
</execution>
<execution>
<id>copy-win-conf</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${pkg.win.dist}/conf</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>logback.xml</exclude>
</excludes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/conf</directory>
<excludes>
<exclude>thingsboard.conf</exclude>
</excludes>
<filtering>true</filtering>
</resource>
</resources>
<filters>
<filter>src/main/filters/windows.properties</filter>
</filters>
</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>
<filters>
<filter>src/main/filters/unix.properties</filter>
</filters>
</configuration>
</execution>
<execution>
<id>copy-install</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/bin/install</outputDirectory>
<resources>
<resource>
<directory>src/main/scripts/install</directory>
<includes>
<include>**/*.sh</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<filters>
<filter>src/main/filters/unix.properties</filter>
</filters>
</configuration>
</execution>
<execution>
<id>copy-windows-control</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${pkg.win.dist}</outputDirectory>
<resources>
<resource>
<directory>src/main/scripts/windows</directory>
<filtering>true</filtering>
</resource>
</resources>
<filters>
<filter>src/main/filters/windows.properties</filter>
</filters>
</configuration>
</execution>
<execution>
<id>copy-windows-install</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${pkg.win.dist}/install</outputDirectory>
<resources>
<resource>
<directory>src/main/scripts/install</directory>
<includes>
<include>logback.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<filters>
<filter>src/main/filters/windows.properties</filter>
</filters>
</configuration>
</execution>
<execution>
<id>copy-data</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/data</outputDirectory>
<resources>
<resource>
<directory>src/main/data</directory>
</resource>
<resource>
<directory>../dao/src/main/resources</directory>
<includes>
<include>**/*.cql</include>
<include>**/*.sql</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId> <artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-winsw-service</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>com.sun.winsw</groupId>
<artifactId>winsw</artifactId>
<classifier>bin</classifier>
<type>exe</type>
<destFileName>service.exe</destFileName>
</artifactItem>
</artifactItems>
<outputDirectory>${pkg.win.dist}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/logback.xml</exclude>
</excludes>
<archive>
<manifestEntries>
<Implementation-Title>ThingsBoard</Implementation-Title>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>org.thingsboard.server.ThingsboardServerApplication</mainClass>
<classifier>boot</classifier>
<layout>ZIP</layout>
<executable>true</executable>
<excludeDevtools>true</excludeDevtools>
<embeddedLaunchScriptProperties>
<confFolder>${pkg.installFolder}/conf</confFolder>
<logFolder>${pkg.unixLogFolder}</logFolder>
<logFilename>${pkg.name}.out</logFilename>
<initInfoProvides>${pkg.name}</initInfoProvides>
</embeddedLaunchScriptProperties>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<artifactId>gradle-maven-plugin</artifactId> <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}-boot.${project.packaging}
</arg>
<arg>-PpkgName=${pkg.name}</arg>
<arg>-PpkgInstallFolder=${pkg.installFolder}</arg>
<arg>-PpkgLogFolder=${pkg.unixLogFolder}</arg>
</args>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>invoke</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId> <artifactId>maven-assembly-plugin</artifactId>
<configuration>
<finalName>${pkg.name}</finalName>
<descriptors>
<descriptor>src/main/assembly/windows.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId> <artifactId>maven-install-plugin</artifactId>
<configuration>
<file>${project.build.directory}/${pkg.name}.deb</file>
<artifactId>${project.artifactId}</artifactId>
<groupId>${project.groupId}</groupId>
<version>${project.version}</version>
<classifier>deb</classifier>
<packaging>deb</packaging>
</configuration>
<executions>
<execution>
<id>install-deb</id>
<phase>package</phase>
<goals>
<goal>install-file</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.xolstice.maven.plugins</groupId> <groupId>org.xolstice.maven.plugins</groupId>

248
application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql

@ -16,50 +16,85 @@
-- call create_partition_ts_kv_table(); -- call create_partition_ts_kv_table();
CREATE OR REPLACE PROCEDURE create_partition_ts_kv_table() LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE create_partition_ts_kv_table()
LANGUAGE plpgsql AS
$$
BEGIN BEGIN
ALTER TABLE ts_kv ALTER TABLE ts_kv
RENAME TO ts_kv_old; DROP CONSTRAINT IF EXISTS ts_kv_unq_key;
ALTER TABLE ts_kv_old ALTER TABLE ts_kv
RENAME CONSTRAINT ts_kv_pkey TO ts_kv_pkey_old; DROP CONSTRAINT IF EXISTS ts_kv_pkey;
CREATE TABLE IF NOT EXISTS ts_kv ALTER TABLE ts_kv
( ADD CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_type, entity_id, key, ts);
LIKE ts_kv_old ALTER TABLE ts_kv
) RENAME TO ts_kv_old;
PARTITION BY RANGE (ts); ALTER TABLE ts_kv_old
ALTER TABLE ts_kv RENAME CONSTRAINT ts_kv_pkey TO ts_kv_pkey_old;
DROP COLUMN entity_type; CREATE TABLE IF NOT EXISTS ts_kv
ALTER TABLE ts_kv (
ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; LIKE ts_kv_old
ALTER TABLE ts_kv )
ALTER COLUMN key TYPE integer USING key::integer; PARTITION BY RANGE (ts);
ALTER TABLE ts_kv ALTER TABLE ts_kv
ADD CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts); DROP COLUMN entity_type;
ALTER TABLE ts_kv
ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid;
ALTER TABLE ts_kv
ALTER COLUMN key TYPE integer USING key::integer;
ALTER TABLE ts_kv
ADD CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts);
CREATE TABLE IF NOT EXISTS ts_kv_indefinite PARTITION OF ts_kv DEFAULT;
END; END;
$$; $$;
-- call create_new_ts_kv_latest_table(); -- call create_new_ts_kv_latest_table();
CREATE OR REPLACE PROCEDURE create_new_ts_kv_latest_table() LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE create_new_ts_kv_latest_table()
LANGUAGE plpgsql AS
$$
BEGIN BEGIN
ALTER TABLE ts_kv_latest IF NOT EXISTS(SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = 'ts_kv_latest_old') THEN
RENAME TO ts_kv_latest_old; ALTER TABLE ts_kv_latest
ALTER TABLE ts_kv_latest_old DROP CONSTRAINT IF EXISTS ts_kv_latest_unq_key;
RENAME CONSTRAINT ts_kv_latest_pkey TO ts_kv_latest_pkey_old; ALTER TABLE ts_kv_latest
CREATE TABLE IF NOT EXISTS ts_kv_latest DROP CONSTRAINT IF EXISTS ts_kv_latest_pkey;
( ALTER TABLE ts_kv_latest
LIKE ts_kv_latest_old ADD CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_type, entity_id, key);
); ALTER TABLE ts_kv_latest
ALTER TABLE ts_kv_latest RENAME TO ts_kv_latest_old;
DROP COLUMN entity_type; ALTER TABLE ts_kv_latest_old
ALTER TABLE ts_kv_latest RENAME CONSTRAINT ts_kv_latest_pkey TO ts_kv_latest_pkey_old;
ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; CREATE TABLE IF NOT EXISTS ts_kv_latest
ALTER TABLE ts_kv_latest (
ALTER COLUMN key TYPE integer USING key::integer; LIKE ts_kv_latest_old
ALTER TABLE ts_kv_latest );
ADD CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key); ALTER TABLE ts_kv_latest
DROP COLUMN entity_type;
ALTER TABLE ts_kv_latest
ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid;
ALTER TABLE ts_kv_latest
ALTER COLUMN key TYPE integer USING key::integer;
ALTER TABLE ts_kv_latest
ADD CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key);
ELSE
RAISE NOTICE 'ts_kv_latest_old table already exists!';
IF NOT EXISTS(SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = 'ts_kv_latest') THEN
CREATE TABLE IF NOT EXISTS ts_kv_latest
(
entity_id uuid NOT NULL,
key int NOT NULL,
ts bigint NOT NULL,
bool_v boolean,
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
json_v json,
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key)
);
END IF;
END IF;
END; END;
$$; $$;
@ -93,8 +128,9 @@ BEGIN
RETURN QUERY SELECT SUBSTRING(year_date.year, 1, 4) AS partition_date, RETURN QUERY SELECT SUBSTRING(year_date.year, 1, 4) AS partition_date,
(extract(epoch from (year_date.year)::timestamp) * 1000)::bigint AS from_ts, (extract(epoch from (year_date.year)::timestamp) * 1000)::bigint AS from_ts,
(extract(epoch from (year_date.year::date + INTERVAL '1 YEAR')::timestamp) * (extract(epoch from (year_date.year::date + INTERVAL '1 YEAR')::timestamp) *
1000)::bigint AS to_ts 1000)::bigint AS to_ts
FROM (SELECT DISTINCT TO_CHAR(TO_TIMESTAMP(ts / 1000), 'YYYY_01_01') AS year FROM ts_kv_old) AS year_date; FROM (SELECT DISTINCT TO_CHAR(TO_TIMESTAMP(ts / 1000), 'YYYY_01_01') AS year
FROM ts_kv_old) AS year_date;
ELSE ELSE
RAISE EXCEPTION 'Failed to parse partitioning property: % !', partition_type; RAISE EXCEPTION 'Failed to parse partitioning property: % !', partition_type;
END CASE; END CASE;
@ -103,13 +139,16 @@ $$ LANGUAGE plpgsql;
-- call create_partitions(); -- call create_partitions();
CREATE OR REPLACE PROCEDURE create_partitions(IN partition_type varchar) LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE create_partitions(IN partition_type varchar)
LANGUAGE plpgsql AS
$$
DECLARE DECLARE
partition_date varchar; partition_date varchar;
from_ts bigint; from_ts bigint;
to_ts bigint; to_ts bigint;
partitions_cursor CURSOR FOR SELECT * FROM get_partitions_data(partition_type); partitions_cursor CURSOR FOR SELECT *
FROM get_partitions_data(partition_type);
BEGIN BEGIN
OPEN partitions_cursor; OPEN partitions_cursor;
LOOP LOOP
@ -127,21 +166,25 @@ $$;
-- call create_ts_kv_dictionary_table(); -- call create_ts_kv_dictionary_table();
CREATE OR REPLACE PROCEDURE create_ts_kv_dictionary_table() LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE create_ts_kv_dictionary_table()
LANGUAGE plpgsql AS
$$
BEGIN BEGIN
CREATE TABLE IF NOT EXISTS ts_kv_dictionary CREATE TABLE IF NOT EXISTS ts_kv_dictionary
( (
key varchar(255) NOT NULL, key varchar(255) NOT NULL,
key_id serial UNIQUE, key_id serial UNIQUE,
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
); );
END; END;
$$; $$;
-- call insert_into_dictionary(); -- call insert_into_dictionary();
CREATE OR REPLACE PROCEDURE insert_into_dictionary() LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE insert_into_dictionary()
LANGUAGE plpgsql AS
$$
DECLARE DECLARE
insert_record RECORD; insert_record RECORD;
@ -164,31 +207,89 @@ BEGIN
END; END;
$$; $$;
-- call insert_into_ts_kv(); CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS
$$
BEGIN
uuid_id := substring(entity_id, 8, 8) || '-' || substring(entity_id, 4, 4) || '-1' || substring(entity_id, 1, 3) ||
'-' || substring(entity_id, 16, 4) || '-' || substring(entity_id, 20, 12);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE insert_into_ts_kv() LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE insert_into_ts_kv(IN path_to_file varchar)
DECLARE LANGUAGE plpgsql AS
insert_size CONSTANT integer := 10000; $$
insert_counter integer DEFAULT 0; BEGIN
insert_record RECORD; EXECUTE format('COPY (SELECT to_uuid(entity_id) AS entity_id,
insert_cursor CURSOR FOR SELECT CONCAT(entity_id_uuid_first_part, '-', entity_id_uuid_second_part, '-1', entity_id_uuid_third_part, '-', entity_id_uuid_fourth_part, '-', entity_id_uuid_fifth_part)::uuid AS entity_id,
ts_kv_records.key AS key, ts_kv_records.key AS key,
ts_kv_records.ts AS ts, ts_kv_records.ts AS ts,
ts_kv_records.bool_v AS bool_v, ts_kv_records.bool_v AS bool_v,
ts_kv_records.str_v AS str_v, ts_kv_records.str_v AS str_v,
ts_kv_records.long_v AS long_v, ts_kv_records.long_v AS long_v,
ts_kv_records.dbl_v AS dbl_v ts_kv_records.dbl_v AS dbl_v
FROM (SELECT SUBSTRING(entity_id, 8, 8) AS entity_id_uuid_first_part, FROM (SELECT entity_id AS entity_id,
SUBSTRING(entity_id, 4, 4) AS entity_id_uuid_second_part, key_id AS key,
SUBSTRING(entity_id, 1, 3) AS entity_id_uuid_third_part, ts,
SUBSTRING(entity_id, 16, 4) AS entity_id_uuid_fourth_part, bool_v,
SUBSTRING(entity_id, 20) AS entity_id_uuid_fifth_part, str_v,
long_v,
dbl_v
FROM ts_kv_old
INNER JOIN ts_kv_dictionary ON (ts_kv_old.key = ts_kv_dictionary.key)) AS ts_kv_records) TO %L;',
path_to_file);
EXECUTE format('COPY ts_kv FROM %L', path_to_file);
END
$$;
-- call insert_into_ts_kv_latest();
CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest(IN path_to_file varchar)
LANGUAGE plpgsql AS
$$
BEGIN
EXECUTE format('COPY (SELECT to_uuid(entity_id) AS entity_id,
ts_kv_latest_records.key AS key,
ts_kv_latest_records.ts AS ts,
ts_kv_latest_records.bool_v AS bool_v,
ts_kv_latest_records.str_v AS str_v,
ts_kv_latest_records.long_v AS long_v,
ts_kv_latest_records.dbl_v AS dbl_v
FROM (SELECT entity_id AS entity_id,
key_id AS key, key_id AS key,
ts, ts,
bool_v, bool_v,
str_v, str_v,
long_v, long_v,
dbl_v dbl_v
FROM ts_kv_latest_old
INNER JOIN ts_kv_dictionary ON (ts_kv_latest_old.key = ts_kv_dictionary.key)) AS ts_kv_latest_records) TO %L;',
path_to_file);
EXECUTE format('COPY ts_kv_latest FROM %L', path_to_file);
END;
$$;
-- call insert_into_ts_kv_cursor();
CREATE OR REPLACE PROCEDURE insert_into_ts_kv_cursor()
LANGUAGE plpgsql AS
$$
DECLARE
insert_size CONSTANT integer := 10000;
insert_counter integer DEFAULT 0;
insert_record RECORD;
insert_cursor CURSOR FOR SELECT to_uuid(entity_id) AS entity_id,
ts_kv_records.key AS key,
ts_kv_records.ts AS ts,
ts_kv_records.bool_v AS bool_v,
ts_kv_records.str_v AS str_v,
ts_kv_records.long_v AS long_v,
ts_kv_records.dbl_v AS dbl_v
FROM (SELECT entity_id AS entity_id,
key_id AS key,
ts,
bool_v,
str_v,
long_v,
dbl_v
FROM ts_kv_old FROM ts_kv_old
INNER JOIN ts_kv_dictionary ON (ts_kv_old.key = ts_kv_dictionary.key)) AS ts_kv_records; INNER JOIN ts_kv_dictionary ON (ts_kv_old.key = ts_kv_dictionary.key)) AS ts_kv_records;
BEGIN BEGIN
@ -211,26 +312,24 @@ BEGIN
END; END;
$$; $$;
-- call insert_into_ts_kv_latest(); -- call insert_into_ts_kv_latest_cursor();
CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest() LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest_cursor()
LANGUAGE plpgsql AS
$$
DECLARE DECLARE
insert_size CONSTANT integer := 10000; insert_size CONSTANT integer := 10000;
insert_counter integer DEFAULT 0; insert_counter integer DEFAULT 0;
insert_record RECORD; insert_record RECORD;
insert_cursor CURSOR FOR SELECT CONCAT(entity_id_uuid_first_part, '-', entity_id_uuid_second_part, '-1', entity_id_uuid_third_part, '-', entity_id_uuid_fourth_part, '-', entity_id_uuid_fifth_part)::uuid AS entity_id, insert_cursor CURSOR FOR SELECT to_uuid(entity_id) AS entity_id,
ts_kv_latest_records.key AS key, ts_kv_latest_records.key AS key,
ts_kv_latest_records.ts AS ts, ts_kv_latest_records.ts AS ts,
ts_kv_latest_records.bool_v AS bool_v, ts_kv_latest_records.bool_v AS bool_v,
ts_kv_latest_records.str_v AS str_v, ts_kv_latest_records.str_v AS str_v,
ts_kv_latest_records.long_v AS long_v, ts_kv_latest_records.long_v AS long_v,
ts_kv_latest_records.dbl_v AS dbl_v ts_kv_latest_records.dbl_v AS dbl_v
FROM (SELECT SUBSTRING(entity_id, 8, 8) AS entity_id_uuid_first_part, FROM (SELECT entity_id AS entity_id,
SUBSTRING(entity_id, 4, 4) AS entity_id_uuid_second_part, key_id AS key,
SUBSTRING(entity_id, 1, 3) AS entity_id_uuid_third_part,
SUBSTRING(entity_id, 16, 4) AS entity_id_uuid_fourth_part,
SUBSTRING(entity_id, 20) AS entity_id_uuid_fifth_part,
key_id AS key,
ts, ts,
bool_v, bool_v,
str_v, str_v,
@ -258,4 +357,3 @@ BEGIN
END; END;
$$; $$;

113
application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql

@ -96,51 +96,36 @@ BEGIN
END; END;
$$; $$;
-- call insert_into_ts_kv(); CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS
$$
BEGIN
uuid_id := substring(entity_id, 8, 8) || '-' || substring(entity_id, 4, 4) || '-1' || substring(entity_id, 1, 3) ||
'-' || substring(entity_id, 16, 4) || '-' || substring(entity_id, 20, 12);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE insert_into_ts_kv() LANGUAGE plpgsql AS $$ -- call insert_into_ts_kv();
DECLARE CREATE OR REPLACE PROCEDURE insert_into_ts_kv(IN path_to_file varchar) LANGUAGE plpgsql AS $$
insert_size CONSTANT integer := 10000;
insert_counter integer DEFAULT 0;
insert_record RECORD;
insert_cursor CURSOR FOR SELECT CONCAT(entity_id_uuid_first_part, '-', entity_id_uuid_second_part, '-1', entity_id_uuid_third_part, '-', entity_id_uuid_fourth_part, '-', entity_id_uuid_fifth_part)::uuid AS entity_id,
new_ts_kv_records.key AS key,
new_ts_kv_records.ts AS ts,
new_ts_kv_records.bool_v AS bool_v,
new_ts_kv_records.str_v AS str_v,
new_ts_kv_records.long_v AS long_v,
new_ts_kv_records.dbl_v AS dbl_v
FROM (SELECT SUBSTRING(entity_id, 8, 8) AS entity_id_uuid_first_part,
SUBSTRING(entity_id, 4, 4) AS entity_id_uuid_second_part,
SUBSTRING(entity_id, 1, 3) AS entity_id_uuid_third_part,
SUBSTRING(entity_id, 16, 4) AS entity_id_uuid_fourth_part,
SUBSTRING(entity_id, 20) AS entity_id_uuid_fifth_part,
key_id AS key,
ts,
bool_v,
str_v,
long_v,
dbl_v
FROM tenant_ts_kv_old
INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS new_ts_kv_records;
BEGIN BEGIN
OPEN insert_cursor;
LOOP EXECUTE format ('COPY (SELECT to_uuid(entity_id) AS entity_id,
insert_counter := insert_counter + 1; new_ts_kv_records.key AS key,
FETCH insert_cursor INTO insert_record; new_ts_kv_records.ts AS ts,
IF NOT FOUND THEN new_ts_kv_records.bool_v AS bool_v,
RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter - 1; new_ts_kv_records.str_v AS str_v,
EXIT; new_ts_kv_records.long_v AS long_v,
END IF; new_ts_kv_records.dbl_v AS dbl_v
INSERT INTO ts_kv(entity_id, key, ts, bool_v, str_v, long_v, dbl_v) FROM (SELECT entity_id AS entity_id,
VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, key_id AS key,
insert_record.long_v, insert_record.dbl_v); ts,
IF MOD(insert_counter, insert_size) = 0 THEN bool_v,
RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter; str_v,
END IF; long_v,
END LOOP; dbl_v
CLOSE insert_cursor; FROM tenant_ts_kv_old
INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS new_ts_kv_records) TO %L;', path_to_file);
EXECUTE format ('COPY ts_kv FROM %L', path_to_file);
END; END;
$$; $$;
@ -177,3 +162,47 @@ BEGIN
CLOSE insert_cursor; CLOSE insert_cursor;
END; END;
$$; $$;
-- call insert_into_ts_kv_cursor();
CREATE OR REPLACE PROCEDURE insert_into_ts_kv_cursor() LANGUAGE plpgsql AS $$
DECLARE
insert_size CONSTANT integer := 10000;
insert_counter integer DEFAULT 0;
insert_record RECORD;
insert_cursor CURSOR FOR SELECT to_uuid(entity_id) AS entity_id,
new_ts_kv_records.key AS key,
new_ts_kv_records.ts AS ts,
new_ts_kv_records.bool_v AS bool_v,
new_ts_kv_records.str_v AS str_v,
new_ts_kv_records.long_v AS long_v,
new_ts_kv_records.dbl_v AS dbl_v
FROM (SELECT entity_id AS entity_id,
key_id AS key,
ts,
bool_v,
str_v,
long_v,
dbl_v
FROM tenant_ts_kv_old
INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS new_ts_kv_records;
BEGIN
OPEN insert_cursor;
LOOP
insert_counter := insert_counter + 1;
FETCH insert_cursor INTO insert_record;
IF NOT FOUND THEN
RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter - 1;
EXIT;
END IF;
INSERT INTO ts_kv(entity_id, key, ts, bool_v, str_v, long_v, dbl_v)
VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v,
insert_record.long_v, insert_record.dbl_v);
IF MOD(insert_counter, insert_size) = 0 THEN
RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter;
END IF;
END LOOP;
CLOSE insert_cursor;
END;
$$;

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

@ -56,6 +56,7 @@ import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.cassandra.CassandraCluster; import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
@ -219,6 +220,10 @@ public class ActorSystemContext {
@Getter @Getter
private MailService mailService; private MailService mailService;
@Autowired
@Getter
private ClaimDevicesService claimDevicesService;
//TODO: separate context for TbCore and TbRuleEngine //TODO: separate context for TbCore and TbRuleEngine
@Autowired(required = false) @Autowired(required = false)
@Getter @Getter

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

@ -39,6 +39,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
@ -232,9 +233,17 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
if (msg.hasSubscriptionInfo()) { if (msg.hasSubscriptionInfo()) {
handleSessionActivity(context, msg.getSessionInfo(), msg.getSubscriptionInfo()); handleSessionActivity(context, msg.getSessionInfo(), msg.getSubscriptionInfo());
} }
if (msg.hasClaimDevice()) {
handleClaimDeviceMsg(context, msg.getSessionInfo(), msg.getClaimDevice());
}
callback.onSuccess(); callback.onSuccess();
} }
private void handleClaimDeviceMsg(ActorContext context, SessionInfoProto sessionInfo, TransportProtos.ClaimDeviceMsg msg) {
DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs());
}
private void reportSessionOpen() { private void reportSessionOpen() {
systemContext.getDeviceStateService().onDeviceConnect(deviceId); systemContext.getDeviceStateService().onDeviceConnect(deviceId);
} }

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

@ -206,7 +206,7 @@ public class TenantActor extends RuleChainManagerActor {
if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) { if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
RuleChain ruleChain = systemContext.getRuleChainService(). RuleChain ruleChain = systemContext.getRuleChainService().
findRuleChainById(tenantId, new RuleChainId(msg.getEntityId().getId())); findRuleChainById(tenantId, new RuleChainId(msg.getEntityId().getId()));
if (ruleChain.getType().equals(RuleChainType.SYSTEM)) { if (ruleChain != null && ruleChain.getType().equals(RuleChainType.SYSTEM)) {
visit(ruleChain, target); visit(ruleChain, target);
} }
} }

7
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -28,7 +28,6 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
@ -37,6 +36,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageData;
@ -84,8 +84,9 @@ public class AlarmController extends BaseController {
public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException { public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException {
try { try {
alarm.setTenantId(getCurrentUser().getTenantId()); alarm.setTenantId(getCurrentUser().getTenantId());
Operation operation = alarm.getId() == null ? Operation.CREATE : Operation.WRITE;
accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarm.getId(), alarm); checkEntity(alarm.getId(), alarm, Resource.ALARM);
Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm)); Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm));
logEntityAction(savedAlarm.getId(), savedAlarm, logEntityAction(savedAlarm.getId(), savedAlarm,
getCurrentUser().getCustomerId(), getCurrentUser().getCustomerId(),

61
application/src/main/java/org/thingsboard/server/controller/AssetController.java

@ -40,6 +40,8 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -51,6 +53,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@RequestMapping("/api") @RequestMapping("/api")
@ -78,18 +82,15 @@ public class AssetController extends BaseController {
try { try {
asset.setTenantId(getCurrentUser().getTenantId()); asset.setTenantId(getCurrentUser().getTenantId());
Operation operation = asset.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(asset.getId(), asset, Resource.ASSET);
accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation,
asset.getId(), asset);
Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); Asset savedAsset = checkNotNull(assetService.saveAsset(asset));
logEntityAction(savedAsset.getId(), savedAsset, logEntityAction(savedAsset.getId(), savedAsset,
savedAsset.getCustomerId(), savedAsset.getCustomerId(),
asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedAsset; return savedAsset;
} catch (Exception e) { } catch (Exception e) {
logEntityAction(emptyId(EntityType.ASSET), asset, logEntityAction(emptyId(EntityType.ASSET), asset,
null, asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); null, asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
@ -140,7 +141,7 @@ public class AssetController extends BaseController {
savedAsset.getCustomerId(), savedAsset.getCustomerId(),
ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, strCustomerId, customer.getName()); ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, strCustomerId, customer.getName());
return savedAsset; return savedAsset;
} catch (Exception e) { } catch (Exception e) {
logEntityAction(emptyId(EntityType.ASSET), null, logEntityAction(emptyId(EntityType.ASSET), null,
@ -220,7 +221,7 @@ public class AssetController extends BaseController {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
if (type != null && type.trim().length()>0) { if (type != null && type.trim().length() > 0) {
return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink)); return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink));
} else { } else {
return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink)); return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink));
@ -259,7 +260,7 @@ public class AssetController extends BaseController {
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId, Operation.READ); checkCustomerId(customerId, Operation.READ);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
if (type != null && type.trim().length()>0) { if (type != null && type.trim().length() > 0) {
return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
} else { } else {
return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
@ -336,9 +337,9 @@ public class AssetController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST) @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Asset assignAssetToEdge(@PathVariable("edgeId") String strEdgeId, public Asset assignAssetToEdge(@PathVariable(EDGE_ID) String strEdgeId,
@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter("edgeId", strEdgeId); checkParameter(EDGE_ID, strEdgeId);
checkParameter(ASSET_ID, strAssetId); checkParameter(ASSET_ID, strAssetId);
try { try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
@ -365,20 +366,20 @@ public class AssetController extends BaseController {
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/asset/{assetId}", method = RequestMethod.DELETE) @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public Asset unassignAssetFromEdge(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { public Asset unassignAssetFromEdge(@PathVariable(EDGE_ID) String strEdgeId,
@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
checkParameter(ASSET_ID, strAssetId); checkParameter(ASSET_ID, strAssetId);
try { try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ);
AssetId assetId = new AssetId(toUUID(strAssetId)); AssetId assetId = new AssetId(toUUID(strAssetId));
Asset asset = checkAssetId(assetId, Operation.UNASSIGN_FROM_EDGE); Asset asset = checkAssetId(assetId, Operation.UNASSIGN_FROM_EDGE);
if (asset.getEdgeId() == null || asset.getEdgeId().getId().equals(ModelConstants.NULL_UUID)) {
throw new IncorrectParameterException("Asset isn't assigned to any edge!");
}
Edge edge = checkEdgeId(asset.getEdgeId(), Operation.READ);
Asset savedAsset = checkNotNull(assetService.unassignAssetFromEdge(getTenantId(), assetId)); Asset savedAsset = checkNotNull(assetService.unassignAssetFromEdge(getTenantId(), assetId, edgeId));
logEntityAction(assetId, asset, logEntityAction(assetId, asset,
asset.getCustomerId(), asset.getCustomerId(),
@ -398,24 +399,20 @@ public class AssetController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/assets", params = {"limit"}, method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/assets", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<Asset> getEdgeAssets( public TimePageData<Asset> getEdgeAssets(
@PathVariable("edgeId") String strEdgeId, @PathVariable(EDGE_ID) String strEdgeId,
@RequestParam int limit, @RequestParam int limit,
@RequestParam(required = false) String type, @RequestParam(required = false) Long startTime,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) Long endTime,
@RequestParam(required = false) String idOffset, @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
@RequestParam(required = false) String textOffset) throws ThingsboardException { @RequestParam(required = false) String offset) throws ThingsboardException {
checkParameter("edgeId", strEdgeId); checkParameter(EDGE_ID, strEdgeId);
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ); checkEdgeId(edgeId, Operation.READ);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
if (type != null && type.trim().length()>0) { return checkNotNull(assetService.findAssetsByTenantIdAndEdgeId(tenantId, edgeId, pageLink).get());
return checkNotNull(assetService.findAssetsByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink));
} else {
return checkNotNull(assetService.findAssetsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
}
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

20
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
@ -107,7 +108,6 @@ import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.mail.MessagingException; import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -337,11 +337,23 @@ public abstract class BaseController {
} }
} }
protected <I extends EntityId, T extends HasTenantId> void checkEntity(I entityId, T entity, Resource resource) throws ThingsboardException {
if (entityId == null) {
accessControlService
.checkPermission(getCurrentUser(), resource, Operation.CREATE, null, entity);
} else {
checkEntityId(entityId, Operation.WRITE);
}
}
protected void checkEntityId(EntityId entityId, Operation operation) throws ThingsboardException { protected void checkEntityId(EntityId entityId, Operation operation) throws ThingsboardException {
try { try {
checkNotNull(entityId); checkNotNull(entityId);
validateId(entityId.getId(), "Incorrect entityId " + entityId); validateId(entityId.getId(), "Incorrect entityId " + entityId);
switch (entityId.getEntityType()) { switch (entityId.getEntityType()) {
case ALARM:
checkAlarmId(new AlarmId(entityId.getId()), operation);
return;
case DEVICE: case DEVICE:
checkDeviceId(new DeviceId(entityId.getId()), operation); checkDeviceId(new DeviceId(entityId.getId()), operation);
return; return;
@ -372,6 +384,12 @@ public abstract class BaseController {
case EDGE: case EDGE:
checkEdgeId(new EdgeId(entityId.getId()), operation); checkEdgeId(new EdgeId(entityId.getId()), operation);
return; return;
case WIDGETS_BUNDLE:
checkWidgetsBundleId(new WidgetsBundleId(entityId.getId()), operation);
return;
case WIDGET_TYPE:
checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation);
return;
default: default:
throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
} }

3
application/src/main/java/org/thingsboard/server/controller/CustomerController.java

@ -100,8 +100,7 @@ public class CustomerController extends BaseController {
try { try {
customer.setTenantId(getCurrentUser().getTenantId()); customer.setTenantId(getCurrentUser().getTenantId());
Operation operation = customer.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(customer.getId(), customer, Resource.CUSTOMER);
accessControlService.checkPermission(getCurrentUser(), Resource.CUSTOMER, operation, customer.getId(), customer);
Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer)); Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer));

21
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -103,15 +103,12 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard", method = RequestMethod.POST) @RequestMapping(value = "/dashboard", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException { public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException {
try { try {
dashboard.setTenantId(getCurrentUser().getTenantId()); dashboard.setTenantId(getCurrentUser().getTenantId());
Operation operation = dashboard.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD);
accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation,
dashboard.getId(), dashboard);
Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard)); Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
@ -155,9 +152,9 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Dashboard assignDashboardToCustomer(@PathVariable("customerId") String strCustomerId, public Dashboard assignDashboardToCustomer(@PathVariable("customerId") String strCustomerId,
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
@ -166,7 +163,7 @@ public class DashboardController extends BaseController {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER);
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(getCurrentUser().getTenantId(), dashboardId, customerId)); Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(getCurrentUser().getTenantId(), dashboardId, customerId));
logEntityAction(dashboardId, savedDashboard, logEntityAction(dashboardId, savedDashboard,
@ -187,7 +184,7 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public Dashboard unassignDashboardFromCustomer(@PathVariable("customerId") String strCustomerId, public Dashboard unassignDashboardFromCustomer(@PathVariable("customerId") String strCustomerId,
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
@ -421,7 +418,7 @@ public class DashboardController extends BaseController {
} }
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<DashboardInfo> getTenantDashboards( public TextPageData<DashboardInfo> getTenantDashboards(
@PathVariable("tenantId") String strTenantId, @PathVariable("tenantId") String strTenantId,
@ -440,7 +437,7 @@ public class DashboardController extends BaseController {
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/tenant/dashboards", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<DashboardInfo> getTenantDashboards( public TextPageData<DashboardInfo> getTenantDashboards(
@RequestParam int limit, @RequestParam int limit,
@ -457,7 +454,7 @@ public class DashboardController extends BaseController {
} }
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/dashboards", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TimePageData<DashboardInfo> getCustomerDashboards( public TimePageData<DashboardInfo> getCustomerDashboards(
@PathVariable("customerId") String strCustomerId, @PathVariable("customerId") String strCustomerId,

45
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -49,6 +49,9 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.device.claim.ClaimResult;
@ -96,10 +99,7 @@ public class DeviceController extends BaseController {
try { try {
device.setTenantId(getCurrentUser().getTenantId()); device.setTenantId(getCurrentUser().getTenantId());
Operation operation = device.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(device.getId(), device, Resource.DEVICE);
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation,
device.getId(), device);
Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken));
@ -519,19 +519,20 @@ public class DeviceController extends BaseController {
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/device/{deviceId}", method = RequestMethod.DELETE) @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public Device unassignDeviceFromEdge(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { public Device unassignDeviceFromEdge(@PathVariable(EDGE_ID) String strEdgeId,
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
checkParameter(DEVICE_ID, strDeviceId); checkParameter(DEVICE_ID, strDeviceId);
try { try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ);
DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
Device device = checkDeviceId(deviceId, Operation.UNASSIGN_FROM_EDGE); Device device = checkDeviceId(deviceId, Operation.UNASSIGN_FROM_EDGE);
if (device.getEdgeId() == null || device.getEdgeId().getId().equals(ModelConstants.NULL_UUID)) {
throw new IncorrectParameterException("Device isn't assigned to any edge!");
}
Edge edge = checkEdgeId(device.getEdgeId(), Operation.READ);
Device savedDevice = checkNotNull(deviceService.unassignDeviceFromEdge(getCurrentUser().getTenantId(), deviceId)); Device savedDevice = checkNotNull(deviceService.unassignDeviceFromEdge(getCurrentUser().getTenantId(), deviceId, edgeId));
logEntityAction(deviceId, device, logEntityAction(deviceId, device,
device.getCustomerId(), device.getCustomerId(),
@ -549,24 +550,20 @@ public class DeviceController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/devices", params = {"limit"}, method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/devices", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<Device> getEdgeDevices( public TimePageData<Device> getEdgeDevices(
@PathVariable("edgeId") String strEdgeId, @PathVariable(EDGE_ID) String strEdgeId,
@RequestParam int limit, @RequestParam int limit,
@RequestParam(required = false) String type, @RequestParam(required = false) Long startTime,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) Long endTime,
@RequestParam(required = false) String idOffset, @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
@RequestParam(required = false) String textOffset) throws ThingsboardException { @RequestParam(required = false) String offset) throws ThingsboardException {
checkParameter("edgeId", strEdgeId); checkParameter(EDGE_ID, strEdgeId);
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ); checkEdgeId(edgeId, Operation.READ);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
if (type != null && type.trim().length()>0) { return checkNotNull(deviceService.findDevicesByTenantIdAndEdgeId(tenantId, edgeId, pageLink).get());
return checkNotNull(deviceService.findDevicesByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink));
} else {
return checkNotNull(deviceService.findDevicesByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
}
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

12
application/src/main/java/org/thingsboard/server/controller/EdgeController.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@ -92,15 +93,16 @@ public class EdgeController extends BaseController {
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation,
edge.getId(), edge); edge.getId(), edge);
Edge result = checkNotNull(edgeService.saveEdge(edge)); Edge savedEdge = checkNotNull(edgeService.saveEdge(edge));
if (created) { if (created) {
ruleChainService.assignRuleChainToEdge(tenantId, defaultRootEdgeRuleChain.getId(), result.getId()); ruleChainService.assignRuleChainToEdge(tenantId, defaultRootEdgeRuleChain.getId(), savedEdge.getId());
edgeService.setEdgeRootRuleChain(tenantId, result, defaultRootEdgeRuleChain.getId()); edgeService.setEdgeRootRuleChain(tenantId, savedEdge, defaultRootEdgeRuleChain.getId());
edgeService.assignDefaultRuleChainsToEdge(tenantId, savedEdge.getId());
} }
logEntityAction(result.getId(), result, null, created ? ActionType.ADDED : ActionType.UPDATED, null); logEntityAction(savedEdge.getId(), savedEdge, null, created ? ActionType.ADDED : ActionType.UPDATED, null);
return result; return savedEdge;
} catch (Exception e) { } catch (Exception e) {
logEntityAction(emptyId(EntityType.EDGE), edge, logEntityAction(emptyId(EntityType.EDGE), edge,
null, edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); null, edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);

45
application/src/main/java/org/thingsboard/server/controller/EntityViewController.java

@ -48,6 +48,8 @@ import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -95,10 +97,7 @@ public class EntityViewController extends BaseController {
try { try {
entityView.setTenantId(getCurrentUser().getTenantId()); entityView.setTenantId(getCurrentUser().getTenantId());
Operation operation = entityView.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(entityView.getId(), entityView, Resource.ENTITY_VIEW);
accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation,
entityView.getId(), entityView);
EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView)); EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView));
List<ListenableFuture<List<Void>>> futures = new ArrayList<>(); List<ListenableFuture<List<Void>>> futures = new ArrayList<>();
@ -400,18 +399,20 @@ public class EntityViewController extends BaseController {
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/entityView/{entityViewId}", method = RequestMethod.DELETE) @RequestMapping(value = "/edge/{edgeId}/entityView/{entityViewId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public EntityView unassignEntityViewFromEdge(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { public EntityView unassignEntityViewFromEdge(@PathVariable(EDGE_ID) String strEdgeId,
@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
checkParameter(ENTITY_VIEW_ID, strEntityViewId); checkParameter(ENTITY_VIEW_ID, strEntityViewId);
try { try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ);
EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
EntityView entityView = checkEntityViewId(entityViewId, Operation.UNASSIGN_FROM_EDGE); EntityView entityView = checkEntityViewId(entityViewId, Operation.UNASSIGN_FROM_EDGE);
if (entityView.getEdgeId() == null || entityView.getEdgeId().getId().equals(ModelConstants.NULL_UUID)) {
throw new IncorrectParameterException("Entity View isn't assigned to any edge!"); EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromEdge(getTenantId(), entityViewId, edgeId));
}
Edge edge = checkEdgeId(entityView.getEdgeId(), Operation.READ);
EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromEdge(getTenantId(), entityViewId));
logEntityAction(entityViewId, entityView, logEntityAction(entityViewId, entityView,
entityView.getCustomerId(), entityView.getCustomerId(),
ActionType.UNASSIGNED_FROM_EDGE, null, strEntityViewId, edge.getId().toString(), edge.getName()); ActionType.UNASSIGNED_FROM_EDGE, null, strEntityViewId, edge.getId().toString(), edge.getName());
@ -428,24 +429,20 @@ public class EntityViewController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/entityViews", params = {"limit"}, method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/entityViews", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<EntityView> getEdgeEntityViews( public TimePageData<EntityView> getEdgeEntityViews(
@PathVariable("edgeId") String strEdgeId, @PathVariable(EDGE_ID) String strEdgeId,
@RequestParam int limit, @RequestParam int limit,
@RequestParam(required = false) String type, @RequestParam(required = false) Long startTime,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) Long endTime,
@RequestParam(required = false) String idOffset, @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
@RequestParam(required = false) String textOffset) throws ThingsboardException { @RequestParam(required = false) String offset) throws ThingsboardException {
checkParameter("edgeId", strEdgeId); checkParameter(EDGE_ID, strEdgeId);
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ); checkEdgeId(edgeId, Operation.READ);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
if (type != null && type.trim().length()>0) { return checkNotNull(entityViewService.findEntityViewsByTenantIdAndEdgeId(tenantId, edgeId, pageLink).get());
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink));
} else {
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
}
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

13
application/src/main/java/org/thingsboard/server/controller/RpcController.java

@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@ -65,7 +66,6 @@ import java.util.UUID;
@Slf4j @Slf4j
public class RpcController extends BaseController { public class RpcController extends BaseController {
public static final int DEFAULT_TIMEOUT = 10000;
protected final ObjectMapper jsonMapper = new ObjectMapper(); protected final ObjectMapper jsonMapper = new ObjectMapper();
@Autowired @Autowired
@ -74,6 +74,12 @@ public class RpcController extends BaseController {
@Autowired @Autowired
private AccessValidator accessValidator; private AccessValidator accessValidator;
@Value("${server.rest.server_side_rpc.min_timeout:5000}")
private long minTimeout;
@Value("${server.rest.server_side_rpc.default_timeout:10000}")
private long defaultTimeout;
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST) @RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -100,7 +106,8 @@ public class RpcController extends BaseController {
SecurityUser currentUser = getCurrentUser(); SecurityUser currentUser = getCurrentUser();
TenantId tenantId = currentUser.getTenantId(); TenantId tenantId = currentUser.getTenantId();
final DeferredResult<ResponseEntity> response = new DeferredResult<>(); final DeferredResult<ResponseEntity> response = new DeferredResult<>();
long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : DEFAULT_TIMEOUT); long timeout = cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout;
long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout);
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData()); ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData());
accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() { accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() {
@Override @Override
@ -109,7 +116,7 @@ public class RpcController extends BaseController {
tenantId, tenantId,
deviceId, deviceId,
oneWay, oneWay,
timeout, expTime,
body body
); );
deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse)); deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse));

23
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -130,15 +130,14 @@ public class RuleChainController extends BaseController {
boolean created = ruleChain.getId() == null; boolean created = ruleChain.getId() == null;
ruleChain.setTenantId(getCurrentUser().getTenantId()); ruleChain.setTenantId(getCurrentUser().getTenantId());
Operation operation = created ? Operation.CREATE : Operation.WRITE; checkEntity(ruleChain.getId(), ruleChain, Resource.RULE_CHAIN);
accessControlService.checkPermission(getCurrentUser(), Resource.RULE_CHAIN, operation,
ruleChain.getId(), ruleChain);
RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain)); RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain));
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), savedRuleChain.getId(), if (RuleChainType.SYSTEM.equals(savedRuleChain.getType())) {
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); tbClusterService.onEntityStateChange(ruleChain.getTenantId(), savedRuleChain.getId(),
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
}
logEntityAction(savedRuleChain.getId(), savedRuleChain, logEntityAction(savedRuleChain.getId(), savedRuleChain,
null, null,
@ -210,7 +209,9 @@ public class RuleChainController extends BaseController {
RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE); RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE);
RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData)); RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData));
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED); if (RuleChainType.SYSTEM.equals(ruleChain.getType())) {
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED);
}
logEntityAction(ruleChain.getId(), ruleChain, logEntityAction(ruleChain.getId(), ruleChain,
null, null,
@ -266,10 +267,12 @@ public class RuleChainController extends BaseController {
referencingRuleChainIds.remove(ruleChain.getId()); referencingRuleChainIds.remove(ruleChain.getId());
referencingRuleChainIds.forEach(referencingRuleChainId -> if (RuleChainType.SYSTEM.equals(ruleChain.getType())) {
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), referencingRuleChainId, ComponentLifecycleEvent.UPDATED)); referencingRuleChainIds.forEach(referencingRuleChainId ->
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), referencingRuleChainId, ComponentLifecycleEvent.UPDATED));
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED); tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED);
}
logEntityAction(ruleChainId, ruleChain, logEntityAction(ruleChainId, ruleChain,
null, null,

4
application/src/main/java/org/thingsboard/server/controller/TenantController.java

@ -72,10 +72,8 @@ public class TenantController extends BaseController {
try { try {
boolean newTenant = tenant.getId() == null; boolean newTenant = tenant.getId() == null;
Operation operation = newTenant ? Operation.CREATE : Operation.WRITE; checkEntity(tenant.getId(), tenant, Resource.TENANT);
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation,
tenant.getId(), tenant);
tenant = checkNotNull(tenantService.saveTenant(tenant)); tenant = checkNotNull(tenantService.saveTenant(tenant));
if (newTenant) { if (newTenant) {
installScripts.createDefaultRuleChains(tenant.getId()); installScripts.createDefaultRuleChains(tenant.getId());

12
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -132,17 +132,13 @@ public class UserController extends BaseController {
@ResponseBody @ResponseBody
public User saveUser(@RequestBody User user, public User saveUser(@RequestBody User user,
@RequestParam(required = false, defaultValue = "true") boolean sendActivationMail, @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail,
HttpServletRequest request) throws ThingsboardException { HttpServletRequest request) throws ThingsboardException {
try { try {
if (getCurrentUser().getAuthority() == Authority.TENANT_ADMIN) { if (getCurrentUser().getAuthority() == Authority.TENANT_ADMIN) {
user.setTenantId(getCurrentUser().getTenantId()); user.setTenantId(getCurrentUser().getTenantId());
} }
Operation operation = user.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(user.getId(), user, Resource.USER);
accessControlService.checkPermission(getCurrentUser(), Resource.USER, operation,
user.getId(), user);
boolean sendEmail = user.getId() == null && sendActivationMail; boolean sendEmail = user.getId() == null && sendActivationMail;
User savedUser = checkNotNull(userService.saveUser(user)); User savedUser = checkNotNull(userService.saveUser(user));
@ -250,7 +246,7 @@ public class UserController extends BaseController {
} }
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/users", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/tenant/{tenantId}/users", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<User> getTenantAdmins( public TextPageData<User> getTenantAdmins(
@PathVariable("tenantId") String strTenantId, @PathVariable("tenantId") String strTenantId,
@ -269,7 +265,7 @@ public class UserController extends BaseController {
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/users", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/users", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<User> getCustomerUsers( public TextPageData<User> getCustomerUsers(
@PathVariable("customerId") String strCustomerId, @PathVariable("customerId") String strCustomerId,

9
application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java

@ -66,10 +66,7 @@ public class WidgetTypeController extends BaseController {
widgetType.setTenantId(getCurrentUser().getTenantId()); widgetType.setTenantId(getCurrentUser().getTenantId());
} }
Operation operation = widgetType.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(widgetType.getId(), widgetType, Resource.WIDGET_TYPE);
accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation,
widgetType.getId(), widgetType);
return checkNotNull(widgetTypeService.saveWidgetType(widgetType)); return checkNotNull(widgetTypeService.saveWidgetType(widgetType));
} catch (Exception e) { } catch (Exception e) {
@ -92,7 +89,7 @@ public class WidgetTypeController extends BaseController {
} }
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/widgetTypes", params = { "isSystem", "bundleAlias"}, method = RequestMethod.GET) @RequestMapping(value = "/widgetTypes", params = {"isSystem", "bundleAlias"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public List<WidgetType> getBundleWidgetTypes( public List<WidgetType> getBundleWidgetTypes(
@RequestParam boolean isSystem, @RequestParam boolean isSystem,
@ -111,7 +108,7 @@ public class WidgetTypeController extends BaseController {
} }
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/widgetType", params = { "isSystem", "bundleAlias", "alias" }, method = RequestMethod.GET) @RequestMapping(value = "/widgetType", params = {"isSystem", "bundleAlias", "alias"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public WidgetType getWidgetType( public WidgetType getWidgetType(
@RequestParam boolean isSystem, @RequestParam boolean isSystem,

8
application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java

@ -67,11 +67,7 @@ public class WidgetsBundleController extends BaseController {
widgetsBundle.setTenantId(getCurrentUser().getTenantId()); widgetsBundle.setTenantId(getCurrentUser().getTenantId());
} }
Operation operation = widgetsBundle.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(widgetsBundle.getId(), widgetsBundle, Resource.WIDGETS_BUNDLE);
accessControlService.checkPermission(getCurrentUser(), Resource.WIDGETS_BUNDLE, operation,
widgetsBundle.getId(), widgetsBundle);
return checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle)); return checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle));
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
@ -93,7 +89,7 @@ public class WidgetsBundleController extends BaseController {
} }
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/widgetsBundles", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/widgetsBundles", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<WidgetsBundle> getWidgetsBundles( public TextPageData<WidgetsBundle> getWidgetsBundles(
@RequestParam int limit, @RequestParam int limit,

9
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -133,13 +133,20 @@ public class ThingsboardInstallService {
databaseEntitiesUpgradeService.upgradeDatabase("2.4.2"); databaseEntitiesUpgradeService.upgradeDatabase("2.4.2");
case "2.4.3": case "2.4.3":
log.info("Upgrading ThingsBoard from version 2.4.3 to 2.5 ..."); log.info("Upgrading ThingsBoard from version 2.4.3 to 2.5.0 ...");
if (databaseTsUpgradeService != null) { if (databaseTsUpgradeService != null) {
databaseTsUpgradeService.upgradeDatabase("2.4.3"); databaseTsUpgradeService.upgradeDatabase("2.4.3");
} }
databaseEntitiesUpgradeService.upgradeDatabase("2.4.3"); databaseEntitiesUpgradeService.upgradeDatabase("2.4.3");
case "2.5.0":
log.info("Upgrading ThingsBoard from version 2.5.0 to 2.5.1 ...");
if (databaseTsUpgradeService != null) {
databaseTsUpgradeService.upgradeDatabase("2.5.0");
}
log.info("Updating system data..."); log.info("Updating system data...");
systemDataLoaderService.deleteSystemWidgetBundle("charts"); systemDataLoaderService.deleteSystemWidgetBundle("charts");

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -554,7 +554,7 @@ public final class EdgeGrpcSession implements Closeable {
case ENTITY_DELETED_RPC_MESSAGE: case ENTITY_DELETED_RPC_MESSAGE:
Device device = ctx.getDeviceService().findDeviceByTenantIdAndName(edge.getTenantId(), deviceName); Device device = ctx.getDeviceService().findDeviceByTenantIdAndName(edge.getTenantId(), deviceName);
if (device != null) { if (device != null) {
ctx.getDeviceService().unassignDeviceFromEdge(edge.getTenantId(), device.getId()); ctx.getDeviceService().unassignDeviceFromEdge(edge.getTenantId(), device.getId(), edge.getId());
} }
break; break;
} }

20
application/src/main/java/org/thingsboard/server/service/edge/rpc/init/DefaultInitEdgeService.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.service.edge.rpc.init; package org.thingsboard.server.service.edge.rpc.init;
import com.google.common.util.concurrent.Futures;
import io.grpc.stub.StreamObserver; import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -54,6 +55,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.EntityViewUpdateMsgCo
import org.thingsboard.server.service.edge.rpc.constructor.RuleChainUpdateMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.RuleChainUpdateMsgConstructor;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Future;
@Service @Service
@Slf4j @Slf4j
@ -100,10 +102,10 @@ public class DefaultInitEdgeService implements InitEdgeService {
private void initDevices(Edge edge, StreamObserver<ResponseMsg> outputStream) { private void initDevices(Edge edge, StreamObserver<ResponseMsg> outputStream) {
try { try {
TextPageLink pageLink = new TextPageLink(100); TimePageLink pageLink = new TimePageLink(100);
TextPageData<Device> pageData; TimePageData<Device> pageData;
do { do {
pageData = deviceService.findDevicesByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), pageLink); pageData = deviceService.findDevicesByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), pageLink).get();
if (!pageData.getData().isEmpty()) { if (!pageData.getData().isEmpty()) {
log.trace("[{}] [{}] device(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size()); log.trace("[{}] [{}] device(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (Device device : pageData.getData()) { for (Device device : pageData.getData()) {
@ -130,10 +132,10 @@ public class DefaultInitEdgeService implements InitEdgeService {
private void initAssets(Edge edge, StreamObserver<ResponseMsg> outputStream) { private void initAssets(Edge edge, StreamObserver<ResponseMsg> outputStream) {
try { try {
TextPageLink pageLink = new TextPageLink(100); TimePageLink pageLink = new TimePageLink(100);
TextPageData<Asset> pageData; TimePageData<Asset> pageData;
do { do {
pageData = assetService.findAssetsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), pageLink); pageData = assetService.findAssetsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), pageLink).get();
if (!pageData.getData().isEmpty()) { if (!pageData.getData().isEmpty()) {
log.trace("[{}] [{}] asset(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size()); log.trace("[{}] [{}] asset(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (Asset asset : pageData.getData()) { for (Asset asset : pageData.getData()) {
@ -160,10 +162,10 @@ public class DefaultInitEdgeService implements InitEdgeService {
private void initEntityViews(Edge edge, StreamObserver<ResponseMsg> outputStream) { private void initEntityViews(Edge edge, StreamObserver<ResponseMsg> outputStream) {
try { try {
TextPageLink pageLink = new TextPageLink(100); TimePageLink pageLink = new TimePageLink(100);
TextPageData<EntityView> pageData; TimePageData<EntityView> pageData;
do { do {
pageData = entityViewService.findEntityViewsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), pageLink); pageData = entityViewService.findEntityViewsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), pageLink).get();
if (!pageData.getData().isEmpty()) { if (!pageData.getData().isEmpty()) {
log.trace("[{}] [{}] entity view(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size()); log.trace("[{}] [{}] entity view(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (EntityView entityView : pageData.getData()) { for (EntityView entityView : pageData.getData()) {

3
application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java

@ -34,6 +34,9 @@ public abstract class AbstractSqlTsDatabaseUpgradeService {
protected static final String CALL_REGEX = "call "; protected static final String CALL_REGEX = "call ";
protected static final String DROP_TABLE = "DROP TABLE "; protected static final String DROP_TABLE = "DROP TABLE ";
protected static final String DROP_PROCEDURE_IF_EXISTS = "DROP PROCEDURE IF EXISTS "; protected static final String DROP_PROCEDURE_IF_EXISTS = "DROP PROCEDURE IF EXISTS ";
protected static final String TS_KV_SQL = "ts_kv.sql";
protected static final String PATH_TO_USERS_PUBLIC_FOLDER = "C:\\Users\\Public";
protected static final String THINGSBOARD_WINDOWS_UPGRADE_DIR = "THINGSBOARD_WINDOWS_UPGRADE_DIR";
@Value("${spring.datasource.url}") @Value("${spring.datasource.url}")
protected String dbUrl; protected String dbUrl;

12
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java

@ -311,18 +311,6 @@ public class CassandraDatabaseUpgradeService extends AbstractCassandraDatabaseUp
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.6.0", SCHEMA_UPDATE_CQL); schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.6.0", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile); loadCql(schemaUpdateFile);
try {
cluster.getSession().execute("alter table asset add edge_id text");
Thread.sleep(2500);
} catch (InvalidQueryException e) {}
try {
cluster.getSession().execute("alter table device add edge_id text");
Thread.sleep(2500);
} catch (InvalidQueryException e) {}
try {
cluster.getSession().execute("alter table entity_view add edge_id text");
Thread.sleep(2500);
} catch (InvalidQueryException e) {}
try { try {
cluster.getSession().execute("alter table rule_chain add type text"); cluster.getSession().execute("alter table rule_chain add type text");
Thread.sleep(2500); Thread.sleep(2500);

2
application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java

@ -48,6 +48,8 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
} }
log.info("Schema updated."); log.info("Schema updated.");
break; break;
case "2.5.0":
break;
default: default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
} }

1
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -131,6 +131,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
node.put("username", ""); node.put("username", "");
node.put("password", ""); node.put("password", "");
node.put("tlsVersion", "TLSv1.2");//NOSONAR, key used to identify password field (not password value itself) node.put("tlsVersion", "TLSv1.2");//NOSONAR, key used to identify password field (not password value itself)
node.put("enableProxy", false);
mailSettings.setJsonValue(node); mailSettings.setJsonValue(node);
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings);
} }

4
application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseSchemaService.java

@ -37,8 +37,6 @@ public class PsqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaServic
@Override @Override
public void createDatabaseSchema() throws Exception { public void createDatabaseSchema() throws Exception {
super.createDatabaseSchema(); super.createDatabaseSchema();
if (partitionType.equals("INDEFINITE")) { executeQuery("CREATE TABLE IF NOT EXISTS ts_kv_indefinite PARTITION OF ts_kv DEFAULT;");
executeQuery("CREATE TABLE ts_kv_indefinite PARTITION OF ts_kv DEFAULT;");
}
} }
} }

114
application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java

@ -16,12 +16,17 @@
package org.thingsboard.server.service.install; package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao; import org.thingsboard.server.dao.util.SqlTsDao;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.sql.Connection; import java.sql.Connection;
@ -37,6 +42,7 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
@Value("${sql.postgres.ts_key_value_partitioning:MONTHS}") @Value("${sql.postgres.ts_key_value_partitioning:MONTHS}")
private String partitionType; private String partitionType;
private static final String TS_KV_LATEST_SQL = "ts_kv_latest.sql";
private static final String LOAD_FUNCTIONS_SQL = "schema_update_psql_ts.sql"; private static final String LOAD_FUNCTIONS_SQL = "schema_update_psql_ts.sql";
private static final String LOAD_TTL_FUNCTIONS_SQL = "schema_update_ttl.sql"; private static final String LOAD_TTL_FUNCTIONS_SQL = "schema_update_ttl.sql";
private static final String LOAD_DROP_PARTITIONS_FUNCTIONS_SQL = "schema_update_psql_drop_partitions.sql"; private static final String LOAD_DROP_PARTITIONS_FUNCTIONS_SQL = "schema_update_psql_drop_partitions.sql";
@ -49,15 +55,17 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
private static final String CREATE_PARTITIONS = "create_partitions(IN partition_type varchar)"; private static final String CREATE_PARTITIONS = "create_partitions(IN partition_type varchar)";
private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()"; private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()";
private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()"; private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()";
private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv()"; private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv(IN path_to_file varchar)";
private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest()"; private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest(IN path_to_file varchar)";
private static final String INSERT_INTO_TS_KV_CURSOR = "insert_into_ts_kv_cursor()";
private static final String INSERT_INTO_TS_KV_LATEST_CURSOR = "insert_into_ts_kv_latest_cursor()";
private static final String CALL_CREATE_PARTITION_TS_KV_TABLE = CALL_REGEX + CREATE_PARTITION_TS_KV_TABLE; private static final String CALL_CREATE_PARTITION_TS_KV_TABLE = CALL_REGEX + CREATE_PARTITION_TS_KV_TABLE;
private static final String CALL_CREATE_NEW_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_NEW_TS_KV_LATEST_TABLE; private static final String CALL_CREATE_NEW_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_NEW_TS_KV_LATEST_TABLE;
private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE; private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE;
private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY; private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY;
private static final String CALL_INSERT_INTO_TS_KV = CALL_REGEX + INSERT_INTO_TS_KV; private static final String CALL_INSERT_INTO_TS_KV_CURSOR = CALL_REGEX + INSERT_INTO_TS_KV_CURSOR;
private static final String CALL_INSERT_INTO_TS_KV_LATEST = CALL_REGEX + INSERT_INTO_TS_KV_LATEST; private static final String CALL_INSERT_INTO_TS_KV_LATEST_CURSOR = CALL_REGEX + INSERT_INTO_TS_KV_LATEST_CURSOR;
private static final String DROP_TABLE_TS_KV_OLD = DROP_TABLE + TS_KV_OLD; private static final String DROP_TABLE_TS_KV_OLD = DROP_TABLE + TS_KV_OLD;
private static final String DROP_TABLE_TS_KV_LATEST_OLD = DROP_TABLE + TS_KV_LATEST_OLD; private static final String DROP_TABLE_TS_KV_LATEST_OLD = DROP_TABLE + TS_KV_LATEST_OLD;
@ -69,6 +77,8 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
private static final String DROP_PROCEDURE_INSERT_INTO_DICTIONARY = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_DICTIONARY; private static final String DROP_PROCEDURE_INSERT_INTO_DICTIONARY = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_DICTIONARY;
private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV; private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV;
private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST; private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST;
private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_CURSOR = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_CURSOR;
private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST_CURSOR = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST_CURSOR;
private static final String DROP_FUNCTION_GET_PARTITION_DATA = "DROP FUNCTION IF EXISTS get_partitions_data;"; private static final String DROP_FUNCTION_GET_PARTITION_DATA = "DROP FUNCTION IF EXISTS get_partitions_data;";
@Override @Override
@ -89,14 +99,63 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE); executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE);
if (!partitionType.equals("INDEFINITE")) { if (!partitionType.equals("INDEFINITE")) {
executeQuery(conn, "call create_partitions('" + partitionType + "')"); executeQuery(conn, "call create_partitions('" + partitionType + "')");
} else {
executeQuery(conn, "CREATE TABLE IF NOT EXISTS ts_kv_indefinite PARTITION OF ts_kv DEFAULT;");
} }
executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE);
executeQuery(conn, CALL_INSERT_INTO_DICTIONARY); executeQuery(conn, CALL_INSERT_INTO_DICTIONARY);
executeQuery(conn, CALL_INSERT_INTO_TS_KV);
executeQuery(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE); Path pathToTempTsKvFile = null;
executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST); Path pathToTempTsKvLatestFile = null;
if (SystemUtils.IS_OS_WINDOWS) {
log.info("Lookup for environment variable: {} ...", THINGSBOARD_WINDOWS_UPGRADE_DIR);
Path pathToDir;
String thingsboardWindowsUpgradeDir = System.getenv("THINGSBOARD_WINDOWS_UPGRADE_DIR");
if (StringUtils.isNotEmpty(thingsboardWindowsUpgradeDir)) {
log.info("Environment variable: {} was found!", THINGSBOARD_WINDOWS_UPGRADE_DIR);
pathToDir = Paths.get(thingsboardWindowsUpgradeDir);
} else {
log.info("Failed to lookup environment variable: {}", THINGSBOARD_WINDOWS_UPGRADE_DIR);
pathToDir = Paths.get(PATH_TO_USERS_PUBLIC_FOLDER);
}
log.info("Directory: {} will be used for creation temporary upgrade files!", pathToDir);
try {
Path tsKvFile = Files.createTempFile(pathToDir, "ts_kv", ".sql");
Path tsKvLatestFile = Files.createTempFile(pathToDir, "ts_kv_latest", ".sql");
pathToTempTsKvFile = tsKvFile.toAbsolutePath();
pathToTempTsKvLatestFile = tsKvLatestFile.toAbsolutePath();
try {
copyTimeseries(conn, pathToTempTsKvFile, pathToTempTsKvLatestFile);
} catch (Exception e) {
insertTimeseries(conn);
}
} catch (IOException | SecurityException e) {
log.warn("Failed to create time-series upgrade files due to: {}", e.getMessage());
insertTimeseries(conn);
}
} else {
try {
Path tempDirPath = Files.createTempDirectory("ts_kv");
File tempDirAsFile = tempDirPath.toFile();
boolean writable = tempDirAsFile.setWritable(true, false);
boolean readable = tempDirAsFile.setReadable(true, false);
boolean executable = tempDirAsFile.setExecutable(true, false);
pathToTempTsKvFile = tempDirPath.resolve(TS_KV_SQL).toAbsolutePath();
pathToTempTsKvLatestFile = tempDirPath.resolve(TS_KV_LATEST_SQL).toAbsolutePath();
try {
if (writable && readable && executable) {
copyTimeseries(conn, pathToTempTsKvFile, pathToTempTsKvLatestFile);
} else {
throw new RuntimeException("Failed to grant write permissions for the: " + tempDirPath + "folder!");
}
} catch (Exception e) {
insertTimeseries(conn);
}
} catch (IOException | SecurityException e) {
log.warn("Failed to create time-series upgrade files due to: {}", e.getMessage());
insertTimeseries(conn);
}
}
removeUpgradeFiles(pathToTempTsKvFile, pathToTempTsKvLatestFile);
executeQuery(conn, DROP_TABLE_TS_KV_OLD); executeQuery(conn, DROP_TABLE_TS_KV_OLD);
executeQuery(conn, DROP_TABLE_TS_KV_LATEST_OLD); executeQuery(conn, DROP_TABLE_TS_KV_LATEST_OLD);
@ -108,6 +167,8 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV); executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV);
executeQuery(conn, DROP_PROCEDURE_CREATE_NEW_TS_KV_LATEST_TABLE); executeQuery(conn, DROP_PROCEDURE_CREATE_NEW_TS_KV_LATEST_TABLE);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST); executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_CURSOR);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST_CURSOR);
executeQuery(conn, DROP_FUNCTION_GET_PARTITION_DATA); executeQuery(conn, DROP_FUNCTION_GET_PARTITION_DATA);
executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN IF NOT EXISTS json_v json;"); executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN IF NOT EXISTS json_v json;");
@ -128,11 +189,46 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
} }
} }
break; break;
case "2.5.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
executeQuery(conn, "CREATE TABLE IF NOT EXISTS ts_kv_indefinite PARTITION OF ts_kv DEFAULT;");
executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
}
break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
} }
} }
private void removeUpgradeFiles(Path pathToTempTsKvFile, Path pathToTempTsKvLatestFile) {
if (pathToTempTsKvFile != null && pathToTempTsKvFile.toFile().exists()) {
boolean deleteTsKvFile = pathToTempTsKvFile.toFile().delete();
if (deleteTsKvFile) {
log.info("Successfully deleted the temp file for ts_kv table upgrade!");
}
}
if (pathToTempTsKvLatestFile != null && pathToTempTsKvLatestFile.toFile().exists()) {
boolean deleteTsKvLatestFile = pathToTempTsKvLatestFile.toFile().delete();
if (deleteTsKvLatestFile) {
log.info("Successfully deleted the temp file for ts_kv_latest table upgrade!");
}
}
}
private void copyTimeseries(Connection conn, Path pathToTempTsKvFile, Path pathToTempTsKvLatestFile) {
executeQuery(conn, "call insert_into_ts_kv('" + pathToTempTsKvFile + "')");
executeQuery(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE);
executeQuery(conn, "call insert_into_ts_kv_latest('" + pathToTempTsKvLatestFile + "')");
}
private void insertTimeseries(Connection conn) {
log.warn("Upgrade script failed using the copy to/from files strategy!" +
" Trying to perfrom the upgrade using Inserts strategy ...");
executeQuery(conn, CALL_INSERT_INTO_TS_KV_CURSOR);
executeQuery(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE);
executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST_CURSOR);
}
@Override @Override
protected void loadSql(Connection conn, String fileName) { protected void loadSql(Connection conn, String fileName) {
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", fileName); Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", fileName);

9
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -238,15 +238,6 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.info("Updating schema ..."); log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.6.0", SCHEMA_UPDATE_SQL); schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.6.0", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn); loadSql(schemaUpdateFile, conn);
try {
conn.createStatement().execute("ALTER TABLE asset ADD edge_id varchar(31)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {}
try {
conn.createStatement().execute("ALTER TABLE device ADD edge_id varchar(31)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {}
try {
conn.createStatement().execute("ALTER TABLE entity_view ADD edge_id varchar(31)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {}
try { try {
conn.createStatement().execute("ALTER TABLE rule_chain ADD type varchar(255) DEFAULT 'SYSTEM'"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script conn.createStatement().execute("ALTER TABLE rule_chain ADD type varchar(255) DEFAULT 'SYSTEM'"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {} } catch (Exception e) {}

87
application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java

@ -16,6 +16,8 @@
package org.thingsboard.server.service.install; package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
@ -23,6 +25,9 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.TimescaleDBTsDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.sql.Connection; import java.sql.Connection;
@ -47,15 +52,16 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
private static final String CREATE_NEW_TS_KV_TABLE = "create_new_ts_kv_table()"; private static final String CREATE_NEW_TS_KV_TABLE = "create_new_ts_kv_table()";
private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()"; private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()";
private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()"; private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()";
private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv()"; private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv(IN path_to_file varchar)";
private static final String INSERT_INTO_TS_KV_CURSOR = "insert_into_ts_kv_cursor()";
private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest()"; private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest()";
private static final String CALL_CREATE_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_TS_KV_LATEST_TABLE; private static final String CALL_CREATE_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_TS_KV_LATEST_TABLE;
private static final String CALL_CREATE_NEW_TENANT_TS_KV_TABLE = CALL_REGEX + CREATE_NEW_TS_KV_TABLE; private static final String CALL_CREATE_NEW_TENANT_TS_KV_TABLE = CALL_REGEX + CREATE_NEW_TS_KV_TABLE;
private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE; private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE;
private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY; private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY;
private static final String CALL_INSERT_INTO_TS_KV = CALL_REGEX + INSERT_INTO_TS_KV;
private static final String CALL_INSERT_INTO_TS_KV_LATEST = CALL_REGEX + INSERT_INTO_TS_KV_LATEST; private static final String CALL_INSERT_INTO_TS_KV_LATEST = CALL_REGEX + INSERT_INTO_TS_KV_LATEST;
private static final String CALL_INSERT_INTO_TS_KV_CURSOR = CALL_REGEX + INSERT_INTO_TS_KV_CURSOR;
private static final String DROP_OLD_TENANT_TS_KV_TABLE = DROP_TABLE + TENANT_TS_KV_OLD_TABLE; private static final String DROP_OLD_TENANT_TS_KV_TABLE = DROP_TABLE + TENANT_TS_KV_OLD_TABLE;
@ -63,7 +69,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
private static final String DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY = DROP_PROCEDURE_IF_EXISTS + CREATE_NEW_TS_KV_TABLE; private static final String DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY = DROP_PROCEDURE_IF_EXISTS + CREATE_NEW_TS_KV_TABLE;
private static final String DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE; private static final String DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE;
private static final String DROP_PROCEDURE_INSERT_INTO_DICTIONARY = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_DICTIONARY; private static final String DROP_PROCEDURE_INSERT_INTO_DICTIONARY = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_DICTIONARY;
private static final String DROP_PROCEDURE_INSERT_INTO_TENANT_TS_KV = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV; private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV;
private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_CURSOR = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_CURSOR;
private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST; private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST;
@Autowired @Autowired
@ -91,7 +98,56 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE);
executeQuery(conn, CALL_INSERT_INTO_DICTIONARY); executeQuery(conn, CALL_INSERT_INTO_DICTIONARY);
executeQuery(conn, CALL_INSERT_INTO_TS_KV);
Path pathToTempTsKvFile = null;
if (SystemUtils.IS_OS_WINDOWS) {
Path pathToDir;
log.info("Lookup for environment variable: {} ...", THINGSBOARD_WINDOWS_UPGRADE_DIR);
String thingsboardWindowsUpgradeDir = System.getenv(THINGSBOARD_WINDOWS_UPGRADE_DIR);
if (StringUtils.isNotEmpty(thingsboardWindowsUpgradeDir)) {
log.info("Environment variable: {} was found!", THINGSBOARD_WINDOWS_UPGRADE_DIR);
pathToDir = Paths.get(thingsboardWindowsUpgradeDir);
} else {
log.info("Failed to lookup environment variable: {}", THINGSBOARD_WINDOWS_UPGRADE_DIR);
pathToDir = Paths.get(PATH_TO_USERS_PUBLIC_FOLDER);
}
log.info("Directory: {} will be used for creation temporary upgrade file!", pathToDir);
try {
Path tsKvFile = Files.createTempFile(pathToDir, "ts_kv", ".sql");
pathToTempTsKvFile = tsKvFile.toAbsolutePath();
try {
executeQuery(conn, "call insert_into_ts_kv('" + pathToTempTsKvFile + "')");
} catch (Exception e) {
insertTimeseries(conn);
}
} catch (IOException | SecurityException e) {
log.warn("Failed to create time-series upgrade files due to: {}", e.getMessage());
insertTimeseries(conn);
}
} else {
try {
Path tempDirPath = Files.createTempDirectory("ts_kv");
File tempDirAsFile = tempDirPath.toFile();
boolean writable = tempDirAsFile.setWritable(true, false);
boolean readable = tempDirAsFile.setReadable(true, false);
boolean executable = tempDirAsFile.setExecutable(true, false);
pathToTempTsKvFile = tempDirPath.resolve(TS_KV_SQL).toAbsolutePath();
try {
if (writable && readable && executable) {
executeQuery(conn, "call insert_into_ts_kv('" + pathToTempTsKvFile + "')");
} else {
throw new RuntimeException("Failed to grant write permissions for the: " + tempDirPath + "folder!");
}
} catch (Exception e) {
insertTimeseries(conn);
}
} catch (IOException | SecurityException e) {
log.warn("Failed to create time-series upgrade files due to: {}", e.getMessage());
insertTimeseries(conn);
}
}
removeUpgradeFile(pathToTempTsKvFile);
executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST); executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, DROP_OLD_TENANT_TS_KV_TABLE); executeQuery(conn, DROP_OLD_TENANT_TS_KV_TABLE);
@ -100,7 +156,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
executeQuery(conn, DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY); executeQuery(conn, DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY);
executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE); executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_DICTIONARY); executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_DICTIONARY);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TENANT_TS_KV); executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_CURSOR);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST); executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN IF NOT EXISTS json_v json;"); executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN IF NOT EXISTS json_v json;");
@ -115,11 +172,31 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
} }
} }
break; break;
case "2.5.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
}
break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
} }
} }
private void insertTimeseries(Connection conn) {
log.warn("Upgrade script failed using the copy to/from files strategy!" +
" Trying to perfrom the upgrade using Inserts strategy ...");
executeQuery(conn, CALL_INSERT_INTO_TS_KV_CURSOR);
}
private void removeUpgradeFile(Path pathToTempTsKvFile) {
if (pathToTempTsKvFile != null && pathToTempTsKvFile.toFile().exists()) {
boolean deleteTsKvFile = pathToTempTsKvFile.toFile().delete();
if (deleteTsKvFile) {
log.info("Successfully deleted the temp file for ts_kv table upgrade!");
}
}
}
@Override @Override
protected void loadSql(Connection conn, String fileName) { protected void loadSql(Connection conn, String fileName) {
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", fileName); Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", fileName);

15
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -115,6 +115,21 @@ public class DefaultMailService implements MailService {
if (enableTls && jsonConfig.has("tlsVersion") && StringUtils.isNoneEmpty(jsonConfig.get("tlsVersion").asText())) { if (enableTls && jsonConfig.has("tlsVersion") && StringUtils.isNoneEmpty(jsonConfig.get("tlsVersion").asText())) {
javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", jsonConfig.get("tlsVersion").asText()); javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", jsonConfig.get("tlsVersion").asText());
} }
boolean enableProxy = jsonConfig.has("enableProxy") && jsonConfig.get("enableProxy").asBoolean();
if (enableProxy) {
javaMailProperties.put(MAIL_PROP + protocol + ".proxy.host", jsonConfig.get("proxyHost").asText());
javaMailProperties.put(MAIL_PROP + protocol + ".proxy.port", jsonConfig.get("proxyPort").asText());
String proxyUser = jsonConfig.get("proxyUser").asText();
if (StringUtils.isNoneEmpty(proxyUser)) {
javaMailProperties.put(MAIL_PROP + protocol + ".proxy.user", proxyUser);
}
String proxyPassword = jsonConfig.get("proxyPassword").asText();
if (StringUtils.isNoneEmpty(proxyPassword)) {
javaMailProperties.put(MAIL_PROP + protocol + ".proxy.password", proxyPassword);
}
}
return javaMailProperties; return javaMailProperties;
} }

45
application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java

@ -46,6 +46,9 @@ import java.util.concurrent.atomic.AtomicInteger;
@Service @Service
public class RemoteJsInvokeService extends AbstractJsInvokeService { public class RemoteJsInvokeService extends AbstractJsInvokeService {
@Value("${queue.js.max_eval_requests_timeout}")
private long maxEvalRequestsTimeout;
@Value("${queue.js.max_requests_timeout}") @Value("${queue.js.max_requests_timeout}")
private long maxRequestsTimeout; private long maxRequestsTimeout;
@ -59,22 +62,22 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
@Value("${js.remote.stats.enabled:false}") @Value("${js.remote.stats.enabled:false}")
private boolean statsEnabled; private boolean statsEnabled;
private final AtomicInteger kafkaPushedMsgs = new AtomicInteger(0); private final AtomicInteger queuePushedMsgs = new AtomicInteger(0);
private final AtomicInteger kafkaInvokeMsgs = new AtomicInteger(0); private final AtomicInteger queueInvokeMsgs = new AtomicInteger(0);
private final AtomicInteger kafkaEvalMsgs = new AtomicInteger(0); private final AtomicInteger queueEvalMsgs = new AtomicInteger(0);
private final AtomicInteger kafkaFailedMsgs = new AtomicInteger(0); private final AtomicInteger queueFailedMsgs = new AtomicInteger(0);
private final AtomicInteger kafkaTimeoutMsgs = new AtomicInteger(0); private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0);
@Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}")
public void printStats() { public void printStats() {
if (statsEnabled) { if (statsEnabled) {
int pushedMsgs = kafkaPushedMsgs.getAndSet(0); int pushedMsgs = queuePushedMsgs.getAndSet(0);
int invokeMsgs = kafkaInvokeMsgs.getAndSet(0); int invokeMsgs = queueInvokeMsgs.getAndSet(0);
int evalMsgs = kafkaEvalMsgs.getAndSet(0); int evalMsgs = queueEvalMsgs.getAndSet(0);
int failed = kafkaFailedMsgs.getAndSet(0); int failed = queueFailedMsgs.getAndSet(0);
int timedOut = kafkaTimeoutMsgs.getAndSet(0); int timedOut = queueTimeoutMsgs.getAndSet(0);
if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) { if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) {
log.info("Kafka JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]", log.info("Queue JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]",
pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut); pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut);
} }
} }
@ -113,22 +116,22 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
log.trace("Post compile request for scriptId [{}]", scriptId); log.trace("Post compile request for scriptId [{}]", scriptId);
ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper)); ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper));
if (maxRequestsTimeout > 0) { if (maxEvalRequestsTimeout > 0) {
future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); future = Futures.withTimeout(future, maxEvalRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
} }
kafkaPushedMsgs.incrementAndGet(); queuePushedMsgs.incrementAndGet();
Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() { Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() {
@Override @Override
public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) { public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) {
kafkaEvalMsgs.incrementAndGet(); queueEvalMsgs.incrementAndGet();
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
kafkaTimeoutMsgs.incrementAndGet(); queueTimeoutMsgs.incrementAndGet();
} }
kafkaFailedMsgs.incrementAndGet(); queueFailedMsgs.incrementAndGet();
} }
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
return Futures.transform(future, response -> { return Futures.transform(future, response -> {
@ -170,20 +173,20 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
if (maxRequestsTimeout > 0) { if (maxRequestsTimeout > 0) {
future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
} }
kafkaPushedMsgs.incrementAndGet(); queuePushedMsgs.incrementAndGet();
Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() { Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() {
@Override @Override
public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) { public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) {
kafkaInvokeMsgs.incrementAndGet(); queueInvokeMsgs.incrementAndGet();
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
onScriptExecutionError(scriptId); onScriptExecutionError(scriptId);
if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
kafkaTimeoutMsgs.incrementAndGet(); queueTimeoutMsgs.incrementAndGet();
} }
kafkaFailedMsgs.incrementAndGet(); queueFailedMsgs.incrementAndGet();
} }
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
return Futures.transform(future, response -> { return Futures.transform(future, response -> {

59
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java

@ -15,6 +15,9 @@
*/ */
package org.thingsboard.server.service.security.auth.oauth2; package org.thingsboard.server.service.security.auth.oauth2;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@ -22,14 +25,21 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
@ -40,11 +50,15 @@ import org.thingsboard.server.service.security.model.UserPrincipal;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@Slf4j @Slf4j
public abstract class AbstractOAuth2ClientMapper { public abstract class AbstractOAuth2ClientMapper {
private static final int DASHBOARDS_REQUEST_LIMIT = 10;
private static final ObjectMapper objectMapper = new ObjectMapper();
@Autowired @Autowired
private UserService userService; private UserService userService;
@ -58,6 +72,9 @@ public abstract class AbstractOAuth2ClientMapper {
@Autowired @Autowired
private CustomerService customerService; private CustomerService customerService;
@Autowired
private DashboardService dashboardService;
@Autowired @Autowired
private InstallScripts installScripts; private InstallScripts installScripts;
@ -92,6 +109,20 @@ public abstract class AbstractOAuth2ClientMapper {
user.setEmail(oauth2User.getEmail()); user.setEmail(oauth2User.getEmail());
user.setFirstName(oauth2User.getFirstName()); user.setFirstName(oauth2User.getFirstName());
user.setLastName(oauth2User.getLastName()); user.setLastName(oauth2User.getLastName());
if (!StringUtils.isEmpty(oauth2User.getDefaultDashboardName())) {
Optional<DashboardId> dashboardIdOpt =
user.getAuthority() == Authority.TENANT_ADMIN ?
getDashboardId(tenantId, oauth2User.getDefaultDashboardName())
: getDashboardId(tenantId, customerId, oauth2User.getDefaultDashboardName());
if (dashboardIdOpt.isPresent()) {
ObjectNode additionalInfo = objectMapper.createObjectNode();
additionalInfo.put("defaultDashboardFullscreen", oauth2User.isAlwaysFullScreen());
additionalInfo.put("defaultDashboardId", dashboardIdOpt.get().getId().toString());
user.setAdditionalInfo(additionalInfo);
}
}
user = userService.saveUser(user); user = userService.saveUser(user);
if (activateUser) { if (activateUser) {
UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
@ -143,4 +174,32 @@ public abstract class AbstractOAuth2ClientMapper {
return customerService.saveCustomer(customer).getId(); return customerService.saveCustomer(customer).getId();
} }
} }
private Optional<DashboardId> getDashboardId(TenantId tenantId, String dashboardName) {
TextPageLink searchTextLink = new TextPageLink(1, dashboardName);
TextPageData<DashboardInfo> dashboardsPage = dashboardService.findDashboardsByTenantId(tenantId, searchTextLink);
return dashboardsPage.getData().stream()
.findAny()
.map(IdBased::getId);
}
private Optional<DashboardId> getDashboardId(TenantId tenantId, CustomerId customerId, String dashboardName) {
TimePageData<DashboardInfo> dashboardsPage = null;
do {
TimePageLink timePageLink = dashboardsPage != null ?
dashboardsPage.getNextPageLink() : new TimePageLink(DASHBOARDS_REQUEST_LIMIT);
try {
dashboardsPage = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, timePageLink).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Failed to get customer's dashboards.", e);
}
Optional<DashboardInfo> dashboardInfoOpt = dashboardsPage.getData().stream()
.filter(dashboardInfo -> dashboardName.equals(dashboardInfo.getName()))
.findAny();
if (dashboardInfoOpt.isPresent()) {
return dashboardInfoOpt.map(DashboardInfo::getId);
}
} while (dashboardsPage.hasNext());
return Optional.empty();
}
} }

4
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java

@ -56,6 +56,10 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen
String customerName = sub.replace(config.getBasic().getCustomerNamePattern()); String customerName = sub.replace(config.getBasic().getCustomerNamePattern());
oauth2User.setCustomerName(customerName); oauth2User.setCustomerName(customerName);
} }
oauth2User.setAlwaysFullScreen(config.getBasic().isAlwaysFullScreen());
if (!StringUtils.isEmpty(config.getBasic().getDefaultDashboardName())) {
oauth2User.setDefaultDashboardName(config.getBasic().getDefaultDashboardName());
}
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser());
} }

4
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java

@ -56,8 +56,8 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
try { try {
return restTemplate.postForEntity(custom.getUrl(), request, OAuth2User.class).getBody(); return restTemplate.postForEntity(custom.getUrl(), request, OAuth2User.class).getBody();
} catch (Exception e) { } catch (Exception e) {
log.error("Can't connect to custom mapper endpoint", e); log.error("There was an error during connection to custom mapper endpoint", e);
throw new RuntimeException("Can't connect to custom mapper endpoint", e); throw new RuntimeException("Unable to login. Please contact your Administrator!");
} }
} }
} }

24
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -32,6 +32,8 @@ import org.thingsboard.server.utils.MiscUtils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Component(value = "oauth2AuthenticationSuccessHandler") @Component(value = "oauth2AuthenticationSuccessHandler")
@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") @ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
@ -57,16 +59,22 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
public void onAuthenticationSuccess(HttpServletRequest request, public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
Authentication authentication) throws IOException { Authentication authentication) throws IOException {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
OAuth2Client oauth2Client = oauth2Configuration.getClientByRegistrationId(token.getAuthorizedClientRegistrationId()); String baseUrl = MiscUtils.constructBaseUrl(request);
OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(oauth2Client.getMapperConfig().getType()); try {
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oauth2Client.getMapperConfig()); OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); OAuth2Client oauth2Client = oauth2Configuration.getClientByRegistrationId(token.getAuthorizedClientRegistrationId());
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(oauth2Client.getMapperConfig().getType());
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oauth2Client.getMapperConfig());
String baseUrl = MiscUtils.constructBaseUrl(request); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken()); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());
} catch (Exception e) {
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString()));
}
} }
} }

9
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -47,4 +47,13 @@ public enum Resource {
public Optional<EntityType> getEntityType() { public Optional<EntityType> getEntityType() {
return Optional.ofNullable(entityType); return Optional.ofNullable(entityType);
} }
public static Resource of(EntityType entityType) {
for (Resource resource : Resource.values()) {
if (resource.getEntityType().get() == entityType) {
return resource;
}
}
throw new IllegalArgumentException("Unknown EntityType: " + entityType.name());
}
} }

18
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -292,28 +292,38 @@ public class DefaultDeviceStateService implements DeviceStateService {
} }
} }
volatile Set<TopicPartitionInfo> pendingPartitions;
@Override @Override
public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) { if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) {
synchronized (this) { synchronized (this) {
pendingPartitions = partitionChangeEvent.getPartitions();
if (!clusterUpdatePending) { if (!clusterUpdatePending) {
clusterUpdatePending = true; clusterUpdatePending = true;
queueExecutor.submit(() -> { queueExecutor.submit(() -> {
clusterUpdatePending = false; clusterUpdatePending = false;
initStateFromDB(partitionChangeEvent.getPartitions()); initStateFromDB();
}); });
} }
} }
} }
} }
private void initStateFromDB(Set<TopicPartitionInfo> partitions) { private void initStateFromDB() {
try { try {
Set<TopicPartitionInfo> addedPartitions = new HashSet<>(partitions); log.info("CURRENT PARTITIONS: {}", partitionedDevices.keySet());
log.info("NEW PARTITIONS: {}", pendingPartitions);
Set<TopicPartitionInfo> addedPartitions = new HashSet<>(pendingPartitions);
addedPartitions.removeAll(partitionedDevices.keySet()); addedPartitions.removeAll(partitionedDevices.keySet());
log.info("ADDED PARTITIONS: {}", addedPartitions);
Set<TopicPartitionInfo> removedPartitions = new HashSet<>(partitionedDevices.keySet()); Set<TopicPartitionInfo> removedPartitions = new HashSet<>(partitionedDevices.keySet());
removedPartitions.removeAll(partitions); removedPartitions.removeAll(pendingPartitions);
log.info("REMOVED PARTITIONS: {}", removedPartitions);
// We no longer manage current partition of devices; // We no longer manage current partition of devices;
removedPartitions.forEach(partition -> { removedPartitions.forEach(partition -> {

2
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -224,7 +224,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
return null; return null;
} }
}, },
s -> (StringUtils.isEmpty(s.getScope()) || scope.equals(s.getScope().name())), s -> (TbAttributeSubscriptionScope.ANY_SCOPE.equals(s.getScope()) || scope.equals(s.getScope().name())),
s -> { s -> {
List<TsKvEntry> subscriptionUpdate = null; List<TsKvEntry> subscriptionUpdate = null;
for (AttributeKvEntry kv : attributes) { for (AttributeKvEntry kv : attributes) {

2
application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscriptionScope.java

@ -17,6 +17,6 @@ package org.thingsboard.server.service.subscription;
public enum TbAttributeSubscriptionScope { public enum TbAttributeSubscriptionScope {
CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE ANY_SCOPE, CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE
} }

4
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java

@ -345,7 +345,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
keys.forEach(key -> subState.put(key, 0L)); keys.forEach(key -> subState.put(key, 0L));
attributesData.forEach(v -> subState.put(v.getKey(), v.getTs())); attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.SERVER_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope()); TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.ANY_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope());
TbAttributeSubscription sub = TbAttributeSubscription.builder() TbAttributeSubscription sub = TbAttributeSubscription.builder()
.serviceId(serviceId) .serviceId(serviceId)
@ -442,7 +442,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
Map<String, Long> subState = new HashMap<>(attributesData.size()); Map<String, Long> subState = new HashMap<>(attributesData.size());
attributesData.forEach(v -> subState.put(v.getKey(), v.getTs())); attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.SERVER_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope()); TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.ANY_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope());
TbAttributeSubscription sub = TbAttributeSubscription.builder() TbAttributeSubscription sub = TbAttributeSubscription.builder()
.serviceId(serviceId) .serviceId(serviceId)

25
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.transport;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
@ -24,13 +25,18 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
@ -48,6 +54,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509Ce
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
import java.util.UUID; import java.util.UUID;
@ -82,6 +89,9 @@ public class DefaultTransportApiService implements TransportApiService {
@Autowired @Autowired
private DbCallbackExecutorService dbCallbackExecutorService; private DbCallbackExecutorService dbCallbackExecutorService;
@Autowired
protected TbClusterService tbClusterService;
private ReentrantLock deviceCreationLock = new ReentrantLock(); private ReentrantLock deviceCreationLock = new ReentrantLock();
@Override @Override
@ -119,14 +129,27 @@ public class DefaultTransportApiService implements TransportApiService {
try { try {
Device device = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), requestMsg.getDeviceName()); Device device = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), requestMsg.getDeviceName());
if (device == null) { if (device == null) {
TenantId tenantId = gateway.getTenantId();
device = new Device(); device = new Device();
device.setTenantId(gateway.getTenantId()); device.setTenantId(tenantId);
device.setName(requestMsg.getDeviceName()); device.setName(requestMsg.getDeviceName());
device.setType(requestMsg.getDeviceType()); device.setType(requestMsg.getDeviceType());
device.setCustomerId(gateway.getCustomerId()); device.setCustomerId(gateway.getCustomerId());
device = deviceService.saveDevice(device); device = deviceService.saveDevice(device);
relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created")); relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created"));
deviceStateService.onDeviceAdded(device); deviceStateService.onDeviceAdded(device);
TbMsgMetaData metaData = new TbMsgMetaData();
CustomerId customerId = gateway.getCustomerId();
if (customerId != null && !customerId.isNullUid()) {
metaData.putValue("customerId", customerId.toString());
}
metaData.putValue("gatewayId", gatewayId.toString());
DeviceId deviceId = device.getId();
ObjectNode entityNode = mapper.valueToTree(device);
TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, mapper.writeValueAsString(entityNode));
tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, null);
} }
return TransportApiResponseMsg.newBuilder() return TransportApiResponseMsg.newBuilder()
.setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build(); .setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build();

23
application/src/main/resources/thingsboard.yml

@ -54,6 +54,13 @@ server:
customer: customer:
enabled: "${TB_SERVER_REST_LIMITS_CUSTOMER_ENABLED:false}" enabled: "${TB_SERVER_REST_LIMITS_CUSTOMER_ENABLED:false}"
configuration: "${TB_SERVER_REST_LIMITS_CUSTOMER_CONFIGURATION:50:1,1000:60}" configuration: "${TB_SERVER_REST_LIMITS_CUSTOMER_CONFIGURATION:50:1,1000:60}"
server_side_rpc:
# Minimum value of the server side RPC timeout. May override value provided in the REST API call.
# Since 2.5 migration to queues, the RPC delay depends on the size of the pending messages in the queue,
# so default UI parameter of 500ms may not be sufficient for loaded environments.
min_timeout: "${MIN_SERVER_SIDE_RPC_TIMEOUT:5000}"
# Default value of the server side RPC timeout.
default_timeout: "${DEFAULT_SERVER_SIDE_RPC_TIMEOUT:10000}"
# Zookeeper connection parameters. Used for service discovery. # Zookeeper connection parameters. Used for service discovery.
zk: zk:
@ -148,6 +155,10 @@ security:
# If this field is not empty, user will be created as a user under defined Customer # If this field is not empty, user will be created as a user under defined Customer
# %{attribute_key} as placeholder for attribute value of attributes of external user object # %{attribute_key} as placeholder for attribute value of attributes of external user object
customerNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_PATTERN:}" customerNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_PATTERN:}"
# If this field is not empty, user will be created with default defined Dashboard
defaultDashboardName: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_DEFAULT_DASHBOARD_NAME:}"
# If this field is set 'true' along with non-empty 'defaultDashboardName', user will start from the defined Dashboard in fullscreen mode
alwaysFullScreen: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_ALWAYS_FULL_SCREEN:false}"
custom: custom:
url: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_URL:}" url: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_URL:}"
username: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_USERNAME:}" username: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_USERNAME:}"
@ -676,7 +687,7 @@ queue:
topic: "${TB_QUEUE_CORE_TOPIC:tb_core}" topic: "${TB_QUEUE_CORE_TOPIC:tb_core}"
poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}"
partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" partitions: "${TB_QUEUE_CORE_PARTITIONS:10}"
pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:2000}"
stats: stats:
enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}" enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}"
print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:60000}" print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:60000}"
@ -688,6 +699,8 @@ queue:
# JS Eval max pending requests # JS Eval max pending requests
max_pending_requests: "${REMOTE_JS_MAX_PENDING_REQUESTS:10000}" max_pending_requests: "${REMOTE_JS_MAX_PENDING_REQUESTS:10000}"
# JS Eval max request timeout # JS Eval max request timeout
max_eval_requests_timeout: "${REMOTE_JS_MAX_EVAL_REQUEST_TIMEOUT:60000}"
# JS max request timeout
max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}" max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}"
# JS response poll interval # JS response poll interval
response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}" response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}"
@ -696,7 +709,7 @@ queue:
rule-engine: rule-engine:
topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}"
poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}"
pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:2000}"
stats: stats:
enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}" enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}"
print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}" print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}"
@ -705,7 +718,7 @@ queue:
topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}" topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}"
poll-interval: "${TB_QUEUE_RE_MAIN_POLL_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_RE_MAIN_POLL_INTERVAL_MS:25}"
partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}" partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}"
pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:2000}"
submit-strategy: submit-strategy:
type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
# For BATCH only # For BATCH only
@ -720,7 +733,7 @@ queue:
topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}" topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}" partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}"
pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:2000}"
submit-strategy: submit-strategy:
type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
# For BATCH only # For BATCH only
@ -735,7 +748,7 @@ queue:
topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}" topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}" partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}"
pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:2000}"
submit-strategy: submit-strategy:
type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
# For BATCH only # For BATCH only

9
application/src/main/scripts/control/rpm/postinst

@ -1,9 +0,0 @@
#!/bin/sh
chown -R ${pkg.user}: ${pkg.logFolder}
chown -R ${pkg.user}: ${pkg.installFolder}
if [ $1 -eq 1 ] ; then
# Initial installation
systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || :
fi

24
application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java

@ -33,7 +33,6 @@ import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher; import org.junit.rules.TestWatcher;
import org.junit.runner.Description; import org.junit.runner.Description;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootContextLoader; import org.springframework.boot.test.context.SpringBootContextLoader;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@ -49,7 +48,6 @@ import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
@ -75,7 +73,6 @@ import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
import org.thingsboard.server.service.security.auth.rest.LoginRequest; import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
@ -223,6 +220,27 @@ public abstract class AbstractControllerTest {
login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD); login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD);
} }
private Tenant savedDifferentTenant;
protected void loginDifferentTenant() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("Different tenant");
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedDifferentTenant);
User differentTenantAdmin = new User();
differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN);
differentTenantAdmin.setTenantId(savedDifferentTenant.getId());
differentTenantAdmin.setEmail("different_tenant@thingsboard.org");
createUserAndLogin(differentTenantAdmin, "testPassword");
}
protected void deleteDifferentTenant() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
.andExpect(status().isOk());
}
protected User createUserAndLogin(User user, String password) throws Exception { protected User createUserAndLogin(User user, String password) throws Exception {
User savedUser = doPost("/api/user", user, User.class); User savedUser = doPost("/api/user", user, User.class);
logout(); logout();

12
application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java

@ -98,6 +98,18 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Assert.assertEquals(foundAsset.getName(), savedAsset.getName()); Assert.assertEquals(foundAsset.getName(), savedAsset.getName());
} }
@Test
public void testUpdateAssetFromDifferentTenant() throws Exception {
Asset asset = new Asset();
asset.setName("My asset");
asset.setType("default");
Asset savedAsset = doPost("/api/asset", asset, Asset.class);
loginDifferentTenant();
doPost("/api/asset", savedAsset, Asset.class, status().isForbidden());
deleteDifferentTenant();
}
@Test @Test
public void testFindAssetById() throws Exception { public void testFindAssetById() throws Exception {
Asset asset = new Asset(); Asset asset = new Asset();

304
application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java

@ -23,6 +23,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After;
import org.junit.Before;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
@ -38,25 +40,39 @@ import com.fasterxml.jackson.core.type.TypeReference;
public abstract class BaseCustomerControllerTest extends AbstractControllerTest { public abstract class BaseCustomerControllerTest extends AbstractControllerTest {
private IdComparator<Customer> idComparator = new IdComparator<>(); private IdComparator<Customer> idComparator = new IdComparator<>();
@Test private Tenant savedTenant;
public void testSaveCustomer() throws Exception { private User tenantAdmin;
@Before
public void beforeTest() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
User tenantAdmin = new User(); tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId()); tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org"); tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe"); tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs"); tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveCustomer() throws Exception {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
@ -66,266 +82,159 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle()); Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle());
savedCustomer.setTitle("My new customer"); savedCustomer.setTitle("My new customer");
doPost("/api/customer", savedCustomer, Customer.class); doPost("/api/customer", savedCustomer, Customer.class);
Customer foundCustomer = doGet("/api/customer/"+savedCustomer.getId().getId().toString(), Customer.class); Customer foundCustomer = doGet("/api/customer/"+savedCustomer.getId().getId().toString(), Customer.class);
Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle()); Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle());
doDelete("/api/customer/"+savedCustomer.getId().getId().toString()) doDelete("/api/customer/"+savedCustomer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
public void testFindCustomerById() throws Exception { public void testUpdateCustomerFromDifferentTenant() throws Exception {
Customer customer = new Customer();
loginSysAdmin(); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
doPost("/api/customer", savedCustomer, Customer.class);
Tenant tenant = new Tenant(); loginDifferentTenant();
tenant.setTitle("My tenant"); doPost("/api/customer", savedCustomer, Customer.class, status().isForbidden());
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); deleteDifferentTenant();
Assert.assertNotNull(savedTenant);
login(tenantAdmin.getName(), "testPassword1");
User tenantAdmin = new User(); doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); .andExpect(status().isOk());
tenantAdmin.setTenantId(savedTenant.getId()); }
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe"); @Test
tenantAdmin.setLastName("Downs"); public void testFindCustomerById() throws Exception {
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
Customer foundCustomer = doGet("/api/customer/"+savedCustomer.getId().getId().toString(), Customer.class); Customer foundCustomer = doGet("/api/customer/" + savedCustomer.getId().getId().toString(), Customer.class);
Assert.assertNotNull(foundCustomer); Assert.assertNotNull(foundCustomer);
Assert.assertEquals(savedCustomer, foundCustomer); Assert.assertEquals(savedCustomer, foundCustomer);
doDelete("/api/customer/"+savedCustomer.getId().getId().toString()) doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
public void testDeleteCustomer() throws Exception { public void testDeleteCustomer() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
doDelete("/api/customer/"+savedCustomer.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/customer/"+savedCustomer.getId().getId().toString()) doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isNotFound()); .andExpect(status().isOk());
loginSysAdmin(); doGet("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isNotFound());
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
public void testSaveCustomerWithEmptyTitle() throws Exception { public void testSaveCustomerWithEmptyTitle() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
doPost("/api/customer", customer) doPost("/api/customer", customer)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Customer title should be specified"))); .andExpect(statusReason(containsString("Customer title should be specified")));
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
public void testSaveCustomerWithInvalidEmail() throws Exception { public void testSaveCustomerWithInvalidEmail() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
customer.setEmail("invalid@mail"); customer.setEmail("invalid@mail");
doPost("/api/customer", customer) doPost("/api/customer", customer)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Invalid email address format 'invalid@mail'"))); .andExpect(statusReason(containsString("Invalid email address format 'invalid@mail'")));
loginSysAdmin(); // loginSysAdmin();
//
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) // doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk()); // .andExpect(status().isOk());
} }
@Test @Test
public void testFindCustomers() throws Exception { public void testFindCustomers() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(tenantId);
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
List<Customer> customers = new ArrayList<>(); List<Customer> customers = new ArrayList<>();
for (int i=0;i<135;i++) { for (int i = 0; i < 135; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
customer.setTitle("Customer"+i); customer.setTitle("Customer" + i);
customers.add(doPost("/api/customer", customer, Customer.class)); customers.add(doPost("/api/customer", customer, Customer.class));
} }
List<Customer> loadedCustomers = new ArrayList<>(); List<Customer> loadedCustomers = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(23); TextPageLink pageLink = new TextPageLink(23);
TextPageData<Customer> pageData = null; TextPageData<Customer> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>() {
}, pageLink);
loadedCustomers.addAll(pageData.getData()); loadedCustomers.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customers, idComparator); Collections.sort(customers, idComparator);
Collections.sort(loadedCustomers, idComparator); Collections.sort(loadedCustomers, idComparator);
Assert.assertEquals(customers, loadedCustomers); Assert.assertEquals(customers, loadedCustomers);
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
public void testFindCustomersByTitle() throws Exception { public void testFindCustomersByTitle() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(tenantId);
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
String title1 = "Customer title 1"; String title1 = "Customer title 1";
List<Customer> customersTitle1 = new ArrayList<>(); List<Customer> customersTitle1 = new ArrayList<>();
for (int i=0;i<143;i++) { for (int i = 0; i < 143; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String title = title1+suffix; String title = title1 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
customer.setTitle(title); customer.setTitle(title);
customersTitle1.add(doPost("/api/customer", customer, Customer.class)); customersTitle1.add(doPost("/api/customer", customer, Customer.class));
} }
String title2 = "Customer title 2"; String title2 = "Customer title 2";
List<Customer> customersTitle2 = new ArrayList<>(); List<Customer> customersTitle2 = new ArrayList<>();
for (int i=0;i<175;i++) { for (int i = 0; i < 175; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String title = title2+suffix; String title = title2 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
customer.setTitle(title); customer.setTitle(title);
customersTitle2.add(doPost("/api/customer", customer, Customer.class)); customersTitle2.add(doPost("/api/customer", customer, Customer.class));
} }
List<Customer> loadedCustomersTitle1 = new ArrayList<>(); List<Customer> loadedCustomersTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(15, title1); TextPageLink pageLink = new TextPageLink(15, title1);
TextPageData<Customer> pageData = null; TextPageData<Customer> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>() {
}, pageLink);
loadedCustomersTitle1.addAll(pageData.getData()); loadedCustomersTitle1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customersTitle1, idComparator); Collections.sort(customersTitle1, idComparator);
Collections.sort(loadedCustomersTitle1, idComparator); Collections.sort(loadedCustomersTitle1, idComparator);
Assert.assertEquals(customersTitle1, loadedCustomersTitle1); Assert.assertEquals(customersTitle1, loadedCustomersTitle1);
List<Customer> loadedCustomersTitle2 = new ArrayList<>(); List<Customer> loadedCustomersTitle2 = new ArrayList<>();
pageLink = new TextPageLink(4, title2); pageLink = new TextPageLink(4, title2);
do { do {
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>() {
}, pageLink);
loadedCustomersTitle2.addAll(pageData.getData()); loadedCustomersTitle2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -334,33 +243,30 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
Collections.sort(customersTitle2, idComparator); Collections.sort(customersTitle2, idComparator);
Collections.sort(loadedCustomersTitle2, idComparator); Collections.sort(loadedCustomersTitle2, idComparator);
Assert.assertEquals(customersTitle2, loadedCustomersTitle2); Assert.assertEquals(customersTitle2, loadedCustomersTitle2);
for (Customer customer : loadedCustomersTitle1) { for (Customer customer : loadedCustomersTitle1) {
doDelete("/api/customer/"+customer.getId().getId().toString()) doDelete("/api/customer/" + customer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, title1); pageLink = new TextPageLink(4, title1);
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Customer customer : loadedCustomersTitle2) { for (Customer customer : loadedCustomersTitle2) {
doDelete("/api/customer/"+customer.getId().getId().toString()) doDelete("/api/customer/" + customer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, title2); pageLink = new TextPageLink(4, title2);
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
} }

14
application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java

@ -16,10 +16,8 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.sql.Time;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -33,7 +31,6 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.model.ModelConstants;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -93,6 +90,17 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertEquals(foundDashboard.getTitle(), savedDashboard.getTitle()); Assert.assertEquals(foundDashboard.getTitle(), savedDashboard.getTitle());
} }
@Test
public void testUpdateDashboardFromDifferentTenant() throws Exception {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("My dashboard");
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
loginDifferentTenant();
doPost("/api/dashboard", savedDashboard, Dashboard.class, status().isForbidden());
deleteDifferentTenant();
}
@Test @Test
public void testFindDashboardById() throws Exception { public void testFindDashboardById() throws Exception {

11
application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java

@ -107,6 +107,17 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertEquals(foundDevice.getName(), savedDevice.getName()); Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
} }
@Test
public void testUpdateDeviceFromDifferentTenant() throws Exception {
Device device = new Device();
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
loginDifferentTenant();
doPost("/api/device", savedDevice, Device.class, status().isForbidden());
deleteDifferentTenant();
}
@Test @Test
public void testFindDeviceById() throws Exception { public void testFindDeviceById() throws Exception {

10
application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java

@ -25,7 +25,6 @@ import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -132,6 +131,15 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
assertEquals(foundEntityView.getKeys(), telemetry); assertEquals(foundEntityView.getKeys(), telemetry);
} }
@Test
public void testUpdateEntityViewFromDifferentTenant() throws Exception {
EntityView savedView = getNewSavedEntityView("Test entity view");
loginDifferentTenant();
doPost("/api/entityView", savedView, EntityView.class, status().isForbidden());
deleteDifferentTenant();
}
@Test @Test
public void testDeleteEntityView() throws Exception { public void testDeleteEntityView() throws Exception {
EntityView view = getNewSavedEntityView("Test entity view"); EntityView view = getNewSavedEntityView("Test entity view");

480
application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java

@ -38,21 +38,23 @@ import java.util.List;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseUserControllerTest extends AbstractControllerTest { public abstract class BaseUserControllerTest extends AbstractControllerTest {
private IdComparator<User> idComparator = new IdComparator<>(); private IdComparator<User> idComparator = new IdComparator<>();
@Test @Test
public void testSaveUser() throws Exception { public void testSaveUser() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org"; String email = "tenant2@thingsboard.org";
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
@ -66,13 +68,13 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
Assert.assertTrue(savedUser.getCreatedTime() > 0); Assert.assertTrue(savedUser.getCreatedTime() > 0);
Assert.assertEquals(user.getEmail(), savedUser.getEmail()); Assert.assertEquals(user.getEmail(), savedUser.getEmail());
User foundUser = doGet("/api/user/"+savedUser.getId().getId().toString(), User.class); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class);
Assert.assertEquals(foundUser, savedUser); Assert.assertEquals(foundUser, savedUser);
logout(); logout();
doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken)
.andExpect(status().isSeeOther()) .andExpect(status().isSeeOther())
.andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken));
JsonNode activateRequest = new ObjectMapper().createObjectNode() JsonNode activateRequest = new ObjectMapper().createObjectNode()
.put("activateToken", TestMailService.currentActivateToken) .put("activateToken", TestMailService.currentActivateToken)
@ -82,36 +84,61 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
validateAndSetJwtToken(tokenInfo, email); validateAndSetJwtToken(tokenInfo, email);
doGet("/api/auth/user") doGet("/api/auth/user")
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) .andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name())))
.andExpect(jsonPath("$.email",is(email))); .andExpect(jsonPath("$.email", is(email)));
logout(); logout();
login(email, "testPassword"); login(email, "testPassword");
doGet("/api/auth/user") doGet("/api/auth/user")
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) .andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name())))
.andExpect(jsonPath("$.email",is(email))); .andExpect(jsonPath("$.email", is(email)));
loginSysAdmin(); loginSysAdmin();
doDelete("/api/user/"+savedUser.getId().getId().toString()) doDelete("/api/user/" + savedUser.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test
public void testUpdateUserFromDifferentTenant() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
loginDifferentTenant();
doPost("/api/user", tenantAdmin, User.class, status().isForbidden());
deleteDifferentTenant();
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test @Test
public void testResetPassword() throws Exception { public void testResetPassword() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org"; String email = "tenant2@thingsboard.org";
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
@ -119,7 +146,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
user.setEmail(email); user.setEmail(email);
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
User savedUser = createUserAndLogin(user, "testPassword1"); User savedUser = createUserAndLogin(user, "testPassword1");
logout(); logout();
@ -127,10 +154,10 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
.put("email", email); .put("email", email);
doPost("/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest) doPost("/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest)
.andExpect(status().isOk()); .andExpect(status().isOk());
doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken) doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken)
.andExpect(status().isSeeOther()) .andExpect(status().isSeeOther())
.andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken)); .andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken));
JsonNode resetPasswordRequest = new ObjectMapper().createObjectNode() JsonNode resetPasswordRequest = new ObjectMapper().createObjectNode()
.put("resetToken", TestMailService.currentResetPasswordToken) .put("resetToken", TestMailService.currentResetPasswordToken)
@ -140,35 +167,35 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
validateAndSetJwtToken(tokenInfo, email); validateAndSetJwtToken(tokenInfo, email);
doGet("/api/auth/user") doGet("/api/auth/user")
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) .andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name())))
.andExpect(jsonPath("$.email",is(email))); .andExpect(jsonPath("$.email", is(email)));
logout(); logout();
login(email, "testPassword2"); login(email, "testPassword2");
doGet("/api/auth/user") doGet("/api/auth/user")
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) .andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name())))
.andExpect(jsonPath("$.email",is(email))); .andExpect(jsonPath("$.email", is(email)));
loginSysAdmin(); loginSysAdmin();
doDelete("/api/user/"+savedUser.getId().getId().toString()) doDelete("/api/user/" + savedUser.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testFindUserById() throws Exception { public void testFindUserById() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org"; String email = "tenant2@thingsboard.org";
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
@ -176,25 +203,25 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
user.setEmail(email); user.setEmail(email);
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
User savedUser = doPost("/api/user", user, User.class); User savedUser = doPost("/api/user", user, User.class);
User foundUser = doGet("/api/user/"+savedUser.getId().getId().toString(), User.class); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class);
Assert.assertNotNull(foundUser); Assert.assertNotNull(foundUser);
Assert.assertEquals(savedUser, foundUser); Assert.assertEquals(savedUser, foundUser);
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testSaveUserWithSameEmail() throws Exception { public void testSaveUserWithSameEmail() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
String email = TENANT_ADMIN_EMAIL; String email = TENANT_ADMIN_EMAIL;
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
@ -202,24 +229,24 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
user.setEmail(email); user.setEmail(email);
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
doPost("/api/user", user) doPost("/api/user", user)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("User with email '" + email + "' already present in database"))); .andExpect(statusReason(containsString("User with email '" + email + "' already present in database")));
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testSaveUserWithInvalidEmail() throws Exception { public void testSaveUserWithInvalidEmail() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
String email = "tenant_thingsboard.org"; String email = "tenant_thingsboard.org";
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
@ -227,62 +254,62 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
user.setEmail(email); user.setEmail(email);
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
doPost("/api/user", user) doPost("/api/user", user)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Invalid email address format '" + email + "'"))); .andExpect(statusReason(containsString("Invalid email address format '" + email + "'")));
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testSaveUserWithEmptyEmail() throws Exception { public void testSaveUserWithEmptyEmail() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId()); user.setTenantId(savedTenant.getId());
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
doPost("/api/user", user) doPost("/api/user", user)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("User email should be specified"))); .andExpect(statusReason(containsString("User email should be specified")));
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testSaveUserWithoutTenant() throws Exception { public void testSaveUserWithoutTenant() throws Exception {
loginSysAdmin(); loginSysAdmin();
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
user.setEmail("tenant2@thingsboard.org"); user.setEmail("tenant2@thingsboard.org");
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
doPost("/api/user", user) doPost("/api/user", user)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Tenant administrator should be assigned to tenant"))); .andExpect(statusReason(containsString("Tenant administrator should be assigned to tenant")));
} }
@Test @Test
public void testDeleteUser() throws Exception { public void testDeleteUser() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org"; String email = "tenant2@thingsboard.org";
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
@ -290,176 +317,182 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
user.setEmail(email); user.setEmail(email);
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
User savedUser = doPost("/api/user", user, User.class); User savedUser = doPost("/api/user", user, User.class);
User foundUser = doGet("/api/user/"+savedUser.getId().getId().toString(), User.class); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class);
Assert.assertNotNull(foundUser); Assert.assertNotNull(foundUser);
doDelete("/api/user/"+savedUser.getId().getId().toString()) doDelete("/api/user/" + savedUser.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
doGet("/api/user/"+savedUser.getId().getId().toString()) doGet("/api/user/" + savedUser.getId().getId().toString())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testFindTenantAdmins() throws Exception { public void testFindTenantAdmins() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
List<User> tenantAdmins = new ArrayList<>(); List<User> tenantAdmins = new ArrayList<>();
for (int i=0;i<64;i++) { for (int i = 0; i < 64; i++) {
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(tenantId); user.setTenantId(tenantId);
user.setEmail("testTenant" + i + "@thingsboard.org"); user.setEmail("testTenant" + i + "@thingsboard.org");
tenantAdmins.add(doPost("/api/user", user, User.class)); tenantAdmins.add(doPost("/api/user", user, User.class));
} }
List<User> loadedTenantAdmins = new ArrayList<>(); List<User> loadedTenantAdmins = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(33); TextPageLink pageLink = new TextPageLink(33);
TextPageData<User> pageData = null; TextPageData<User> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
loadedTenantAdmins.addAll(pageData.getData()); loadedTenantAdmins.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(tenantAdmins, idComparator); Collections.sort(tenantAdmins, idComparator);
Collections.sort(loadedTenantAdmins, idComparator); Collections.sort(loadedTenantAdmins, idComparator);
Assert.assertEquals(tenantAdmins, loadedTenantAdmins); Assert.assertEquals(tenantAdmins, loadedTenantAdmins);
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
pageLink = new TextPageLink(33); pageLink = new TextPageLink(33);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty()); Assert.assertTrue(pageData.getData().isEmpty());
} }
@Test @Test
public void testFindTenantAdminsByEmail() throws Exception { public void testFindTenantAdminsByEmail() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
String email1 = "testEmail1"; String email1 = "testEmail1";
List<User> tenantAdminsEmail1 = new ArrayList<>(); List<User> tenantAdminsEmail1 = new ArrayList<>();
for (int i=0;i<124;i++) { for (int i = 0; i < 124; i++) {
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(tenantId); user.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String email = email1+suffix+ "@thingsboard.org"; String email = email1 + suffix + "@thingsboard.org";
email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase();
user.setEmail(email); user.setEmail(email);
tenantAdminsEmail1.add(doPost("/api/user", user, User.class)); tenantAdminsEmail1.add(doPost("/api/user", user, User.class));
} }
String email2 = "testEmail2"; String email2 = "testEmail2";
List<User> tenantAdminsEmail2 = new ArrayList<>(); List<User> tenantAdminsEmail2 = new ArrayList<>();
for (int i=0;i<112;i++) { for (int i = 0; i < 112; i++) {
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(tenantId); user.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String email = email2+suffix+ "@thingsboard.org"; String email = email2 + suffix + "@thingsboard.org";
email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase();
user.setEmail(email); user.setEmail(email);
tenantAdminsEmail2.add(doPost("/api/user", user, User.class)); tenantAdminsEmail2.add(doPost("/api/user", user, User.class));
} }
List<User> loadedTenantAdminsEmail1 = new ArrayList<>(); List<User> loadedTenantAdminsEmail1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(33, email1); TextPageLink pageLink = new TextPageLink(33, email1);
TextPageData<User> pageData = null; TextPageData<User> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
loadedTenantAdminsEmail1.addAll(pageData.getData()); loadedTenantAdminsEmail1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(tenantAdminsEmail1, idComparator); Collections.sort(tenantAdminsEmail1, idComparator);
Collections.sort(loadedTenantAdminsEmail1, idComparator); Collections.sort(loadedTenantAdminsEmail1, idComparator);
Assert.assertEquals(tenantAdminsEmail1, loadedTenantAdminsEmail1); Assert.assertEquals(tenantAdminsEmail1, loadedTenantAdminsEmail1);
List<User> loadedTenantAdminsEmail2 = new ArrayList<>(); List<User> loadedTenantAdminsEmail2 = new ArrayList<>();
pageLink = new TextPageLink(16, email2); pageLink = new TextPageLink(16, email2);
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
loadedTenantAdminsEmail2.addAll(pageData.getData()); loadedTenantAdminsEmail2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(tenantAdminsEmail2, idComparator); Collections.sort(tenantAdminsEmail2, idComparator);
Collections.sort(loadedTenantAdminsEmail2, idComparator); Collections.sort(loadedTenantAdminsEmail2, idComparator);
Assert.assertEquals(tenantAdminsEmail2, loadedTenantAdminsEmail2); Assert.assertEquals(tenantAdminsEmail2, loadedTenantAdminsEmail2);
for (User user : loadedTenantAdminsEmail1) { for (User user : loadedTenantAdminsEmail1) {
doDelete("/api/user/"+user.getId().getId().toString()) doDelete("/api/user/" + user.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, email1); pageLink = new TextPageLink(4, email1);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (User user : loadedTenantAdminsEmail2) { for (User user : loadedTenantAdminsEmail2) {
doDelete("/api/user/"+user.getId().getId().toString()) doDelete("/api/user/" + user.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, email2); pageLink = new TextPageLink(4, email2);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testFindCustomerUsers() throws Exception { public void testFindCustomerUsers() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
User tenantAdmin = new User(); User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
@ -467,59 +500,60 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
tenantAdmin.setEmail("tenant2@thingsboard.org"); tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe"); tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs"); tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
CustomerId customerId = savedCustomer.getId(); CustomerId customerId = savedCustomer.getId();
List<User> customerUsers = new ArrayList<>(); List<User> customerUsers = new ArrayList<>();
for (int i=0;i<56;i++) { for (int i = 0; i < 56; i++) {
User user = new User(); User user = new User();
user.setAuthority(Authority.CUSTOMER_USER); user.setAuthority(Authority.CUSTOMER_USER);
user.setCustomerId(customerId); user.setCustomerId(customerId);
user.setEmail("testCustomer" + i + "@thingsboard.org"); user.setEmail("testCustomer" + i + "@thingsboard.org");
customerUsers.add(doPost("/api/user", user, User.class)); customerUsers.add(doPost("/api/user", user, User.class));
} }
List<User> loadedCustomerUsers = new ArrayList<>(); List<User> loadedCustomerUsers = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(33); TextPageLink pageLink = new TextPageLink(33);
TextPageData<User> pageData = null; TextPageData<User> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
loadedCustomerUsers.addAll(pageData.getData()); loadedCustomerUsers.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customerUsers, idComparator); Collections.sort(customerUsers, idComparator);
Collections.sort(loadedCustomerUsers, idComparator); Collections.sort(loadedCustomerUsers, idComparator);
Assert.assertEquals(customerUsers, loadedCustomerUsers); Assert.assertEquals(customerUsers, loadedCustomerUsers);
doDelete("/api/customer/"+customerId.getId().toString()) doDelete("/api/customer/" + customerId.getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testFindCustomerUsersByEmail() throws Exception { public void testFindCustomerUsersByEmail() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
User tenantAdmin = new User(); User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
@ -527,105 +561,109 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
tenantAdmin.setEmail("tenant2@thingsboard.org"); tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe"); tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs"); tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
CustomerId customerId = savedCustomer.getId(); CustomerId customerId = savedCustomer.getId();
String email1 = "testEmail1"; String email1 = "testEmail1";
List<User> customerUsersEmail1 = new ArrayList<>(); List<User> customerUsersEmail1 = new ArrayList<>();
for (int i=0;i<74;i++) { for (int i = 0; i < 74; i++) {
User user = new User(); User user = new User();
user.setAuthority(Authority.CUSTOMER_USER); user.setAuthority(Authority.CUSTOMER_USER);
user.setCustomerId(customerId); user.setCustomerId(customerId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String email = email1+suffix+ "@thingsboard.org"; String email = email1 + suffix + "@thingsboard.org";
email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase();
user.setEmail(email); user.setEmail(email);
customerUsersEmail1.add(doPost("/api/user", user, User.class)); customerUsersEmail1.add(doPost("/api/user", user, User.class));
} }
String email2 = "testEmail2"; String email2 = "testEmail2";
List<User> customerUsersEmail2 = new ArrayList<>(); List<User> customerUsersEmail2 = new ArrayList<>();
for (int i=0;i<92;i++) { for (int i = 0; i < 92; i++) {
User user = new User(); User user = new User();
user.setAuthority(Authority.CUSTOMER_USER); user.setAuthority(Authority.CUSTOMER_USER);
user.setCustomerId(customerId); user.setCustomerId(customerId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String email = email2+suffix+ "@thingsboard.org"; String email = email2 + suffix + "@thingsboard.org";
email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase();
user.setEmail(email); user.setEmail(email);
customerUsersEmail2.add(doPost("/api/user", user, User.class)); customerUsersEmail2.add(doPost("/api/user", user, User.class));
} }
List<User> loadedCustomerUsersEmail1 = new ArrayList<>(); List<User> loadedCustomerUsersEmail1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(33, email1); TextPageLink pageLink = new TextPageLink(33, email1);
TextPageData<User> pageData = null; TextPageData<User> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
loadedCustomerUsersEmail1.addAll(pageData.getData()); loadedCustomerUsersEmail1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customerUsersEmail1, idComparator); Collections.sort(customerUsersEmail1, idComparator);
Collections.sort(loadedCustomerUsersEmail1, idComparator); Collections.sort(loadedCustomerUsersEmail1, idComparator);
Assert.assertEquals(customerUsersEmail1, loadedCustomerUsersEmail1); Assert.assertEquals(customerUsersEmail1, loadedCustomerUsersEmail1);
List<User> loadedCustomerUsersEmail2 = new ArrayList<>(); List<User> loadedCustomerUsersEmail2 = new ArrayList<>();
pageLink = new TextPageLink(16, email2); pageLink = new TextPageLink(16, email2);
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
loadedCustomerUsersEmail2.addAll(pageData.getData()); loadedCustomerUsersEmail2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customerUsersEmail2, idComparator); Collections.sort(customerUsersEmail2, idComparator);
Collections.sort(loadedCustomerUsersEmail2, idComparator); Collections.sort(loadedCustomerUsersEmail2, idComparator);
Assert.assertEquals(customerUsersEmail2, loadedCustomerUsersEmail2); Assert.assertEquals(customerUsersEmail2, loadedCustomerUsersEmail2);
for (User user : loadedCustomerUsersEmail1) { for (User user : loadedCustomerUsersEmail1) {
doDelete("/api/user/"+user.getId().getId().toString()) doDelete("/api/user/" + user.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, email1); pageLink = new TextPageLink(4, email1);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (User user : loadedCustomerUsersEmail2) { for (User user : loadedCustomerUsersEmail2) {
doDelete("/api/user/"+user.getId().getId().toString()) doDelete("/api/user/" + user.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, email2); pageLink = new TextPageLink(4, email2);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
doDelete("/api/customer/"+customerId.getId().toString()) doDelete("/api/customer/" + customerId.getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
} }

14
application/src/test/java/org/thingsboard/server/controller/BaseWidgetTypeControllerTest.java

@ -64,7 +64,6 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes
WidgetsBundle widgetsBundle = new WidgetsBundle(); WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("My widgets bundle"); widgetsBundle.setTitle("My widgets bundle");
savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class);
} }
@After @After
@ -100,6 +99,19 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes
Assert.assertEquals(foundWidgetType.getName(), savedWidgetType.getName()); Assert.assertEquals(foundWidgetType.getName(), savedWidgetType.getName());
} }
@Test
public void testUpdateWidgetTypeFromDifferentTenant() throws Exception {
WidgetType widgetType = new WidgetType();
widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
widgetType.setName("Widget Type");
widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class);
loginDifferentTenant();
doPost("/api/widgetType", savedWidgetType, WidgetType.class, status().isForbidden());
deleteDifferentTenant();
}
@Test @Test
public void testFindWidgetTypeById() throws Exception { public void testFindWidgetTypeById() throws Exception {
WidgetType widgetType = new WidgetType(); WidgetType widgetType = new WidgetType();

11
application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java

@ -88,6 +88,17 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController
Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle()); Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle());
} }
@Test
public void testUpdateWidgetsBundleFromDifferentTenant() throws Exception {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("My widgets bundle");
WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class);
loginDifferentTenant();
doPost("/api/widgetsBundle", savedWidgetsBundle, WidgetsBundle.class, status().isForbidden());
deleteDifferentTenant();
}
@Test @Test
public void testFindWidgetsBundleById() throws Exception { public void testFindWidgetsBundleById() throws Exception {
WidgetsBundle widgetsBundle = new WidgetsBundle(); WidgetsBundle widgetsBundle = new WidgetsBundle();

50
application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java

@ -1,50 +0,0 @@
/**
* Copyright © 2016-2020 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.rules;
import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomCassandraCQLUnit;
import org.thingsboard.server.dao.CustomSqlUnit;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.rules.flow.nosql.*Test",
"org.thingsboard.server.rules.lifecycle.nosql.*Test"
})
public class RuleEngineNoSqlTestSuite {
@ClassRule
public static CustomCassandraCQLUnit cassandraUnit =
new CustomCassandraCQLUnit(
Arrays.asList(
new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),
new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)),
"cassandra-test.yaml", 30000l);
@BeforeClass
public static void cleanupInMemStorage(){
InMemoryStorage.getInstance().cleanup();
}
}

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

8
common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java

@ -25,6 +25,8 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -67,9 +69,7 @@ public interface AssetService {
Asset assignAssetToEdge(TenantId tenantId, AssetId assetId, EdgeId edgeId); Asset assignAssetToEdge(TenantId tenantId, AssetId assetId, EdgeId edgeId);
Asset unassignAssetFromEdge(TenantId tenantId, AssetId assetId); Asset unassignAssetFromEdge(TenantId tenantId, AssetId assetId, EdgeId edgeId);
TextPageData<Asset> findAssetsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TextPageLink pageLink); ListenableFuture<TimePageData<Asset>> findAssetsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink);
TextPageData<Asset> findAssetsByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, TextPageLink pageLink);
} }

11
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java

@ -25,11 +25,13 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import java.util.List; import java.util.List;
public interface DeviceService { public interface DeviceService {
Device findDeviceById(TenantId tenantId, DeviceId deviceId); Device findDeviceById(TenantId tenantId, DeviceId deviceId);
ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId); ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId);
@ -68,10 +70,7 @@ public interface DeviceService {
Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId); Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);
Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId); Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);
TextPageData<Device> findDevicesByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TextPageLink pageLink);
TextPageData<Device> findDevicesByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, TextPageLink pageLink);
ListenableFuture<TimePageData<Device>> findDevicesByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink);
} }

2
common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java

@ -82,6 +82,8 @@ public interface EdgeService {
Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException; Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException;
void assignDefaultRuleChainsToEdge(TenantId tenantId, EdgeId edgeId);
ListenableFuture<TimePageData<Edge>> findEdgesByTenantIdAndRuleChainId(TenantId tenantId, RuleChainId ruleChainId, TimePageLink pageLink); ListenableFuture<TimePageData<Edge>> findEdgesByTenantIdAndRuleChainId(TenantId tenantId, RuleChainId ruleChainId, TimePageLink pageLink);
ListenableFuture<TimePageData<Edge>> findEdgesByTenantIdAndDashboardId(TenantId tenantId, DashboardId dashboardId, TimePageLink pageLink); ListenableFuture<TimePageData<Edge>> findEdgesByTenantIdAndDashboardId(TenantId tenantId, DashboardId dashboardId, TimePageLink pageLink);

10
common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java

@ -26,6 +26,8 @@ import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import java.util.List; import java.util.List;
@ -68,11 +70,7 @@ public interface EntityViewService {
EntityView assignEntityViewToEdge(TenantId tenantId, EntityViewId entityViewId, EdgeId edgeId); EntityView assignEntityViewToEdge(TenantId tenantId, EntityViewId entityViewId, EdgeId edgeId);
EntityView unassignEntityViewFromEdge(TenantId tenantId, EntityViewId entityViewId); EntityView unassignEntityViewFromEdge(TenantId tenantId, EntityViewId entityViewId, EdgeId edgeId);
TextPageData<EntityView> findEntityViewsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TextPageLink pageLink);
TextPageData<EntityView> findEntityViewsByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, TextPageLink pageLink);
ListenableFuture<TimePageData<EntityView>> findEntityViewsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink);
} }

2
common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2User.java

@ -28,4 +28,6 @@ public class OAuth2User {
private String email; private String email;
private String firstName; private String firstName;
private String lastName; private String lastName;
private boolean alwaysFullScreen;
private String defaultDashboardName;
} }

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

13
common/data/src/main/java/org/thingsboard/server/common/data/Device.java

@ -18,7 +18,6 @@ package org.thingsboard.server.common.data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -28,7 +27,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
private TenantId tenantId; private TenantId tenantId;
private CustomerId customerId; private CustomerId customerId;
private EdgeId edgeId;
private String name; private String name;
private String type; private String type;
private String label; private String label;
@ -48,7 +46,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.name = device.getName(); this.name = device.getName();
this.type = device.getType(); this.type = device.getType();
this.label = device.getLabel(); this.label = device.getLabel();
this.edgeId = device.getEdgeId();
} }
public TenantId getTenantId() { public TenantId getTenantId() {
@ -67,14 +64,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.customerId = customerId; this.customerId = customerId;
} }
public EdgeId getEdgeId() {
return edgeId;
}
public void setEdgeId(EdgeId edgeId) {
this.edgeId = edgeId;
}
@Override @Override
public String getName() { public String getName() {
return name; return name;
@ -112,8 +101,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
builder.append(tenantId); builder.append(tenantId);
builder.append(", customerId="); builder.append(", customerId=");
builder.append(customerId); builder.append(customerId);
builder.append(", edgeId=");
builder.append(edgeId);
builder.append(", name="); builder.append(", name=");
builder.append(name); builder.append(name);
builder.append(", type="); builder.append(", type=");

2
common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java

@ -19,7 +19,6 @@ import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -40,7 +39,6 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
private EntityId entityId; private EntityId entityId;
private TenantId tenantId; private TenantId tenantId;
private CustomerId customerId; private CustomerId customerId;
private EdgeId edgeId;
private String name; private String name;
private String type; private String type;
private TelemetryEntityView keys; private TelemetryEntityView keys;

15
common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java

@ -15,13 +15,10 @@
*/ */
package org.thingsboard.server.common.data.asset; package org.thingsboard.server.common.data.asset;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -34,7 +31,6 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
private String name; private String name;
private String type; private String type;
private String label; private String label;
private EdgeId edgeId;
public Asset() { public Asset() {
super(); super();
@ -51,7 +47,6 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
this.name = asset.getName(); this.name = asset.getName();
this.type = asset.getType(); this.type = asset.getType();
this.label = asset.getLabel(); this.label = asset.getLabel();
this.edgeId = asset.getEdgeId();
} }
public TenantId getTenantId() { public TenantId getTenantId() {
@ -70,14 +65,6 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
this.customerId = customerId; this.customerId = customerId;
} }
public EdgeId getEdgeId() {
return edgeId;
}
public void setEdgeId(EdgeId edgeId) {
this.edgeId = edgeId;
}
@Override @Override
public String getName() { public String getName() {
return name; return name;
@ -115,8 +102,6 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
builder.append(tenantId); builder.append(tenantId);
builder.append(", customerId="); builder.append(", customerId=");
builder.append(customerId); builder.append(customerId);
builder.append(", edgeId=");
builder.append(edgeId);
builder.append(", name="); builder.append(", name=");
builder.append(name); builder.append(name);
builder.append(", type="); builder.append(", type=");

3
common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java

@ -22,6 +22,7 @@ public enum RelationTypeGroup {
DASHBOARD, DASHBOARD,
RULE_CHAIN, RULE_CHAIN,
RULE_NODE, RULE_NODE,
EDGE EDGE,
EDGE_DEFAULT_RULE_CHAIN
} }

2
common/edge-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

30
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java

@ -40,6 +40,7 @@ import java.util.UUID;
public final class TbMsg implements Serializable { public final class TbMsg implements Serializable {
private final UUID id; private final UUID id;
private final long ts;
private final String type; private final String type;
private final EntityId originator; private final EntityId originator;
private final TbMsgMetaData metaData; private final TbMsgMetaData metaData;
@ -51,38 +52,43 @@ public final class TbMsg implements Serializable {
transient private final TbMsgCallback callback; transient private final TbMsgCallback callback;
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(UUID.randomUUID(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, null, null, TbMsgCallback.EMPTY); return new TbMsg(UUID.randomUUID(), System.currentTimeMillis(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, null, null, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(UUID.randomUUID(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, TbMsgCallback.EMPTY); return new TbMsg(UUID.randomUUID(), System.currentTimeMillis(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) {
return new TbMsg(UUID.randomUUID(), type, originator, metaData.copy(), dataType, data, null, null, TbMsgCallback.EMPTY); return new TbMsg(UUID.randomUUID(), System.currentTimeMillis(), type, originator, metaData.copy(), dataType, data, null, null, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(UUID.randomUUID(), type, originator, metaData.copy(), dataType, data, ruleChainId, ruleNodeId, TbMsgCallback.EMPTY); return new TbMsg(UUID.randomUUID(), System.currentTimeMillis(), type, originator, metaData.copy(), dataType, data, ruleChainId, ruleNodeId, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) {
return new TbMsg(UUID.randomUUID(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, null, null, callback); return new TbMsg(UUID.randomUUID(), System.currentTimeMillis(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, null, null, callback);
} }
public static TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { public static TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), origMsg.getDataType(), return new TbMsg(origMsg.getId(), origMsg.getTs(), type, originator, metaData.copy(), origMsg.getDataType(),
data, origMsg.getRuleChainId(), origMsg.getRuleNodeId(), origMsg.getCallback()); data, origMsg.getRuleChainId(), origMsg.getRuleNodeId(), origMsg.getCallback());
} }
public static TbMsg newMsg(TbMsg tbMsg, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public static TbMsg newMsg(TbMsg tbMsg, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(UUID.randomUUID(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(), return new TbMsg(UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(),
tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, TbMsgCallback.EMPTY); tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, TbMsgCallback.EMPTY);
} }
private TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, private TbMsg(UUID id, long ts, String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data,
RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgCallback callback) { RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgCallback callback) {
this.id = id; this.id = id;
if (ts > 0) {
this.ts = ts;
} else {
this.ts = System.currentTimeMillis();
}
this.type = type; this.type = type;
this.originator = originator; this.originator = originator;
this.metaData = metaData; this.metaData = metaData;
@ -105,6 +111,7 @@ public final class TbMsg implements Serializable {
public static byte[] toByteArray(TbMsg msg) { public static byte[] toByteArray(TbMsg msg) {
MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder(); MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder();
builder.setId(msg.getId().toString()); builder.setId(msg.getId().toString());
builder.setTs(msg.getTs());
builder.setType(msg.getType()); builder.setType(msg.getType());
builder.setEntityType(msg.getOriginator().getEntityType().name()); builder.setEntityType(msg.getOriginator().getEntityType().name());
builder.setEntityIdMSB(msg.getOriginator().getId().getMostSignificantBits()); builder.setEntityIdMSB(msg.getOriginator().getId().getMostSignificantBits());
@ -124,7 +131,6 @@ public final class TbMsg implements Serializable {
builder.setMetaData(MsgProtos.TbMsgMetaDataProto.newBuilder().putAllData(msg.getMetaData().getData()).build()); builder.setMetaData(MsgProtos.TbMsgMetaDataProto.newBuilder().putAllData(msg.getMetaData().getData()).build());
} }
builder.setDataType(msg.getDataType().ordinal()); builder.setDataType(msg.getDataType().ordinal());
builder.setData(msg.getData()); builder.setData(msg.getData());
return builder.build().toByteArray(); return builder.build().toByteArray();
@ -144,18 +150,18 @@ public final class TbMsg implements Serializable {
ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB())); ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB()));
} }
TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()]; TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()];
return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, callback); return new TbMsg(UUID.fromString(proto.getId()), proto.getTs(), proto.getType(), entityId, metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, callback);
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
throw new IllegalStateException("Could not parse protobuf for TbMsg", e); throw new IllegalStateException("Could not parse protobuf for TbMsg", e);
} }
} }
public TbMsg copyWithRuleChainId(RuleChainId ruleChainId) { public TbMsg copyWithRuleChainId(RuleChainId ruleChainId) {
return new TbMsg(this.id, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, null, callback); return new TbMsg(this.id, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, null, callback);
} }
public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(this.id, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, callback); return new TbMsg(this.id, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, callback);
} }
public TbMsgCallback getCallback() { public TbMsgCallback getCallback() {

1
common/message/src/main/proto/tbmsg.proto

@ -44,4 +44,5 @@ message TbMsgProto {
int32 dataType = 13; int32 dataType = 13;
string data = 14; string data = 14;
int64 ts = 15;
} }

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>common</artifactId> <artifactId>common</artifactId>

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusConsumerTemplate.java

@ -101,7 +101,7 @@ public class TbServiceBusConsumerTemplate<T extends TbQueueMsg> extends Abstract
@Override @Override
protected void doSubscribe(List<String> topicNames) { protected void doSubscribe(List<String> topicNames) {
createReceivers(); createReceivers();
messagesPerQueue = receivers.size() / partitions.size(); messagesPerQueue = receivers.size() / Math.max(partitions.size(), 1);
} }
@Override @Override

7
common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java

@ -85,7 +85,12 @@ public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> i
subscribed = true; subscribed = true;
} }
List<R> records = doPoll(durationInMillis); List<R> records;
if (partitions.isEmpty()) {
records = Collections.emptyList();
} else {
records = doPoll(durationInMillis);
}
if (!records.isEmpty()) { if (!records.isEmpty()) {
List<T> result = new ArrayList<>(records.size()); List<T> result = new ArrayList<>(records.size());
records.forEach(record -> { records.forEach(record -> {

9
common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java

@ -36,6 +36,7 @@ import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -148,6 +149,14 @@ public class HashPartitionService implements PartitionService {
} }
} }
}); });
oldPartitions.forEach((serviceQueueKey, partitions) -> {
if (!myPartitions.containsKey(serviceQueueKey)) {
log.info("[{}] NO MORE PARTITIONS FOR CURRENT KEY", serviceQueueKey);
applicationEventPublisher.publishEvent(new PartitionChangeEvent(this, serviceQueueKey, Collections.emptySet()));
}
});
myPartitions.forEach((serviceQueueKey, partitions) -> { myPartitions.forEach((serviceQueueKey, partitions) -> {
if (!partitions.equals(oldPartitions.get(serviceQueueKey))) { if (!partitions.equals(oldPartitions.get(serviceQueueKey))) {
log.info("[{}] NEW PARTITIONS: {}", serviceQueueKey, partitions); log.info("[{}] NEW PARTITIONS: {}", serviceQueueKey, partitions);

8
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java

@ -71,8 +71,12 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
@Override @Override
protected void doSubscribe(List<String> topicNames) { protected void doSubscribe(List<String> topicNames) {
topicNames.forEach(admin::createTopicIfNotExists); if (!topicNames.isEmpty()) {
consumer.subscribe(topicNames); topicNames.forEach(admin::createTopicIfNotExists);
consumer.subscribe(topicNames);
} else {
consumer.unsubscribe();
}
} }
@Override @Override

2
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java

@ -200,7 +200,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
consumerBuilder.settings(kafkaSettings); consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(transportApiSettings.getRequestsTopic()); consumerBuilder.topic(transportApiSettings.getRequestsTopic());
consumerBuilder.clientId("monolith-transport-api-consumer-" + serviceInfoProvider.getServiceId()); consumerBuilder.clientId("monolith-transport-api-consumer-" + serviceInfoProvider.getServiceId());
consumerBuilder.groupId("monolith-transport-api-consumer-" + serviceInfoProvider.getServiceId()); consumerBuilder.groupId("monolith-transport-api-consumer");
consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders()));
consumerBuilder.admin(transportApiAdmin); consumerBuilder.admin(transportApiAdmin);
return consumerBuilder.build(); return consumerBuilder.build();

2
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java

@ -170,7 +170,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
consumerBuilder.settings(kafkaSettings); consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(transportApiSettings.getRequestsTopic()); consumerBuilder.topic(transportApiSettings.getRequestsTopic());
consumerBuilder.clientId("tb-core-transport-api-consumer-" + serviceInfoProvider.getServiceId()); consumerBuilder.clientId("tb-core-transport-api-consumer-" + serviceInfoProvider.getServiceId());
consumerBuilder.groupId("tb-core-transport-api-consumer-" + serviceInfoProvider.getServiceId()); consumerBuilder.groupId("tb-core-transport-api-consumer");
consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders()));
consumerBuilder.admin(transportApiAdmin); consumerBuilder.admin(transportApiAdmin);
return consumerBuilder.build(); return consumerBuilder.build();

2
common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubConsumerTemplate.java

@ -106,7 +106,7 @@ public class TbPubSubConsumerTemplate<T extends TbQueueMsg> extends AbstractPara
subscriptionNames = new LinkedHashSet<>(topicNames); subscriptionNames = new LinkedHashSet<>(topicNames);
subscriptionNames.forEach(admin::createTopicIfNotExists); subscriptionNames.forEach(admin::createTopicIfNotExists);
initNewExecutor(subscriptionNames.size() + 1); initNewExecutor(subscriptionNames.size() + 1);
messagesPerTopic = pubSubSettings.getMaxMessages() / subscriptionNames.size(); messagesPerTopic = pubSubSettings.getMaxMessages() / Math.max(subscriptionNames.size(), 1);
} }
@Override @Override

2
common/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -294,7 +294,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
break; break;
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS); log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS, e);
grantedQoSList.add(FAILURE.value()); grantedQoSList.add(FAILURE.value());
} }
} }

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

6
common/transport/transport-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>
@ -52,10 +52,6 @@
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<artifactId>util</artifactId> <artifactId>util</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>queue</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>

2
common/util/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>dao</artifactId> <artifactId>dao</artifactId>

16
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -341,16 +341,6 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} }
} }
private void deleteRelation(TenantId tenantId, EntityRelation alarmRelation) {
log.debug("Deleting Alarm relation: {}", alarmRelation);
relationService.deleteRelation(tenantId, alarmRelation);
}
private void createRelation(TenantId tenantId, EntityRelation alarmRelation) {
log.debug("Creating Alarm relation: {}", alarmRelation);
relationService.saveRelation(tenantId, alarmRelation);
}
private Alarm merge(Alarm existing, Alarm alarm) { private Alarm merge(Alarm existing, Alarm alarm) {
if (alarm.getStartTs() > existing.getEndTs()) { if (alarm.getStartTs() > existing.getEndTs()) {
existing.setEndTs(alarm.getStartTs()); existing.setEndTs(alarm.getStartTs());
@ -395,7 +385,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} }
} }
private void createAlarmRelation(TenantId tenantId, EntityId entityId, EntityId alarmId, AlarmStatus status, boolean createAnyRelation) { private void createAlarmRelation(TenantId tenantId, EntityId entityId, EntityId alarmId, AlarmStatus status, boolean createAnyRelation) throws ExecutionException, InterruptedException {
if (createAnyRelation) { if (createAnyRelation) {
createRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + AlarmSearchStatus.ANY.name(), RelationTypeGroup.ALARM)); createRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + AlarmSearchStatus.ANY.name(), RelationTypeGroup.ALARM));
} }
@ -404,13 +394,13 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
createRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM)); createRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM));
} }
private void deleteAlarmRelation(TenantId tenantId, EntityId entityId, EntityId alarmId, AlarmStatus status) { private void deleteAlarmRelation(TenantId tenantId, EntityId entityId, EntityId alarmId, AlarmStatus status) throws ExecutionException, InterruptedException {
deleteRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.name(), RelationTypeGroup.ALARM)); deleteRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.name(), RelationTypeGroup.ALARM));
deleteRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getClearSearchStatus().name(), RelationTypeGroup.ALARM)); deleteRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getClearSearchStatus().name(), RelationTypeGroup.ALARM));
deleteRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM)); deleteRelation(tenantId, new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM));
} }
private void updateAlarmRelation(TenantId tenantId, EntityId entityId, EntityId alarmId, AlarmStatus oldStatus, AlarmStatus newStatus) { private void updateAlarmRelation(TenantId tenantId, EntityId entityId, EntityId alarmId, AlarmStatus oldStatus, AlarmStatus newStatus) throws ExecutionException, InterruptedException {
deleteAlarmRelation(tenantId, entityId, alarmId, oldStatus); deleteAlarmRelation(tenantId, entityId, alarmId, oldStatus);
createAlarmRelation(tenantId, entityId, alarmId, newStatus, false); createAlarmRelation(tenantId, entityId, alarmId, newStatus, false);
} }

16
dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import java.util.List; import java.util.List;
@ -116,23 +117,12 @@ public interface AssetDao extends Dao<Asset> {
ListenableFuture<List<EntitySubtype>> findTenantAssetTypesAsync(UUID tenantId); ListenableFuture<List<EntitySubtype>> findTenantAssetTypesAsync(UUID tenantId);
/** /**
* Find assets by tenantId, customerId and page link. * Find assets by tenantId, edgeId and page link.
* *
* @param tenantId the tenantId * @param tenantId the tenantId
* @param edgeId the edgeId * @param edgeId the edgeId
* @param pageLink the page link * @param pageLink the page link
* @return the list of asset objects * @return the list of asset objects
*/ */
List<Asset> findAssetsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TextPageLink pageLink); ListenableFuture<List<Asset>> findAssetsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink);
/**
* Find assets by tenantId, customerId, type and page link.
*
* @param tenantId the tenantId
* @param edgeId the edgeId
* @param type the type
* @param pageLink the page link
* @return the list of asset objects
*/
List<Asset> findAssetsByTenantIdAndEdgeIdAndType(UUID tenantId, UUID edgeId, String type, TextPageLink pageLink);
} }

65
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.asset; package org.thingsboard.server.dao.asset;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
@ -29,12 +30,14 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.asset.AssetSearchQuery;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
@ -42,9 +45,13 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
@ -52,6 +59,7 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -89,6 +97,9 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
@Autowired @Autowired
private EntityViewService entityViewService; private EntityViewService entityViewService;
@Autowired
private EdgeService edgeService;
@Autowired @Autowired
private CacheManager cacheManager; private CacheManager cacheManager;
@ -285,36 +296,52 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
@Override @Override
public Asset assignAssetToEdge(TenantId tenantId, AssetId assetId, EdgeId edgeId) { public Asset assignAssetToEdge(TenantId tenantId, AssetId assetId, EdgeId edgeId) {
Asset asset = findAssetById(tenantId, assetId); Asset asset = findAssetById(tenantId, assetId);
asset.setEdgeId(edgeId); Edge edge = edgeService.findEdgeById(tenantId, edgeId);
return saveAsset(asset); if (edge == null) {
throw new DataValidationException("Can't assign asset to non-existent edge!");
}
if (!edge.getTenantId().getId().equals(asset.getTenantId().getId())) {
throw new DataValidationException("Can't assign asset to edge from different tenant!");
}
try {
createRelation(tenantId, new EntityRelation(edgeId, assetId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE));
} catch (ExecutionException | InterruptedException e) {
log.warn("[{}] Failed to create asset relation. Edge Id: [{}]", assetId, edgeId);
throw new RuntimeException(e);
}
return asset;
} }
@Override @Override
public Asset unassignAssetFromEdge(TenantId tenantId, AssetId assetId) { public Asset unassignAssetFromEdge(TenantId tenantId, AssetId assetId, EdgeId edgeId) {
Asset asset = findAssetById(tenantId, assetId); Asset asset = findAssetById(tenantId, assetId);
asset.setEdgeId(null); Edge edge = edgeService.findEdgeById(tenantId, edgeId);
return saveAsset(asset); if (edge == null) {
throw new DataValidationException("Can't unassign asset from non-existent edge!");
}
try {
deleteRelation(tenantId, new EntityRelation(edgeId, assetId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE));
} catch (ExecutionException | InterruptedException e) {
log.warn("[{}] Failed to delete asset relation. Edge Id: [{}]", assetId, edgeId);
throw new RuntimeException(e);
}
return asset;
} }
@Override @Override
public TextPageData<Asset> findAssetsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TextPageLink pageLink) { public ListenableFuture<TimePageData<Asset>> findAssetsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink) {
log.trace("Executing findAssetsByTenantIdAndEdgeId, tenantId [{}], edgeId [{}], pageLink [{}]", tenantId, edgeId, pageLink); log.trace("Executing findAssetsByTenantIdAndEdgeId, tenantId [{}], edgeId [{}], pageLink [{}]", tenantId, edgeId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(edgeId, INCORRECT_EDGE_ID + edgeId); validateId(edgeId, INCORRECT_EDGE_ID + edgeId);
validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
List<Asset> assets = assetDao.findAssetsByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink); ListenableFuture<List<Asset>> assets = assetDao.findAssetsByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink);
return new TextPageData<>(assets, pageLink); return Futures.transform(assets, new Function<List<Asset>, TimePageData<Asset>>() {
} @Nullable
@Override
@Override public TimePageData<Asset> apply(@Nullable List<Asset> assets) {
public TextPageData<Asset> findAssetsByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, TextPageLink pageLink) { return new TimePageData<>(assets, pageLink);
log.trace("Executing findAssetsByTenantIdAndEdgeIdAndType, tenantId [{}], edgeId [{}], type [{}], pageLink [{}]", tenantId, edgeId, type, pageLink); }
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); }, MoreExecutors.directExecutor());
validateId(edgeId, INCORRECT_EDGE_ID + edgeId);
validateString(type, "Incorrect type " + type);
validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
List<Asset> assets = assetDao.findAssetsByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink);
return new TextPageData<>(assets, pageLink);
} }
private DataValidator<Asset> assetValidator = private DataValidator<Asset> assetValidator =

44
dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java

@ -25,16 +25,23 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.EntitySubtypeEntity; import org.thingsboard.server.dao.model.EntitySubtypeEntity;
import org.thingsboard.server.dao.model.nosql.AssetEntity; import org.thingsboard.server.dao.model.nosql.AssetEntity;
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.util.NoSqlDao; import org.thingsboard.server.dao.util.NoSqlDao;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -68,6 +75,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
@NoSqlDao @NoSqlDao
public class CassandraAssetDao extends CassandraAbstractSearchTextDao<AssetEntity, Asset> implements AssetDao { public class CassandraAssetDao extends CassandraAbstractSearchTextDao<AssetEntity, Asset> implements AssetDao {
@Autowired
private RelationDao relationDao;
@Override @Override
protected Class<AssetEntity> getColumnFamilyClass() { protected Class<AssetEntity> getColumnFamilyClass() {
return AssetEntity.class; return AssetEntity.class;
@ -190,30 +200,16 @@ public class CassandraAssetDao extends CassandraAbstractSearchTextDao<AssetEntit
} }
@Override @Override
public List<Asset> findAssetsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TextPageLink pageLink) { public ListenableFuture<List<Asset>> findAssetsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) {
// log.debug("Try to find assets by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); log.debug("Try to find assets by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);
// List<AssetEntity> assetEntities = findPageWithTextSearch(new TenantId(tenantId), ASSET_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new TenantId(tenantId), new EdgeId(edgeId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE, EntityType.ASSET, pageLink);
// Arrays.asList(eq(ASSET_CUSTOMER_ID_PROPERTY, customerId), return Futures.transformAsync(relations, input -> {
// eq(ASSET_TENANT_ID_PROPERTY, tenantId)), List<ListenableFuture<Asset>> assetFutures = new ArrayList<>(input.size());
// pageLink); for (EntityRelation relation : input) {
// assetFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId()));
// log.trace("Found assets [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", assetEntities, tenantId, customerId, pageLink); }
// return DaoUtil.convertDataList(assetEntities); return Futures.successfulAsList(assetFutures);
throw new UnsupportedOperationException("Cassandra is not supported yet"); }, MoreExecutors.directExecutor());
}
@Override
public List<Asset> findAssetsByTenantIdAndEdgeIdAndType(UUID tenantId, UUID edgeId, String type, TextPageLink pageLink) {
// log.debug("Try to find assets by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", tenantId, customerId, type, pageLink);
// List<AssetEntity> assetEntities = findPageWithTextSearch(new TenantId(tenantId), ASSET_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
// Arrays.asList(eq(ASSET_TYPE_PROPERTY, type),
// eq(ASSET_CUSTOMER_ID_PROPERTY, customerId),
// eq(ASSET_TENANT_ID_PROPERTY, tenantId)),
// pageLink);
//
// log.trace("Found assets [{}] by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", assetEntities, tenantId, customerId, type, pageLink);
// return DaoUtil.convertDataList(assetEntities);
throw new UnsupportedOperationException("Cassandra is not supported yet");
} }
} }

10
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -162,16 +162,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
} }
} }
private void deleteRelation(TenantId tenantId, EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
log.debug("Deleting Dashboard relation: {}", dashboardRelation);
relationService.deleteRelationAsync(tenantId, dashboardRelation).get();
}
private void createRelation(TenantId tenantId, EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
log.debug("Creating Dashboard relation: {}", dashboardRelation);
relationService.saveRelationAsync(tenantId, dashboardRelation).get();
}
@Override @Override
public void deleteDashboard(TenantId tenantId, DashboardId dashboardId) { public void deleteDashboard(TenantId tenantId, DashboardId dashboardId) {
log.trace("Executing deleteDashboard [{}]", dashboardId); log.trace("Executing deleteDashboard [{}]", dashboardId);

42
dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java

@ -25,16 +25,22 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.EntitySubtypeEntity; import org.thingsboard.server.dao.model.EntitySubtypeEntity;
import org.thingsboard.server.dao.model.nosql.DeviceEntity; import org.thingsboard.server.dao.model.nosql.DeviceEntity;
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.util.NoSqlDao; import org.thingsboard.server.dao.util.NoSqlDao;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -68,6 +74,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
@NoSqlDao @NoSqlDao
public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEntity, Device> implements DeviceDao { public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEntity, Device> implements DeviceDao {
@Autowired
private RelationDao relationDao;
@Override @Override
protected Class<DeviceEntity> getColumnFamilyClass() { protected Class<DeviceEntity> getColumnFamilyClass() {
return DeviceEntity.class; return DeviceEntity.class;
@ -190,30 +199,17 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
} }
@Override @Override
public List<Device> findDevicesByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TextPageLink pageLink) { public ListenableFuture<List<Device>> findDevicesByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) {
// log.debug("Try to find devices by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); log.debug("Try to find devices by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);
// List<DeviceEntity> deviceEntities = findPageWithTextSearch(new TenantId(tenantId), DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new TenantId(tenantId), new EdgeId(edgeId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE, EntityType.DEVICE, pageLink);
// Arrays.asList(eq(DEVICE_CUSTOMER_ID_PROPERTY, customerId), return Futures.transformAsync(relations, input -> {
// eq(DEVICE_TENANT_ID_PROPERTY, tenantId)), List<ListenableFuture<Device>> deviceFutures = new ArrayList<>(input.size());
// pageLink); for (EntityRelation relation : input) {
// deviceFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId()));
// log.trace("Found devices [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", deviceEntities, tenantId, customerId, pageLink); }
// return DaoUtil.convertDataList(deviceEntities); return Futures.successfulAsList(deviceFutures);
throw new UnsupportedOperationException("Cassandra is not supported yet"); }, MoreExecutors.directExecutor());
} }
@Override
public List<Device> findDevicesByTenantIdAndEdgeIdAndType(UUID tenantId, UUID edgeId, String type, TextPageLink pageLink) {
// log.debug("Try to find devices by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", tenantId, customerId, type, pageLink);
// List<DeviceEntity> deviceEntities = findPageWithTextSearch(new TenantId(tenantId), DEVICE_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
// Arrays.asList(eq(DEVICE_TYPE_PROPERTY, type),
// eq(DEVICE_CUSTOMER_ID_PROPERTY, customerId),
// eq(DEVICE_TENANT_ID_PROPERTY, tenantId)),
// pageLink);
//
// log.trace("Found devices [{}] by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", deviceEntities, tenantId, customerId, type, pageLink);
// return DaoUtil.convertDataList(deviceEntities);
throw new UnsupportedOperationException("Cassandra is not supported yet");
}
} }

14
dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import java.util.List; import java.util.List;
@ -124,16 +125,5 @@ public interface DeviceDao extends Dao<Device> {
* @param pageLink the page link * @param pageLink the page link
* @return the list of device objects * @return the list of device objects
*/ */
List<Device> findDevicesByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TextPageLink pageLink); ListenableFuture<List<Device>> findDevicesByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink);
/**
* Find devices by tenantId, edgeId, type and page link.
*
* @param tenantId the tenantId
* @param edgeId the edgeId
* @param type the type
* @param pageLink the page link
* @return the list of device objects
*/
List<Device> findDevicesByTenantIdAndEdgeIdAndType(UUID tenantId, UUID edgeId, String type, TextPageLink pageLink);
} }

64
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -41,19 +41,26 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -98,6 +105,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
@Autowired @Autowired
private EntityViewService entityViewService; private EntityViewService entityViewService;
@Autowired
private EdgeService edgeService;
@Autowired @Autowired
private CacheManager cacheManager; private CacheManager cacheManager;
@ -324,36 +334,52 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
@Override @Override
public Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId) { public Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId) {
Device device = findDeviceById(tenantId, deviceId); Device device = findDeviceById(tenantId, deviceId);
device.setEdgeId(edgeId); Edge edge = edgeService.findEdgeById(tenantId, edgeId);
return saveDevice(device); if (edge == null) {
throw new DataValidationException("Can't assign device to non-existent edge!");
}
if (!edge.getTenantId().getId().equals(device.getTenantId().getId())) {
throw new DataValidationException("Can't assign device to edge from different tenant!");
}
try {
createRelation(tenantId, new EntityRelation(edgeId, deviceId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE));
} catch (ExecutionException | InterruptedException e) {
log.warn("[{}] Failed to create device relation. Edge Id: [{}]", deviceId, edgeId);
throw new RuntimeException(e);
}
return device;
} }
@Override @Override
public Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId) { public Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId) {
Device device = findDeviceById(tenantId, deviceId); Device device = findDeviceById(tenantId, deviceId);
device.setEdgeId(null); Edge edge = edgeService.findEdgeById(tenantId, edgeId);
return saveDevice(device); if (edge == null) {
throw new DataValidationException("Can't unassign device from non-existent edge!");
}
try {
deleteRelation(tenantId, new EntityRelation(edgeId, deviceId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE));
} catch (ExecutionException | InterruptedException e) {
log.warn("[{}] Failed to delete device relation. Edge Id: [{}]", deviceId, edgeId);
throw new RuntimeException(e);
}
return device;
} }
@Override @Override
public TextPageData<Device> findDevicesByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TextPageLink pageLink) { public ListenableFuture<TimePageData<Device>> findDevicesByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink) {
log.trace("Executing findDevicesByTenantIdAndEdgeId, tenantId [{}], edgeId [{}], pageLink [{}]", tenantId, edgeId, pageLink); log.trace("Executing findDevicesByTenantIdAndEdgeId, tenantId [{}], edgeId [{}], pageLink [{}]", tenantId, edgeId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(edgeId, INCORRECT_EDGE_ID + edgeId); validateId(edgeId, INCORRECT_EDGE_ID + edgeId);
validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
List<Device> devices = deviceDao.findDevicesByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink); ListenableFuture<List<Device>> devices = deviceDao.findDevicesByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink);
return new TextPageData<>(devices, pageLink); return Futures.transform(devices, new Function<List<Device>, TimePageData<Device>>() {
} @Nullable
@Override
@Override public TimePageData<Device> apply(@Nullable List<Device> devices) {
public TextPageData<Device> findDevicesByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, TextPageLink pageLink) { return new TimePageData<>(devices, pageLink);
log.trace("Executing findDevicesByTenantIdAndEdgeIdAndType, tenantId [{}], edgeId [{}], type [{}], pageLink [{}]", tenantId, edgeId, type, pageLink); }
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); }, MoreExecutors.directExecutor());
validateId(edgeId, INCORRECT_EDGE_ID + edgeId);
validateString(type, "Incorrect type " + type);
validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
List<Device> devices = deviceDao.findDevicesByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink);
return new TextPageData<>(devices, pageLink);
} }
private DataValidator<Device> deviceValidator = private DataValidator<Device> deviceValidator =

12
dao/src/main/java/org/thingsboard/server/dao/edge/CassandraEdgeDao.java

@ -110,26 +110,26 @@ public class CassandraEdgeDao extends CassandraAbstractSearchTextDao<EdgeEntity,
} }
@Override @Override
public ListenableFuture<List<Edge>> findEdgesByTenantIdAndRuleChainId(UUID tenantId, UUID ruleChainId, TimePageLink pageLink) { public ListenableFuture<List<Edge>> findEdgesByTenantIdAndRuleChainId(UUID tenantId, UUID ruleChainId) {
log.debug("Try to find edges by tenantId [{}], ruleChainId [{}] and pageLink [{}]", tenantId, ruleChainId, pageLink); log.debug("Try to find edges by tenantId [{}], ruleChainId [{}]", tenantId, ruleChainId);
ListenableFuture<List<EntityRelation>> relations = relationDao.findAllByToAndType(new TenantId(tenantId), new RuleChainId(ruleChainId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE); ListenableFuture<List<EntityRelation>> relations = relationDao.findAllByToAndType(new TenantId(tenantId), new RuleChainId(ruleChainId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE);
return Futures.transformAsync(relations, input -> { return Futures.transformAsync(relations, input -> {
List<ListenableFuture<Edge>> edgeFutures = new ArrayList<>(input.size()); List<ListenableFuture<Edge>> edgeFutures = new ArrayList<>(input.size());
for (EntityRelation relation : input) { for (EntityRelation relation : input) {
edgeFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId())); edgeFutures.add(findByIdAsync(new TenantId(tenantId), relation.getFrom().getId()));
} }
return Futures.successfulAsList(edgeFutures); return Futures.successfulAsList(edgeFutures);
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
} }
@Override @Override
public ListenableFuture<List<Edge>> findEdgesByTenantIdAndDashboardId(UUID tenantId, UUID dashboardId, TimePageLink pageLink) { public ListenableFuture<List<Edge>> findEdgesByTenantIdAndDashboardId(UUID tenantId, UUID dashboardId) {
log.debug("Try to find edges by tenantId [{}], dashboardId [{}] and pageLink [{}]", tenantId, dashboardId, pageLink); log.debug("Try to find edges by tenantId [{}], dashboardId [{}]", tenantId, dashboardId);
ListenableFuture<List<EntityRelation>> relations = relationDao.findAllByToAndType(new TenantId(tenantId), new DashboardId(dashboardId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE); ListenableFuture<List<EntityRelation>> relations = relationDao.findAllByToAndType(new TenantId(tenantId), new DashboardId(dashboardId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE);
return Futures.transformAsync(relations, input -> { return Futures.transformAsync(relations, input -> {
List<ListenableFuture<Edge>> edgeFutures = new ArrayList<>(input.size()); List<ListenableFuture<Edge>> edgeFutures = new ArrayList<>(input.size());
for (EntityRelation relation : input) { for (EntityRelation relation : input) {
edgeFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId())); edgeFutures.add(findByIdAsync(new TenantId(tenantId), relation.getFrom().getId()));
} }
return Futures.successfulAsList(edgeFutures); return Futures.successfulAsList(edgeFutures);
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());

10
dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java

@ -127,22 +127,20 @@ public interface EdgeDao extends Dao<Edge> {
Optional<Edge> findByRoutingKey(UUID tenantId, String routingKey); Optional<Edge> findByRoutingKey(UUID tenantId, String routingKey);
/** /**
* Find edges by tenantId, ruleChainId and page link. * Find edges by tenantId and ruleChainId.
* *
* @param tenantId the tenantId * @param tenantId the tenantId
* @param ruleChainId the ruleChainId * @param ruleChainId the ruleChainId
* @param pageLink the page link
* @return the list of rule chain objects * @return the list of rule chain objects
*/ */
ListenableFuture<List<Edge>> findEdgesByTenantIdAndRuleChainId(UUID tenantId, UUID ruleChainId, TimePageLink pageLink); ListenableFuture<List<Edge>> findEdgesByTenantIdAndRuleChainId(UUID tenantId, UUID ruleChainId);
/** /**
* Find edges by tenantId, dashboardId and page link. * Find edges by tenantId and dashboardId.
* *
* @param tenantId the tenantId * @param tenantId the tenantId
* @param dashboardId the dashboardId * @param dashboardId the dashboardId
* @param pageLink the page link
* @return the list of rule chain objects * @return the list of rule chain objects
*/ */
ListenableFuture<List<Edge>> findEdgesByTenantIdAndDashboardId(UUID tenantId, UUID dashboardId, TimePageLink pageLink); ListenableFuture<List<Edge>> findEdgesByTenantIdAndDashboardId(UUID tenantId, UUID dashboardId);
} }

101
dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java

@ -62,6 +62,7 @@ import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
@ -75,6 +76,7 @@ import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
@ -144,6 +146,9 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
@Autowired @Autowired
private EntityViewService entityViewService; private EntityViewService entityViewService;
@Autowired
private RelationService relationService;
private ExecutorService tsCallBackExecutor; private ExecutorService tsCallBackExecutor;
@PostConstruct @PostConstruct
@ -219,7 +224,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
dashboardService.unassignEdgeDashboards(tenantId, edgeId); dashboardService.unassignEdgeDashboards(tenantId, edgeId);
// TODO: validate that rule chains are removed by deleteEntityRelations(tenantId, edgeId); call // TODO: validate that rule chains are removed by deleteEntityRelations(tenantId, edgeId); call
ruleChainService.unassignEdgeRuleChains(tenantId, edgeId); ruleChainService.unassignEdgeRuleChains(tenantId, edgeId);
List<Object> list = new ArrayList<>(); List<Object> list = new ArrayList<>();
list.add(edge.getTenantId()); list.add(edge.getTenantId());
@ -385,15 +390,18 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
} }
private void processCustomTbMsg(TenantId tenantId, TbMsg tbMsg, FutureCallback<Void> callback) { private void processCustomTbMsg(TenantId tenantId, TbMsg tbMsg, FutureCallback<Void> callback) {
EdgeId edgeId = getEdgeIdByOriginatorId(tenantId, tbMsg.getOriginator()); ListenableFuture<EdgeId> edgeIdFuture = getEdgeIdByOriginatorId(tenantId, tbMsg.getOriginator());
EdgeQueueEntityType edgeQueueEntityType = getEdgeQueueTypeByEntityType(tbMsg.getOriginator().getEntityType()); Futures.transform(edgeIdFuture, edgeId -> {
if (edgeId != null && edgeQueueEntityType != null) { EdgeQueueEntityType edgeQueueEntityType = getEdgeQueueTypeByEntityType(tbMsg.getOriginator().getEntityType());
try { if (edgeId != null && edgeQueueEntityType != null) {
saveEventToEdgeQueue(tenantId, edgeId, edgeQueueEntityType, tbMsg.getType(), Base64.encodeBase64String(TbMsg.toByteArray(tbMsg)), callback); try {
} catch (IOException e) { saveEventToEdgeQueue(tenantId, edgeId, edgeQueueEntityType, tbMsg.getType(), Base64.encodeBase64String(TbMsg.toByteArray(tbMsg)), callback);
log.error("Error while saving custom tbMsg into Edge Queue", e); } catch (IOException e) {
log.error("Error while saving custom tbMsg into Edge Queue", e);
}
} }
} return null;
}, MoreExecutors.directExecutor());
} }
private EdgeQueueEntityType getEdgeQueueTypeByEntityType(EntityType entityType) { private EdgeQueueEntityType getEdgeQueueTypeByEntityType(EntityType entityType) {
@ -410,23 +418,30 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
} }
} }
private EdgeId getEdgeIdByOriginatorId(TenantId tenantId, EntityId originatorId) { private ListenableFuture<EdgeId> getEdgeIdByOriginatorId(TenantId tenantId, EntityId originatorId) {
switch (originatorId.getEntityType()) { List<EntityRelation> originatorEdgeRelations = relationService.findByToAndType(tenantId, originatorId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE);
case DEVICE: if (originatorEdgeRelations != null && originatorEdgeRelations.size() > 0) {
Device device = deviceService.findDeviceById(tenantId, new DeviceId(originatorId.getId())); return Futures.immediateFuture(new EdgeId(originatorEdgeRelations.get(0).getFrom().getId()));
return device.getEdgeId(); } else {
case ASSET: return Futures.immediateFuture(null);
Asset asset = assetService.findAssetById(tenantId, new AssetId(originatorId.getId()));
return asset.getEdgeId();
case ENTITY_VIEW:
EntityView entityView = entityViewService.findEntityViewById(tenantId, new EntityViewId(originatorId.getId()));
return entityView.getEdgeId();
default:
log.info("Unsupported entity type: [{}]", originatorId.getEntityType());
return null;
} }
} }
private void pushEventToEdge(TenantId tenantId, EntityId originatorId, EdgeQueueEntityType edgeQueueEntityType, TbMsg tbMsg, FutureCallback<Void> callback) {
ListenableFuture<EdgeId> edgeIdFuture = getEdgeIdByOriginatorId(tenantId, originatorId);
Futures.transform(edgeIdFuture, edgeId -> {
if (edgeId != null) {
try {
pushEventToEdge(tenantId, edgeId, edgeQueueEntityType, tbMsg, callback);
} catch (Exception e) {
log.error("Failed to push event to edge, edgeId [{}], tbMsg [{}]", edgeId, tbMsg, e);
}
}
return null;
},
MoreExecutors.directExecutor());
}
private void processDevice(TenantId tenantId, TbMsg tbMsg, FutureCallback<Void> callback) throws IOException { private void processDevice(TenantId tenantId, TbMsg tbMsg, FutureCallback<Void> callback) throws IOException {
switch (tbMsg.getType()) { switch (tbMsg.getType()) {
case DataConstants.ENTITY_ASSIGNED_TO_EDGE: case DataConstants.ENTITY_ASSIGNED_TO_EDGE:
@ -437,9 +452,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
case DataConstants.ENTITY_CREATED: case DataConstants.ENTITY_CREATED:
case DataConstants.ENTITY_UPDATED: case DataConstants.ENTITY_UPDATED:
Device device = mapper.readValue(tbMsg.getData(), Device.class); Device device = mapper.readValue(tbMsg.getData(), Device.class);
if (device.getEdgeId() != null) { pushEventToEdge(tenantId, device.getId(), EdgeQueueEntityType.DEVICE, tbMsg, callback);
pushEventToEdge(tenantId, device.getEdgeId(), EdgeQueueEntityType.DEVICE, tbMsg, callback);
}
break; break;
default: default:
log.warn("Unsupported msgType [{}], tbMsg [{}]", tbMsg.getType(), tbMsg); log.warn("Unsupported msgType [{}], tbMsg [{}]", tbMsg.getType(), tbMsg);
@ -451,10 +464,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
case DataConstants.ENTITY_DELETED: case DataConstants.ENTITY_DELETED:
case DataConstants.ENTITY_CREATED: case DataConstants.ENTITY_CREATED:
case DataConstants.ENTITY_UPDATED: case DataConstants.ENTITY_UPDATED:
Edge edge = mapper.readValue(tbMsg.getData(), Edge.class); // TODO: voba - handle properly edge creation
if (edge != null) {
pushEventToEdge(tenantId, edge.getId(), EdgeQueueEntityType.EDGE, tbMsg, callback);
}
break; break;
default: default:
log.warn("Unsupported msgType [{}], tbMsg [{}]", tbMsg.getType(), tbMsg); log.warn("Unsupported msgType [{}], tbMsg [{}]", tbMsg.getType(), tbMsg);
@ -471,9 +481,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
case DataConstants.ENTITY_CREATED: case DataConstants.ENTITY_CREATED:
case DataConstants.ENTITY_UPDATED: case DataConstants.ENTITY_UPDATED:
Asset asset = mapper.readValue(tbMsg.getData(), Asset.class); Asset asset = mapper.readValue(tbMsg.getData(), Asset.class);
if (asset.getEdgeId() != null) { pushEventToEdge(tenantId, asset.getId(), EdgeQueueEntityType.ASSET, tbMsg, callback);
pushEventToEdge(tenantId, asset.getEdgeId(), EdgeQueueEntityType.ASSET, tbMsg, callback);
}
break; break;
default: default:
log.warn("Unsupported msgType [{}], tbMsg [{}]", tbMsg.getType(), tbMsg); log.warn("Unsupported msgType [{}], tbMsg [{}]", tbMsg.getType(), tbMsg);
@ -490,9 +498,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
case DataConstants.ENTITY_CREATED: case DataConstants.ENTITY_CREATED:
case DataConstants.ENTITY_UPDATED: case DataConstants.ENTITY_UPDATED:
EntityView entityView = mapper.readValue(tbMsg.getData(), EntityView.class); EntityView entityView = mapper.readValue(tbMsg.getData(), EntityView.class);
if (entityView.getEdgeId() != null) { pushEventToEdge(tenantId, entityView.getId(), EdgeQueueEntityType.ENTITY_VIEW, tbMsg, callback);
pushEventToEdge(tenantId, entityView.getEdgeId(), EdgeQueueEntityType.ENTITY_VIEW, tbMsg, callback);
}
break; break;
default: default:
log.warn("Unsupported msgType [{}], tbMsg [{}]", tbMsg.getType(), tbMsg); log.warn("Unsupported msgType [{}], tbMsg [{}]", tbMsg.getType(), tbMsg);
@ -507,10 +513,9 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
case DataConstants.ALARM_ACK: case DataConstants.ALARM_ACK:
case DataConstants.ALARM_CLEAR: case DataConstants.ALARM_CLEAR:
Alarm alarm = mapper.readValue(tbMsg.getData(), Alarm.class); Alarm alarm = mapper.readValue(tbMsg.getData(), Alarm.class);
EdgeId edgeId = getEdgeIdByOriginatorId(tenantId, alarm.getOriginator());
EdgeQueueEntityType edgeQueueEntityType = getEdgeQueueTypeByEntityType(alarm.getOriginator().getEntityType()); EdgeQueueEntityType edgeQueueEntityType = getEdgeQueueTypeByEntityType(alarm.getOriginator().getEntityType());
if (edgeId != null && edgeQueueEntityType != null) { if (edgeQueueEntityType != null) {
pushEventToEdge(tenantId, edgeId, EdgeQueueEntityType.ALARM, tbMsg, callback); pushEventToEdge(tenantId, alarm.getOriginator(), EdgeQueueEntityType.ALARM, tbMsg, callback);
} }
break; break;
default: default:
@ -665,13 +670,27 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
return savedEdge; return savedEdge;
} }
@Override
public void assignDefaultRuleChainsToEdge(TenantId tenantId, EdgeId edgeId) {
log.trace("Executing assignDefaultRuleChainsToEdge, tenantId [{}], edgeId [{}]", tenantId, edgeId);
ListenableFuture<List<RuleChain>> future = ruleChainService.findDefaultEdgeRuleChainsByTenantId(tenantId);
Futures.transform(future, ruleChains -> {
if (ruleChains != null && !ruleChains.isEmpty()) {
for (RuleChain ruleChain : ruleChains) {
ruleChainService.assignRuleChainToEdge(tenantId, ruleChain.getId(), edgeId);
}
}
return null;
}, MoreExecutors.directExecutor());
}
@Override @Override
public ListenableFuture<TimePageData<Edge>> findEdgesByTenantIdAndRuleChainId(TenantId tenantId, RuleChainId ruleChainId, TimePageLink pageLink) { public ListenableFuture<TimePageData<Edge>> findEdgesByTenantIdAndRuleChainId(TenantId tenantId, RuleChainId ruleChainId, TimePageLink pageLink) {
log.trace("Executing findEdgesByTenantIdAndRuleChainId, tenantId [{}], ruleChainId [{}], pageLink [{}]", tenantId, ruleChainId, pageLink); log.trace("Executing findEdgesByTenantIdAndRuleChainId, tenantId [{}], ruleChainId [{}], pageLink [{}]", tenantId, ruleChainId, pageLink);
Validator.validateId(tenantId, "Incorrect tenantId " + tenantId); Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
Validator.validateId(ruleChainId, "Incorrect ruleChainId " + ruleChainId); Validator.validateId(ruleChainId, "Incorrect ruleChainId " + ruleChainId);
Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
ListenableFuture<List<Edge>> edges = edgeDao.findEdgesByTenantIdAndRuleChainId(tenantId.getId(), ruleChainId.getId(), pageLink); ListenableFuture<List<Edge>> edges = edgeDao.findEdgesByTenantIdAndRuleChainId(tenantId.getId(), ruleChainId.getId());
return Futures.transform(edges, new Function<List<Edge>, TimePageData<Edge>>() { return Futures.transform(edges, new Function<List<Edge>, TimePageData<Edge>>() {
@Nullable @Nullable
@ -688,7 +707,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
Validator.validateId(tenantId, "Incorrect tenantId " + tenantId); Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
Validator.validateId(dashboardId, "Incorrect dashboardId " + dashboardId); Validator.validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
ListenableFuture<List<Edge>> edges = edgeDao.findEdgesByTenantIdAndDashboardId(tenantId.getId(), dashboardId.getId(), pageLink); ListenableFuture<List<Edge>> edges = edgeDao.findEdgesByTenantIdAndDashboardId(tenantId.getId(), dashboardId.getId());
return Futures.transform(edges, new Function<List<Edge>, TimePageData<Edge>>() { return Futures.transform(edges, new Function<List<Edge>, TimePageData<Edge>>() {
@Nullable @Nullable

12
dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java

@ -21,10 +21,12 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutionException;
@Slf4j @Slf4j
public abstract class AbstractEntityService { public abstract class AbstractEntityService {
@ -42,6 +44,16 @@ public abstract class AbstractEntityService {
sqlDatabaseUsed = "sql".equalsIgnoreCase(databaseType); sqlDatabaseUsed = "sql".equalsIgnoreCase(databaseType);
} }
protected void createRelation(TenantId tenantId, EntityRelation relation) throws ExecutionException, InterruptedException {
log.debug("Creating relation: {}", relation);
relationService.saveRelation(tenantId, relation);
}
protected void deleteRelation(TenantId tenantId, EntityRelation relation) throws ExecutionException, InterruptedException {
log.debug("Deleting relation: {}", relation);
relationService.deleteRelation(tenantId, relation);
}
protected void deleteEntityRelations(TenantId tenantId, EntityId entityId) { protected void deleteEntityRelations(TenantId tenantId, EntityId entityId) {
log.trace("Executing deleteEntityRelations [{}]", entityId); log.trace("Executing deleteEntityRelations [{}]", entityId);
relationService.deleteEntityRelations(tenantId, entityId); relationService.deleteEntityRelations(tenantId, entityId);

44
dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java

@ -25,16 +25,23 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.EntitySubtypeEntity; import org.thingsboard.server.dao.model.EntitySubtypeEntity;
import org.thingsboard.server.dao.model.nosql.EntityViewEntity; import org.thingsboard.server.dao.model.nosql.EntityViewEntity;
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.util.NoSqlDao; import org.thingsboard.server.dao.util.NoSqlDao;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -73,6 +80,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_PROPERTY
@NoSqlDao @NoSqlDao
public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<EntityViewEntity, EntityView> implements EntityViewDao { public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<EntityViewEntity, EntityView> implements EntityViewDao {
@Autowired
private RelationDao relationDao;
@Override @Override
protected Class<EntityViewEntity> getColumnFamilyClass() { protected Class<EntityViewEntity> getColumnFamilyClass() {
return EntityViewEntity.class; return EntityViewEntity.class;
@ -186,30 +196,16 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
} }
@Override @Override
public List<EntityView> findEntityViewsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TextPageLink pageLink) { public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) {
// log.debug("Try to find entity views by tenantId [{}], customerId[{}] and pageLink [{}]", log.debug("Try to find entity views by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);
// tenantId, customerId, pageLink); ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new TenantId(tenantId), new EdgeId(edgeId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE, EntityType.ENTITY_VIEW, pageLink);
// List<EntityViewEntity> entityViewEntities = findPageWithTextSearch(new TenantId(tenantId), return Futures.transformAsync(relations, input -> {
// ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF, List<ListenableFuture<EntityView>> entityViewFutures = new ArrayList<>(input.size());
// Arrays.asList(eq(CUSTOMER_ID_PROPERTY, customerId), eq(TENANT_ID_PROPERTY, tenantId)), for (EntityRelation relation : input) {
// pageLink); entityViewFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId()));
// log.trace("Found find entity views [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", }
// entityViewEntities, tenantId, customerId, pageLink); return Futures.successfulAsList(entityViewFutures);
// return DaoUtil.convertDataList(entityViewEntities); }, MoreExecutors.directExecutor());
throw new UnsupportedOperationException("Cassandra is not supported yet");
} }
@Override
public List<EntityView> findEntityViewsByTenantIdAndEdgeIdAndType(UUID tenantId, UUID edgeId, String type, TextPageLink pageLink) {
// log.debug("Try to find entity views by tenantId [{}], customerId[{}], type [{}] and pageLink [{}]",
// tenantId, customerId, type, pageLink);
// List<EntityViewEntity> entityViewEntities = findPageWithTextSearch(new TenantId(tenantId),
// ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_TYPE_CF,
// Arrays.asList(eq(DEVICE_TYPE_PROPERTY, type), eq(CUSTOMER_ID_PROPERTY, customerId), eq(TENANT_ID_PROPERTY, tenantId)),
// pageLink);
// log.trace("Found find entity views [{}] by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]",
// entityViewEntities, tenantId, customerId, type, pageLink);
// return DaoUtil.convertDataList(entityViewEntities);
throw new UnsupportedOperationException("Cassandra is not supported yet");
}
} }

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

Loading…
Cancel
Save