Browse Source

Merge branch 'develop/3.0' into develop/3.0-edge

pull/3811/head
Volodymyr Babak 6 years ago
parent
commit
bfb25845e9
  1. 136
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  2. 24
      application/src/main/data/json/system/widget_bundles/maps.json
  3. 96
      application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql
  4. 128
      application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql
  5. 5
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  6. 4
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  7. 15
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  8. 3
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  9. 17
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  10. 121
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  11. 79
      application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java
  12. 300
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
  13. 18
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  14. 33
      application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java
  15. 6
      application/src/main/java/org/thingsboard/server/service/install/PsqlEntityDatabaseSchemaService.java
  16. 59
      application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java
  17. 2
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  18. 4
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java
  19. 63
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java
  20. 314
      application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java
  21. 168
      application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java
  22. 64
      application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumnData.java
  23. 28
      application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumnType.java
  24. 308
      application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlTable.java
  25. 22
      application/src/main/java/org/thingsboard/server/service/install/migrate/EntitiesMigrateService.java
  26. 8
      application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java
  27. 10
      application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java
  28. 7
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
  29. 22
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  30. 18
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  31. 16
      application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java
  32. 2
      application/src/main/resources/thingsboard.yml
  33. 4
      application/src/main/scripts/control/deb/postinst
  34. 10
      application/src/main/scripts/control/deb/preinst
  35. 4
      application/src/main/scripts/control/rpm/postinst
  36. 6
      application/src/main/scripts/control/rpm/preinst
  37. 2
      application/src/main/scripts/control/thingsboard.service
  38. 2
      application/src/main/scripts/install/install.sh
  39. 2
      application/src/main/scripts/install/upgrade.sh
  40. 14
      application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java
  41. 3
      common/queue/src/main/java/org/thingsboard/server/kafka/AsyncCallbackTemplate.java
  42. 11
      common/util/src/main/java/org/thingsboard/common/util/DonAsynchron.java
  43. 15
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  44. 34
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  45. 18
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  46. 9
      dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesServiceImpl.java
  47. 35
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  48. 13
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  49. 9
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  50. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java
  51. 4
      dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestEntity.java
  52. 1
      dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java
  53. 19
      dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java
  54. 4
      dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java
  55. 7
      dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java
  56. 38
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  57. 3
      dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java
  58. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  59. 13
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java
  60. 12
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java
  61. 51
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java
  62. 47
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/HsqlRelationInsertRepository.java
  63. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java
  64. 43
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/PsqlRelationInsertRepository.java
  65. 24
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java
  66. 39
      dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java
  67. 21
      dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java
  68. 41
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java
  69. 29
      dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java
  70. 48
      dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java
  71. 10
      dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java
  72. 5
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
  73. 17
      dao/src/main/resources/sql/schema-timescale-idx.sql
  74. 5
      dao/src/main/resources/sql/schema-timescale.sql
  75. 2
      dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java
  76. 8
      dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java
  77. 9
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAdminSettingsServiceTest.java
  78. 2
      dao/src/test/resources/sql/timescale/drop-all-tables.sql
  79. 7
      docker/README.md
  80. 24
      docker/docker-create-log-folders.sh
  81. 2
      k8s/database-setup.yml
  82. 4
      k8s/k8s-delete-all.sh
  83. 2
      k8s/k8s-install-tb.sh
  84. 2
      k8s/k8s-upgrade-tb.sh
  85. 2
      k8s/postgres.yml
  86. 4
      msa/js-executor/docker/Dockerfile
  87. 4
      msa/js-executor/docker/start-js-executor.sh
  88. 1
      msa/js-executor/pom.xml
  89. 4
      msa/tb-node/docker/Dockerfile
  90. 4
      msa/tb-node/docker/start-tb-node.sh
  91. 1
      msa/tb-node/pom.xml
  92. 1
      msa/tb/pom.xml
  93. 2
      msa/transport/coap/docker/Dockerfile
  94. 2
      msa/transport/coap/docker/start-tb-coap-transport.sh
  95. 1
      msa/transport/coap/pom.xml
  96. 2
      msa/transport/http/docker/Dockerfile
  97. 2
      msa/transport/http/docker/start-tb-http-transport.sh
  98. 1
      msa/transport/http/pom.xml
  99. 2
      msa/transport/mqtt/docker/Dockerfile
  100. 2
      msa/transport/mqtt/docker/start-tb-mqtt-transport.sh

136
application/src/main/data/json/system/widget_bundles/input_widgets.json

File diff suppressed because one or more lines are too long

24
application/src/main/data/json/system/widget_bundles/maps.json

File diff suppressed because one or more lines are too long

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

@ -14,33 +14,9 @@
-- limitations under the License.
--
-- select check_version();
-- call create_partition_ts_kv_table();
CREATE OR REPLACE FUNCTION check_version() RETURNS boolean AS $$
DECLARE
current_version integer;
valid_version boolean;
BEGIN
RAISE NOTICE 'Check the current installed PostgreSQL version...';
SELECT current_setting('server_version_num') INTO current_version;
IF current_version < 100000 THEN
valid_version := FALSE;
ELSE
valid_version := TRUE;
END IF;
IF valid_version = FALSE THEN
RAISE NOTICE 'Postgres version should be at least more than 10!';
ELSE
RAISE NOTICE 'PostgreSQL version is valid!';
RAISE NOTICE 'Schema update started...';
END IF;
RETURN valid_version;
END;
$$ LANGUAGE 'plpgsql';
-- select create_partition_ts_kv_table();
CREATE OR REPLACE FUNCTION create_partition_ts_kv_table() RETURNS VOID AS $$
CREATE OR REPLACE PROCEDURE create_partition_ts_kv_table() LANGUAGE plpgsql AS $$
BEGIN
ALTER TABLE ts_kv
@ -57,11 +33,11 @@ BEGIN
ALTER TABLE ts_kv
ALTER COLUMN key TYPE integer USING key::integer;
END;
$$ LANGUAGE 'plpgsql';
$$;
-- select create_new_ts_kv_latest_table();
-- call create_new_ts_kv_latest_table();
CREATE OR REPLACE FUNCTION create_new_ts_kv_latest_table() RETURNS VOID AS $$
CREATE OR REPLACE PROCEDURE create_new_ts_kv_latest_table() LANGUAGE plpgsql AS $$
BEGIN
ALTER TABLE ts_kv_latest
@ -81,13 +57,13 @@ BEGIN
ALTER TABLE ts_kv_latest
ADD CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key);
END;
$$ LANGUAGE 'plpgsql';
$$;
-- call create_partitions();
-- select create_partitions();
CREATE OR REPLACE PROCEDURE create_partitions() LANGUAGE plpgsql AS $$
CREATE OR REPLACE FUNCTION create_partitions() RETURNS VOID AS
$$
DECLARE
partition_date varchar;
from_ts bigint;
@ -111,11 +87,11 @@ BEGIN
CLOSE key_cursor;
END;
$$ language 'plpgsql';
$$;
-- select create_ts_kv_dictionary_table();
-- call create_ts_kv_dictionary_table();
CREATE OR REPLACE FUNCTION create_ts_kv_dictionary_table() RETURNS VOID AS $$
CREATE OR REPLACE PROCEDURE create_ts_kv_dictionary_table() LANGUAGE plpgsql AS $$
BEGIN
CREATE TABLE IF NOT EXISTS ts_kv_dictionary
@ -125,12 +101,12 @@ BEGIN
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
);
END;
$$ LANGUAGE 'plpgsql';
$$;
-- call insert_into_dictionary();
-- select insert_into_dictionary();
CREATE OR REPLACE PROCEDURE insert_into_dictionary() LANGUAGE plpgsql AS $$
CREATE OR REPLACE FUNCTION insert_into_dictionary() RETURNS VOID AS
$$
DECLARE
insert_record RECORD;
key_cursor CURSOR FOR SELECT DISTINCT key
@ -150,28 +126,27 @@ BEGIN
END LOOP;
CLOSE key_cursor;
END;
$$ language 'plpgsql';
$$;
-- select insert_into_ts_kv();
-- call insert_into_ts_kv();
CREATE OR REPLACE FUNCTION insert_into_ts_kv() RETURNS void AS
$$
CREATE OR REPLACE PROCEDURE insert_into_ts_kv() LANGUAGE plpgsql AS $$
DECLARE
insert_size CONSTANT integer := 10000;
insert_counter integer DEFAULT 0;
insert_record RECORD;
insert_cursor CURSOR FOR SELECT CONCAT(first_part_uuid, '-', second_part_uuid, '-1', third_part_uuid, '-', fourth_part_uuid, '-', fifth_part_uuid)::uuid 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.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 SUBSTRING(entity_id, 8, 8) AS first_part_uuid,
SUBSTRING(entity_id, 4, 4) AS second_part_uuid,
SUBSTRING(entity_id, 1, 3) AS third_part_uuid,
SUBSTRING(entity_id, 16, 4) AS fourth_part_uuid,
SUBSTRING(entity_id, 20) AS fifth_part_uuid,
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,
@ -198,28 +173,27 @@ BEGIN
END LOOP;
CLOSE insert_cursor;
END;
$$ LANGUAGE 'plpgsql';
$$;
-- select insert_into_ts_kv_latest();
-- call insert_into_ts_kv_latest();
CREATE OR REPLACE FUNCTION insert_into_ts_kv_latest() RETURNS void AS
$$
CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest() LANGUAGE plpgsql AS $$
DECLARE
insert_size CONSTANT integer := 10000;
insert_counter integer DEFAULT 0;
insert_record RECORD;
insert_cursor CURSOR FOR SELECT CONCAT(first_part_uuid, '-', second_part_uuid, '-1', third_part_uuid, '-', fourth_part_uuid, '-', fifth_part_uuid)::uuid 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_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 SUBSTRING(entity_id, 8, 8) AS first_part_uuid,
SUBSTRING(entity_id, 4, 4) AS second_part_uuid,
SUBSTRING(entity_id, 1, 3) AS third_part_uuid,
SUBSTRING(entity_id, 16, 4) AS fourth_part_uuid,
SUBSTRING(entity_id, 20) AS fifth_part_uuid,
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,
@ -246,6 +220,6 @@ BEGIN
END LOOP;
CLOSE insert_cursor;
END;
$$ LANGUAGE 'plpgsql';
$$;

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

@ -14,60 +14,32 @@
-- limitations under the License.
--
-- select check_version();
-- call create_new_ts_kv_table();
CREATE OR REPLACE FUNCTION check_version() RETURNS boolean AS $$
DECLARE
current_version integer;
valid_version boolean;
BEGIN
RAISE NOTICE 'Check the current installed PostgreSQL version...';
SELECT current_setting('server_version_num') INTO current_version;
IF current_version < 90600 THEN
valid_version := FALSE;
ELSE
valid_version := TRUE;
END IF;
IF valid_version = FALSE THEN
RAISE NOTICE 'Postgres version should be at least more than 9.6!';
ELSE
RAISE NOTICE 'PostgreSQL version is valid!';
RAISE NOTICE 'Schema update started...';
END IF;
RETURN valid_version;
END;
$$ LANGUAGE 'plpgsql';
-- select create_new_tenant_ts_kv_table();
CREATE OR REPLACE FUNCTION create_new_tenant_ts_kv_table() RETURNS VOID AS $$
CREATE OR REPLACE PROCEDURE create_new_ts_kv_table() LANGUAGE plpgsql AS $$
BEGIN
ALTER TABLE tenant_ts_kv
RENAME TO tenant_ts_kv_old;
CREATE TABLE IF NOT EXISTS tenant_ts_kv
CREATE TABLE IF NOT EXISTS ts_kv
(
LIKE tenant_ts_kv_old
);
ALTER TABLE tenant_ts_kv
ALTER COLUMN tenant_id TYPE uuid USING tenant_id::uuid;
ALTER TABLE tenant_ts_kv
ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid;
ALTER TABLE tenant_ts_kv
ALTER COLUMN key TYPE integer USING key::integer;
ALTER TABLE tenant_ts_kv
ADD CONSTRAINT tenant_ts_kv_pkey PRIMARY KEY(tenant_id, entity_id, key, ts);
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 INDEX ts_kv_pkey RENAME TO tenant_ts_kv_pkey_old;
ALTER INDEX idx_tenant_ts_kv RENAME TO idx_tenant_ts_kv_old;
ALTER INDEX tenant_ts_kv_ts_idx RENAME TO tenant_ts_kv_ts_idx_old;
-- PERFORM create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => 86400000, if_not_exists => true);
CREATE INDEX IF NOT EXISTS idx_tenant_ts_kv ON tenant_ts_kv(tenant_id, entity_id, key, ts);
ALTER TABLE ts_kv ADD CONSTRAINT ts_kv_pkey PRIMARY KEY(entity_id, key, ts);
-- CREATE INDEX IF NOT EXISTS ts_kv_ts_idx ON ts_kv(ts DESC);
ALTER TABLE ts_kv DROP COLUMN IF EXISTS tenant_id;
END;
$$ LANGUAGE 'plpgsql';
$$;
-- select create_ts_kv_latest_table();
-- call create_ts_kv_latest_table();
CREATE OR REPLACE FUNCTION create_ts_kv_latest_table() RETURNS VOID AS $$
CREATE OR REPLACE PROCEDURE create_ts_kv_latest_table() LANGUAGE plpgsql AS $$
BEGIN
CREATE TABLE IF NOT EXISTS ts_kv_latest
@ -82,12 +54,12 @@ BEGIN
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key)
);
END;
$$ LANGUAGE 'plpgsql';
$$;
-- select create_ts_kv_dictionary_table();
-- call create_ts_kv_dictionary_table();
CREATE OR REPLACE FUNCTION create_ts_kv_dictionary_table() RETURNS VOID AS $$
CREATE OR REPLACE PROCEDURE create_ts_kv_dictionary_table() LANGUAGE plpgsql AS $$
BEGIN
CREATE TABLE IF NOT EXISTS ts_kv_dictionary
@ -97,12 +69,12 @@ BEGIN
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
);
END;
$$ LANGUAGE 'plpgsql';
$$;
-- select insert_into_dictionary();
-- call insert_into_dictionary();
CREATE OR REPLACE PROCEDURE insert_into_dictionary() LANGUAGE plpgsql AS $$
CREATE OR REPLACE FUNCTION insert_into_dictionary() RETURNS VOID AS
$$
DECLARE
insert_record RECORD;
key_cursor CURSOR FOR SELECT DISTINCT key
@ -122,34 +94,28 @@ BEGIN
END LOOP;
CLOSE key_cursor;
END;
$$ language 'plpgsql';
$$;
-- call insert_into_ts_kv();
-- select insert_into_tenant_ts_kv();
CREATE OR REPLACE PROCEDURE insert_into_ts_kv() LANGUAGE plpgsql AS $$
CREATE OR REPLACE FUNCTION insert_into_tenant_ts_kv() RETURNS void AS
$$
DECLARE
insert_size CONSTANT integer := 10000;
insert_counter integer DEFAULT 0;
insert_record RECORD;
insert_cursor CURSOR FOR SELECT CONCAT(tenant_id_first_part_uuid, '-', tenant_id_second_part_uuid, '-1', tenant_id_third_part_uuid, '-', tenant_id_fourth_part_uuid, '-', tenant_id_fifth_part_uuid)::uuid AS tenant_id,
CONCAT(entity_id_first_part_uuid, '-', entity_id_second_part_uuid, '-1', entity_id_third_part_uuid, '-', entity_id_fourth_part_uuid, '-', entity_id_fifth_part_uuid)::uuid AS entity_id,
tenant_ts_kv_records.key AS key,
tenant_ts_kv_records.ts AS ts,
tenant_ts_kv_records.bool_v AS bool_v,
tenant_ts_kv_records.str_v AS str_v,
tenant_ts_kv_records.long_v AS long_v,
tenant_ts_kv_records.dbl_v AS dbl_v
FROM (SELECT SUBSTRING(tenant_id, 8, 8) AS tenant_id_first_part_uuid,
SUBSTRING(tenant_id, 4, 4) AS tenant_id_second_part_uuid,
SUBSTRING(tenant_id, 1, 3) AS tenant_id_third_part_uuid,
SUBSTRING(tenant_id, 16, 4) AS tenant_id_fourth_part_uuid,
SUBSTRING(tenant_id, 20) AS tenant_id_fifth_part_uuid,
SUBSTRING(entity_id, 8, 8) AS entity_id_first_part_uuid,
SUBSTRING(entity_id, 4, 4) AS entity_id_second_part_uuid,
SUBSTRING(entity_id, 1, 3) AS entity_id_third_part_uuid,
SUBSTRING(entity_id, 16, 4) AS entity_id_fourth_part_uuid,
SUBSTRING(entity_id, 20) AS entity_id_fifth_part_uuid,
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,
@ -157,31 +123,31 @@ DECLARE
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 tenant_ts_kv_records;
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 tenant_ts_kv table!',insert_counter - 1;
RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter - 1;
EXIT;
END IF;
INSERT INTO tenant_ts_kv(tenant_id, entity_id, key, ts, bool_v, str_v, long_v, dbl_v)
VALUES (insert_record.tenant_id, insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v,
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 tenant_ts_kv table!',insert_counter;
RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter;
END IF;
END LOOP;
CLOSE insert_cursor;
END;
$$ LANGUAGE 'plpgsql';
$$;
-- call insert_into_ts_kv_latest();
-- select insert_into_ts_kv_latest();
CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest() LANGUAGE plpgsql AS $$
CREATE OR REPLACE FUNCTION insert_into_ts_kv_latest() RETURNS void AS
$$
DECLARE
insert_size CONSTANT integer := 10000;
insert_counter integer DEFAULT 0;
@ -191,7 +157,7 @@ DECLARE
latest_records.key AS key,
latest_records.entity_id AS entity_id,
latest_records.ts AS ts
FROM (SELECT DISTINCT key AS key, entity_id AS entity_id, MAX(ts) AS ts FROM tenant_ts_kv GROUP BY key, entity_id) AS latest_records;
FROM (SELECT DISTINCT key AS key, entity_id AS entity_id, MAX(ts) AS ts FROM ts_kv GROUP BY key, entity_id) AS latest_records;
BEGIN
OPEN insert_cursor;
LOOP
@ -201,7 +167,7 @@ BEGIN
RAISE NOTICE '% records have been inserted into the ts_kv_latest table!',insert_counter - 1;
EXIT;
END IF;
SELECT entity_id AS entity_id, key AS key, ts AS ts, bool_v AS bool_v, str_v AS str_v, long_v AS long_v, dbl_v AS dbl_v INTO insert_record FROM tenant_ts_kv WHERE entity_id = latest_record.entity_id AND key = latest_record.key AND ts = latest_record.ts;
SELECT entity_id AS entity_id, key AS key, ts AS ts, bool_v AS bool_v, str_v AS str_v, long_v AS long_v, dbl_v AS dbl_v INTO insert_record FROM ts_kv WHERE entity_id = latest_record.entity_id AND key = latest_record.key AND ts = latest_record.ts;
INSERT INTO ts_kv_latest(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
@ -210,4 +176,4 @@ BEGIN
END LOOP;
CLOSE insert_cursor;
END;
$$ LANGUAGE 'plpgsql';
$$;

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

@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import lombok.Getter;
@ -475,7 +476,7 @@ public class ActorSystemContext {
public void onFailure(Throwable th) {
log.error("Could not save debug Event for Node", th);
}
});
}, MoreExecutors.directExecutor());
} catch (IOException ex) {
log.warn("Failed to persist rule node debug message", ex);
}
@ -528,7 +529,7 @@ public class ActorSystemContext {
public void onFailure(Throwable th) {
log.error("Could not save debug Event for Rule Chain", th);
}
});
}, MoreExecutors.directExecutor());
}
public static Exception toException(Throwable error) {

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

@ -20,9 +20,9 @@ import com.datastax.driver.core.utils.UUIDs;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
@ -292,7 +292,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
.build();
sendToTransport(responseMsg, sessionInfo);
}
});
}, MoreExecutors.directExecutor());
}
private ListenableFuture<List<List<AttributeKvEntry>>> getAttributesKvEntries(GetAttributeRequestMsg request) {

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

@ -18,6 +18,7 @@ package org.thingsboard.server.controller;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
@ -30,7 +31,13 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.ClaimRequest;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.edge.Edge;
@ -42,7 +49,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.ClaimRequest;
import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -490,11 +496,12 @@ public class DeviceController extends BaseController {
deferredResult.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
}
}
@Override
public void onFailure(Throwable t) {
deferredResult.setErrorResult(t);
}
});
}, MoreExecutors.directExecutor());
return deferredResult;
} catch (Exception e) {
throw handleException(e);
@ -531,7 +538,7 @@ public class DeviceController extends BaseController {
public void onFailure(Throwable t) {
deferredResult.setErrorResult(t);
}
});
}, MoreExecutors.directExecutor());
return deferredResult;
} catch (Exception e) {
throw handleException(e);

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

@ -18,6 +18,7 @@ package org.thingsboard.server.controller;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
@ -170,7 +171,7 @@ public class EntityViewController extends BaseController {
});
}
return null;
});
}, MoreExecutors.directExecutor());
} else {
return Futures.immediateFuture(null);
}

17
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -22,6 +22,7 @@ import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
@ -174,7 +175,7 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> getTimeseriesKeys(
@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
(result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result)));
(result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor()));
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@ -210,7 +211,7 @@ public class TelemetryController extends BaseController {
List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg))
.collect(Collectors.toList());
Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes));
Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());
});
}
@ -462,7 +463,7 @@ public class TelemetryController extends BaseController {
} else {
future = tsService.findLatest(user.getTenantId(), entityId, toKeysList(keys));
}
Futures.addCallback(future, getTsKvListCallback(result, useStrictDataTypes));
Futures.addCallback(future, getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());
}
private void getAttributeValuesCallback(@Nullable DeferredResult<ResponseEntity> result, SecurityUser user, EntityId entityId, String scope, String keys) {
@ -470,9 +471,9 @@ public class TelemetryController extends BaseController {
FutureCallback<List<AttributeKvEntry>> callback = getAttributeValuesToResponseCallback(result, user, scope, entityId, keyList);
if (!StringUtils.isEmpty(scope)) {
if (keyList != null && !keyList.isEmpty()) {
Futures.addCallback(attributesService.find(user.getTenantId(), entityId, scope, keyList), callback);
Futures.addCallback(attributesService.find(user.getTenantId(), entityId, scope, keyList), callback, MoreExecutors.directExecutor());
} else {
Futures.addCallback(attributesService.findAll(user.getTenantId(), entityId, scope), callback);
Futures.addCallback(attributesService.findAll(user.getTenantId(), entityId, scope), callback, MoreExecutors.directExecutor());
}
} else {
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
@ -486,12 +487,12 @@ public class TelemetryController extends BaseController {
ListenableFuture<List<AttributeKvEntry>> future = mergeAllAttributesFutures(futures);
Futures.addCallback(future, callback);
Futures.addCallback(future, callback, MoreExecutors.directExecutor());
}
}
private void getAttributeKeysCallback(@Nullable DeferredResult<ResponseEntity> result, TenantId tenantId, EntityId entityId, String scope) {
Futures.addCallback(attributesService.findAll(tenantId, entityId, scope), getAttributeKeysToResponseCallback(result));
Futures.addCallback(attributesService.findAll(tenantId, entityId, scope), getAttributeKeysToResponseCallback(result), MoreExecutors.directExecutor());
}
private void getAttributeKeysCallback(@Nullable DeferredResult<ResponseEntity> result, TenantId tenantId, EntityId entityId) {
@ -502,7 +503,7 @@ public class TelemetryController extends BaseController {
ListenableFuture<List<AttributeKvEntry>> future = mergeAllAttributesFutures(futures);
Futures.addCallback(future, getAttributeKeysToResponseCallback(result));
Futures.addCallback(future, getAttributeKeysToResponseCallback(result), MoreExecutors.directExecutor());
}
private FutureCallback<List<TsKvEntry>> getTsKeysToResponseCallback(final DeferredResult<ResponseEntity> response) {

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

@ -28,6 +28,7 @@ import org.thingsboard.server.service.install.DatabaseEntitiesUpgradeService;
import org.thingsboard.server.service.install.EntityDatabaseSchemaService;
import org.thingsboard.server.service.install.SystemDataLoaderService;
import org.thingsboard.server.service.install.TsDatabaseSchemaService;
import org.thingsboard.server.service.install.migrate.EntitiesMigrateService;
import org.thingsboard.server.service.install.update.DataUpdateService;
@Service
@ -68,99 +69,95 @@ public class ThingsboardInstallService {
@Autowired
private DataUpdateService dataUpdateService;
@Autowired(required = false)
private EntitiesMigrateService entitiesMigrateService;
public void performInstall() {
try {
if (isUpgrade) {
log.info("Starting ThingsBoard Upgrade from version {} ...", upgradeFromVersion);
switch (upgradeFromVersion) {
case "1.2.3": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion
log.info("Upgrading ThingsBoard from version 1.2.3 to 1.3.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("1.2.3");
if ("2.5.0-cassandra".equals(upgradeFromVersion)) {
log.info("Migrating ThingsBoard entities data from cassandra to SQL database ...");
entitiesMigrateService.migrate();
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
} else {
switch (upgradeFromVersion) {
case "1.2.3": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion
log.info("Upgrading ThingsBoard from version 1.2.3 to 1.3.0 ...");
case "1.3.0": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion
log.info("Upgrading ThingsBoard from version 1.3.0 to 1.3.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("1.2.3");
databaseEntitiesUpgradeService.upgradeDatabase("1.3.0");
case "1.3.0": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion
log.info("Upgrading ThingsBoard from version 1.3.0 to 1.3.1 ...");
case "1.3.1": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion
log.info("Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("1.3.0");
databaseEntitiesUpgradeService.upgradeDatabase("1.3.1");
case "1.3.1": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion
log.info("Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ...");
case "1.4.0":
log.info("Upgrading ThingsBoard from version 1.4.0 to 2.0.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("1.3.1");
databaseEntitiesUpgradeService.upgradeDatabase("1.4.0");
case "1.4.0":
log.info("Upgrading ThingsBoard from version 1.4.0 to 2.0.0 ...");
dataUpdateService.updateData("1.4.0");
databaseEntitiesUpgradeService.upgradeDatabase("1.4.0");
case "2.0.0":
log.info("Upgrading ThingsBoard from version 2.0.0 to 2.1.1 ...");
dataUpdateService.updateData("1.4.0");
databaseEntitiesUpgradeService.upgradeDatabase("2.0.0");
case "2.0.0":
log.info("Upgrading ThingsBoard from version 2.0.0 to 2.1.1 ...");
case "2.1.1":
log.info("Upgrading ThingsBoard from version 2.1.1 to 2.1.2 ...");
databaseEntitiesUpgradeService.upgradeDatabase("2.0.0");
databaseEntitiesUpgradeService.upgradeDatabase("2.1.1");
case "2.1.3":
log.info("Upgrading ThingsBoard from version 2.1.3 to 2.2.0 ...");
case "2.1.1":
log.info("Upgrading ThingsBoard from version 2.1.1 to 2.1.2 ...");
databaseEntitiesUpgradeService.upgradeDatabase("2.1.3");
databaseEntitiesUpgradeService.upgradeDatabase("2.1.1");
case "2.1.3":
log.info("Upgrading ThingsBoard from version 2.1.3 to 2.2.0 ...");
case "2.3.0":
log.info("Upgrading ThingsBoard from version 2.3.0 to 2.3.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("2.1.3");
databaseEntitiesUpgradeService.upgradeDatabase("2.3.0");
case "2.3.0":
log.info("Upgrading ThingsBoard from version 2.3.0 to 2.3.1 ...");
case "2.3.1":
log.info("Upgrading ThingsBoard from version 2.3.1 to 2.4.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("2.3.0");
databaseEntitiesUpgradeService.upgradeDatabase("2.3.1");
case "2.3.1":
log.info("Upgrading ThingsBoard from version 2.3.1 to 2.4.0 ...");
case "2.4.0":
log.info("Upgrading ThingsBoard from version 2.4.0 to 2.4.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("2.3.1");
case "2.4.1":
log.info("Upgrading ThingsBoard from version 2.4.1 to 2.4.2 ...");
case "2.4.0":
log.info("Upgrading ThingsBoard from version 2.4.0 to 2.4.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("2.4.1");
case "2.4.2":
log.info("Upgrading ThingsBoard from version 2.4.2 to 2.4.3 ...");
case "2.4.1":
log.info("Upgrading ThingsBoard from version 2.4.1 to 2.4.2 ...");
databaseEntitiesUpgradeService.upgradeDatabase("2.4.2");
databaseEntitiesUpgradeService.upgradeDatabase("2.4.1");
case "2.4.2":
log.info("Upgrading ThingsBoard from version 2.4.2 to 2.4.3 ...");
case "2.4.3":
log.info("Upgrading ThingsBoard from version 2.4.3 to 2.5 ...");
databaseEntitiesUpgradeService.upgradeDatabase("2.4.2");
if (databaseTsUpgradeService != null) {
databaseTsUpgradeService.upgradeDatabase("2.4.3");
}
databaseEntitiesUpgradeService.upgradeDatabase("2.4.3");
case "2.4.3":
log.info("Upgrading ThingsBoard from version 2.4.3 to 2.5 ...");
log.info("Updating system data...");
if (databaseTsUpgradeService != null) {
databaseTsUpgradeService.upgradeDatabase("2.4.3");
}
databaseEntitiesUpgradeService.upgradeDatabase("2.4.3");
systemDataLoaderService.deleteSystemWidgetBundle("charts");
systemDataLoaderService.deleteSystemWidgetBundle("cards");
systemDataLoaderService.deleteSystemWidgetBundle("maps");
systemDataLoaderService.deleteSystemWidgetBundle("analogue_gauges");
systemDataLoaderService.deleteSystemWidgetBundle("digital_gauges");
systemDataLoaderService.deleteSystemWidgetBundle("gpio_widgets");
systemDataLoaderService.deleteSystemWidgetBundle("alarm_widgets");
systemDataLoaderService.deleteSystemWidgetBundle("control_widgets");
systemDataLoaderService.deleteSystemWidgetBundle("maps_v2");
systemDataLoaderService.deleteSystemWidgetBundle("gateway_widgets");
systemDataLoaderService.deleteSystemWidgetBundle("input_widgets");
systemDataLoaderService.deleteSystemWidgetBundle("date");
systemDataLoaderService.deleteSystemWidgetBundle("entity_admin_widgets");
log.info("Updating system data...");
systemDataLoaderService.loadSystemWidgets();
break;
default:
throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);
systemDataLoaderService.updateSystemWidgets();
break;
default:
throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);
}
}
log.info("Upgrade finished successfully!");

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

@ -22,37 +22,18 @@ import org.springframework.beans.factory.annotation.Value;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.sql.SQLWarning;
import java.sql.Statement;
@Slf4j
public abstract class AbstractSqlTsDatabaseUpgradeService {
protected static final String CALL_REGEX = "call ";
protected static final String CHECK_VERSION = "check_version()";
protected static final String DROP_TABLE = "DROP TABLE ";
protected static final String DROP_FUNCTION_IF_EXISTS = "DROP FUNCTION IF EXISTS ";
private static final String CALL_CHECK_VERSION = CALL_REGEX + CHECK_VERSION;
private static final String FUNCTION = "function: {}";
private static final String DROP_STATEMENT = "drop statement: {}";
private static final String QUERY = "query: {}";
private static final String SUCCESSFULLY_EXECUTED = "Successfully executed ";
private static final String FAILED_TO_EXECUTE = "Failed to execute ";
private static final String FAILED_DUE_TO = " due to: {}";
protected static final String SUCCESSFULLY_EXECUTED_FUNCTION = SUCCESSFULLY_EXECUTED + FUNCTION;
protected static final String FAILED_TO_EXECUTE_FUNCTION_DUE_TO = FAILED_TO_EXECUTE + FUNCTION + FAILED_DUE_TO;
protected static final String SUCCESSFULLY_EXECUTED_DROP_STATEMENT = SUCCESSFULLY_EXECUTED + DROP_STATEMENT;
protected static final String FAILED_TO_EXECUTE_DROP_STATEMENT = FAILED_TO_EXECUTE + DROP_STATEMENT + FAILED_DUE_TO;
protected static final String SUCCESSFULLY_EXECUTED_QUERY = SUCCESSFULLY_EXECUTED + QUERY;
protected static final String FAILED_TO_EXECUTE_QUERY = FAILED_TO_EXECUTE + QUERY + FAILED_DUE_TO;
protected static final String DROP_PROCEDURE_IF_EXISTS = "DROP PROCEDURE IF EXISTS ";
@Value("${spring.datasource.url}")
protected String dbUrl;
@ -74,50 +55,38 @@ public abstract class AbstractSqlTsDatabaseUpgradeService {
}
protected boolean checkVersion(Connection conn) {
log.info("Check the current PostgreSQL version...");
boolean versionValid = false;
try {
CallableStatement callableStatement = conn.prepareCall("{? = " + CALL_CHECK_VERSION + " }");
callableStatement.registerOutParameter(1, Types.BOOLEAN);
callableStatement.execute();
versionValid = callableStatement.getBoolean(1);
callableStatement.close();
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT current_setting('server_version_num')");
resultSet.next();
if(resultSet.getLong(1) > 110000) {
versionValid = true;
}
statement.close();
} catch (Exception e) {
log.info("Failed to check current PostgreSQL version due to: {}", e.getMessage());
}
return versionValid;
}
protected void executeFunction(Connection conn, String query) {
log.info("{} ... ", query);
try {
CallableStatement callableStatement = conn.prepareCall("{" + query + "}");
callableStatement.execute();
callableStatement.close();
log.info(SUCCESSFULLY_EXECUTED_FUNCTION, query.replace(CALL_REGEX, ""));
Thread.sleep(2000);
} catch (Exception e) {
log.info(FAILED_TO_EXECUTE_FUNCTION_DUE_TO, query, e.getMessage());
}
}
protected void executeDropStatement(Connection conn, String query) {
try {
conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
log.info(SUCCESSFULLY_EXECUTED_DROP_STATEMENT, query);
Thread.sleep(5000);
} catch (InterruptedException | SQLException e) {
log.info(FAILED_TO_EXECUTE_DROP_STATEMENT, query, e.getMessage());
}
}
protected void executeQuery(Connection conn, String query) {
try {
conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
log.info(SUCCESSFULLY_EXECUTED_QUERY, query);
Statement statement = conn.createStatement();
statement.execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
SQLWarning warnings = statement.getWarnings();
if (warnings != null) {
log.info("{}", warnings.getMessage());
SQLWarning nextWarning = warnings.getNextWarning();
while (nextWarning != null) {
log.info("{}", nextWarning.getMessage());
nextWarning = nextWarning.getNextWarning();
}
}
Thread.sleep(5000);
log.info("Successfully executed query: {}", query);
} catch (InterruptedException | SQLException e) {
log.info(FAILED_TO_EXECUTE_QUERY, query, e.getMessage());
log.info("Failed to execute query: {} due to: {}", query, e.getMessage());
}
}

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

@ -1,300 +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.service.install;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.exceptions.InvalidQueryException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.service.install.cql.CassandraDbHelper;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO;
import static org.thingsboard.server.service.install.DatabaseHelper.ASSET;
import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS;
import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION;
import static org.thingsboard.server.service.install.DatabaseHelper.CUSTOMER_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.DASHBOARD;
import static org.thingsboard.server.service.install.DatabaseHelper.DEVICE;
import static org.thingsboard.server.service.install.DatabaseHelper.END_TS;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_TYPE;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEW;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEWS;
import static org.thingsboard.server.service.install.DatabaseHelper.ID;
import static org.thingsboard.server.service.install.DatabaseHelper.KEYS;
import static org.thingsboard.server.service.install.DatabaseHelper.NAME;
import static org.thingsboard.server.service.install.DatabaseHelper.SEARCH_TEXT;
import static org.thingsboard.server.service.install.DatabaseHelper.START_TS;
import static org.thingsboard.server.service.install.DatabaseHelper.TENANT_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.TITLE;
import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
@Service
@NoSqlDao
@Profile("install")
@Slf4j
public class CassandraDatabaseUpgradeService extends AbstractCassandraDatabaseUpgradeService implements DatabaseEntitiesUpgradeService {
private static final String SCHEMA_UPDATE_CQL = "schema_update.cql";
@Autowired
private DashboardService dashboardService;
@Autowired
private InstallScripts installScripts;
@Override
public void upgradeDatabase(String fromVersion) throws Exception {
switch (fromVersion) {
case "1.2.3":
log.info("Upgrading Cassandara DataBase from version {} to 1.3.0 ...", fromVersion);
//Dump devices, assets and relations
cluster.getSession();
KeyspaceMetadata ks = cluster.getCluster().getMetadata().getKeyspace(cluster.getKeyspaceName());
log.info("Dumping devices ...");
Path devicesDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), DEVICE,
new String[]{"id", TENANT_ID, CUSTOMER_ID, "name", SEARCH_TEXT, ADDITIONAL_INFO, "type"},
new String[]{"", "", "", "", "", "", "default"},
"tb-devices");
log.info("Devices dumped.");
log.info("Dumping assets ...");
Path assetsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), ASSET,
new String[]{"id", TENANT_ID, CUSTOMER_ID, "name", SEARCH_TEXT, ADDITIONAL_INFO, "type"},
new String[]{"", "", "", "", "", "", "default"},
"tb-assets");
log.info("Assets dumped.");
log.info("Dumping relations ...");
Path relationsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), "relation",
new String[]{"from_id", "from_type", "to_id", "to_type", "relation_type", ADDITIONAL_INFO, "relation_type_group"},
new String[]{"", "", "", "", "", "", "COMMON"},
"tb-relations");
log.info("Relations dumped.");
log.info("Updating schema ...");
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.0", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
//Restore devices, assets and relations
log.info("Restoring devices ...");
if (devicesDump != null) {
CassandraDbHelper.loadCf(ks, cluster.getSession(), DEVICE,
new String[]{"id", TENANT_ID, CUSTOMER_ID, "name", SEARCH_TEXT, ADDITIONAL_INFO, "type"}, devicesDump);
Files.deleteIfExists(devicesDump);
}
log.info("Devices restored.");
log.info("Dumping device types ...");
Path deviceTypesDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), DEVICE,
new String[]{TENANT_ID, "type"},
new String[]{"", ""},
"tb-device-types");
if (deviceTypesDump != null) {
CassandraDbHelper.appendToEndOfLine(deviceTypesDump, "DEVICE");
}
log.info("Device types dumped.");
log.info("Loading device types ...");
if (deviceTypesDump != null) {
CassandraDbHelper.loadCf(ks, cluster.getSession(), "entity_subtype",
new String[]{TENANT_ID, "type", "entity_type"}, deviceTypesDump);
Files.deleteIfExists(deviceTypesDump);
}
log.info("Device types loaded.");
log.info("Restoring assets ...");
if (assetsDump != null) {
CassandraDbHelper.loadCf(ks, cluster.getSession(), ASSET,
new String[]{"id", TENANT_ID, CUSTOMER_ID, "name", SEARCH_TEXT, ADDITIONAL_INFO, "type"}, assetsDump);
Files.deleteIfExists(assetsDump);
}
log.info("Assets restored.");
log.info("Dumping asset types ...");
Path assetTypesDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), ASSET,
new String[]{TENANT_ID, "type"},
new String[]{"", ""},
"tb-asset-types");
if (assetTypesDump != null) {
CassandraDbHelper.appendToEndOfLine(assetTypesDump, "ASSET");
}
log.info("Asset types dumped.");
log.info("Loading asset types ...");
if (assetTypesDump != null) {
CassandraDbHelper.loadCf(ks, cluster.getSession(), "entity_subtype",
new String[]{TENANT_ID, "type", "entity_type"}, assetTypesDump);
Files.deleteIfExists(assetTypesDump);
}
log.info("Asset types loaded.");
log.info("Restoring relations ...");
if (relationsDump != null) {
CassandraDbHelper.loadCf(ks, cluster.getSession(), "relation",
new String[]{"from_id", "from_type", "to_id", "to_type", "relation_type", ADDITIONAL_INFO, "relation_type_group"}, relationsDump);
Files.deleteIfExists(relationsDump);
}
log.info("Relations restored.");
break;
case "1.3.0":
break;
case "1.3.1":
cluster.getSession();
ks = cluster.getCluster().getMetadata().getKeyspace(cluster.getKeyspaceName());
log.info("Dumping dashboards ...");
Path dashboardsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), DASHBOARD,
new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION},
new String[]{"", "", "", "", "", "", ""},
"tb-dashboards", true);
log.info("Dashboards dumped.");
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
log.info("Restoring dashboards ...");
if (dashboardsDump != null) {
CassandraDbHelper.loadCf(ks, cluster.getSession(), DASHBOARD,
new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump, true);
DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, false);
Files.deleteIfExists(dashboardsDump);
}
log.info("Dashboards restored.");
break;
case "1.4.0":
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.0.0", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
break;
case "2.0.0":
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.1", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
break;
case "2.1.1":
log.info("Upgrading Cassandra DataBase from version {} to 2.1.2 ...", fromVersion);
cluster.getSession();
ks = cluster.getCluster().getMetadata().getKeyspace(cluster.getKeyspaceName());
log.info("Dumping entity views ...");
Path entityViewsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), ENTITY_VIEWS,
new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, NAME, TYPE, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO},
new String[]{"", "", "", "", "", "", "default", "", "0", "0", "", ""},
"tb-entity-views");
log.info("Entity views dumped.");
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.2", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
log.info("Restoring entity views ...");
if (entityViewsDump != null) {
CassandraDbHelper.loadCf(ks, cluster.getSession(), ENTITY_VIEW,
new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, NAME, TYPE, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO}, entityViewsDump);
Files.deleteIfExists(entityViewsDump);
}
log.info("Entity views restored.");
break;
case "2.1.3":
break;
case "2.3.0":
break;
case "2.3.1":
log.info("Updating schema ...");
String updateDeviceTableStmt = "alter table device add label text";
try {
cluster.getSession().execute(updateDeviceTableStmt);
Thread.sleep(2500);
} catch (InvalidQueryException e) {
}
log.info("Schema updated.");
break;
case "2.4.1":
log.info("Updating schema ...");
String updateAssetTableStmt = "alter table asset add label text";
try {
log.info("Updating assets ...");
cluster.getSession().execute(updateAssetTableStmt);
Thread.sleep(2500);
log.info("Assets updated.");
} catch (InvalidQueryException e) {
}
log.info("Schema updated.");
break;
case "2.4.2":
log.info("Updating schema ...");
String updateAlarmTableStmt = "alter table alarm add propagate_relation_types text";
try {
log.info("Updating alarms ...");
cluster.getSession().execute(updateAlarmTableStmt);
Thread.sleep(2500);
log.info("Alarms updated.");
} catch (InvalidQueryException e) {
}
log.info("Schema updated.");
break;
case "2.4.3":
log.info("Updating schema ...");
String updateAttributeKvTableStmt = "alter table attributes_kv_cf add json_v text";
try {
log.info("Updating attributes ...");
cluster.getSession().execute(updateAttributeKvTableStmt);
Thread.sleep(2500);
log.info("Attributes updated.");
} catch (InvalidQueryException e) {
}
log.info("Schema updated.");
break;
default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
}
}
}

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

@ -168,6 +168,24 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
installScripts.loadSystemWidgets();
}
@Override
public void updateSystemWidgets() throws Exception {
this.deleteSystemWidgetBundle("charts");
this.deleteSystemWidgetBundle("cards");
this.deleteSystemWidgetBundle("maps");
this.deleteSystemWidgetBundle("analogue_gauges");
this.deleteSystemWidgetBundle("digital_gauges");
this.deleteSystemWidgetBundle("gpio_widgets");
this.deleteSystemWidgetBundle("alarm_widgets");
this.deleteSystemWidgetBundle("control_widgets");
this.deleteSystemWidgetBundle("maps_v2");
this.deleteSystemWidgetBundle("gateway_widgets");
this.deleteSystemWidgetBundle("input_widgets");
this.deleteSystemWidgetBundle("date");
this.deleteSystemWidgetBundle("entity_admin_widgets");
installScripts.loadSystemWidgets();
}
private User createUser(Authority authority,
TenantId tenantId,
CustomerId customerId,

33
application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java

@ -0,0 +1,33 @@
/**
* 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.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlDao;
@Service
@HsqlDao
@SqlDao
@Profile("install")
public class HsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
implements EntityDatabaseSchemaService {
protected HsqlEntityDatabaseSchemaService() {
super("schema-entities-hsql.sql", "schema-entities-idx.sql");
}
}

6
application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java → application/src/main/java/org/thingsboard/server/service/install/PsqlEntityDatabaseSchemaService.java

@ -17,14 +17,16 @@ package org.thingsboard.server.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlDao;
@Service
@SqlDao
@PsqlDao
@Profile("install")
public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
public class PsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
implements EntityDatabaseSchemaService {
public SqlEntityDatabaseSchemaService() {
public PsqlEntityDatabaseSchemaService() {
super("schema-entities.sql", "schema-entities-idx.sql");
}
}

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

@ -57,49 +57,46 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
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_FUNCTION_CHECK_VERSION = DROP_FUNCTION_IF_EXISTS + CHECK_VERSION;
private static final String DROP_FUNCTION_CREATE_PARTITION_TS_KV_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_PARTITION_TS_KV_TABLE;
private static final String DROP_FUNCTION_CREATE_NEW_TS_KV_LATEST_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_NEW_TS_KV_LATEST_TABLE;
private static final String DROP_FUNCTION_CREATE_PARTITIONS = DROP_FUNCTION_IF_EXISTS + CREATE_PARTITIONS;
private static final String DROP_FUNCTION_CREATE_TS_KV_DICTIONARY_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE;
private static final String DROP_FUNCTION_INSERT_INTO_DICTIONARY = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_DICTIONARY;
private static final String DROP_FUNCTION_INSERT_INTO_TS_KV = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_TS_KV;
private static final String DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_TS_KV_LATEST;
private static final String DROP_PROCEDURE_CREATE_PARTITION_TS_KV_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_PARTITION_TS_KV_TABLE;
private static final String DROP_PROCEDURE_CREATE_NEW_TS_KV_LATEST_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_NEW_TS_KV_LATEST_TABLE;
private static final String DROP_PROCEDURE_CREATE_PARTITIONS = DROP_PROCEDURE_IF_EXISTS + CREATE_PARTITIONS;
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_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;
@Override
public void upgradeDatabase(String fromVersion) throws Exception {
switch (fromVersion) {
case "2.4.3":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating timeseries schema ...");
log.info("Load upgrade functions ...");
loadSql(conn);
log.info("Check the current PostgreSQL version...");
boolean versionValid = checkVersion(conn);
if (!versionValid) {
log.info("PostgreSQL version should be at least more than 10!");
log.info("Please upgrade your PostgreSQL and restart the script!");
throw new RuntimeException("PostgreSQL version should be at least more than 11, please upgrade your PostgreSQL and restart the script!");
} else {
log.info("PostgreSQL version is valid!");
log.info("Updating schema ...");
executeFunction(conn, CALL_CREATE_PARTITION_TS_KV_TABLE);
executeFunction(conn, CALL_CREATE_PARTITIONS);
executeFunction(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE);
executeFunction(conn, CALL_INSERT_INTO_DICTIONARY);
executeFunction(conn, CALL_INSERT_INTO_TS_KV);
executeFunction(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE);
executeFunction(conn, CALL_INSERT_INTO_TS_KV_LATEST);
log.info("Load upgrade functions ...");
loadSql(conn);
log.info("Updating timeseries schema ...");
executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE);
executeQuery(conn, CALL_CREATE_PARTITIONS);
executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE);
executeQuery(conn, CALL_INSERT_INTO_DICTIONARY);
executeQuery(conn, CALL_INSERT_INTO_TS_KV);
executeQuery(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE);
executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST);
executeDropStatement(conn, DROP_TABLE_TS_KV_OLD);
executeDropStatement(conn, DROP_TABLE_TS_KV_LATEST_OLD);
executeQuery(conn, DROP_TABLE_TS_KV_OLD);
executeQuery(conn, DROP_TABLE_TS_KV_LATEST_OLD);
executeDropStatement(conn, DROP_FUNCTION_CHECK_VERSION);
executeDropStatement(conn, DROP_FUNCTION_CREATE_PARTITION_TS_KV_TABLE);
executeDropStatement(conn, DROP_FUNCTION_CREATE_PARTITIONS);
executeDropStatement(conn, DROP_FUNCTION_CREATE_TS_KV_DICTIONARY_TABLE);
executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_DICTIONARY);
executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV);
executeDropStatement(conn, DROP_FUNCTION_CREATE_NEW_TS_KV_LATEST_TABLE);
executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, DROP_PROCEDURE_CREATE_PARTITION_TS_KV_TABLE);
executeQuery(conn, DROP_PROCEDURE_CREATE_PARTITIONS);
executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_DICTIONARY);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV);
executeQuery(conn, DROP_PROCEDURE_CREATE_NEW_TS_KV_LATEST_TABLE);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN json_v json;");
executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN json_v json;");

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

@ -23,6 +23,8 @@ public interface SystemDataLoaderService {
void loadSystemWidgets() throws Exception;
void updateSystemWidgets() throws Exception;
void loadDemoData() throws Exception;
void deleteSystemWidgetBundle(String bundleAlias) throws Exception;

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

@ -45,13 +45,13 @@ public class TimescaleTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaS
private long chunkTimeInterval;
public TimescaleTsDatabaseSchemaService() {
super("schema-timescale.sql", "schema-timescale-idx.sql");
super("schema-timescale.sql", null);
}
@Override
public void createDatabaseSchema() throws Exception {
super.createDatabaseSchema();
executeQuery("SELECT create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => " + chunkTimeInterval + ", if_not_exists => true);");
executeQuery("SELECT create_hypertable('ts_kv', 'ts', chunk_time_interval => " + chunkTimeInterval + ", if_not_exists => true);");
}
private void executeQuery(String query) {

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

@ -43,27 +43,27 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
private static final String TENANT_TS_KV_OLD_TABLE = "tenant_ts_kv_old;";
private static final String CREATE_TS_KV_LATEST_TABLE = "create_ts_kv_latest_table()";
private static final String CREATE_NEW_TENANT_TS_KV_TABLE = "create_new_tenant_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 INSERT_INTO_DICTIONARY = "insert_into_dictionary()";
private static final String INSERT_INTO_TENANT_TS_KV = "insert_into_tenant_ts_kv()";
private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv()";
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_NEW_TENANT_TS_KV_TABLE = CALL_REGEX + CREATE_NEW_TENANT_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_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY;
private static final String CALL_INSERT_INTO_TS_KV = CALL_REGEX + INSERT_INTO_TENANT_TS_KV;
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 DROP_OLD_TENANT_TS_KV_TABLE = DROP_TABLE + TENANT_TS_KV_OLD_TABLE;
private static final String DROP_FUNCTION_CREATE_TS_KV_LATEST_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_TS_KV_LATEST_TABLE;
private static final String DROP_FUNCTION_CREATE_TENANT_TS_KV_TABLE_COPY = DROP_FUNCTION_IF_EXISTS + CREATE_NEW_TENANT_TS_KV_TABLE;
private static final String DROP_FUNCTION_CREATE_TS_KV_DICTIONARY_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE;
private static final String DROP_FUNCTION_INSERT_INTO_DICTIONARY = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_DICTIONARY;
private static final String DROP_FUNCTION_INSERT_INTO_TENANT_TS_KV = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_TENANT_TS_KV;
private static final String DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_TS_KV_LATEST;
private static final String DROP_PROCEDURE_CREATE_TS_KV_LATEST_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_TS_KV_LATEST_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_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_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST;
@Autowired
private InstallScripts installScripts;
@ -73,41 +73,38 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
switch (fromVersion) {
case "2.4.3":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating timescale schema ...");
log.info("Load upgrade functions ...");
loadSql(conn);
log.info("Check the current PostgreSQL version...");
boolean versionValid = checkVersion(conn);
if (!versionValid) {
log.info("PostgreSQL version should be at least more than 9.6!");
log.info("Please upgrade your PostgreSQL and restart the script!");
throw new RuntimeException("PostgreSQL version should be at least more than 11, please upgrade your PostgreSQL and restart the script!");
} else {
log.info("PostgreSQL version is valid!");
log.info("Updating schema ...");
executeFunction(conn, CALL_CREATE_TS_KV_LATEST_TABLE);
executeFunction(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE);
log.info("Load upgrade functions ...");
loadSql(conn);
log.info("Updating timescale schema ...");
executeQuery(conn, CALL_CREATE_TS_KV_LATEST_TABLE);
executeQuery(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE);
executeQuery(conn, "SELECT create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => " + chunkTimeInterval + ", if_not_exists => true);");
executeQuery(conn, "SELECT create_hypertable('ts_kv', 'ts', chunk_time_interval => " + chunkTimeInterval + ", if_not_exists => true);");
executeFunction(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE);
executeFunction(conn, CALL_INSERT_INTO_DICTIONARY);
executeFunction(conn, CALL_INSERT_INTO_TS_KV);
executeFunction(conn, CALL_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE);
executeQuery(conn, CALL_INSERT_INTO_DICTIONARY);
executeQuery(conn, CALL_INSERT_INTO_TS_KV);
executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST);
//executeQuery(conn, "SELECT set_chunk_time_interval('tenant_ts_kv', " + chunkTimeInterval +");");
executeQuery(conn, DROP_OLD_TENANT_TS_KV_TABLE);
executeDropStatement(conn, DROP_OLD_TENANT_TS_KV_TABLE);
executeDropStatement(conn, DROP_FUNCTION_CREATE_TS_KV_LATEST_TABLE);
executeDropStatement(conn, DROP_FUNCTION_CREATE_TENANT_TS_KV_TABLE_COPY);
executeDropStatement(conn, DROP_FUNCTION_CREATE_TS_KV_DICTIONARY_TABLE);
executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_DICTIONARY);
executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TENANT_TS_KV);
executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_LATEST_TABLE);
executeQuery(conn, DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY);
executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_DICTIONARY);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TENANT_TS_KV);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN json_v json;");
executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN json_v json;");
log.info("schema timeseries updated!");
log.info("schema timescale updated!");
}
}
break;

314
application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java

@ -0,0 +1,314 @@
/**
* 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.service.install.migrate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.util.NoSqlAnyDao;
import org.thingsboard.server.dao.util.SqlDao;
import org.thingsboard.server.service.install.EntityDatabaseSchemaService;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Arrays;
import java.util.List;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.bigintColumn;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.booleanColumn;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.doubleColumn;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.enumToIntColumn;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.idColumn;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.jsonColumn;
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.stringColumn;
@Service
@Profile("install")
@SqlDao
@NoSqlAnyDao
@Slf4j
public class CassandraEntitiesToSqlMigrateService implements EntitiesMigrateService {
@Autowired
private EntityDatabaseSchemaService entityDatabaseSchemaService;
@Autowired
protected CassandraCluster cluster;
@Value("${spring.datasource.url}")
protected String dbUrl;
@Value("${spring.datasource.username}")
protected String dbUserName;
@Value("${spring.datasource.password}")
protected String dbPassword;
@Override
public void migrate() throws Exception {
log.info("Performing migration of entities data from cassandra to SQL database ...");
entityDatabaseSchemaService.createDatabaseSchema();
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
conn.setAutoCommit(false);
for (CassandraToSqlTable table: tables) {
table.migrateToSql(cluster.getSession(), conn);
}
} catch (Exception e) {
log.error("Unexpected error during ThingsBoard entities data migration!", e);
throw e;
}
}
private static List<CassandraToSqlTable> tables = Arrays.asList(
new CassandraToSqlTable("admin_settings",
idColumn("id"),
stringColumn("key"),
stringColumn("json_value")),
new CassandraToSqlTable("alarm",
idColumn("id"),
idColumn("tenant_id"),
stringColumn("type"),
idColumn("originator_id"),
enumToIntColumn("originator_type", EntityType.class),
stringColumn("severity"),
stringColumn("status"),
bigintColumn("start_ts"),
bigintColumn("end_ts"),
bigintColumn("ack_ts"),
bigintColumn("clear_ts"),
stringColumn("details", "additional_info"),
booleanColumn("propagate"),
stringColumn("propagate_relation_types")),
new CassandraToSqlTable("asset",
idColumn("id"),
idColumn("tenant_id"),
idColumn("customer_id"),
stringColumn("name"),
stringColumn("type"),
stringColumn("label"),
stringColumn("search_text"),
stringColumn("additional_info")) {
@Override
protected boolean onConstraintViolation(List<CassandraToSqlColumnData[]> batchData,
CassandraToSqlColumnData[] data, String constraint) {
if (constraint.equalsIgnoreCase("asset_name_unq_key")) {
this.handleUniqueNameViolation(data, "asset");
return true;
}
return super.onConstraintViolation(batchData, data, constraint);
}
},
new CassandraToSqlTable("audit_log_by_tenant_id", "audit_log",
idColumn("id"),
idColumn("tenant_id"),
idColumn("customer_id"),
idColumn("entity_id"),
stringColumn("entity_type"),
stringColumn("entity_name"),
idColumn("user_id"),
stringColumn("user_name"),
stringColumn("action_type"),
stringColumn("action_data"),
stringColumn("action_status"),
stringColumn("action_failure_details")),
new CassandraToSqlTable("attributes_kv_cf", "attribute_kv",
idColumn("entity_id"),
stringColumn("entity_type"),
stringColumn("attribute_type"),
stringColumn("attribute_key"),
booleanColumn("bool_v"),
stringColumn("str_v"),
bigintColumn("long_v"),
doubleColumn("dbl_v"),
jsonColumn("json_v"),
bigintColumn("last_update_ts")),
new CassandraToSqlTable("component_descriptor",
idColumn("id"),
stringColumn("type"),
stringColumn("scope"),
stringColumn("name"),
stringColumn("search_text"),
stringColumn("clazz"),
stringColumn("configuration_descriptor"),
stringColumn("actions")),
new CassandraToSqlTable("customer",
idColumn("id"),
idColumn("tenant_id"),
stringColumn("title"),
stringColumn("search_text"),
stringColumn("country"),
stringColumn("state"),
stringColumn("city"),
stringColumn("address"),
stringColumn("address2"),
stringColumn("zip"),
stringColumn("phone"),
stringColumn("email"),
stringColumn("additional_info")),
new CassandraToSqlTable("dashboard",
idColumn("id"),
idColumn("tenant_id"),
stringColumn("title"),
stringColumn("search_text"),
stringColumn("assigned_customers"),
stringColumn("configuration")),
new CassandraToSqlTable("device",
idColumn("id"),
idColumn("tenant_id"),
idColumn("customer_id"),
stringColumn("name"),
stringColumn("type"),
stringColumn("label"),
stringColumn("search_text"),
stringColumn("additional_info")) {
@Override
protected boolean onConstraintViolation(List<CassandraToSqlColumnData[]> batchData,
CassandraToSqlColumnData[] data, String constraint) {
if (constraint.equalsIgnoreCase("device_name_unq_key")) {
this.handleUniqueNameViolation(data, "device");
return true;
}
return super.onConstraintViolation(batchData, data, constraint);
}
},
new CassandraToSqlTable("device_credentials",
idColumn("id"),
idColumn("device_id"),
stringColumn("credentials_type"),
stringColumn("credentials_id"),
stringColumn("credentials_value")),
new CassandraToSqlTable("event",
idColumn("id"),
idColumn("tenant_id"),
idColumn("entity_id"),
stringColumn("entity_type"),
stringColumn("event_type"),
stringColumn("event_uid"),
stringColumn("body")),
new CassandraToSqlTable("relation",
idColumn("from_id"),
stringColumn("from_type"),
idColumn("to_id"),
stringColumn("to_type"),
stringColumn("relation_type_group"),
stringColumn("relation_type"),
stringColumn("additional_info")),
new CassandraToSqlTable("user", "tb_user",
idColumn("id"),
idColumn("tenant_id"),
idColumn("customer_id"),
stringColumn("email"),
stringColumn("search_text"),
stringColumn("authority"),
stringColumn("first_name"),
stringColumn("last_name"),
stringColumn("additional_info")) {
@Override
protected boolean onConstraintViolation(List<CassandraToSqlColumnData[]> batchData,
CassandraToSqlColumnData[] data, String constraint) {
if (constraint.equalsIgnoreCase("tb_user_email_key")) {
this.handleUniqueEmailViolation(data);
return true;
}
return super.onConstraintViolation(batchData, data, constraint);
}
},
new CassandraToSqlTable("tenant",
idColumn("id"),
stringColumn("title"),
stringColumn("search_text"),
stringColumn("region"),
stringColumn("country"),
stringColumn("state"),
stringColumn("city"),
stringColumn("address"),
stringColumn("address2"),
stringColumn("zip"),
stringColumn("phone"),
stringColumn("email"),
stringColumn("additional_info")),
new CassandraToSqlTable("user_credentials",
idColumn("id"),
idColumn("user_id"),
booleanColumn("enabled"),
stringColumn("password"),
stringColumn("activate_token"),
stringColumn("reset_token")) {
@Override
protected boolean onConstraintViolation(List<CassandraToSqlColumnData[]> batchData,
CassandraToSqlColumnData[] data, String constraint) {
if (constraint.equalsIgnoreCase("user_credentials_user_id_key")) {
String id = UUIDConverter.fromString(this.getColumnData(data, "id").getValue()).toString();
log.warn("Found user credentials record with duplicate user_id [id:[{}]]. Record will be ignored!", id);
this.ignoreRecord(batchData, data);
return true;
}
return super.onConstraintViolation(batchData, data, constraint);
}
},
new CassandraToSqlTable("widget_type",
idColumn("id"),
idColumn("tenant_id"),
stringColumn("bundle_alias"),
stringColumn("alias"),
stringColumn("name"),
stringColumn("descriptor")),
new CassandraToSqlTable("widgets_bundle",
idColumn("id"),
idColumn("tenant_id"),
stringColumn("alias"),
stringColumn("title"),
stringColumn("search_text")),
new CassandraToSqlTable("rule_chain",
idColumn("id"),
idColumn("tenant_id"),
stringColumn("name"),
stringColumn("search_text"),
idColumn("first_rule_node_id"),
booleanColumn("root"),
booleanColumn("debug_mode"),
stringColumn("configuration"),
stringColumn("additional_info")),
new CassandraToSqlTable("rule_node",
idColumn("id"),
idColumn("rule_chain_id"),
stringColumn("type"),
stringColumn("name"),
booleanColumn("debug_mode"),
stringColumn("search_text"),
stringColumn("configuration"),
stringColumn("additional_info")),
new CassandraToSqlTable("entity_view",
idColumn("id"),
idColumn("tenant_id"),
idColumn("customer_id"),
idColumn("entity_id"),
stringColumn("entity_type"),
stringColumn("name"),
stringColumn("type"),
stringColumn("keys"),
bigintColumn("start_ts"),
bigintColumn("end_ts"),
stringColumn("search_text"),
stringColumn("additional_info"))
);
}

168
application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java

@ -0,0 +1,168 @@
/**
* 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.service.install.migrate;
import com.datastax.driver.core.Row;
import lombok.Data;
import org.thingsboard.server.common.data.UUIDConverter;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.regex.Pattern;
@Data
public class CassandraToSqlColumn {
private static final ThreadLocal<Pattern> PATTERN_THREAD_LOCAL = ThreadLocal.withInitial(() -> Pattern.compile(String.valueOf(Character.MIN_VALUE)));
private static final String EMPTY_STR = "";
private int index;
private int sqlIndex;
private String cassandraColumnName;
private String sqlColumnName;
private CassandraToSqlColumnType type;
private int sqlType;
private int size;
private Class<? extends Enum> enumClass;
public static CassandraToSqlColumn idColumn(String name) {
return new CassandraToSqlColumn(name, CassandraToSqlColumnType.ID);
}
public static CassandraToSqlColumn stringColumn(String name) {
return new CassandraToSqlColumn(name, CassandraToSqlColumnType.STRING);
}
public static CassandraToSqlColumn stringColumn(String cassandraColumnName, String sqlColumnName) {
return new CassandraToSqlColumn(cassandraColumnName, sqlColumnName);
}
public static CassandraToSqlColumn bigintColumn(String name) {
return new CassandraToSqlColumn(name, CassandraToSqlColumnType.BIGINT);
}
public static CassandraToSqlColumn doubleColumn(String name) {
return new CassandraToSqlColumn(name, CassandraToSqlColumnType.DOUBLE);
}
public static CassandraToSqlColumn booleanColumn(String name) {
return new CassandraToSqlColumn(name, CassandraToSqlColumnType.BOOLEAN);
}
public static CassandraToSqlColumn jsonColumn(String name) {
return new CassandraToSqlColumn(name, CassandraToSqlColumnType.JSON);
}
public static CassandraToSqlColumn enumToIntColumn(String name, Class<? extends Enum> enumClass) {
return new CassandraToSqlColumn(name, CassandraToSqlColumnType.ENUM_TO_INT, enumClass);
}
public CassandraToSqlColumn(String columnName) {
this(columnName, columnName, CassandraToSqlColumnType.STRING, null);
}
public CassandraToSqlColumn(String columnName, CassandraToSqlColumnType type) {
this(columnName, columnName, type, null);
}
public CassandraToSqlColumn(String columnName, CassandraToSqlColumnType type, Class<? extends Enum> enumClass) {
this(columnName, columnName, type, enumClass);
}
public CassandraToSqlColumn(String cassandraColumnName, String sqlColumnName) {
this(cassandraColumnName, sqlColumnName, CassandraToSqlColumnType.STRING, null);
}
public CassandraToSqlColumn(String cassandraColumnName, String sqlColumnName, CassandraToSqlColumnType type,
Class<? extends Enum> enumClass) {
this.cassandraColumnName = cassandraColumnName;
this.sqlColumnName = sqlColumnName;
this.type = type;
this.enumClass = enumClass;
}
public String getColumnValue(Row row) {
if (row.isNull(index)) {
return null;
} else {
switch (this.type) {
case ID:
return UUIDConverter.fromTimeUUID(row.getUUID(index));
case DOUBLE:
return Double.toString(row.getDouble(index));
case INTEGER:
return Integer.toString(row.getInt(index));
case FLOAT:
return Float.toString(row.getFloat(index));
case BIGINT:
return Long.toString(row.getLong(index));
case BOOLEAN:
return Boolean.toString(row.getBool(index));
case STRING:
case JSON:
case ENUM_TO_INT:
default:
String value = row.getString(index);
return this.replaceNullChars(value);
}
}
}
public void setColumnValue(PreparedStatement sqlInsertStatement, String value) throws SQLException {
if (value == null) {
sqlInsertStatement.setNull(this.sqlIndex, this.sqlType);
} else {
switch (this.type) {
case DOUBLE:
sqlInsertStatement.setDouble(this.sqlIndex, Double.parseDouble(value));
break;
case INTEGER:
sqlInsertStatement.setInt(this.sqlIndex, Integer.parseInt(value));
break;
case FLOAT:
sqlInsertStatement.setFloat(this.sqlIndex, Float.parseFloat(value));
break;
case BIGINT:
sqlInsertStatement.setLong(this.sqlIndex, Long.parseLong(value));
break;
case BOOLEAN:
sqlInsertStatement.setBoolean(this.sqlIndex, Boolean.parseBoolean(value));
break;
case ENUM_TO_INT:
Enum enumVal = Enum.valueOf(this.enumClass, value);
int intValue = enumVal.ordinal();
sqlInsertStatement.setInt(this.sqlIndex, intValue);
break;
case JSON:
case STRING:
case ID:
default:
sqlInsertStatement.setString(this.sqlIndex, value);
break;
}
}
}
private String replaceNullChars(String strValue) {
if (strValue != null) {
return PATTERN_THREAD_LOCAL.get().matcher(strValue).replaceAll(EMPTY_STR);
}
return strValue;
}
}

64
application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumnData.java

@ -0,0 +1,64 @@
/**
* 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.service.install.migrate;
import lombok.Data;
@Data
public class CassandraToSqlColumnData {
private String value;
private String originalValue;
private int constraintCounter = 0;
public CassandraToSqlColumnData(String value) {
this.value = value;
this.originalValue = value;
}
public int nextContraintCounter() {
return ++constraintCounter;
}
public String getNextConstraintStringValue(CassandraToSqlColumn column) {
int counter = this.nextContraintCounter();
String newValue = this.originalValue + counter;
int overflow = newValue.length() - column.getSize();
if (overflow > 0) {
newValue = this.originalValue.substring(0, this.originalValue.length()-overflow) + counter;
}
return newValue;
}
public String getNextConstraintEmailValue(CassandraToSqlColumn column) {
int counter = this.nextContraintCounter();
String[] emailValues = this.originalValue.split("@");
String newValue = emailValues[0] + "+" + counter + "@" + emailValues[1];
int overflow = newValue.length() - column.getSize();
if (overflow > 0) {
newValue = emailValues[0].substring(0, emailValues[0].length()-overflow) + "+" + counter + "@" + emailValues[1];
}
return newValue;
}
public String getLogValue() {
if (this.value != null && this.value.length() > 255) {
return this.value.substring(0, 255) + "...[truncated " + (this.value.length() - 255) + " symbols]";
}
return this.value;
}
}

28
application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumnType.java

@ -0,0 +1,28 @@
/**
* 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.service.install.migrate;
public enum CassandraToSqlColumnType {
ID,
DOUBLE,
INTEGER,
FLOAT,
BIGINT,
BOOLEAN,
STRING,
JSON,
ENUM_TO_INT
}

308
application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlTable.java

@ -0,0 +1,308 @@
/**
* 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.service.install.migrate;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.SimpleStatement;
import com.datastax.driver.core.Statement;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.postgresql.util.PSQLException;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Data
@Slf4j
public class CassandraToSqlTable {
private static final int DEFAULT_BATCH_SIZE = 10000;
private String cassandraCf;
private String sqlTableName;
private List<CassandraToSqlColumn> columns;
private int batchSize = DEFAULT_BATCH_SIZE;
private PreparedStatement sqlInsertStatement;
public CassandraToSqlTable(String tableName, CassandraToSqlColumn... columns) {
this(tableName, tableName, DEFAULT_BATCH_SIZE, columns);
}
public CassandraToSqlTable(String tableName, String sqlTableName, CassandraToSqlColumn... columns) {
this(tableName, sqlTableName, DEFAULT_BATCH_SIZE, columns);
}
public CassandraToSqlTable(String tableName, int batchSize, CassandraToSqlColumn... columns) {
this(tableName, tableName, batchSize, columns);
}
public CassandraToSqlTable(String cassandraCf, String sqlTableName, int batchSize, CassandraToSqlColumn... columns) {
this.cassandraCf = cassandraCf;
this.sqlTableName = sqlTableName;
this.batchSize = batchSize;
this.columns = Arrays.asList(columns);
for (int i=0;i<columns.length;i++) {
this.columns.get(i).setIndex(i);
this.columns.get(i).setSqlIndex(i+1);
}
}
public void migrateToSql(Session session, Connection conn) throws SQLException {
log.info("[{}] Migrating data from cassandra '{}' Column Family to '{}' SQL table...", this.sqlTableName, this.cassandraCf, this.sqlTableName);
DatabaseMetaData metadata = conn.getMetaData();
java.sql.ResultSet resultSet = metadata.getColumns(null, null, this.sqlTableName, null);
while (resultSet.next()) {
String name = resultSet.getString("COLUMN_NAME");
int sqlType = resultSet.getInt("DATA_TYPE");
int size = resultSet.getInt("COLUMN_SIZE");
CassandraToSqlColumn column = this.getColumn(name);
column.setSize(size);
column.setSqlType(sqlType);
}
this.sqlInsertStatement = createSqlInsertStatement(conn);
Statement cassandraSelectStatement = createCassandraSelectStatement();
cassandraSelectStatement.setFetchSize(100);
ResultSet rs = session.execute(cassandraSelectStatement);
Iterator<Row> iter = rs.iterator();
int rowCounter = 0;
List<CassandraToSqlColumnData[]> batchData;
boolean hasNext;
do {
batchData = this.extractBatchData(iter);
hasNext = batchData.size() == this.batchSize;
this.batchInsert(batchData, conn);
rowCounter += batchData.size();
log.info("[{}] {} records migrated so far...", this.sqlTableName, rowCounter);
} while (hasNext);
this.sqlInsertStatement.close();
log.info("[{}] {} total records migrated.", this.sqlTableName, rowCounter);
log.info("[{}] Finished migration data from cassandra '{}' Column Family to '{}' SQL table.",
this.sqlTableName, this.cassandraCf, this.sqlTableName);
}
private List<CassandraToSqlColumnData[]> extractBatchData(Iterator<Row> iter) {
List<CassandraToSqlColumnData[]> batchData = new ArrayList<>();
while (iter.hasNext() && batchData.size() < this.batchSize) {
Row row = iter.next();
if (row != null) {
CassandraToSqlColumnData[] data = this.extractRowData(row);
batchData.add(data);
}
}
return batchData;
}
private CassandraToSqlColumnData[] extractRowData(Row row) {
CassandraToSqlColumnData[] data = new CassandraToSqlColumnData[this.columns.size()];
for (CassandraToSqlColumn column: this.columns) {
String value = column.getColumnValue(row);
data[column.getIndex()] = new CassandraToSqlColumnData(value);
}
return this.validateColumnData(data);
}
private CassandraToSqlColumnData[] validateColumnData(CassandraToSqlColumnData[] data) {
for (int i=0;i<data.length;i++) {
CassandraToSqlColumn column = this.columns.get(i);
if (column.getType() == CassandraToSqlColumnType.STRING) {
CassandraToSqlColumnData columnData = data[i];
String value = columnData.getValue();
if (value != null && value.length() > column.getSize()) {
log.warn("[{}] Value size [{}] exceeds maximum size [{}] of column [{}] and will be truncated!",
this.sqlTableName,
value.length(), column.getSize(), column.getSqlColumnName());
log.warn("[{}] Affected data:\n{}", this.sqlTableName, this.dataToString(data));
value = value.substring(0, column.getSize());
columnData.setOriginalValue(value);
columnData.setValue(value);
}
}
}
return data;
}
private void batchInsert(List<CassandraToSqlColumnData[]> batchData, Connection conn) throws SQLException {
boolean retry = false;
for (CassandraToSqlColumnData[] data : batchData) {
for (CassandraToSqlColumn column: this.columns) {
column.setColumnValue(this.sqlInsertStatement, data[column.getIndex()].getValue());
}
try {
this.sqlInsertStatement.executeUpdate();
} catch (SQLException e) {
if (this.handleInsertException(batchData, data, conn, e)) {
retry = true;
break;
} else {
throw e;
}
}
}
if (retry) {
this.batchInsert(batchData, conn);
} else {
conn.commit();
}
}
private boolean handleInsertException(List<CassandraToSqlColumnData[]> batchData,
CassandraToSqlColumnData[] data,
Connection conn, SQLException ex) throws SQLException {
conn.commit();
String constraint = extractConstraintName(ex).orElse(null);
if (constraint != null) {
if (this.onConstraintViolation(batchData, data, constraint)) {
return true;
} else {
log.error("[{}] Unhandled constraint violation [{}] during insert!", this.sqlTableName, constraint);
log.error("[{}] Affected data:\n{}", this.sqlTableName, this.dataToString(data));
}
} else {
log.error("[{}] Unhandled exception during insert!", this.sqlTableName);
log.error("[{}] Affected data:\n{}", this.sqlTableName, this.dataToString(data));
}
return false;
}
private String dataToString(CassandraToSqlColumnData[] data) {
StringBuffer stringData = new StringBuffer("{\n");
for (int i=0;i<data.length;i++) {
String columnName = this.columns.get(i).getSqlColumnName();
String value = data[i].getLogValue();
stringData.append("\"").append(columnName).append("\": ").append("[").append(value).append("]\n");
}
stringData.append("}");
return stringData.toString();
}
protected boolean onConstraintViolation(List<CassandraToSqlColumnData[]> batchData,
CassandraToSqlColumnData[] data, String constraint) {
return false;
}
protected void handleUniqueNameViolation(CassandraToSqlColumnData[] data, String entityType) {
CassandraToSqlColumn nameColumn = this.getColumn("name");
CassandraToSqlColumn searchTextColumn = this.getColumn("search_text");
CassandraToSqlColumnData nameColumnData = data[nameColumn.getIndex()];
CassandraToSqlColumnData searchTextColumnData = data[searchTextColumn.getIndex()];
String prevName = nameColumnData.getValue();
String newName = nameColumnData.getNextConstraintStringValue(nameColumn);
nameColumnData.setValue(newName);
searchTextColumnData.setValue(searchTextColumnData.getNextConstraintStringValue(searchTextColumn));
String id = UUIDConverter.fromString(this.getColumnData(data, "id").getValue()).toString();
log.warn("Found {} with duplicate name [id:[{}]]. Attempting to rename {} from '{}' to '{}'...", entityType, id, entityType, prevName, newName);
}
protected void handleUniqueEmailViolation(CassandraToSqlColumnData[] data) {
CassandraToSqlColumn emailColumn = this.getColumn("email");
CassandraToSqlColumn searchTextColumn = this.getColumn("search_text");
CassandraToSqlColumnData emailColumnData = data[emailColumn.getIndex()];
CassandraToSqlColumnData searchTextColumnData = data[searchTextColumn.getIndex()];
String prevEmail = emailColumnData.getValue();
String newEmail = emailColumnData.getNextConstraintEmailValue(emailColumn);
emailColumnData.setValue(newEmail);
searchTextColumnData.setValue(searchTextColumnData.getNextConstraintEmailValue(searchTextColumn));
String id = UUIDConverter.fromString(this.getColumnData(data, "id").getValue()).toString();
log.warn("Found user with duplicate email [id:[{}]]. Attempting to rename email from '{}' to '{}'...", id, prevEmail, newEmail);
}
protected void ignoreRecord(List<CassandraToSqlColumnData[]> batchData, CassandraToSqlColumnData[] data) {
log.warn("[{}] Affected data:\n{}", this.sqlTableName, this.dataToString(data));
int index = batchData.indexOf(data);
if (index > 0) {
batchData.remove(index);
}
}
protected CassandraToSqlColumn getColumn(String sqlColumnName) {
return this.columns.stream().filter(col -> col.getSqlColumnName().equals(sqlColumnName)).findFirst().get();
}
protected CassandraToSqlColumnData getColumnData(CassandraToSqlColumnData[] data, String sqlColumnName) {
CassandraToSqlColumn column = this.getColumn(sqlColumnName);
return data[column.getIndex()];
}
private Optional<String> extractConstraintName(SQLException ex) {
final String sqlState = JdbcExceptionHelper.extractSqlState( ex );
if (sqlState != null) {
String sqlStateClassCode = JdbcExceptionHelper.determineSqlStateClassCode( sqlState );
if ( sqlStateClassCode != null ) {
if (Arrays.asList(
"23", // "integrity constraint violation"
"27", // "triggered data change violation"
"44" // "with check option violation"
).contains(sqlStateClassCode)) {
if (ex instanceof PSQLException) {
return Optional.of(((PSQLException)ex).getServerErrorMessage().getConstraint());
}
}
}
}
return Optional.empty();
}
private Statement createCassandraSelectStatement() {
StringBuilder selectStatementBuilder = new StringBuilder();
selectStatementBuilder.append("SELECT ");
for (CassandraToSqlColumn column : columns) {
selectStatementBuilder.append(column.getCassandraColumnName()).append(",");
}
selectStatementBuilder.deleteCharAt(selectStatementBuilder.length() - 1);
selectStatementBuilder.append(" FROM ").append(cassandraCf);
return new SimpleStatement(selectStatementBuilder.toString());
}
private PreparedStatement createSqlInsertStatement(Connection conn) throws SQLException {
StringBuilder insertStatementBuilder = new StringBuilder();
insertStatementBuilder.append("INSERT INTO ").append(this.sqlTableName).append(" (");
for (CassandraToSqlColumn column : columns) {
insertStatementBuilder.append(column.getSqlColumnName()).append(",");
}
insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
insertStatementBuilder.append(") VALUES (");
for (CassandraToSqlColumn column : columns) {
if (column.getType() == CassandraToSqlColumnType.JSON) {
insertStatementBuilder.append("cast(? AS json)");
} else {
insertStatementBuilder.append("?");
}
insertStatementBuilder.append(",");
}
insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
insertStatementBuilder.append(")");
return conn.prepareStatement(insertStatementBuilder.toString());
}
}

22
application/src/main/java/org/thingsboard/server/service/install/migrate/EntitiesMigrateService.java

@ -0,0 +1,22 @@
/**
* 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.service.install.migrate;
public interface EntitiesMigrateService {
void migrate() throws Exception;
}

8
application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.script;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
@ -28,20 +29,17 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@ -140,7 +138,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
if (maxRequestsTimeout > 0) {
result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
}
Futures.addCallback(result, evalCallback);
Futures.addCallback(result, evalCallback, MoreExecutors.directExecutor());
return result;
}
@ -163,7 +161,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
if (maxRequestsTimeout > 0) {
result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
}
Futures.addCallback(result, invokeCallback);
Futures.addCallback(result, invokeCallback, MoreExecutors.directExecutor());
return result;
}

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

@ -18,6 +18,7 @@ package org.thingsboard.server.service.script;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -40,7 +41,6 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@Slf4j
@ConditionalOnProperty(prefix = "js", value = "evaluator", havingValue = "remote", matchIfMissing = true)
@ -166,7 +166,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
}
kafkaFailedMsgs.incrementAndGet();
}
});
}, MoreExecutors.directExecutor());
return Futures.transform(future, response -> {
JsInvokeProtos.JsCompileResponse compilationResult = response.getCompileResponse();
UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB());
@ -178,7 +178,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails());
throw new RuntimeException(compilationResult.getErrorDetails());
}
});
}, MoreExecutors.directExecutor());
}
@Override
@ -217,7 +217,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
}
kafkaFailedMsgs.incrementAndGet();
}
});
}, MoreExecutors.directExecutor());
return Futures.transform(future, response -> {
JsInvokeProtos.JsInvokeResponse invokeResult = response.getInvokeResponse();
if (invokeResult.getSuccess()) {
@ -226,7 +226,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails());
throw new RuntimeException(invokeResult.getErrorDetails());
}
});
}, MoreExecutors.directExecutor());
}
@Override

7
application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java

@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.id.EntityId;
@ -121,7 +122,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
} else {
return Futures.immediateFuture(unbindMsg(json, msg));
}
});
}, MoreExecutors.directExecutor());
}
@Override
@ -174,7 +175,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
} else {
return Futures.immediateFuture(json.asBoolean());
}
});
}, MoreExecutors.directExecutor());
}
@Override
@ -232,7 +233,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
return Futures.immediateFailedFuture(new ScriptException(e));
}
}
});
}, MoreExecutors.directExecutor());
}
public void destroy() {

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

@ -64,14 +64,26 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.common.data.DataConstants.*;
import static org.thingsboard.server.common.data.DataConstants.ACTIVITY_EVENT;
import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT;
import static org.thingsboard.server.common.data.DataConstants.DISCONNECT_EVENT;
import static org.thingsboard.server.common.data.DataConstants.INACTIVITY_EVENT;
import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
/**
* Created by ashvayka on 01.05.18.
@ -401,7 +413,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
public void onFailure(Throwable t) {
log.warn("Failed to register device to the state service", t);
}
});
}, MoreExecutors.directExecutor());
} else {
sendDeviceEvent(device.getTenantId(), device.getId(), address.get(), true, false, false);
}
@ -456,10 +468,10 @@ public class DefaultDeviceStateService implements DeviceStateService {
private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) {
if (persistToTelemetry) {
ListenableFuture<List<TsKvEntry>> tsData = tsService.findLatest(TenantId.SYS_TENANT_ID, device.getId(), PERSISTENT_ATTRIBUTES);
return Futures.transform(tsData, extractDeviceStateData(device));
return Futures.transform(tsData, extractDeviceStateData(device), MoreExecutors.directExecutor());
} else {
ListenableFuture<List<AttributeKvEntry>> attrData = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), DataConstants.SERVER_SCOPE, PERSISTENT_ATTRIBUTES);
return Futures.transform(attrData, extractDeviceStateData(device));
return Futures.transform(attrData, extractDeviceStateData(device), MoreExecutors.directExecutor());
}
}

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

@ -21,6 +21,7 @@ import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -54,9 +55,6 @@ import org.thingsboard.server.service.telemetry.cmd.SubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmd;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.exception.AccessDeniedException;
import org.thingsboard.server.service.telemetry.exception.EntityNotFoundException;
import org.thingsboard.server.service.telemetry.exception.InternalErrorException;
import org.thingsboard.server.service.telemetry.exception.UnauthorizedException;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import org.thingsboard.server.service.telemetry.sub.SubscriptionState;
@ -70,12 +68,14 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -616,7 +616,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
ListenableFuture<List<AttributeKvEntry>> future = mergeAllAttributesFutures(futures);
Futures.addCallback(future, callback);
Futures.addCallback(future, callback, MoreExecutors.directExecutor());
}
@Override
@ -630,7 +630,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
return new FutureCallback<ValidationResult>() {
@Override
public void onSuccess(@Nullable ValidationResult result) {
Futures.addCallback(attributesService.find(tenantId, entityId, scope, keys), callback);
Futures.addCallback(attributesService.find(tenantId, entityId, scope, keys), callback, MoreExecutors.directExecutor());
}
@Override
@ -650,7 +650,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
ListenableFuture<List<AttributeKvEntry>> future = mergeAllAttributesFutures(futures);
Futures.addCallback(future, callback);
Futures.addCallback(future, callback, MoreExecutors.directExecutor());
}
@Override
@ -664,7 +664,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
return new FutureCallback<ValidationResult>() {
@Override
public void onSuccess(@Nullable ValidationResult result) {
Futures.addCallback(attributesService.findAll(tenantId, entityId, scope), callback);
Futures.addCallback(attributesService.findAll(tenantId, entityId, scope), callback, MoreExecutors.directExecutor());
}
@Override

16
application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java

@ -19,10 +19,9 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Device;
@ -42,19 +41,10 @@ import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponse
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
import org.thingsboard.server.kafka.TBKafkaConsumerTemplate;
import org.thingsboard.server.kafka.TBKafkaProducerTemplate;
import org.thingsboard.server.kafka.TbKafkaResponseTemplate;
import org.thingsboard.server.kafka.TbKafkaSettings;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.state.DeviceStateService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
/**
@ -145,7 +135,7 @@ public class LocalTransportApiService implements TransportApiService {
try {
ValidateDeviceCredentialsResponseMsg.Builder builder = ValidateDeviceCredentialsResponseMsg.newBuilder();
builder.setDeviceInfo(getDeviceInfoProto(device));
if(!StringUtils.isEmpty(credentials.getCredentialsValue())){
if (!StringUtils.isEmpty(credentials.getCredentialsValue())) {
builder.setCredentialsBody(credentials.getCredentialsValue());
}
return TransportApiResponseMsg.newBuilder()
@ -154,7 +144,7 @@ public class LocalTransportApiService implements TransportApiService {
log.warn("[{}] Failed to lookup device by id", deviceId, e);
return getEmptyTransportApiResponse();
}
});
}, MoreExecutors.directExecutor());
}
private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException {

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

@ -301,7 +301,7 @@ caffeine:
redis:
# standalone or cluster
connection:
type: standalone
type: "${REDIS_CONNECTION_TYPE:standalone}"
standalone:
host: "${REDIS_HOST:localhost}"
port: "${REDIS_PORT:6379}"

4
application/src/main/scripts/control/deb/postinst

@ -2,8 +2,8 @@
set -e
chown -R ${pkg.name}: ${pkg.logFolder}
chown -R ${pkg.name}: ${pkg.installFolder}
chown -R ${pkg.user}: ${pkg.logFolder}
chown -R ${pkg.user}: ${pkg.installFolder}
systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || :
exit 0

10
application/src/main/scripts/control/deb/preinst

@ -2,21 +2,21 @@
set -e
if ! getent group ${pkg.name} >/dev/null; then
addgroup --system ${pkg.name}
if ! getent group ${pkg.user} >/dev/null; then
addgroup --system ${pkg.user}
fi
if ! getent passwd ${pkg.name} >/dev/null; then
if ! getent passwd ${pkg.user} >/dev/null; then
adduser --quiet \
--system \
--ingroup ${pkg.name} \
--ingroup ${pkg.user} \
--quiet \
--disabled-login \
--disabled-password \
--home ${pkg.installFolder} \
--no-create-home \
-gecos "Thingsboard application" \
${pkg.name}
${pkg.user}
fi
exit 0

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

@ -1,7 +1,7 @@
#!/bin/sh
chown -R ${pkg.name}: ${pkg.logFolder}
chown -R ${pkg.name}: ${pkg.installFolder}
chown -R ${pkg.user}: ${pkg.logFolder}
chown -R ${pkg.user}: ${pkg.installFolder}
if [ $1 -eq 1 ] ; then
# Initial installation

6
application/src/main/scripts/control/rpm/preinst

@ -1,6 +1,6 @@
#!/bin/sh
getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name}
getent passwd ${pkg.name} >/dev/null || \
useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \
getent group ${pkg.user} >/dev/null || groupadd -r ${pkg.user}
getent passwd ${pkg.user} >/dev/null || \
useradd -d ${pkg.installFolder} -g ${pkg.user} -M -r ${pkg.user} -s /sbin/nologin \
-c "Thingsboard application"

2
application/src/main/scripts/control/thingsboard.service

@ -3,7 +3,7 @@ Description=${pkg.name}
After=syslog.target
[Service]
User=${pkg.name}
User=${pkg.user}
ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar
SuccessExitStatus=143

2
application/src/main/scripts/install/install.sh

@ -44,7 +44,7 @@ installDir=${pkg.installFolder}/data
source "${CONF_FOLDER}/${configfile}"
run_user=${pkg.name}
run_user=${pkg.user}
su -s /bin/sh -c "java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
-Dinstall.data_dir=${installDir} \

2
application/src/main/scripts/install/upgrade.sh

@ -43,7 +43,7 @@ installDir=${pkg.installFolder}/data
source "${CONF_FOLDER}/${configfile}"
run_user=${pkg.name}
run_user=${pkg.user}
su -s /bin/sh -c "java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
-Dinstall.data_dir=${installDir} \

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

@ -92,19 +92,7 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("is prohibited")));
}
@Test
public void testSaveAdminSettingsWithNewJsonStructure() throws Exception {
loginSysAdmin();
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
JsonNode json = adminSettings.getJsonValue();
((ObjectNode) json).put("newKey", "my new value");
adminSettings.setJsonValue(json);
doPost("/api/admin/settings", adminSettings)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Provided json structure is different")));
}
@Test
public void testSendTestMail() throws Exception {
loginSysAdmin();

3
common/queue/src/main/java/org/thingsboard/server/kafka/AsyncCallbackTemplate.java

@ -18,6 +18,7 @@ package org.thingsboard.server.kafka;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
@ -59,7 +60,7 @@ public class AsyncCallbackTemplate {
if (executor != null) {
Futures.addCallback(future, callback, executor);
} else {
Futures.addCallback(future, callback);
Futures.addCallback(future, callback, MoreExecutors.directExecutor());
}
}

11
common/util/src/main/java/org/thingsboard/common/util/DonAsynchron.java

@ -18,19 +18,20 @@ package org.thingsboard.common.util;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
public class DonAsynchron {
public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess,
Consumer<Throwable> onFailure) {
public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess,
Consumer<Throwable> onFailure) {
withCallback(future, onSuccess, onFailure, null);
}
public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess,
Consumer<Throwable> onFailure, Executor executor) {
public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess,
Consumer<Throwable> onFailure, Executor executor) {
FutureCallback<T> callback = new FutureCallback<T>() {
@Override
public void onSuccess(T result) {
@ -49,7 +50,7 @@ public class DonAsynchron {
if (executor != null) {
Futures.addCallback(future, callback, executor);
} else {
Futures.addCallback(future, callback);
Futures.addCallback(future, callback, MoreExecutors.directExecutor());
}
}
}

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

@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -53,7 +54,6 @@ import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
@ -264,9 +264,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> {
alarmInfo.setOriginatorName(originatorName);
return alarmInfo;
}
);
});
}, MoreExecutors.directExecutor());
}, MoreExecutors.directExecutor());
}
@Override
@ -282,12 +281,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
alarmInfo.setOriginatorName(originatorName);
return alarmInfo;
}
}, MoreExecutors.directExecutor()
));
}
return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> {
return new PageData(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), alarms.hasNext());
});
return Futures.transform(Futures.successfulAsList(alarmFutures),
alarmInfos -> new PageData(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(),
alarms.hasNext()), MoreExecutors.directExecutor());
}
return Futures.immediateFuture(alarms);
}

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

@ -16,9 +16,9 @@
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.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
@ -29,7 +29,6 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
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.EntityType;
import org.thingsboard.server.common.data.EntityView;
@ -64,7 +63,10 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.CacheConstants.ASSET_CACHE;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.*;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateIds;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
import static org.thingsboard.server.dao.service.Validator.validateString;
@Service
@Slf4j
@ -301,9 +303,9 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
}
}
return Futures.successfulAsList(futures);
});
}, MoreExecutors.directExecutor());
assets = Futures.transform(assets, assetList ->
assetList == null ? Collections.emptyList() : assetList.stream().filter(asset -> query.getAssetTypes().contains(asset.getType())).collect(Collectors.toList())
assetList == null ? Collections.emptyList() : assetList.stream().filter(asset -> query.getAssetTypes().contains(asset.getType())).collect(Collectors.toList()), MoreExecutors.directExecutor()
);
return assets;
}
@ -317,7 +319,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
assetTypes -> {
assetTypes.sort(Comparator.comparing(EntitySubtype::getType));
return assetTypes;
});
}, MoreExecutors.directExecutor());
}
@Override
@ -411,18 +413,18 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
};
private PaginatedRemover<TenantId, Asset> tenantAssetsRemover =
new PaginatedRemover<TenantId, Asset>() {
new PaginatedRemover<TenantId, Asset>() {
@Override
protected PageData<Asset> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return assetDao.findAssetsByTenantId(id.getId(), pageLink);
}
@Override
protected PageData<Asset> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return assetDao.findAssetsByTenantId(id.getId(), pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, Asset entity) {
deleteAsset(tenantId, new AssetId(entity.getId().getId()));
}
};
@Override
protected void removeEntity(TenantId tenantId, Asset entity) {
deleteAsset(tenantId, new AssetId(entity.getId().getId()));
}
};
private PaginatedRemover<CustomerId, Asset> customerAssetsUnasigner = new PaginatedRemover<CustomerId, Asset>() {

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

@ -15,8 +15,6 @@
*/
package org.thingsboard.server.dao.dashboard;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@ -33,7 +31,6 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
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.customer.CustomerDao;
@ -42,12 +39,9 @@ import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.TimePaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -66,7 +60,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired
private TenantDao tenantDao;
@Autowired
private CustomerDao customerDao;
@ -107,7 +101,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
dashboardValidator.validate(dashboard, DashboardInfo::getTenantId);
return dashboardDao.save(dashboard.getTenantId(), dashboard);
}
@Override
public Dashboard assignDashboardToCustomer(TenantId tenantId, DashboardId dashboardId, CustomerId customerId) {
Dashboard dashboard = findDashboardById(tenantId, dashboardId);
@ -323,11 +317,11 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
}
}
}
};
};
private PaginatedRemover<TenantId, DashboardInfo> tenantDashboardsRemover =
new PaginatedRemover<TenantId, DashboardInfo>() {
@Override
protected PageData<DashboardInfo> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return dashboardInfoDao.findDashboardsByTenantId(id.getId(), pageLink);
@ -356,7 +350,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
protected void removeEntity(TenantId tenantId, DashboardInfo entity) {
unassignDashboardFromCustomer(customer.getTenantId(), new DashboardId(entity.getUuidId()), this.customer.getId());
}
}
private class CustomerDashboardsUpdater extends PaginatedRemover<Customer, DashboardInfo> {

9
dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesServiceImpl.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.device;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -97,9 +98,9 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
}
log.warn("Failed to find claimingAllowed attribute for device or it is already claimed![{}]", device.getName());
throw new IllegalArgumentException();
});
}, MoreExecutors.directExecutor());
}
});
}, MoreExecutors.directExecutor());
}
private ClaimDataInfo getClaimData(Cache cache, Device device) throws ExecutionException, InterruptedException {
@ -138,9 +139,9 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
if (device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
device.setCustomerId(customerId);
Device savedDevice = deviceService.saveDevice(device);
return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS));
return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS), MoreExecutors.directExecutor());
}
return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED));
return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED), MoreExecutors.directExecutor());
}
} else {
log.warn("Failed to find the device's claiming message![{}]", device.getName());

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

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.device;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.hibernate.exception.ConstraintViolationException;
@ -28,7 +29,13 @@ import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
@ -328,7 +335,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
}
}
return Futures.successfulAsList(futures);
});
}, MoreExecutors.directExecutor());
devices = Futures.transform(devices, new Function<List<Device>, List<Device>>() {
@Nullable
@ -336,7 +343,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
public List<Device> apply(@Nullable List<Device> deviceList) {
return deviceList == null ? Collections.emptyList() : deviceList.stream().filter(device -> query.getDeviceTypes().contains(device.getType())).collect(Collectors.toList());
}
});
}, MoreExecutors.directExecutor());
return devices;
}
@ -350,7 +357,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
deviceTypes -> {
deviceTypes.sort(Comparator.comparing(EntitySubtype::getType));
return deviceTypes;
});
}, MoreExecutors.directExecutor());
}
@Override
@ -444,18 +451,18 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
};
private PaginatedRemover<TenantId, Device> tenantDevicesRemover =
new PaginatedRemover<TenantId, Device>() {
new PaginatedRemover<TenantId, Device>() {
@Override
protected PageData<Device> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return deviceDao.findDevicesByTenantId(id.getId(), pageLink);
}
@Override
protected PageData<Device> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return deviceDao.findDevicesByTenantId(id.getId(), pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, Device entity) {
deleteDevice(tenantId, new DeviceId(entity.getUuidId()));
}
};
@Override
protected void removeEntity(TenantId tenantId, Device entity) {
deleteDevice(tenantId, new DeviceId(entity.getUuidId()));
}
};
private PaginatedRemover<CustomerId, Device> customerDeviceUnasigner = new PaginatedRemover<CustomerId, Device>() {

13
dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java

@ -18,12 +18,21 @@ package org.thingsboard.server.dao.entity;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
@ -109,7 +118,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
default:
throw new IllegalStateException("Not Implemented!");
}
entityName = Futures.transform(hasName, (Function<HasName, String>) hasName1 -> hasName1 != null ? hasName1.getName() : null );
entityName = Futures.transform(hasName, (Function<HasName, String>) hasName1 -> hasName1 != null ? hasName1.getName() : null, MoreExecutors.directExecutor());
return entityName;
}

9
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java

@ -19,6 +19,7 @@ import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -240,7 +241,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
}
}
return Futures.successfulAsList(futures);
});
}, MoreExecutors.directExecutor());
entityViews = Futures.transform(entityViews, new Function<List<EntityView>, List<EntityView>>() {
@Nullable
@ -248,7 +249,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
public List<EntityView> apply(@Nullable List<EntityView> entityViewList) {
return entityViewList == null ? Collections.emptyList() : entityViewList.stream().filter(entityView -> query.getEntityViewTypes().contains(entityView.getType())).collect(Collectors.toList());
}
});
}, MoreExecutors.directExecutor());
return entityViews;
}
@ -287,7 +288,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
public void onFailure(Throwable t) {
log.error("Error while finding entity views by tenantId and entityId", t);
}
});
}, MoreExecutors.directExecutor());
return entityViewsFuture;
}
}
@ -320,7 +321,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
entityViewTypes -> {
entityViewTypes.sort(Comparator.comparing(EntitySubtype::getType));
return entityViewTypes;
});
}, MoreExecutors.directExecutor());
}
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java

@ -36,6 +36,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLU
import static org.thingsboard.server.dao.model.ModelConstants.DOUBLE_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.JSON_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.LONG_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN;
@ -53,6 +54,10 @@ public abstract class AbstractTsKvEntity implements ToData<TsKvEntry> {
@Column(name = ENTITY_ID_COLUMN, columnDefinition = "uuid")
protected UUID entityId;
@Id
@Column(name = KEY_COLUMN)
protected int key;
@Id
@Column(name = TS_COLUMN)
protected Long ts;

4
dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestEntity.java

@ -69,10 +69,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN;
})
public final class TsKvLatestEntity extends AbstractTsKvEntity {
@Id
@Column(name = KEY_COLUMN)
private int key;
@Override
public boolean isNotEmpty() {
return strValue != null || longValue != null || doubleValue != null || booleanValue != null || jsonValue != null;

1
dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java

@ -31,7 +31,6 @@ public class TimescaleTsKvCompositeKey implements Serializable {
@Transient
private static final long serialVersionUID = -4089175869616037523L;
private UUID tenantId;
private UUID entityId;
private int key;
private long ts;

19
dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java

@ -18,25 +18,18 @@ package org.thingsboard.server.dao.model.sqlts.timescale.ts;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.model.ToData;
import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity;
import javax.persistence.Column;
import javax.persistence.ColumnResult;
import javax.persistence.ConstructorResult;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.SqlResultSetMappings;
import javax.persistence.Table;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_COLUMN;
import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.FIND_AVG;
import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.FIND_AVG_QUERY;
import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.FIND_COUNT;
@ -52,7 +45,7 @@ import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.F
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "tenant_ts_kv")
@Table(name = "ts_kv")
@IdClass(TimescaleTsKvCompositeKey.class)
@SqlResultSetMappings({
@SqlResultSetMapping(
@ -116,15 +109,7 @@ import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.F
resultSetMapping = "timescaleCountMapping"
)
})
public final class TimescaleTsKvEntity extends AbstractTsKvEntity implements ToData<TsKvEntry> {
@Id
@Column(name = TENANT_ID_COLUMN, columnDefinition = "uuid")
private UUID tenantId;
@Id
@Column(name = KEY_COLUMN)
private int key;
public final class TimescaleTsKvEntity extends AbstractTsKvEntity {
public TimescaleTsKvEntity() {
}

4
dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java

@ -32,10 +32,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN;
@IdClass(TsKvCompositeKey.class)
public final class TsKvEntity extends AbstractTsKvEntity {
@Id
@Column(name = KEY_COLUMN)
private int key;
public TsKvEntity() {
}

7
dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java

@ -22,6 +22,7 @@ import com.datastax.driver.core.Statement;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.Uninterruptibles;
import org.thingsboard.server.dao.exception.BufferLimitException;
import org.thingsboard.server.dao.util.AsyncRateLimiter;
@ -44,9 +45,9 @@ public class RateLimitedResultSetFuture implements ResultSetFuture {
rateLimiter.release();
}
return Futures.immediateFailedFuture(t);
});
}, MoreExecutors.directExecutor());
this.originalFuture = Futures.transform(rateLimitFuture,
i -> executeAsyncWithRelease(rateLimiter, session, statement));
i -> executeAsyncWithRelease(rateLimiter, session, statement), MoreExecutors.directExecutor());
}
@ -145,7 +146,7 @@ public class RateLimitedResultSetFuture implements ResultSetFuture {
public void onFailure(Throwable t) {
rateLimiter.release();
}
});
}, MoreExecutors.directExecutor());
return resultSetFuture;
} catch (RuntimeException re) {
rateLimiter.release();

38
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java

@ -16,7 +16,10 @@
package org.thingsboard.server.dao.relation;
import com.google.common.base.Function;
import com.google.common.util.concurrent.*;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
@ -206,17 +209,20 @@ public class BaseRelationService implements RelationService {
relations -> {
List<ListenableFuture<Boolean>> results = deleteRelationGroupsAsync(tenantId, relations, cache, true);
return Futures.allAsList(results);
});
}, MoreExecutors.directExecutor());
ListenableFuture<List<Boolean>> outboundDeletions = Futures.transformAsync(outboundRelations,
relations -> {
List<ListenableFuture<Boolean>> results = deleteRelationGroupsAsync(tenantId, relations, cache, false);
return Futures.allAsList(results);
});
}, MoreExecutors.directExecutor());
ListenableFuture<List<List<Boolean>>> deletionsFuture = Futures.allAsList(inboundDeletions, outboundDeletions);
return Futures.transform(Futures.transformAsync(deletionsFuture, (deletions) -> relationDao.deleteOutboundRelationsAsync(tenantId, entityId)), result -> null);
return Futures.transform(Futures.transformAsync(deletionsFuture,
(deletions) -> relationDao.deleteOutboundRelationsAsync(tenantId, entityId),
MoreExecutors.directExecutor()),
result -> null, MoreExecutors.directExecutor());
}
private List<ListenableFuture<Boolean>> deleteRelationGroupsAsync(TenantId tenantId, List<List<EntityRelation>> relations, Cache cache, boolean deleteFromDb) {
@ -306,9 +312,11 @@ public class BaseRelationService implements RelationService {
public void onSuccess(@Nullable List<EntityRelation> result) {
cache.putIfAbsent(fromAndTypeGroup, result);
}
@Override
public void onFailure(Throwable t) {}
});
public void onFailure(Throwable t) {
}
}, MoreExecutors.directExecutor());
return relationsFuture;
}
}
@ -328,7 +336,7 @@ public class BaseRelationService implements RelationService {
EntityRelationInfo::setToName))
);
return Futures.successfulAsList(futures);
});
}, MoreExecutors.directExecutor());
}
@Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}")
@ -385,9 +393,11 @@ public class BaseRelationService implements RelationService {
public void onSuccess(@Nullable List<EntityRelation> result) {
cache.putIfAbsent(toAndTypeGroup, result);
}
@Override
public void onFailure(Throwable t) {}
});
public void onFailure(Throwable t) {
}
}, MoreExecutors.directExecutor());
return relationsFuture;
}
}
@ -407,7 +417,7 @@ public class BaseRelationService implements RelationService {
EntityRelationInfo::setFromName))
);
return Futures.successfulAsList(futures);
});
}, MoreExecutors.directExecutor());
}
private ListenableFuture<EntityRelationInfo> fetchRelationInfoAsync(TenantId tenantId, EntityRelation relation,
@ -418,7 +428,7 @@ public class BaseRelationService implements RelationService {
EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation);
entityNameSetter.accept(entityRelationInfo1, entityName1);
return entityRelationInfo1;
});
}, MoreExecutors.directExecutor());
}
@Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}")
@ -466,7 +476,7 @@ public class BaseRelationService implements RelationService {
}
}
return relations;
});
}, MoreExecutors.directExecutor());
} catch (Exception e) {
log.warn("Failed to query relations: [{}]", query, e);
throw new RuntimeException(e);
@ -493,7 +503,7 @@ public class BaseRelationService implements RelationService {
}))
);
return Futures.successfulAsList(futures);
});
}, MoreExecutors.directExecutor());
}
protected void validate(EntityRelation relation) {
@ -600,7 +610,7 @@ public class BaseRelationService implements RelationService {
}
//TODO: try to remove this blocking operation
List<Set<EntityRelation>> relations = Futures.successfulAsList(futures).get();
if (fetchLastLevelOnly && lvl > 0){
if (fetchLastLevelOnly && lvl > 0) {
children.clear();
}
relations.forEach(r -> r.forEach(children::add));

3
dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java

@ -73,9 +73,6 @@ public class AdminSettingsServiceImpl implements AdminSettingsService {
if (!existentAdminSettings.getKey().equals(adminSettings.getKey())) {
throw new DataValidationException("Changing key of admin settings entry is prohibited!");
}
if (adminSettings.getKey().equals("mail")) {
validateJsonStructure(existentAdminSettings.getJsonValue(), adminSettings.getJsonValue());
}
}
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -15,14 +15,12 @@
*/
package org.thingsboard.server.dao.sql.alarm;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@ -31,8 +29,6 @@ import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
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.alarm.AlarmDao;
import org.thingsboard.server.dao.alarm.BaseAlarmService;
@ -41,7 +37,6 @@ import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

13
dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java

@ -15,9 +15,11 @@
*/
package org.thingsboard.server.dao.sql.attributes;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey;
import org.thingsboard.server.dao.model.sql.AttributeKvEntity;
@ -34,5 +36,16 @@ public interface AttributeKvRepository extends CrudRepository<AttributeKvEntity,
List<AttributeKvEntity> findAllByEntityTypeAndEntityIdAndAttributeType(@Param("entityType") EntityType entityType,
@Param("entityId") String entityId,
@Param("attributeType") String attributeType);
@Transactional
@Modifying
@Query("DELETE FROM AttributeKvEntity a WHERE a.id.entityType = :entityType " +
"AND a.id.entityId = :entityId " +
"AND a.id.attributeType = :attributeType " +
"AND a.id.attributeKey = :attributeKey")
void delete(@Param("entityType") EntityType entityType,
@Param("entityId") String entityId,
@Param("attributeType") String attributeType,
@Param("attributeKey") String attributeKey);
}

12
dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java

@ -138,16 +138,10 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
@Override
public ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List<String> keys) {
List<AttributeKvEntity> entitiesToDelete = keys
.stream()
.map(key -> {
AttributeKvEntity entityToDelete = new AttributeKvEntity();
entityToDelete.setId(new AttributeKvCompositeKey(entityId.getEntityType(), fromTimeUUID(entityId.getId()), attributeType, key));
return entityToDelete;
}).collect(Collectors.toList());
return service.submit(() -> {
attributeKvRepository.deleteAll(entitiesToDelete);
keys.forEach(key ->
attributeKvRepository.delete(entityId.getEntityType(), UUIDConverter.fromTimeUUID(entityId.getId()), attributeType, key)
);
return null;
});
}

51
dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java

@ -0,0 +1,51 @@
/**
* 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.dao.sql.relation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.jpa.repository.Modifying;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
@Slf4j
public abstract class AbstractRelationInsertRepository implements RelationInsertRepository {
@PersistenceContext
protected EntityManager entityManager;
protected Query getQuery(RelationEntity entity, String query) {
Query nativeQuery = entityManager.createNativeQuery(query, RelationEntity.class);
if (entity.getAdditionalInfo() == null) {
nativeQuery.setParameter("additionalInfo", null);
} else {
nativeQuery.setParameter("additionalInfo", entity.getAdditionalInfo().toString());
}
return nativeQuery
.setParameter("fromId", entity.getFromId())
.setParameter("fromType", entity.getFromType())
.setParameter("toId", entity.getToId())
.setParameter("toType", entity.getToType())
.setParameter("relationTypeGroup", entity.getRelationTypeGroup())
.setParameter("relationType", entity.getRelationType());
}
@Modifying
protected abstract RelationEntity processSaveOrUpdate(RelationEntity entity);
}

47
dao/src/main/java/org/thingsboard/server/dao/sql/relation/HsqlRelationInsertRepository.java

@ -0,0 +1,47 @@
/**
* 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.dao.sql.relation;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.RelationCompositeKey;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlDao;
@HsqlDao
@SqlDao
@Repository
@Transactional
public class HsqlRelationInsertRepository extends AbstractRelationInsertRepository implements RelationInsertRepository {
private static final String INSERT_ON_CONFLICT_DO_UPDATE = "MERGE INTO relation USING (VALUES :fromId, :fromType, :toId, :toType, :relationTypeGroup, :relationType, :additionalInfo) R " +
"(from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info) " +
"ON (relation.from_id = R.from_id AND relation.from_type = R.from_type AND relation.relation_type_group = R.relation_type_group AND relation.relation_type = R.relation_type AND relation.to_id = R.to_id AND relation.to_type = R.to_type) " +
"WHEN MATCHED THEN UPDATE SET relation.additional_info = R.additional_info " +
"WHEN NOT MATCHED THEN INSERT (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info) VALUES (R.from_id, R.from_type, R.to_id, R.to_type, R.relation_type_group, R.relation_type, R.additional_info)";
@Override
public RelationEntity saveOrUpdate(RelationEntity entity) {
return processSaveOrUpdate(entity);
}
@Override
protected RelationEntity processSaveOrUpdate(RelationEntity entity) {
getQuery(entity, INSERT_ON_CONFLICT_DO_UPDATE).executeUpdate();
return entityManager.find(RelationEntity.class, new RelationCompositeKey(entity.toData()));
}
}

7
dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java

@ -58,6 +58,9 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
@Autowired
private RelationRepository relationRepository;
@Autowired
private RelationInsertRepository relationInsertRepository;
@Override
public ListenableFuture<List<EntityRelation>> findAllByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup) {
return service.submit(() -> DaoUtil.convertDataList(
@ -119,12 +122,12 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
@Override
public boolean saveRelation(TenantId tenantId, EntityRelation relation) {
return relationRepository.save(new RelationEntity(relation)) != null;
return relationInsertRepository.saveOrUpdate(new RelationEntity(relation)) != null;
}
@Override
public ListenableFuture<Boolean> saveRelationAsync(TenantId tenantId, EntityRelation relation) {
return service.submit(() -> relationRepository.save(new RelationEntity(relation)) != null);
return service.submit(() -> relationInsertRepository.saveOrUpdate(new RelationEntity(relation)) != null);
}
@Override

43
dao/src/main/java/org/thingsboard/server/dao/sql/relation/PsqlRelationInsertRepository.java

@ -0,0 +1,43 @@
/**
* 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.dao.sql.relation;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlDao;
@PsqlDao
@SqlDao
@Repository
@Transactional
public class PsqlRelationInsertRepository extends AbstractRelationInsertRepository implements RelationInsertRepository {
private static final String INSERT_ON_CONFLICT_DO_UPDATE = "INSERT INTO relation (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info)" +
" VALUES (:fromId, :fromType, :toId, :toType, :relationTypeGroup, :relationType, :additionalInfo) " +
"ON CONFLICT (from_id, from_type, relation_type_group, relation_type, to_id, to_type) DO UPDATE SET additional_info = :additionalInfo returning *";
@Override
public RelationEntity saveOrUpdate(RelationEntity entity) {
return processSaveOrUpdate(entity);
}
@Override
protected RelationEntity processSaveOrUpdate(RelationEntity entity) {
return (RelationEntity) getQuery(entity, INSERT_ON_CONFLICT_DO_UPDATE).getSingleResult();
}
}

24
dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java

@ -0,0 +1,24 @@
/**
* 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.dao.sql.relation;
import org.thingsboard.server.dao.model.sql.RelationEntity;
public interface RelationInsertRepository {
RelationEntity saveOrUpdate(RelationEntity entity);
}

39
dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sqlts;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -95,7 +96,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
@Override
public ListenableFuture<Void> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
return getRemoveLatestFuture(tenantId, entityId, query);
return getRemoveLatestFuture(entityId, query);
}
@Override
@ -124,9 +125,9 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
}
@Override
protected ListenableFuture<List<TsKvEntry>> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) {
protected ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, ReadTsKvQuery query) {
if (query.getAggregation() == Aggregation.NONE) {
return findAllAsyncWithLimit(tenantId, entityId, query);
return findAllAsyncWithLimit(entityId, query);
} else {
long stepTs = query.getStartTs();
List<ListenableFuture<Optional<TsKvEntry>>> futures = new ArrayList<>();
@ -134,7 +135,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
long startTs = stepTs;
long endTs = stepTs + query.getInterval();
long ts = startTs + (endTs - startTs) / 2;
futures.add(findAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, ts, query.getAggregation()));
futures.add(findAndAggregateAsync(entityId, query.getKey(), startTs, endTs, ts, query.getAggregation()));
stepTs = endTs;
}
return getTskvEntriesFuture(Futures.allAsList(futures));
@ -142,7 +143,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
}
@Override
protected ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) {
protected ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) {
Integer keyId = getOrSaveKeyId(query.getKey());
List<TsKvEntity> tsKvEntities = tsKvRepository.findAllWithLimit(
entityId.getId(),
@ -156,9 +157,9 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
return Futures.immediateFuture(DaoUtil.convertDataList(tsKvEntities));
}
protected ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) {
private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) {
List<CompletableFuture<TsKvEntity>> entitiesFutures = new ArrayList<>();
switchAggregation(tenantId, entityId, key, startTs, endTs, aggregation, entitiesFutures);
switchAggregation(entityId, key, startTs, endTs, aggregation, entitiesFutures);
return Futures.transform(setFutures(entitiesFutures), entity -> {
if (entity != null && entity.isNotEmpty()) {
entity.setEntityId(entityId.getId());
@ -168,32 +169,32 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
} else {
return Optional.empty();
}
});
}, MoreExecutors.directExecutor());
}
protected void switchAggregation(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List<CompletableFuture<TsKvEntity>> entitiesFutures) {
protected void switchAggregation(EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List<CompletableFuture<TsKvEntity>> entitiesFutures) {
switch (aggregation) {
case AVG:
findAvg(tenantId, entityId, key, startTs, endTs, entitiesFutures);
findAvg(entityId, key, startTs, endTs, entitiesFutures);
break;
case MAX:
findMax(tenantId, entityId, key, startTs, endTs, entitiesFutures);
findMax(entityId, key, startTs, endTs, entitiesFutures);
break;
case MIN:
findMin(tenantId, entityId, key, startTs, endTs, entitiesFutures);
findMin(entityId, key, startTs, endTs, entitiesFutures);
break;
case SUM:
findSum(tenantId, entityId, key, startTs, endTs, entitiesFutures);
findSum(entityId, key, startTs, endTs, entitiesFutures);
break;
case COUNT:
findCount(tenantId, entityId, key, startTs, endTs, entitiesFutures);
findCount(entityId, key, startTs, endTs, entitiesFutures);
break;
default:
throw new IllegalArgumentException("Not supported aggregation type: " + aggregation);
}
}
protected void findCount(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) {
protected void findCount(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) {
Integer keyId = getOrSaveKeyId(key);
entitiesFutures.add(tsKvRepository.findCount(
entityId.getId(),
@ -202,7 +203,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
endTs));
}
protected void findSum(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) {
protected void findSum(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) {
Integer keyId = getOrSaveKeyId(key);
entitiesFutures.add(tsKvRepository.findSum(
entityId.getId(),
@ -211,7 +212,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
endTs));
}
protected void findMin(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) {
protected void findMin(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) {
Integer keyId = getOrSaveKeyId(key);
entitiesFutures.add(tsKvRepository.findStringMin(
entityId.getId(),
@ -225,7 +226,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
endTs));
}
protected void findMax(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) {
protected void findMax(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) {
Integer keyId = getOrSaveKeyId(key);
entitiesFutures.add(tsKvRepository.findStringMax(
entityId.getId(),
@ -239,7 +240,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
endTs));
}
protected void findAvg(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) {
protected void findAvg(EntityId entityId, String key, long startTs, long endTs, List<CompletableFuture<TsKvEntity>> entitiesFutures) {
Integer keyId = getOrSaveKeyId(key);
entitiesFutures.add(tsKvRepository.findAvg(
entityId.getId(),

21
dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java

@ -20,6 +20,7 @@ import com.google.common.collect.Lists;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
@ -126,7 +127,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
protected ListenableFuture<List<TsKvEntry>> processFindAllAsync(TenantId tenantId, EntityId entityId, List<ReadTsKvQuery> queries) {
List<ListenableFuture<List<TsKvEntry>>> futures = queries
.stream()
.map(query -> findAllAsync(tenantId, entityId, query))
.map(query -> findAllAsync(entityId, query))
.collect(Collectors.toList());
return Futures.transform(Futures.allAsList(futures), new Function<List<List<TsKvEntry>>, List<TsKvEntry>>() {
@Nullable
@ -143,9 +144,9 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
}, service);
}
protected abstract ListenableFuture<List<TsKvEntry>> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query);
protected abstract ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, ReadTsKvQuery query);
protected abstract ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query);
protected abstract ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query);
protected ListenableFuture<List<TsKvEntry>> getTskvEntriesFuture(ListenableFuture<List<Optional<TsKvEntry>>> future) {
return Futures.transform(future, new Function<List<Optional<TsKvEntry>>, List<TsKvEntry>>() {
@ -163,12 +164,12 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
}, service);
}
protected ListenableFuture<List<TsKvEntry>> findNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
protected ListenableFuture<List<TsKvEntry>> findNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) {
long startTs = 0;
long endTs = query.getStartTs() - 1;
ReadTsKvQuery findNewLatestQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, endTs - startTs, 1,
Aggregation.NONE, DESC_ORDER);
return findAllAsync(tenantId, entityId, findNewLatestQuery);
return findAllAsync(entityId, findNewLatestQuery);
}
protected ListenableFuture<TsKvEntry> getFindLatestFuture(EntityId entityId, String key) {
@ -188,7 +189,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
return Futures.immediateFuture(result);
}
protected ListenableFuture<Void> getRemoveLatestFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
protected ListenableFuture<Void> getRemoveLatestFuture(EntityId entityId, DeleteTsKvQuery query) {
ListenableFuture<TsKvEntry> latestFuture = getFindLatestFuture(entityId, query.getKey());
ListenableFuture<Boolean> booleanFuture = Futures.transform(latestFuture, tsKvEntry -> {
@ -216,7 +217,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
if (query.getRewriteLatestIfDeleted()) {
ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
if (isRemove) {
return getNewLatestEntryFuture(tenantId, entityId, query);
return getNewLatestEntryFuture(entityId, query);
}
return Futures.immediateFuture(null);
}, service);
@ -235,7 +236,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
public void onFailure(Throwable t) {
log.warn("[{}] Failed to process remove of the latest value", entityId, t);
}
});
}, MoreExecutors.directExecutor());
return resultFuture;
}
@ -295,8 +296,8 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
return keyId;
}
private ListenableFuture<Void> getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
ListenableFuture<List<TsKvEntry>> future = findNewLatestEntryFuture(tenantId, entityId, query);
private ListenableFuture<Void> getNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) {
ListenableFuture<List<TsKvEntry>> future = findNewLatestEntryFuture(entityId, query);
return Futures.transformAsync(future, entryList -> {
if (entryList.size() == 1) {
return getSaveLatestFuture(entityId, entryList.get(0));

41
dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java

@ -37,8 +37,8 @@ import java.util.List;
public class TimescaleInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository<TimescaleTsKvEntity> {
private static final String INSERT_OR_UPDATE =
"INSERT INTO tenant_ts_kv (tenant_id, entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES(?, ?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " +
"ON CONFLICT (tenant_id, entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);";
"INSERT INTO ts_kv (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES(?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " +
"ON CONFLICT (entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);";
@Override
public void saveOrUpdate(List<EntityContainer<TimescaleTsKvEntity>> entities) {
@ -46,41 +46,40 @@ public class TimescaleInsertTsRepository extends AbstractInsertRepository implem
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
TimescaleTsKvEntity tsKvEntity = entities.get(i).getEntity();
ps.setObject(1, tsKvEntity.getTenantId());
ps.setObject(2, tsKvEntity.getEntityId());
ps.setInt(3, tsKvEntity.getKey());
ps.setLong(4, tsKvEntity.getTs());
ps.setObject(1, tsKvEntity.getEntityId());
ps.setInt(2, tsKvEntity.getKey());
ps.setLong(3, tsKvEntity.getTs());
if (tsKvEntity.getBooleanValue() != null) {
ps.setBoolean(5, tsKvEntity.getBooleanValue());
ps.setBoolean(4, tsKvEntity.getBooleanValue());
ps.setBoolean(9, tsKvEntity.getBooleanValue());
} else {
ps.setNull(5, Types.BOOLEAN);
ps.setNull(10, Types.BOOLEAN);
ps.setNull(4, Types.BOOLEAN);
ps.setNull(9, Types.BOOLEAN);
}
ps.setString(6, replaceNullChars(tsKvEntity.getStrValue()));
ps.setString(11, replaceNullChars(tsKvEntity.getStrValue()));
ps.setString(5, replaceNullChars(tsKvEntity.getStrValue()));
ps.setString(10, replaceNullChars(tsKvEntity.getStrValue()));
if (tsKvEntity.getLongValue() != null) {
ps.setLong(7, tsKvEntity.getLongValue());
ps.setLong(12, tsKvEntity.getLongValue());
ps.setLong(6, tsKvEntity.getLongValue());
ps.setLong(11, tsKvEntity.getLongValue());
} else {
ps.setNull(7, Types.BIGINT);
ps.setNull(12, Types.BIGINT);
ps.setNull(6, Types.BIGINT);
ps.setNull(11, Types.BIGINT);
}
if (tsKvEntity.getDoubleValue() != null) {
ps.setDouble(8, tsKvEntity.getDoubleValue());
ps.setDouble(13, tsKvEntity.getDoubleValue());
ps.setDouble(7, tsKvEntity.getDoubleValue());
ps.setDouble(12, tsKvEntity.getDoubleValue());
} else {
ps.setNull(8, Types.DOUBLE);
ps.setNull(13, Types.DOUBLE);
ps.setNull(7, Types.DOUBLE);
ps.setNull(12, Types.DOUBLE);
}
ps.setString(9, replaceNullChars(tsKvEntity.getJsonValue()));
ps.setString(14, replaceNullChars(tsKvEntity.getJsonValue()));
ps.setString(8, replaceNullChars(tsKvEntity.getJsonValue()));
ps.setString(13, replaceNullChars(tsKvEntity.getJsonValue()));
}
@Override

29
dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java

@ -36,13 +36,13 @@ public class AggregationRepository {
public static final String FIND_SUM = "findSum";
public static final String FIND_COUNT = "findCount";
public static final String FROM_WHERE_CLAUSE = "FROM tenant_ts_kv tskv WHERE tskv.tenant_id = cast(:tenantId AS uuid) AND tskv.entity_id = cast(:entityId AS uuid) AND tskv.key= cast(:entityKey AS int) AND tskv.ts > :startTs AND tskv.ts <= :endTs GROUP BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket ORDER BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket";
public static final String FROM_WHERE_CLAUSE = "FROM ts_kv tskv WHERE tskv.entity_id = cast(:entityId AS uuid) AND tskv.key= cast(:entityKey AS int) AND tskv.ts > :startTs AND tskv.ts <= :endTs GROUP BY tskv.entity_id, tskv.key, tsBucket ORDER BY tskv.entity_id, tskv.key, tsBucket";
public static final String FIND_AVG_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, 'AVG' AS aggType ";
public static final String FIND_MAX_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MAX(COALESCE(tskv.long_v, -9223372036854775807)) AS longValue, MAX(COALESCE(tskv.dbl_v, -1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MAX(tskv.str_v) AS strValue, MAX(tskv.json_v) AS jsonValue, 'MAX' AS aggType ";
public static final String FIND_MAX_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MAX(COALESCE(tskv.long_v, -9223372036854775807)) AS longValue, MAX(COALESCE(tskv.dbl_v, -1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MAX(tskv.str_v) AS strValue, 'MAX' AS aggType ";
public static final String FIND_MIN_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MIN(COALESCE(tskv.long_v, 9223372036854775807)) AS longValue, MIN(COALESCE(tskv.dbl_v, 1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MIN(tskv.str_v) AS strValue, MIN(tskv.json_v) AS jsonValue,'MIN' AS aggType ";
public static final String FIND_MIN_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MIN(COALESCE(tskv.long_v, 9223372036854775807)) AS longValue, MIN(COALESCE(tskv.dbl_v, 1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MIN(tskv.str_v) AS strValue, 'MIN' AS aggType ";
public static final String FIND_SUM_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, null AS jsonValue, 'SUM' AS aggType ";
@ -52,43 +52,42 @@ public class AggregationRepository {
private EntityManager entityManager;
@Async
public CompletableFuture<List<TimescaleTsKvEntity>> findAvg(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) {
public CompletableFuture<List<TimescaleTsKvEntity>> findAvg(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) {
@SuppressWarnings("unchecked")
List<TimescaleTsKvEntity> resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_AVG);
List<TimescaleTsKvEntity> resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_AVG);
return CompletableFuture.supplyAsync(() -> resultList);
}
@Async
public CompletableFuture<List<TimescaleTsKvEntity>> findMax(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) {
public CompletableFuture<List<TimescaleTsKvEntity>> findMax(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) {
@SuppressWarnings("unchecked")
List<TimescaleTsKvEntity> resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_MAX);
List<TimescaleTsKvEntity> resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_MAX);
return CompletableFuture.supplyAsync(() -> resultList);
}
@Async
public CompletableFuture<List<TimescaleTsKvEntity>> findMin(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) {
public CompletableFuture<List<TimescaleTsKvEntity>> findMin(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) {
@SuppressWarnings("unchecked")
List<TimescaleTsKvEntity> resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_MIN);
List<TimescaleTsKvEntity> resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_MIN);
return CompletableFuture.supplyAsync(() -> resultList);
}
@Async
public CompletableFuture<List<TimescaleTsKvEntity>> findSum(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) {
public CompletableFuture<List<TimescaleTsKvEntity>> findSum(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) {
@SuppressWarnings("unchecked")
List<TimescaleTsKvEntity> resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_SUM);
List<TimescaleTsKvEntity> resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_SUM);
return CompletableFuture.supplyAsync(() -> resultList);
}
@Async
public CompletableFuture<List<TimescaleTsKvEntity>> findCount(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) {
public CompletableFuture<List<TimescaleTsKvEntity>> findCount(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) {
@SuppressWarnings("unchecked")
List<TimescaleTsKvEntity> resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_COUNT);
List<TimescaleTsKvEntity> resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_COUNT);
return CompletableFuture.supplyAsync(() -> resultList);
}
private List getResultList(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs, String query) {
private List getResultList(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs, String query) {
return entityManager.createNamedQuery(query)
.setParameter("tenantId", tenantId)
.setParameter("entityId", entityId)
.setParameter("entityKey", entityKey)
.setParameter("timeBucket", timeBucket)

48
dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sqlts.timescale;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -87,24 +88,23 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
}
@Override
protected ListenableFuture<List<TsKvEntry>> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) {
protected ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, ReadTsKvQuery query) {
if (query.getAggregation() == Aggregation.NONE) {
return findAllAsyncWithLimit(tenantId, entityId, query);
return findAllAsyncWithLimit(entityId, query);
} else {
long startTs = query.getStartTs();
long endTs = query.getEndTs();
long timeBucket = query.getInterval();
ListenableFuture<List<Optional<TsKvEntry>>> future = findAllAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, timeBucket, query.getAggregation());
ListenableFuture<List<Optional<TsKvEntry>>> future = findAllAndAggregateAsync(entityId, query.getKey(), startTs, endTs, timeBucket, query.getAggregation());
return getTskvEntriesFuture(future);
}
}
@Override
protected ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) {
protected ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) {
String strKey = query.getKey();
Integer keyId = getOrSaveKeyId(strKey);
List<TimescaleTsKvEntity> timescaleTsKvEntities = tsKvRepository.findAllWithLimit(
tenantId.getId(),
entityId.getId(),
keyId,
query.getStartTs(),
@ -116,8 +116,8 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
return Futures.immediateFuture(DaoUtil.convertDataList(timescaleTsKvEntities));
}
private ListenableFuture<List<Optional<TsKvEntry>>> findAllAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long timeBucket, Aggregation aggregation) {
CompletableFuture<List<TimescaleTsKvEntity>> listCompletableFuture = switchAggregation(key, startTs, endTs, timeBucket, aggregation, entityId.getId(), tenantId.getId());
private ListenableFuture<List<Optional<TsKvEntry>>> findAllAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long timeBucket, Aggregation aggregation) {
CompletableFuture<List<TimescaleTsKvEntity>> listCompletableFuture = switchAggregation(key, startTs, endTs, timeBucket, aggregation, entityId.getId());
SettableFuture<List<TimescaleTsKvEntity>> listenableFuture = SettableFuture.create();
listCompletableFuture.whenComplete((timescaleTsKvEntities, throwable) -> {
if (throwable != null) {
@ -132,7 +132,6 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
timescaleTsKvEntities.forEach(entity -> {
if (entity != null && entity.isNotEmpty()) {
entity.setEntityId(entityId.getId());
entity.setTenantId(tenantId.getId());
entity.setStrKey(key);
result.add(Optional.of(DaoUtil.getData(entity)));
} else {
@ -143,7 +142,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
} else {
return Collections.emptyList();
}
});
}, MoreExecutors.directExecutor());
}
@Override
@ -166,7 +165,6 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
String strKey = tsKvEntry.getKey();
Integer keyId = getOrSaveKeyId(strKey);
TimescaleTsKvEntity entity = new TimescaleTsKvEntity();
entity.setTenantId(tenantId.getId());
entity.setEntityId(entityId.getId());
entity.setTs(tsKvEntry.getTs());
entity.setKey(keyId);
@ -196,7 +194,6 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
Integer keyId = getOrSaveKeyId(strKey);
return service.submit(() -> {
tsKvRepository.delete(
tenantId.getId(),
entityId.getId(),
keyId,
query.getStartTs(),
@ -207,7 +204,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
@Override
public ListenableFuture<Void> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
return getRemoveLatestFuture(tenantId, entityId, query);
return getRemoveLatestFuture(entityId, query);
}
@Override
@ -215,27 +212,26 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
return service.submit(() -> null);
}
private CompletableFuture<List<TimescaleTsKvEntity>> switchAggregation(String key, long startTs, long endTs, long timeBucket, Aggregation aggregation, UUID entityId, UUID tenantId) {
private CompletableFuture<List<TimescaleTsKvEntity>> switchAggregation(String key, long startTs, long endTs, long timeBucket, Aggregation aggregation, UUID entityId) {
switch (aggregation) {
case AVG:
return findAvg(key, startTs, endTs, timeBucket, entityId, tenantId);
return findAvg(key, startTs, endTs, timeBucket, entityId);
case MAX:
return findMax(key, startTs, endTs, timeBucket, entityId, tenantId);
return findMax(key, startTs, endTs, timeBucket, entityId);
case MIN:
return findMin(key, startTs, endTs, timeBucket, entityId, tenantId);
return findMin(key, startTs, endTs, timeBucket, entityId);
case SUM:
return findSum(key, startTs, endTs, timeBucket, entityId, tenantId);
return findSum(key, startTs, endTs, timeBucket, entityId);
case COUNT:
return findCount(key, startTs, endTs, timeBucket, entityId, tenantId);
return findCount(key, startTs, endTs, timeBucket, entityId);
default:
throw new IllegalArgumentException("Not supported aggregation type: " + aggregation);
}
}
private CompletableFuture<List<TimescaleTsKvEntity>> findCount(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) {
private CompletableFuture<List<TimescaleTsKvEntity>> findCount(String key, long startTs, long endTs, long timeBucket, UUID entityId) {
Integer keyId = getOrSaveKeyId(key);
return aggregationRepository.findCount(
tenantId,
entityId,
keyId,
timeBucket,
@ -243,10 +239,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
endTs);
}
private CompletableFuture<List<TimescaleTsKvEntity>> findSum(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) {
private CompletableFuture<List<TimescaleTsKvEntity>> findSum(String key, long startTs, long endTs, long timeBucket, UUID entityId) {
Integer keyId = getOrSaveKeyId(key);
return aggregationRepository.findSum(
tenantId,
entityId,
keyId,
timeBucket,
@ -254,10 +249,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
endTs);
}
private CompletableFuture<List<TimescaleTsKvEntity>> findMin(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) {
private CompletableFuture<List<TimescaleTsKvEntity>> findMin(String key, long startTs, long endTs, long timeBucket, UUID entityId) {
Integer keyId = getOrSaveKeyId(key);
return aggregationRepository.findMin(
tenantId,
entityId,
keyId,
timeBucket,
@ -265,10 +259,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
endTs);
}
private CompletableFuture<List<TimescaleTsKvEntity>> findMax(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) {
private CompletableFuture<List<TimescaleTsKvEntity>> findMax(String key, long startTs, long endTs, long timeBucket, UUID entityId) {
Integer keyId = getOrSaveKeyId(key);
return aggregationRepository.findMax(
tenantId,
entityId,
keyId,
timeBucket,
@ -276,10 +269,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
endTs);
}
private CompletableFuture<List<TimescaleTsKvEntity>> findAvg(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) {
private CompletableFuture<List<TimescaleTsKvEntity>> findAvg(String key, long startTs, long endTs, long timeBucket, UUID entityId) {
Integer keyId = getOrSaveKeyId(key);
return aggregationRepository.findAvg(
tenantId,
entityId,
keyId,
timeBucket,

10
dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java

@ -31,12 +31,10 @@ import java.util.UUID;
@TimescaleDBTsDao
public interface TsKvTimescaleRepository extends CrudRepository<TimescaleTsKvEntity, TimescaleTsKvCompositeKey> {
@Query("SELECT tskv FROM TimescaleTsKvEntity tskv WHERE tskv.tenantId = :tenantId " +
"AND tskv.entityId = :entityId " +
@Query("SELECT tskv FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " +
"AND tskv.key = :entityKey " +
"AND tskv.ts > :startTs AND tskv.ts <= :endTs")
List<TimescaleTsKvEntity> findAllWithLimit(
@Param("tenantId") UUID tenantId,
@Param("entityId") UUID entityId,
@Param("entityKey") int key,
@Param("startTs") long startTs,
@ -44,12 +42,10 @@ public interface TsKvTimescaleRepository extends CrudRepository<TimescaleTsKvEnt
@Transactional
@Modifying
@Query("DELETE FROM TimescaleTsKvEntity tskv WHERE tskv.tenantId = :tenantId " +
"AND tskv.entityId = :entityId " +
@Query("DELETE FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " +
"AND tskv.key = :entityKey " +
"AND tskv.ts > :startTs AND tskv.ts <= :endTs")
void delete(@Param("tenantId") UUID tenantId,
@Param("entityId") UUID entityId,
void delete(@Param("entityId") UUID entityId,
@Param("entityKey") int key,
@Param("startTs") long startTs,
@Param("endTs") long endTs);

5
dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java

@ -28,6 +28,7 @@ import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -330,7 +331,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
stmt.setInt(6, (int) ttl);
}
futures.add(getFuture(executeAsyncWrite(tenantId, stmt), rs -> null));
return Futures.transform(Futures.allAsList(futures), result -> null);
return Futures.transform(Futures.allAsList(futures), result -> null, MoreExecutors.directExecutor());
}
private void processSetNullValues(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl, List<ListenableFuture<Void>> futures, long partition, DataType type) {
@ -545,7 +546,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
public void onFailure(Throwable t) {
log.warn("[{}] Failed to process remove of the latest value", entityId, t);
}
});
}, MoreExecutors.directExecutor());
return resultFuture;
}

17
dao/src/main/resources/sql/schema-timescale-idx.sql

@ -1,17 +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.
--
CREATE INDEX IF NOT EXISTS idx_tenant_ts_kv ON tenant_ts_kv(tenant_id, entity_id, key, ts);

5
dao/src/main/resources/sql/schema-timescale.sql

@ -16,8 +16,7 @@
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
CREATE TABLE IF NOT EXISTS tenant_ts_kv (
tenant_id uuid NOT NULL,
CREATE TABLE IF NOT EXISTS ts_kv (
entity_id uuid NOT NULL,
key int NOT NULL,
ts bigint NOT NULL,
@ -26,7 +25,7 @@ CREATE TABLE IF NOT EXISTS tenant_ts_kv (
long_v bigint,
dbl_v double precision,
json_v json,
CONSTRAINT tenant_ts_kv_pkey PRIMARY KEY (tenant_id, entity_id, key, ts)
CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts)
);
CREATE TABLE IF NOT EXISTS ts_kv_dictionary (

2
dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java

@ -44,7 +44,7 @@ public class SqlDaoServiceTestSuite {
// @ClassRule
// public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
// Arrays.asList("sql/schema-timescale.sql", "sql/schema-timescale-idx.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"),
// Arrays.asList("sql/schema-timescale.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"),
// "sql/timescale/drop-all-tables.sql",
// "sql-test.properties"
// );

8
dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java

@ -119,7 +119,7 @@ public class RateLimitedResultSetFutureTest {
resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement);
ListenableFuture<Row> transform = Futures.transform(resultSetFuture, ResultSet::one);
ListenableFuture<Row> transform = Futures.transform(resultSetFuture, ResultSet::one, MoreExecutors.directExecutor());
Row actualRow = transform.get();
assertSame(row, actualRow);
@ -132,7 +132,7 @@ public class RateLimitedResultSetFutureTest {
when(rateLimiter.acquireAsync()).thenReturn(Futures.immediateFuture(null));
when(session.executeAsync(statement)).thenThrow(new UnsupportedFeatureException(ProtocolVersion.V3, "hjg"));
resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement);
ListenableFuture<Row> transform = Futures.transform(resultSetFuture, ResultSet::one);
ListenableFuture<Row> transform = Futures.transform(resultSetFuture, ResultSet::one, MoreExecutors.directExecutor());
try {
transform.get();
fail();
@ -156,7 +156,7 @@ public class RateLimitedResultSetFutureTest {
when(realFuture.get()).thenThrow(new ExecutionException("Fail", new TimeoutException("timeout")));
resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement);
ListenableFuture<Row> transform = Futures.transform(resultSetFuture, ResultSet::one);
ListenableFuture<Row> transform = Futures.transform(resultSetFuture, ResultSet::one, MoreExecutors.directExecutor());
try {
transform.get();
fail();
@ -177,7 +177,7 @@ public class RateLimitedResultSetFutureTest {
when(rateLimiter.acquireAsync()).thenReturn(future);
resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement);
ListenableFuture<Row> transform = Futures.transform(resultSetFuture, ResultSet::one);
ListenableFuture<Row> transform = Futures.transform(resultSetFuture, ResultSet::one, MoreExecutors.directExecutor());
// TimeUnit.MILLISECONDS.sleep(200);
future.cancel(false);
latch.countDown();

9
dao/src/test/java/org/thingsboard/server/dao/service/BaseAdminSettingsServiceTest.java

@ -67,13 +67,4 @@ public abstract class BaseAdminSettingsServiceTest extends AbstractServiceTest {
adminSettings.setKey("newKey");
adminSettingsService.saveAdminSettings(SYSTEM_TENANT_ID, adminSettings);
}
@Test(expected = DataValidationException.class)
public void testSaveAdminSettingsWithNewJsonStructure() {
AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(SYSTEM_TENANT_ID, "mail");
JsonNode json = adminSettings.getJsonValue();
((ObjectNode) json).put("newKey", "my new value");
adminSettings.setJsonValue(json);
adminSettingsService.saveAdminSettings(SYSTEM_TENANT_ID, adminSettings);
}
}

2
dao/src/test/resources/sql/timescale/drop-all-tables.sql

@ -12,7 +12,7 @@ DROP TABLE IF EXISTS event;
DROP TABLE IF EXISTS relation;
DROP TABLE IF EXISTS tb_user;
DROP TABLE IF EXISTS tenant;
DROP TABLE IF EXISTS tenant_ts_kv;
DROP TABLE IF EXISTS ts_kv;
DROP TABLE IF EXISTS ts_kv_latest;
DROP TABLE IF EXISTS user_credentials;
DROP TABLE IF EXISTS widget_type;

7
docker/README.md

@ -17,6 +17,13 @@ In order to set database type change the value of `DATABASE` variable in `.env`
**NOTE**: According to the database type corresponding docker service will be deployed (see `docker-compose.postgres.yml`, `docker-compose.cassandra.yml` for details).
Execute the following command to create log folders for the services and chown of these folders to the docker container users.
To be able to change user, **chown** command is used, which requires sudo permissions (script will request password for a sudo access):
`
$ ./docker-create-log-folders.sh
`
Execute the following command to run installation:
`

24
docker/docker-create-log-folders.sh

@ -0,0 +1,24 @@
#!/bin/bash
#
# 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.
#
mkdir -p tb-node/log/ && sudo chown -R 799:799 tb-node/log/
mkdir -p tb-transports/coap/log && sudo chown -R 799:799 tb-transports/coap/log
mkdir -p tb-transports/http/log && sudo chown -R 799:799 tb-transports/http/log
mkdir -p tb-transports/mqtt/log && sudo chown -R 799:799 tb-transports/mqtt/log

2
k8s/database-setup.yml

@ -39,5 +39,5 @@ spec:
volumeMounts:
- mountPath: /config
name: tb-node-config
command: ['sh', '-c', 'while [ ! -f /install-finished ]; do sleep 2; done;']
command: ['sh', '-c', 'while [ ! -f /tmp/install-finished ]; do sleep 2; done;']
restartPolicy: Never

4
k8s/k8s-delete-all.sh

@ -15,4 +15,6 @@
# limitations under the License.
#
kubectl -n thingsboard delete svc,sts,deploy,pv,pvc,cm,po,ing --all
kubectl -n thingsboard delete svc,sts,deploy,cm,po,ing --all
kubectl -n thingsboard get pvc --no-headers=true | awk '//{print $1}' | xargs kubectl -n thingsboard delete --ignore-not-found=true pvc

2
k8s/k8s-install-tb.sh

@ -22,7 +22,7 @@ function installTb() {
kubectl apply -f tb-node-configmap.yml
kubectl apply -f database-setup.yml &&
kubectl wait --for=condition=Ready pod/tb-db-setup --timeout=120s &&
kubectl exec tb-db-setup -- sh -c 'export INSTALL_TB=true; export LOAD_DEMO='"$loadDemo"'; start-tb-node.sh; touch /install-finished;'
kubectl exec tb-db-setup -- sh -c 'export INSTALL_TB=true; export LOAD_DEMO='"$loadDemo"'; start-tb-node.sh; touch /tmp/install-finished;'
kubectl delete pod tb-db-setup

2
k8s/k8s-upgrade-tb.sh

@ -38,6 +38,6 @@ fi
kubectl apply -f database-setup.yml &&
kubectl wait --for=condition=Ready pod/tb-db-setup --timeout=120s &&
kubectl exec tb-db-setup -- sh -c 'export UPGRADE_TB=true; export FROM_VERSION='"$fromVersion"'; start-tb-node.sh; touch /install-finished;'
kubectl exec tb-db-setup -- sh -c 'export UPGRADE_TB=true; export FROM_VERSION='"$fromVersion"'; start-tb-node.sh; touch /tmp/install-finished;'
kubectl delete pod tb-db-setup

2
k8s/postgres.yml

@ -58,6 +58,8 @@ spec:
env:
- name: POSTGRES_DB
value: "thingsboard"
- name: POSTGRES_PASSWORD
value: "postgres"
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:

4
msa/js-executor/docker/Dockerfile

@ -14,7 +14,7 @@
# limitations under the License.
#
FROM debian:stretch
FROM thingsboard/base
COPY start-js-executor.sh ${pkg.name}.deb /tmp/
@ -25,4 +25,6 @@ RUN dpkg -i /tmp/${pkg.name}.deb
RUN update-rc.d ${pkg.name} disable
USER ${pkg.user}
CMD ["start-js-executor.sh"]

4
msa/js-executor/docker/start-js-executor.sh

@ -26,4 +26,6 @@ identity=${pkg.name}
source "${CONF_FOLDER}/${configfile}"
su -s /bin/sh -c "$mainfile"
cd ${pkg.installFolder}/bin
exec /bin/sh -c "$mainfile"

1
msa/js-executor/pom.xml

@ -36,7 +36,6 @@
<main.dir>${basedir}/../..</main.dir>
<pkg.name>tb-js-executor</pkg.name>
<docker.name>tb-js-executor</docker.name>
<pkg.user>thingsboard</pkg.user>
<pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<pkg.linux.dist>${project.build.directory}/package/linux</pkg.linux.dist>

4
msa/tb-node/docker/Dockerfile

@ -25,4 +25,8 @@ RUN dpkg -i /tmp/${pkg.name}.deb
RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
RUN chown -R ${pkg.user}:${pkg.user} /tmp
USER ${pkg.user}
CMD ["start-tb-node.sh"]

4
msa/tb-node/docker/start-tb-node.sh

@ -18,12 +18,14 @@
CONF_FOLDER="/config"
jarfile=${pkg.installFolder}/bin/${pkg.name}.jar
configfile=${pkg.name}.conf
run_user=${pkg.name}
run_user=${pkg.user}
source "${CONF_FOLDER}/${configfile}"
export LOADER_PATH=/config,${LOADER_PATH}
cd ${pkg.installFolder}/bin
if [ "$INSTALL_TB" == "true" ]; then
if [ "$LOAD_DEMO" == "true" ]; then

1
msa/tb-node/pom.xml

@ -36,7 +36,6 @@
<main.dir>${basedir}/../..</main.dir>
<pkg.name>thingsboard</pkg.name>
<docker.name>tb-node</docker.name>
<pkg.user>thingsboard</pkg.user>
<pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
</properties>

1
msa/tb/pom.xml

@ -38,7 +38,6 @@
<tb.docker.name>tb</tb.docker.name>
<tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name>
<tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name>
<pkg.user>thingsboard</pkg.user>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<pkg.upgradeVersion>2.4.2</pkg.upgradeVersion>
</properties>

2
msa/transport/coap/docker/Dockerfile

@ -25,4 +25,6 @@ RUN dpkg -i /tmp/${pkg.name}.deb
RUN update-rc.d ${pkg.name} disable
USER ${pkg.user}
CMD ["start-tb-coap-transport.sh"]

2
msa/transport/coap/docker/start-tb-coap-transport.sh

@ -25,6 +25,8 @@ export LOADER_PATH=/config,${LOADER_PATH}
echo "Starting '${project.name}' ..."
cd ${pkg.installFolder}/bin
exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.coap.ThingsboardCoapTransportApplication \
-Dspring.jpa.hibernate.ddl-auto=none \
-Dlogging.config=/config/logback.xml \

1
msa/transport/coap/pom.xml

@ -36,7 +36,6 @@
<main.dir>${basedir}/../../..</main.dir>
<pkg.name>tb-coap-transport</pkg.name>
<docker.name>tb-coap-transport</docker.name>
<pkg.user>thingsboard</pkg.user>
<pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
</properties>

2
msa/transport/http/docker/Dockerfile

@ -25,4 +25,6 @@ RUN dpkg -i /tmp/${pkg.name}.deb
RUN update-rc.d ${pkg.name} disable
USER ${pkg.user}
CMD ["start-tb-http-transport.sh"]

2
msa/transport/http/docker/start-tb-http-transport.sh

@ -25,6 +25,8 @@ export LOADER_PATH=/config,${LOADER_PATH}
echo "Starting '${project.name}' ..."
cd ${pkg.installFolder}/bin
exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.http.ThingsboardHttpTransportApplication \
-Dspring.jpa.hibernate.ddl-auto=none \
-Dlogging.config=/config/logback.xml \

1
msa/transport/http/pom.xml

@ -36,7 +36,6 @@
<main.dir>${basedir}/../../..</main.dir>
<pkg.name>tb-http-transport</pkg.name>
<docker.name>tb-http-transport</docker.name>
<pkg.user>thingsboard</pkg.user>
<pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
</properties>

2
msa/transport/mqtt/docker/Dockerfile

@ -25,4 +25,6 @@ RUN dpkg -i /tmp/${pkg.name}.deb
RUN update-rc.d ${pkg.name} disable
USER ${pkg.user}
CMD ["start-tb-mqtt-transport.sh"]

2
msa/transport/mqtt/docker/start-tb-mqtt-transport.sh

@ -25,6 +25,8 @@ export LOADER_PATH=/config,${LOADER_PATH}
echo "Starting '${project.name}' ..."
cd ${pkg.installFolder}/bin
exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.mqtt.ThingsboardMqttTransportApplication \
-Dspring.jpa.hibernate.ddl-auto=none \
-Dlogging.config=/config/logback.xml \

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

Loading…
Cancel
Save