diff --git a/application/pom.xml b/application/pom.xml
index c2d80d62ac..d97f6d92e3 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.9.0-RC
+ 4.0.0-SNAPSHOT
thingsboard
application
diff --git a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
index 9adeb4f49e..e663ff779d 100644
--- a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
+++ b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
@@ -33,8 +33,13 @@
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries",
+ "configurationVersion": 1,
"configuration": {
- "defaultTTL": 0
+ "defaultTTL": 0,
+ "useServerTs": false,
+ "persistenceSettings": {
+ "type": "ON_EVERY_MESSAGE"
+ }
},
"externalId": null
},
@@ -185,4 +190,4 @@
],
"ruleChainConnections": null
}
-}
\ No newline at end of file
+}
diff --git a/application/src/main/data/json/system/scada_symbols/3-phase-voltage-relay-hp.svg b/application/src/main/data/json/system/scada_symbols/3-phase-voltage-relay-hp.svg
new file mode 100644
index 0000000000..f6d881b172
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/3-phase-voltage-relay-hp.svg
@@ -0,0 +1,548 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/apartments-hp.svg b/application/src/main/data/json/system/scada_symbols/apartments-hp.svg
new file mode 100644
index 0000000000..9e2c965f79
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/apartments-hp.svg
@@ -0,0 +1,344 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/battery-hp.svg b/application/src/main/data/json/system/scada_symbols/battery-hp.svg
new file mode 100644
index 0000000000..8b63a9ab29
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/battery-hp.svg
@@ -0,0 +1,473 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/bottom-light-bulb-hp.svg b/application/src/main/data/json/system/scada_symbols/bottom-light-bulb-hp.svg
new file mode 100644
index 0000000000..4f0603b904
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/bottom-light-bulb-hp.svg
@@ -0,0 +1,346 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/consumers-hp.svg b/application/src/main/data/json/system/scada_symbols/consumers-hp.svg
new file mode 100644
index 0000000000..e4005d7ae2
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/consumers-hp.svg
@@ -0,0 +1,346 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/crane-hp.svg b/application/src/main/data/json/system/scada_symbols/crane-hp.svg
new file mode 100644
index 0000000000..7e7c64c99c
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/crane-hp.svg
@@ -0,0 +1,346 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/curcuit-breaker-hp.svg b/application/src/main/data/json/system/scada_symbols/curcuit-breaker-hp.svg
new file mode 100644
index 0000000000..12f8b4944f
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/curcuit-breaker-hp.svg
@@ -0,0 +1,444 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/drawwork-hp.svg b/application/src/main/data/json/system/scada_symbols/drawwork-hp.svg
new file mode 100644
index 0000000000..bb98d8cb5e
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/drawwork-hp.svg
@@ -0,0 +1,355 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/drill-hp.svg b/application/src/main/data/json/system/scada_symbols/drill-hp.svg
new file mode 100644
index 0000000000..66e06398ed
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/drill-hp.svg
@@ -0,0 +1,354 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/drilling-line-hp.svg b/application/src/main/data/json/system/scada_symbols/drilling-line-hp.svg
new file mode 100644
index 0000000000..60bce1a645
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/drilling-line-hp.svg
@@ -0,0 +1,361 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/drilling-rig-hp.svg b/application/src/main/data/json/system/scada_symbols/drilling-rig-hp.svg
new file mode 100644
index 0000000000..76c9af6454
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/drilling-rig-hp.svg
@@ -0,0 +1,354 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/electrical-distribution-board-hp.svg b/application/src/main/data/json/system/scada_symbols/electrical-distribution-board-hp.svg
new file mode 100644
index 0000000000..0fcfe4dde0
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/electrical-distribution-board-hp.svg
@@ -0,0 +1,332 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/energy-meter-hp.svg b/application/src/main/data/json/system/scada_symbols/energy-meter-hp.svg
new file mode 100644
index 0000000000..606eb1153d
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/energy-meter-hp.svg
@@ -0,0 +1,482 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/four-rate-energy-meter-hp.svg b/application/src/main/data/json/system/scada_symbols/four-rate-energy-meter-hp.svg
new file mode 100644
index 0000000000..de4fb13836
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/four-rate-energy-meter-hp.svg
@@ -0,0 +1,876 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/fuel-generator-hp.svg b/application/src/main/data/json/system/scada_symbols/fuel-generator-hp.svg
new file mode 100644
index 0000000000..52a3c70d6e
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/fuel-generator-hp.svg
@@ -0,0 +1,359 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/high-voltage-tower-hp.svg b/application/src/main/data/json/system/scada_symbols/high-voltage-tower-hp.svg
new file mode 100644
index 0000000000..3aef3ff948
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/high-voltage-tower-hp.svg
@@ -0,0 +1,297 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/hook-hp.svg b/application/src/main/data/json/system/scada_symbols/hook-hp.svg
new file mode 100644
index 0000000000..742d1fe811
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/hook-hp.svg
@@ -0,0 +1,293 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-curcuit-breaker-hp.svg b/application/src/main/data/json/system/scada_symbols/horizontal-curcuit-breaker-hp.svg
new file mode 100644
index 0000000000..17fd1ba69e
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/horizontal-curcuit-breaker-hp.svg
@@ -0,0 +1,439 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-energy-system-controller-hp.svg b/application/src/main/data/json/system/scada_symbols/horizontal-energy-system-controller-hp.svg
new file mode 100644
index 0000000000..13ee85821b
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/horizontal-energy-system-controller-hp.svg
@@ -0,0 +1,378 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/house-hp.svg b/application/src/main/data/json/system/scada_symbols/house-hp.svg
new file mode 100644
index 0000000000..d95c47bb8b
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/house-hp.svg
@@ -0,0 +1,349 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/industrial-fuel-generator-hp.svg b/application/src/main/data/json/system/scada_symbols/industrial-fuel-generator-hp.svg
new file mode 100644
index 0000000000..2b7a476a92
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/industrial-fuel-generator-hp.svg
@@ -0,0 +1,358 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/inverter-hp.svg b/application/src/main/data/json/system/scada_symbols/inverter-hp.svg
new file mode 100644
index 0000000000..73f14c8e75
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/inverter-hp.svg
@@ -0,0 +1,578 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/large-inverter-hp.svg b/application/src/main/data/json/system/scada_symbols/large-inverter-hp.svg
new file mode 100644
index 0000000000..11bc5da0a6
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/large-inverter-hp.svg
@@ -0,0 +1,578 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/low-voltage-tower-hp.svg b/application/src/main/data/json/system/scada_symbols/low-voltage-tower-hp.svg
new file mode 100644
index 0000000000..812e616433
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/low-voltage-tower-hp.svg
@@ -0,0 +1,278 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/low-voltage-transformer-tower-hp.svg b/application/src/main/data/json/system/scada_symbols/low-voltage-transformer-tower-hp.svg
new file mode 100644
index 0000000000..2985dc600c
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/low-voltage-transformer-tower-hp.svg
@@ -0,0 +1,334 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/manufacture-hp.svg b/application/src/main/data/json/system/scada_symbols/manufacture-hp.svg
new file mode 100644
index 0000000000..5b35f2eaf2
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/manufacture-hp.svg
@@ -0,0 +1,344 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/meter.svg b/application/src/main/data/json/system/scada_symbols/meter.svg
new file mode 100644
index 0000000000..02b2833133
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/meter.svg
@@ -0,0 +1,743 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/platform-hp.svg b/application/src/main/data/json/system/scada_symbols/platform-hp.svg
new file mode 100644
index 0000000000..af9b72a2e5
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/platform-hp.svg
@@ -0,0 +1,267 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/power-socket-hp.svg b/application/src/main/data/json/system/scada_symbols/power-socket-hp.svg
new file mode 100644
index 0000000000..5524b3bb14
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/power-socket-hp.svg
@@ -0,0 +1,371 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/power-transformer-hp.svg b/application/src/main/data/json/system/scada_symbols/power-transformer-hp.svg
new file mode 100644
index 0000000000..67e75be827
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/power-transformer-hp.svg
@@ -0,0 +1,359 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/preventer-hp.svg b/application/src/main/data/json/system/scada_symbols/preventer-hp.svg
new file mode 100644
index 0000000000..0b193a5665
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/preventer-hp.svg
@@ -0,0 +1,343 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/rotor-hp.svg b/application/src/main/data/json/system/scada_symbols/rotor-hp.svg
new file mode 100644
index 0000000000..729e85eac1
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/rotor-hp.svg
@@ -0,0 +1,346 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/single-key-switch-hp.svg b/application/src/main/data/json/system/scada_symbols/single-key-switch-hp.svg
new file mode 100644
index 0000000000..77d6e7f95d
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/single-key-switch-hp.svg
@@ -0,0 +1,373 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/small-left-meter.svg b/application/src/main/data/json/system/scada_symbols/small-left-meter.svg
new file mode 100644
index 0000000000..a480412366
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/small-left-meter.svg
@@ -0,0 +1,717 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/small-meter.svg b/application/src/main/data/json/system/scada_symbols/small-meter.svg
new file mode 100644
index 0000000000..551c8d520a
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/small-meter.svg
@@ -0,0 +1,688 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/small-power-transformer-hp.svg b/application/src/main/data/json/system/scada_symbols/small-power-transformer-hp.svg
new file mode 100644
index 0000000000..049c75ab58
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/small-power-transformer-hp.svg
@@ -0,0 +1,359 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/small-right-center.svg b/application/src/main/data/json/system/scada_symbols/small-right-center.svg
new file mode 100644
index 0000000000..de1bee5acd
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/small-right-center.svg
@@ -0,0 +1,717 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/solar-panel-hp.svg b/application/src/main/data/json/system/scada_symbols/solar-panel-hp.svg
new file mode 100644
index 0000000000..b7b7724f89
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/solar-panel-hp.svg
@@ -0,0 +1,353 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/stand-solar-panel-hp.svg b/application/src/main/data/json/system/scada_symbols/stand-solar-panel-hp.svg
new file mode 100644
index 0000000000..93e622a021
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/stand-solar-panel-hp.svg
@@ -0,0 +1,357 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/three-rate-energy-meter-hp.svg b/application/src/main/data/json/system/scada_symbols/three-rate-energy-meter-hp.svg
new file mode 100644
index 0000000000..f2b7bf4d54
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/three-rate-energy-meter-hp.svg
@@ -0,0 +1,747 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/top-light-bulb-hp.svg b/application/src/main/data/json/system/scada_symbols/top-light-bulb-hp.svg
new file mode 100644
index 0000000000..ed6855884a
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/top-light-bulb-hp.svg
@@ -0,0 +1,346 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/two-key-switch-hp.svg b/application/src/main/data/json/system/scada_symbols/two-key-switch-hp.svg
new file mode 100644
index 0000000000..40e04a3bf5
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/two-key-switch-hp.svg
@@ -0,0 +1,489 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/two-rate-energy-meter-hp.svg b/application/src/main/data/json/system/scada_symbols/two-rate-energy-meter-hp.svg
new file mode 100644
index 0000000000..7adc401836
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/two-rate-energy-meter-hp.svg
@@ -0,0 +1,618 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/vertical-energy-system-controller-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-energy-system-controller-hp.svg
new file mode 100644
index 0000000000..cebe949c36
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/vertical-energy-system-controller-hp.svg
@@ -0,0 +1,378 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/voltage-relay-hp.svg b/application/src/main/data/json/system/scada_symbols/voltage-relay-hp.svg
new file mode 100644
index 0000000000..eaa0b02455
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/voltage-relay-hp.svg
@@ -0,0 +1,439 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/voltage-stabilizer-hp.svg b/application/src/main/data/json/system/scada_symbols/voltage-stabilizer-hp.svg
new file mode 100644
index 0000000000..aafc2af5a0
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/voltage-stabilizer-hp.svg
@@ -0,0 +1,584 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/wind-turbine-cluster-hp.svg b/application/src/main/data/json/system/scada_symbols/wind-turbine-cluster-hp.svg
new file mode 100644
index 0000000000..74855dd35a
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/wind-turbine-cluster-hp.svg
@@ -0,0 +1,370 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/wind-turbine-hp.svg b/application/src/main/data/json/system/scada_symbols/wind-turbine-hp.svg
new file mode 100644
index 0000000000..a4282c16e7
--- /dev/null
+++ b/application/src/main/data/json/system/scada_symbols/wind-turbine-hp.svg
@@ -0,0 +1,360 @@
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json b/application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json
index ffb0fee835..33efaacc0c 100644
--- a/application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json
+++ b/application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json
@@ -26,6 +26,12 @@
"hp_bottom_tee_connector",
"hp_right_tee_connector",
"hp_left_tee_connector",
- "hp_top_tee_connector"
+ "hp_top_tee_connector",
+ "hp_drawwork",
+ "hp_crane",
+ "hp_consumers",
+ "hp_house",
+ "hp_apartments",
+ "hp_manufacture"
]
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_bundles/high_performance_scada_energy_system.json b/application/src/main/data/json/system/widget_bundles/high_performance_scada_energy_system.json
new file mode 100644
index 0000000000..a06ccc0819
--- /dev/null
+++ b/application/src/main/data/json/system/widget_bundles/high_performance_scada_energy_system.json
@@ -0,0 +1,48 @@
+{
+ "widgetsBundle": {
+ "alias": "high_performance_scada_energy_system",
+ "title": "High-performance SCADA energy system",
+ "image": "tb-image:aHBfc2NhZGFfZW5lcmd5X3N5c3RlbV9idW5kbGVfaW1hZ2UucG5n:IkhpZ2gtcGVyZm9ybWFuY2UgU0NBREEgZW5lcmd5IHN5c3RlbSIgc3lzdGVtIGJ1bmRsZSBpbWFnZQ==:SU1BR0U=;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAllBMVEXe3t7b29vf398AAADf39/e3t7e3t7////Gxsbj4+OpqanFxcW5ubmtra2xsbGVlZWlpaWhoaHS0tLx8fG2trbU1NTBwcGvr6+MjIybm5twcHC+vr6jo6OYmJizs7Orq6vExMSZmZnCwsKdnZ2ampqRkZE3Nze7u7t+fn6EhIRjY2NVVVVsbGx5eXmJiYlEREQvLy8aGhprAy3xAAAABnRSTlPvIK8Av79l/pT7AAAJyElEQVR42uzVXW/aMBiGYWin57VjmyRmNGFLKOWjQACt/P8/t7yJQVMPrE62KkBcR88ZuokTD56HPwa4dU/D58Hw9jNabcYT7sIdHKve4F5C8Ai5No+Qa/MIuTaPkGvzCLk2QSHKIB6rFAIEhayniCc9fVx2c2ou++OUwi88ZGEtYrHbfZOf9/59r9w2f963Fj7hIUaOK0SSWyKSS8s7461rsOmk3TMNj/CQKVGBOJSiTsJNkpjcACjHxOYWHsEhCyJhEYWW1FkDeKNeDcD0c2LgERpi+McrxFFqIYQeaQCqnshWrQAUL7zFGh7BIVNq5YjDJm1Jnfd7lySrnQLT23aX8AgPWbjTG0VFTHKJyohlXLKZ8xy/wiMwxB3rsUEwd0yZ+ecdyQCs+yk1PAJDMuokiMFmxMZl/9W6PJ2EOlrBIzAkp84IUahym6yOG7D0WCarXQE2Oq6Scm3gERoyJxbrA5wJfiCV4l3zljVPu5R8IW7gERZy+fKPC0SgJu4e+XS0Ss+/FSkkI2eKQJ8vxOx7L8RXcjaI4W3GF2KhAKhU8CXYbTHjrZfwCAz5Tc4LohA/pVy7nbZRqds7IVYaHqEhc3ImFgGKkZM1TVO6XR4Oh9rtX+2uRo4BYodYQY7UCCDofwggdoiRdJYDuOEQYuFXYjEqk68qqwKIHZJHCoGQ9FVSgcUNSe8lxNBFBXarIVrSmQG71ZC/1Jpdc6sgEIbv3AXRIB9VE2OStmN6mpnT8/9/3RlSbGTxzmFC9yYTfV14wrIsRLQk/f4ikHDhM+ANVSD6BSB/VLg39SbDJKCyBxFst1o0tkEOsEP2ID20q/nXEFH2IK+Aai1tVVSUOYgrrnYrsx0VEQ2Zg3A6HVq/rlNRkzlI7U/jouMgutsyKmuQEulpHMYHdKW9X8kapIl//hHcIK2Isgbp4zWDRznr9C3KGuQAzkSQorSf60R0zhnEb9G1WF77K7AvYxETm0HYJ0sEUuko2V6lu/Zax6JhM8j+a58IZIjqkav13uuoaum3gtgJJkwDYqJ6ZARvZ0VFm0Nr4oC3NCBv3teBhJGzHS2I240g3MXVjScBEXTzcVm0S0WHbSBsYsAY+6fTgJDJLtOBdB2wzn0kD62yMYdew2z94dSIbaFFB2T/9Ql6YglA5kh6UxUf7X0cwNu9J/b1VCk5izaB7J1nt470t2TpVwup5pGwc/q1P0Q73ymzBYRPrqF7KzdMMEcYAHK79CpHDaDNUcHDEHFraE0ffLaPKUGJYizqwGldnAQKcyyuNrjO0YotIN3+YX2CEbno0OelKCofRgMLb/WiyLZolBYijqKZy5GakbudyhOkbHXM4Yt2UayQgCxzBMER1jiK2oOskZzb/EDqgvqTi7Kx8SJLRcfcQI60oWLwd16W5Qi+RCR5gYQcwYuSPKgiSxkNXE4glEO3D2cYvi6gBNXKfEAoB17oDv1loZaGkuQCQji0sG/0Pyu+BGFIHjjmASKDNrTr+VmRHmB4GoSchTM+B5CWLSisQOKrYvQwnvtiSy9n/PNBSvUYC/7hO4enWswgofNyd+Je33Hxw2KvzwYpR991I6wG9L44ADNVXYYgqhGSwY8I70/5b+fyuSBKAjDRCaPBmX4HZ8a3ik1dDt/OK7VrEVZEWvB3YQFG9VQQ+b+9c1tuGwTCcI+wIJCEhK26suW2aafpRfv+j9ehIgtZuQk20QRn8t9kRP7Y+5nDamUUDfbzr1AzWT2oDTQ65G+YQfiPYPqmBxD3TU0jh9tnHlo74CgGzHLRH5gKqUJ6xGBSTM6mLxwl1NMNrc1FPdLxexzsZq6iFONegCDBdJxPWgBNzvDgwrVnG5GqDavPBemkIBxM+ewHd4Rq/lEtTYREHLvn2njmJnJQB/Nq64sPtcWhFf34AswXjcd/JkvqrGcAoSey3xR+iauiX4sdjrBwTjwG00RIngGk2vJIdcUwRuMNNecCZDTC9pFJn/w0/kfSwjlqk0Eoh5Cdb67ji7o7AA4IEkwu+AFNgpCsKQpCxsQ0YLvDq9FkcdUCrtoKTTEtM1MGSS4Ijoj5KiJDWceFpmiyyz4Eacm2pzrq3WlVEgrSIQeM97deWayiPGzlffFkBm8Kqs3msRm/AgherIaBvOsco41NNk6I+46aUFVtgKNpJVGQnYOo5bB8xw5wZZWzyUfXyhF6nz0ATm986IysnWnHVhIF+dMcW92xpXxhC7EJws0KejoOf5xJeNMJdXq6GRqWqfydDyDIWRvAcgOdciZYcyrkg2hBzqMdmZOKTb0zFQ5CN2doDwJdbLoSkF5HDf2ySDBzzchWUz6IX3JjECzbqKlskNpvckA1WAJSk2HrKR/kK90c5IKmw61N3BxUGQVBzdmTSis4XApiKUjLvczS9JgU+X4rHQE/RMgDORAQMtwkmlJeDCVU6HhtHtbXLhPkBrNfFAztgGPiBwycT7XXPl7nJH9UQw4IjgZJyHzcxJQCgn+lRQQCKRe38kA0TePqBIjhTscUkPoUCKSA6DyQAUEiMjrcRm9KAWmn7SwAsFsvmSA7HjJAfIyGkDntqEmngPzu+cXqf2eA1HRL2ci9FF3JhhQQyTMkM0C2y4TopalpLBrEcqeJBO00UFN7BSBb0kA6AGZT0SDw34TYLkwlg3RAJzbQ/zCAaUCWDOJvBAVCFncAVr9QNIjAcjAiIx2gPEhVMIgWpBxUgl7qx+pXFA1CT7h1jyBVMF0NSK/jBhq38Q26YJA9TeMmAqGmkkFqWg7uA4imJlMwyFdaDtYcpWn1uy8YxD5w20WzMF0ByCFuoHFLNKWCbCRKcREdrAZyQ9O4XILg/SNtMogA1Ib30cFqIIqm8SNHHRamgoeWpuWg4ihLl+RjwSADBTEcJWn1q9LnyGfUrTtArQWCMZq4gcY9XlFCHEMDStEleSgYZEtXqImjDDWNySC9QPXxgVg9j0xx0DRue/byC59Qkm8+Ba0Oso3zCI0b7kwFDy2gSy1w1LQwlQuCFbqKG0gHAKyYEDfKKlB9Jkio0CEOmsQNAk1PDvJ9RpAiF0TgjtWogcStEKR6ahDZI1AeiBakHNQxSEfKeJEAouQZ+o7939+6Y3U5CP26T5N9xdiWBGL5eUKOsDtXZYL0OjTQuA0tfk8LnelC8DAk9WUge1rXNjGIWpoeVqXhLH2KmMBJmeoCkP1u90Pe6dfOCRtCGzG17FE1QogmeYrSpf0CkJZfoG0CCNb7yVUE/1JdN0jV4s3nhYIYlkiigHMx/mQ5IKa5QIYlvTBLVqUbxU7p9Rk9heoVpDS9gpSmV5DS9ApSml7Mg3XffGAvQu9ezkO037+9/sdov/n49v1fKIFpFaO3gooAAAAASUVORK5CYII=",
+ "scada": true,
+ "description": "Bundle with high-performance SCADA symbols for energy system",
+ "order": 9410,
+ "name": "High-performance SCADA energy system"
+ },
+ "widgetTypeFqns": [
+ "hp_solar_panel",
+ "hp_stand_solar_panel",
+ "hp_wind_turbine",
+ "hp_wind_turbine_cluster",
+ "hp_fuel_generator",
+ "hp_industrial_fuel_generator",
+ "hp_circuit_breaker2",
+ "hp_horizontal_circuit_breaker",
+ "hp_voltage_relay",
+ "hp_3_phase_voltage_relay",
+ "hp_voltage_stabilizer",
+ "hp_energy_meter",
+ "hp_two_rate_energy_meter",
+ "hp_three_rate_energy_meter",
+ "hp_four_rate_energy_meter",
+ "hp_electrical_distribution_board",
+ "hp_power_socket",
+ "hp_single_key_switch",
+ "hp_two_key_switch",
+ "hp_top_light_bulb",
+ "hp_bottom_light_bulb",
+ "hp_battery",
+ "hp_inverter",
+ "hp_large_inverter",
+ "hp_horizontal_energy_systems_controller",
+ "hp_vertical_energy_systems_controller",
+ "hp_small_power_transformer",
+ "hp_power_transformer",
+ "hp_low_voltage_transformer_tower",
+ "hp_low_voltage_tower",
+ "hp_high_voltage_tower",
+ "hp_consumers",
+ "hp_house",
+ "hp_apartments",
+ "hp_manufacture"
+ ]
+}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_bundles/high_performance_scada_oil_gas.json b/application/src/main/data/json/system/widget_bundles/high_performance_scada_oil_gas.json
new file mode 100644
index 0000000000..87b778be21
--- /dev/null
+++ b/application/src/main/data/json/system/widget_bundles/high_performance_scada_oil_gas.json
@@ -0,0 +1,20 @@
+{
+ "widgetsBundle": {
+ "alias": "high_performance_scada_oil_gas",
+ "title": "High-performance SCADA oil & gas",
+ "image": "tb-image:aHBfc2NhZGFfb2lsX2dhc19zeXN0ZW1fYnVuZGxlX2ltYWdlLnBuZw==:IkhpZ2gtcGVyZm9ybWFuY2UgU0NBREEgb2lsICYgZ2FzIiBzeXN0ZW0gYnVuZGxlIGltYWdl:SU1BR0U=;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAflBMVEXe3t7b29vf398AAADf39/e3t7e3t7////Gxsapqam5ubnR0NDj4+Otra2hoaHS0tLi4uK4uLjx8fGYmJikpKSdnZ2VlZWWlpbU1NS1tbWnp6eKioqysrLLy8uampra2tqvr6/X19eRkZF+fn7Dw8N1dXW/vr6FhYViYmJwcHCzXr36AAAABnRSTlPvIK8Av79l/pT7AAAI0UlEQVR42uza23LaMBSF4Z5msbyFpOFMAXcg0HT6/i/YGqvdNhBiYXtipf0v7CRX+UaSbWQ+fP745QNS79PHzx8+ps8A8JvxCe+idzCtwpC8Fwj+Q4bWvw0R7oQiJIbTAxAvwOmAIsFgiodYXxxPODecMYmGeD4fAOQoGtCYxEIMgcMQJbEQD5wlCvEYRLEQoig/wCBkMIgiIYKyE+claTBzKxJClO0AegDI8x0GUTREz5NTbvJUR0RwzjjAn1GpQlgcDif4nfiDAZAqBAIc8nBjNyY3ef6MQRR/HzlI7ZI7lCfHaIg5oA5Bu5bZzZaIKh6Cha9BHFq2HN2sb4gjra84rHAreCDKcjUr+n4b8n1WtHKW0LqDSGGwyFFmaOBMLGRv57uM4+moSWNONvuF7RrC8vhsBTBSEJwBEdfXUXRfux+RADIiJM4OSIIQVs8sHUmOiKlBSgd8pGS/2M8zrhutkemY2XyZ7TuGeKlBXHAJ4qN1q9mP+4ofs5WTXq5agipkqePzYLP7kBkiioIQIRrAifouSggCOsN0IaIQxyorJiOrb4uy5StrZFG2WVnTF8QZkAh5NjW4ebbKxqPo1pPdk2paQ7xHaGsAWMTMreXW7ibTUYum3DxtXBcQHRA5H6PmVjbqpKxLCEE9FUliEP49Uc9FNjGIBIdCwHSnFm8cwKQg9PqPXxgEr3ZkB4zp+tgBxNSWif7cDAJx3542nLa4+NrtkugAItefR3TxN4x2tthN1uPQ6G7jP01W2dyxszu7BXELAkIQk7Gh433I0ZaJ6fZZi8RtCEiPagN/aHS4hNSN2rAhwpch1qLasCEEX4JYCuIy4lbfXn+M36ycGHQMEby02K0aG27QbSbjadM7Bzfzhe0YAtYg6oAASGU7iLi8IaoD9OlAJBAUog5AkoHI1cBQHVEQu3nabyacNlsi2W6ebdkhRBe6QtQBIjKxDV4rzB0NtE4goiSFSI131SBf9ChEP+qKwV3IIF+9GWgsIRUHPNFDPUC8oJItDq7iePvX7U0hgmokIALt7b9L9yAEFENo6YwIL34TIk2IQPu796ulOrXC3q/29pethhDvoYV3PBzU3GoIEVRypr73W/TmcysaUjiqe7+hVCBE6OwIkPrfLTqvzxEpHApRSSojEiDBcXv/2qPv2kPoURQcNYhKBH3XHmJQFhw1COSsTAMixUEddQhcKSH6rj3EAlDHBaSUpDEiBCqOS0iQ0KPbeppa6riGBAn6rD3kynENKSWCfmsP4YXjGlJIHoYcs4t+ekQUARF1XENUQjyUrEcXrYmIYiDquIao5Fd797rcJgxEAbjX1UogBBRfErupm16mff8XbO2RWYuDqyCBx+30/EiMmTb6ugtSgYwtJcW2Q0hbUSSJkJ04ECJZO0rJRkE2hJkF4l4EYUMp0Qh5IswMEEPGvQDClAZ5RoglzAwQJpFch3DqRLK/WWsZEslVCFMqRE+4H5ILCSWWDf+OYQu0hDhGCDNh8iEFhRIbvhCCS5HYEiG1IUguRIZn+Cok55crqwm3R/IgRlrq/J3ZXLZW1u2exwnPCmRDROJj4PGB5Io8T3i+LA/CNJQUXFBRyf4syGEM0hFkvoqIpCJyFhxzttaeILNBRGKJ2BEVFsbupktcMwZpHEFyIc7RQGL8Y4GOxZHUW7KIjy3k8yE4NsvGT/bE4kjuraoFBS7kF4GQXR/fNcaRYcqGbJSKH+05EBwbnoUr3EVToxUGF/JLVETmePcCdco0ggv5hSDM67Wxds1MEEcTs79VaxkcWsU+6yg7sbVwIZ8PKQhi5UU2hFmpCeffnIpMg0z9+bYch9SWJEtBmC3z6UtuRar1D30lP9YVQXIgfPXNEcfEK9mdL0ezC868jS+KSJaoiBDYMmX1Fu/9iJvgiNeN9z06yoLEHb4iZPN6a3UugWpacbSstN98pvkgPPqWh4gkBdKVZ4falgKpWbXavzzQghXBhzNRPqWxjo1UbwXCx+1GmmshCD4uC39gSmP5MdccQI46aa5lWotDDUqcm9pYYxClW2muBSoy/kh5Ukm+6VO++Hm8d/jjpf3p9ydD4g6BiGQ6pPPnWqmCHOyyQ3UzQRi3QghKOAHSBPPIEhBLkqIigIhkIoR3/ajbAeTpEvKFZ28tcQhEJNMghWbuIY0uw5m91rqHMAslB1KEDoSgxMV/sOmUuoAoHa61tuoColTH+RDH6BBIIBayiToeVAhRtRaHLlUIUQ8mG2LAARCQRHuLH9UAIlO5nwp1K5CjhLMh4AAISKIV2akAwuW5DnLyrTmAqEMuhMEBEJDEIPsygPgRw7cA0la5FQEHQFDCsStyoxBuL9cqTQhRXT5EHAhBSfy0ZcsBpJHp4wgYh9Q2C+IcOBCCEhO/iyCQmi8ndN2vHEOI+pAFMehACEpc/MqiQJp2DNI2A4jNg6ADISjh+JVFgWjlcxKx8tEDSJcFYXQgBCU2slgEiPzPkHvWrBCDDoSghJMgzWnzGuSQBbEVRSDTJXoA2fbNFKzn6wFklQMxa0qBUGGi04hAlEBgc67WKigNQuYFV60bfcpOha3V9mtgfUrjPZxVkVQIT3xkQ2b2VqvRVHRzSHRGtFuFkUVWG7k2nwLhFEj8SvbTHxyqHd27o2UrktZbrhtxlP0/fqMgmyILYigJEl/JM0iai3bbNuBgyoLwUhByq1JdpNXBZv3UBpvPUo+bV4QpkvWu7BkffsAh8bWntN2B6I4hRHa1b5ibx9WmYK/qx87UrR6ZWW+0JclNWivpSjaz75oPD+L4+sFPxzIJ3q4i+Xepv5yXJKuCIFkQHAtLrP9yTpEKkXBX6YfDLsJIgcBf+bG8mo9YstTMD4HD7JO6mk80iKGFkt9aUyD3VRForSkQlyiZHYJn0EmQxN5aAIIjmQi5n4oMIX9tRUaer66vZkunfCafe4LISKAU8cLcU2tV3Odj+cJ85z7mBpL/H+x41/kPubf8Mx+s++od/RN58+98iPbb13//x2i/ev/67S8YXTU9MiU8PgAAAABJRU5ErkJggg==",
+ "scada": true,
+ "description": "Bundle with high-performance SCADA symbols for oil and gas system",
+ "order": 9405,
+ "name": "High-performance SCADA oil & gas"
+ },
+ "widgetTypeFqns": [
+ "hp_drilling_rig",
+ "hp_hook",
+ "hp_rotor",
+ "hp_preventer",
+ "hp_drill",
+ "hp_drilling_line",
+ "hp_platform"
+ ]
+}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_bundles/scada_fluid_system.json b/application/src/main/data/json/system/widget_bundles/scada_fluid_system.json
index 9aa3b299a8..e6f1e6a876 100644
--- a/application/src/main/data/json/system/widget_bundles/scada_fluid_system.json
+++ b/application/src/main/data/json/system/widget_bundles/scada_fluid_system.json
@@ -42,6 +42,10 @@
"vertical_inline_flow_meter",
"left_analog_water_level_meter",
"right_analog_water_level_meter",
+ "meter",
+ "small_meter",
+ "small_right_meter",
+ "small_left_meter",
"leak_sensor",
"centrifugal_pump",
"small_right_motor_pump",
diff --git a/application/src/main/data/json/system/widget_types/update_device_attribute.json b/application/src/main/data/json/system/widget_types/update_device_attribute.json
index 92111daf54..763ba93bc9 100644
--- a/application/src/main/data/json/system/widget_types/update_device_attribute.json
+++ b/application/src/main/data/json/system/widget_types/update_device_attribute.json
@@ -9,7 +9,7 @@
"sizeX": 4,
"sizeY": 2,
"resources": [],
- "templateHtml": "
",
+ "templateHtml": "",
"templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-mdc-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}",
"controllerScript": "self.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n let entityAttributeType = self.ctx.settings.entityAttributeType;\n let entityParameters = JSON.parse(self.ctx.settings.entityParameters);\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton\n .bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n let attributeService = self.ctx.$scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n\n self.ctx.$scope.sendUpdate = function() {\n let attributes = [];\n for (let key in entityParameters) {\n attributes.push({\n \"key\": key,\n \"value\": entityParameters[key]\n });\n }\n \n let entityId = {\n entityType: \"DEVICE\",\n id: self.ctx.defaultSubscription.targetDeviceId\n };\n attributeService.saveEntityAttributes(entityId,\n entityAttributeType, attributes).subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n\n );\n };\n}\n",
"settingsSchema": "",
diff --git a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json
index bce88d62b0..325e01003f 100644
--- a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json
+++ b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json
@@ -19,8 +19,13 @@
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries",
+ "configurationVersion": 1,
"configuration": {
- "defaultTTL": 0
+ "defaultTTL": 0,
+ "useServerTs": false,
+ "persistenceSettings": {
+ "type": "ON_EVERY_MESSAGE"
+ }
}
},
{
@@ -134,4 +139,4 @@
],
"ruleChainConnections": null
}
-}
\ No newline at end of file
+}
diff --git a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
index ee38849c1b..4dc202d740 100644
--- a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
+++ b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
@@ -18,8 +18,13 @@
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries",
+ "configurationVersion": 1,
"configuration": {
- "defaultTTL": 0
+ "defaultTTL": 0,
+ "useServerTs": false,
+ "persistenceSettings": {
+ "type": "ON_EVERY_MESSAGE"
+ }
}
},
{
diff --git a/application/src/main/data/upgrade/basic/schema_update.sql b/application/src/main/data/upgrade/basic/schema_update.sql
index 6b87dc6dde..5d66056701 100644
--- a/application/src/main/data/upgrade/basic/schema_update.sql
+++ b/application/src/main/data/upgrade/basic/schema_update.sql
@@ -14,3 +14,50 @@
-- limitations under the License.
--
+-- UPDATE SAVE TIME SERIES NODES START
+
+DO $$
+ BEGIN
+ -- Check if the rule_node table exists
+ IF EXISTS (
+ SELECT 1
+ FROM information_schema.tables
+ WHERE table_name = 'rule_node'
+ ) THEN
+
+ UPDATE rule_node
+ SET configuration = (
+ (configuration::jsonb - 'skipLatestPersistence')
+ || jsonb_build_object(
+ 'persistenceSettings', jsonb_build_object(
+ 'type', 'ADVANCED',
+ 'timeseries', jsonb_build_object('type', 'ON_EVERY_MESSAGE'),
+ 'latest', jsonb_build_object('type', 'SKIP'),
+ 'webSockets', jsonb_build_object('type', 'ON_EVERY_MESSAGE')
+ )
+ )
+ )::text,
+ configuration_version = 1
+ WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode'
+ AND configuration_version = 0
+ AND configuration::jsonb ->> 'skipLatestPersistence' = 'true';
+
+ UPDATE rule_node
+ SET configuration = (
+ (configuration::jsonb - 'skipLatestPersistence')
+ || jsonb_build_object(
+ 'persistenceSettings', jsonb_build_object(
+ 'type', 'ON_EVERY_MESSAGE'
+ )
+ )
+ )::text,
+ configuration_version = 1
+ WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode'
+ AND configuration_version = 0
+ AND (configuration::jsonb ->> 'skipLatestPersistence' != 'true' OR configuration::jsonb ->> 'skipLatestPersistence' IS NULL);
+
+ END IF;
+ END;
+$$;
+
+-- UPDATE SAVE TIME SERIES NODES END
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index 0070eba4a1..c3c524b2bb 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
@@ -173,7 +173,10 @@ public class DefaultTbContext implements TbContext {
if (!msg.isValid()) {
return;
}
- TbMsg tbMsg = msg.copyWithRuleChainId(ruleChainId);
+ TbMsg tbMsg = msg.copy()
+ .ruleChainId(ruleChainId)
+ .resetRuleNodeId()
+ .build();
tbMsg.pushToStack(nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId());
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getQueueName(), getTenantId(), tbMsg.getOriginator());
doEnqueue(tpi, tbMsg, new SimpleTbQueueCallback(md -> ack(msg), t -> tellFailure(msg, t)));
@@ -361,19 +364,28 @@ public class DefaultTbContext implements TbContext {
nodeCtx.setSelf(self);
}
- @Override
- public TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data) {
- return newMsg(queueName, type, originator, null, metaData, data);
- }
-
@Override
public TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) {
- return TbMsg.newMsg(queueName, type, originator, customerId, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId());
+ return TbMsg.newMsg()
+ .queueName(queueName)
+ .type(type)
+ .originator(originator)
+ .customerId(customerId)
+ .copyMetaData(metaData)
+ .data(data)
+ .ruleChainId(nodeCtx.getSelf().getRuleChainId())
+ .ruleNodeId(nodeCtx.getSelf().getId())
+ .build();
}
@Override
public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
- return TbMsg.transformMsg(origMsg, type, originator, metaData, data);
+ return origMsg.transform()
+ .type(type)
+ .originator(originator)
+ .metaData(metaData)
+ .data(data)
+ .build();
}
@Override
@@ -383,22 +395,41 @@ public class DefaultTbContext implements TbContext {
@Override
public TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) {
- return TbMsg.newMsg(queueName, type, originator, customerId, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId());
+ return TbMsg.newMsg()
+ .queueName(queueName)
+ .type(type)
+ .originator(originator)
+ .customerId(customerId)
+ .copyMetaData(metaData)
+ .data(data)
+ .ruleChainId(nodeCtx.getSelf().getRuleChainId())
+ .ruleNodeId(nodeCtx.getSelf().getId())
+ .build();
}
@Override
public TbMsg transformMsg(TbMsg origMsg, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) {
- return TbMsg.transformMsg(origMsg, type, originator, metaData, data);
+ return origMsg.transform()
+ .type(type)
+ .originator(originator)
+ .metaData(metaData)
+ .data(data)
+ .build();
}
@Override
public TbMsg transformMsg(TbMsg origMsg, TbMsgMetaData metaData, String data) {
- return TbMsg.transformMsg(origMsg, metaData, data);
+ return origMsg.transform()
+ .metaData(metaData)
+ .data(data)
+ .build();
}
@Override
public TbMsg transformMsgOriginator(TbMsg origMsg, EntityId originator) {
- return TbMsg.transformMsgOriginator(origMsg, originator);
+ return origMsg.transform()
+ .originator(originator)
+ .build();
}
@Override
@@ -497,7 +528,14 @@ public class DefaultTbContext implements TbContext {
defaultQueueName = profile.getDefaultQueueName();
defaultRuleChainId = profile.getDefaultRuleChainId();
}
- return TbMsg.newMsg(defaultQueueName, action, id, msgMetaData, msgData, defaultRuleChainId, null);
+ return TbMsg.newMsg()
+ .queueName(defaultQueueName)
+ .type(action)
+ .originator(id)
+ .copyMetaData(msgMetaData)
+ .data(msgData)
+ .ruleChainId(defaultRuleChainId)
+ .build();
}
public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, TbMsgType actionMsgType, K profile) {
@@ -515,7 +553,14 @@ public class DefaultTbContext implements TbContext {
defaultQueueName = profile.getDefaultQueueName();
defaultRuleChainId = profile.getDefaultRuleChainId();
}
- return TbMsg.newMsg(defaultQueueName, actionMsgType, id, msgMetaData, msgData, defaultRuleChainId, null);
+ return TbMsg.newMsg()
+ .queueName(defaultQueueName)
+ .type(actionMsgType)
+ .originator(id)
+ .copyMetaData(msgMetaData)
+ .data(msgData)
+ .ruleChainId(defaultRuleChainId)
+ .build();
}
@Override
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
index 460da228c3..be0812795d 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
@@ -16,6 +16,7 @@
package org.thingsboard.server.actors.ruleChain;
import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.common.util.DebugModeUtil;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorRef;
@@ -35,7 +36,6 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
-import org.thingsboard.common.util.DebugModeUtil;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.plugin.RuleNodeUpdatedMsg;
@@ -217,7 +217,10 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor().addProperty("refreshToken", new Schema<>().type("string")))));
+
+ operation.requestBody(requestBody);
+
+ operation.responses(loginResponses);
+
+ operation.addTagsItem("login-endpoint");
+ var pathItem = new PathItem().post(operation);
+ openAPI.path(REFRESH_TOKEN_ENDPOINT, pathItem);
+ }
+
@Bean
public GroupedOpenApi groupedApi(SpringDocParameterNameDiscoverer localSpringDocParameterNameDiscoverer) {
return GroupedOpenApi.builder()
diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
index 67ff1b3fc5..e8c53ff5a3 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
@@ -239,7 +239,12 @@ public class RpcV2Controller extends AbstractRpcController {
rpcService.deleteRpc(getTenantId(), rpcId);
rpc.setStatus(RpcStatus.DELETED);
- TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_DELETED, rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc));
+ TbMsg msg = TbMsg.newMsg()
+ .type(TbMsgType.RPC_DELETED)
+ .originator(rpc.getDeviceId())
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(JacksonUtil.toString(rpc))
+ .build();
tbClusterService.pushMsgToRuleEngine(getTenantId(), rpc.getDeviceId(), msg, null);
}
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
index 63a435e6f1..d8d1a83998 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -384,7 +384,12 @@ public class RuleChainController extends BaseController {
}
engine = new RuleNodeTbelScriptEngine(getTenantId(), tbelInvokeService, script, argNames);
}
- TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data);
+ TbMsg inMsg = TbMsg.newMsg()
+ .type(msgType)
+ .copyMetaData(new TbMsgMetaData(metadata))
+ .dataType(TbMsgDataType.JSON)
+ .data(data)
+ .build();
switch (scriptType) {
case "update":
output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleEngineController.java b/application/src/main/java/org/thingsboard/server/controller/RuleEngineController.java
index 8cda3d2212..f1a272ac14 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleEngineController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleEngineController.java
@@ -169,7 +169,14 @@ public class RuleEngineController extends BaseController {
metaData.put("serviceId", serviceInfoProvider.getServiceId());
metaData.put("requestUUID", requestId.toString());
metaData.put("expirationTime", Long.toString(expTime));
- TbMsg msg = TbMsg.newMsg(queueName, TbMsgType.REST_API_REQUEST, entityId, currentUser.getCustomerId(), new TbMsgMetaData(metaData), requestBody);
+ TbMsg msg = TbMsg.newMsg()
+ .queueName(queueName)
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(entityId)
+ .customerId(currentUser.getCustomerId())
+ .copyMetaData(new TbMsgMetaData(metaData))
+ .data(requestBody)
+ .build();
ruleEngineCallService.processRestApiCallToRuleEngine(currentUser.getTenantId(), requestId, msg, queueName != null,
reply -> reply(new LocalRequestMetaData(msg, currentUser, result), reply));
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
index b1733a9af0..fa69c99245 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
@@ -47,6 +47,10 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
+import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
+import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType;
@@ -399,7 +403,7 @@ public class TelemetryController extends BaseController {
public DeferredResult saveEntityAttributesV2(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
- @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope")AttributeScope scope,
+ @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope") AttributeScope scope,
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(getTenantId(), entityId, scope, request);
@@ -423,8 +427,8 @@ public class TelemetryController extends BaseController {
public DeferredResult saveEntityTelemetry(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
- @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope,
- @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException {
+ @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope") String scope,
+ @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, 0L);
}
@@ -447,9 +451,9 @@ public class TelemetryController extends BaseController {
public DeferredResult saveEntityTelemetryWithTTL(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
- @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope,
- @Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true)@PathVariable("ttl")Long ttl,
- @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException {
+ @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope") String scope,
+ @Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true) @PathVariable("ttl") Long ttl,
+ @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, ttl);
}
@@ -518,19 +522,25 @@ public class TelemetryController extends BaseController {
for (String key : keys) {
deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted, deleteLatest));
}
- tsSubService.deleteTimeseriesAndNotify(tenantId, entityId, keys, deleteTsKvQueries, new FutureCallback<>() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null);
- result.setResult(new ResponseEntity<>(HttpStatus.OK));
- }
-
- @Override
- public void onFailure(Throwable t) {
- logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, t);
- result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
- }
- });
+ tsSubService.deleteTimeseries(TimeseriesDeleteRequest.builder()
+ .tenantId(tenantId)
+ .entityId(entityId)
+ .keys(keys)
+ .deleteHistoryQueries(deleteTsKvQueries)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable List tmp) {
+ logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null);
+ result.setResult(new ResponseEntity<>(HttpStatus.OK));
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, t);
+ result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
+ }
+ })
+ .build());
});
}
@@ -550,8 +560,8 @@ public class TelemetryController extends BaseController {
@ResponseBody
public DeferredResult deleteDeviceAttributes(
@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr,
- @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope")AttributeScope scope,
- @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException {
+ @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope") AttributeScope scope,
+ @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
return deleteAttributes(entityId, scope, keysStr);
}
@@ -573,8 +583,8 @@ public class TelemetryController extends BaseController {
public DeferredResult deleteEntityAttributes(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
- @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope")AttributeScope scope,
- @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException {
+ @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope") AttributeScope scope,
+ @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return deleteAttributes(entityId, scope, keysStr);
}
@@ -587,24 +597,30 @@ public class TelemetryController extends BaseController {
SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> {
- tsSubService.deleteAndNotify(tenantId, entityId, scope.name(), keys, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- logAttributesDeleted(user, entityId, scope, keys, null);
- if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) {
- DeviceId deviceId = new DeviceId(entityId.getId());
- tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(
- user.getTenantId(), deviceId, scope.name(), keys), null);
- }
- result.setResult(new ResponseEntity<>(HttpStatus.OK));
- }
-
- @Override
- public void onFailure(Throwable t) {
- logAttributesDeleted(user, entityId, scope, keys, t);
- result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
- }
- });
+ tsSubService.deleteAttributes(AttributesDeleteRequest.builder()
+ .tenantId(tenantId)
+ .entityId(entityId)
+ .scope(scope)
+ .keys(keys)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ logAttributesDeleted(user, entityId, scope, keys, null);
+ if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) {
+ DeviceId deviceId = new DeviceId(entityId.getId());
+ tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(
+ user.getTenantId(), deviceId, scope.name(), keys), null);
+ }
+ result.setResult(new ResponseEntity<>(HttpStatus.OK));
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ logAttributesDeleted(user, entityId, scope, keys, t);
+ result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
+ }
+ })
+ .build());
});
}
@@ -624,19 +640,25 @@ public class TelemetryController extends BaseController {
}
SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> {
- tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- logAttributesUpdated(user, entityId, scope, attributes, null);
- result.setResult(new ResponseEntity(HttpStatus.OK));
- }
-
- @Override
- public void onFailure(Throwable t) {
- logAttributesUpdated(user, entityId, scope, attributes, t);
- AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
- }
- });
+ tsSubService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(entityId)
+ .scope(scope)
+ .entries(attributes)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ logAttributesUpdated(user, entityId, scope, attributes, null);
+ result.setResult(new ResponseEntity(HttpStatus.OK));
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ logAttributesUpdated(user, entityId, scope, attributes, t);
+ AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+ })
+ .build());
});
} else {
return getImmediateDeferredResult("Request is not a JSON object", HttpStatus.BAD_REQUEST);
@@ -672,19 +694,26 @@ public class TelemetryController extends BaseController {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays());
}
- tsSubService.saveAndNotify(tenantId, user.getCustomerId(), entityId, entries, tenantTtl, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- logTelemetryUpdated(user, entityId, entries, null);
- result.setResult(new ResponseEntity(HttpStatus.OK));
- }
-
- @Override
- public void onFailure(Throwable t) {
- logTelemetryUpdated(user, entityId, entries, t);
- AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
- }
- });
+ tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .customerId(user.getCustomerId())
+ .entityId(entityId)
+ .entries(entries)
+ .ttl(tenantTtl)
+ .callback(new FutureCallback() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ logTelemetryUpdated(user, entityId, entries, null);
+ result.setResult(new ResponseEntity(HttpStatus.OK));
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ logTelemetryUpdated(user, entityId, entries, t);
+ AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+ })
+ .build());
});
}
diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
index ab6d32fb44..868adf645a 100644
--- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
@@ -172,7 +172,14 @@ public class EntityActionService {
if (tenantId != null && !tenantId.isSysTenantId()) {
processNotificationRules(tenantId, entityId, entity, actionType, user, additionalInfo);
}
- TbMsg tbMsg = TbMsg.newMsg(msgType.get(), entityId, customerId, metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode));
+ TbMsg tbMsg = TbMsg.newMsg()
+ .type(msgType.get())
+ .originator(entityId)
+ .customerId(customerId)
+ .copyMetaData(metaData)
+ .dataType(TbMsgDataType.JSON)
+ .data(JacksonUtil.toString(entityNode))
+ .build();
tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null);
} catch (Exception e) {
log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e);
diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
index 60bf529ace..3a351528e4 100644
--- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
@@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.MailService;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageRecordState;
@@ -91,9 +92,9 @@ import java.util.stream.Collectors;
public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService implements TbApiUsageStateService {
public static final String HOURLY = "Hourly";
- public static final FutureCallback VOID_CALLBACK = new FutureCallback() {
+ public static final FutureCallback VOID_CALLBACK = new FutureCallback() {
@Override
- public void onSuccess(@Nullable Integer result) {
+ public void onSuccess(@Nullable Void result) {
}
@Override
@@ -214,7 +215,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
updateLock.unlock();
}
log.trace("[{}][{}] Saving new stats: {}", tenantId, ownerId, updatedEntries);
- tsWsService.saveAndNotifyInternal(tenantId, usageState.getApiUsageState().getId(), updatedEntries, VOID_CALLBACK);
+ tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(usageState.getApiUsageState().getId())
+ .entries(updatedEntries)
+ .callback(VOID_CALLBACK)
+ .build());
if (!result.isEmpty()) {
persistAndNotify(usageState, result);
}
@@ -321,7 +327,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
}
}
if (!profileThresholds.isEmpty()) {
- tsWsService.saveAndNotifyInternal(tenantId, id, profileThresholds, VOID_CALLBACK);
+ tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(id)
+ .entries(profileThresholds)
+ .callback(VOID_CALLBACK)
+ .build());
}
}
@@ -348,7 +359,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
long ts = System.currentTimeMillis();
List stateTelemetry = new ArrayList<>();
result.forEach((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))));
- tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK);
+ tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
+ .tenantId(state.getTenantId())
+ .entityId(state.getApiUsageState().getId())
+ .entries(stateTelemetry)
+ .callback(VOID_CALLBACK)
+ .build());
if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) {
String email = tenantService.findTenantById(state.getTenantId()).getEmail();
@@ -436,7 +452,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
.map(key -> new BasicTsKvEntry(state.getCurrentCycleTs(), new LongDataEntry(key.getApiCountKey(), 0L)))
.collect(Collectors.toList());
- tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), counts, VOID_CALLBACK);
+ tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
+ .tenantId(state.getTenantId())
+ .entityId(state.getApiUsageState().getId())
+ .entries(counts)
+ .callback(VOID_CALLBACK)
+ .build());
}
BaseApiUsageState getOrFetchState(TenantId tenantId, EntityId ownerId) {
diff --git a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
index a77b7d0874..f82f4c74d5 100644
--- a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
+++ b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
@@ -28,6 +28,8 @@ import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer;
@@ -37,7 +39,6 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
-import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
@@ -178,11 +179,12 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
return Futures.immediateFuture(new ReclaimResult(unassignedCustomer));
}
SettableFuture result = SettableFuture.create();
- telemetryService.saveAndNotify(
- tenantId, savedDevice.getId(), AttributeScope.SERVER_SCOPE, List.of(
- new BaseAttributeKvEntry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true), System.currentTimeMillis())
- ),
- new FutureCallback<>() {
+ telemetryService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(savedDevice.getId())
+ .scope(AttributeScope.SERVER_SCOPE)
+ .entry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true))
+ .callback(new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
result.set(new ReclaimResult(unassignedCustomer));
@@ -192,7 +194,8 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
public void onFailure(Throwable t) {
result.setException(t);
}
- });
+ })
+ .build());
return result;
}
cacheEviction(device.getId());
@@ -221,18 +224,13 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
cache.evict(data.getKey());
}
SettableFuture result = SettableFuture.create();
- telemetryService.deleteAndNotify(device.getTenantId(),
- device.getId(), AttributeScope.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME), new FutureCallback<>() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- result.set(tmp);
- }
-
- @Override
- public void onFailure(Throwable t) {
- result.setException(t);
- }
- });
+ telemetryService.deleteAttributes(AttributesDeleteRequest.builder()
+ .tenantId(device.getTenantId())
+ .entityId(device.getId())
+ .scope(AttributeScope.SERVER_SCOPE)
+ .keys(Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME))
+ .future(result)
+ .build());
return result;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java
index f5b6411bf7..0fddcff793 100644
--- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java
+++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java
@@ -253,7 +253,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, TbMsgType type) {
try {
JsonNode entityNode = JacksonUtil.valueToTree(request);
- TbMsg msg = TbMsg.newMsg(type, device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.toString(entityNode));
+ TbMsg msg = TbMsg.newMsg()
+ .type(type)
+ .originator(device.getId())
+ .customerId(device.getCustomerId())
+ .copyMetaData(createTbMsgMetaData(device))
+ .data(JacksonUtil.toString(entityNode))
+ .build();
sendToRuleEngine(device.getTenantId(), msg, null);
} catch (IllegalArgumentException e) {
log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e);
@@ -263,7 +269,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
private void pushDeviceCreatedEventToRuleEngine(Device device) {
try {
ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device);
- TbMsg msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode));
+ TbMsg msg = TbMsg.newMsg()
+ .type(TbMsgType.ENTITY_CREATED)
+ .originator(device.getId())
+ .customerId(device.getCustomerId())
+ .copyMetaData(createTbMsgMetaData(device))
+ .data(JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode))
+ .build();
sendToRuleEngine(device.getTenantId(), msg, null);
} catch (JsonProcessingException | IllegalArgumentException e) {
log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), TbMsgType.ENTITY_CREATED.name(), e);
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
index 73a504245d..7c958343ca 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
@@ -32,6 +32,8 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope;
@@ -41,7 +43,6 @@ import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.msg.TbMsgType;
@@ -68,7 +69,6 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.io.IOException;
import java.io.InputStream;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@@ -506,24 +506,40 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
private void save(TenantId tenantId, EdgeId edgeId, String key, long value) {
log.debug("[{}][{}] Updating long edge telemetry [{}] [{}]", tenantId, edgeId, key, value);
if (persistToTelemetry) {
- tsSubService.saveAndNotify(
- tenantId, edgeId,
- Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))),
- new AttributeSaveCallback(tenantId, edgeId, key, value));
+ tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(edgeId)
+ .entry(new LongDataEntry(key, value))
+ .callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
+ .build());
} else {
- tsSubService.saveAttrAndNotify(tenantId, edgeId, AttributeScope.SERVER_SCOPE, key, value, new AttributeSaveCallback(tenantId, edgeId, key, value));
+ tsSubService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(edgeId)
+ .scope(AttributeScope.SERVER_SCOPE)
+ .entry(new LongDataEntry(key, value))
+ .callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
+ .build());
}
}
private void save(TenantId tenantId, EdgeId edgeId, String key, boolean value) {
log.debug("[{}][{}] Updating boolean edge telemetry [{}] [{}]", tenantId, edgeId, key, value);
if (persistToTelemetry) {
- tsSubService.saveAndNotify(
- tenantId, edgeId,
- Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))),
- new AttributeSaveCallback(tenantId, edgeId, key, value));
+ tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(edgeId)
+ .entry(new BooleanDataEntry(key, value))
+ .callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
+ .build());
} else {
- tsSubService.saveAttrAndNotify(tenantId, edgeId, AttributeScope.SERVER_SCOPE, key, value, new AttributeSaveCallback(tenantId, edgeId, key, value));
+ tsSubService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(edgeId)
+ .scope(AttributeScope.SERVER_SCOPE)
+ .entry(new BooleanDataEntry(key, value))
+ .callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
+ .build());
}
}
@@ -578,7 +594,13 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
md.putValue("edgeName", edge.getName());
md.putValue("edgeType", edge.getType());
}
- TbMsg tbMsg = TbMsg.newMsg(msgType, edgeId, md, TbMsgDataType.JSON, data);
+ TbMsg tbMsg = TbMsg.newMsg()
+ .type(msgType)
+ .originator(edgeId)
+ .copyMetaData(md)
+ .dataType(TbMsgDataType.JSON)
+ .data(data)
+ .build();
clusterService.pushMsgToRuleEngine(tenantId, edgeId, tbMsg, null);
} catch (Exception e) {
log.warn("[{}][{}] Failed to push {}", tenantId, edge.getId(), msgType, e);
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
index e448fbd937..eaf3c1a95a 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
@@ -343,7 +343,14 @@ public abstract class BaseEdgeProcessor {
protected void pushEntityEventToRuleEngine(TenantId tenantId, EntityId entityId, CustomerId customerId,
TbMsgType msgType, String msgData, TbMsgMetaData metaData) {
- TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, msgData);
+ TbMsg tbMsg = TbMsg.newMsg()
+ .type(msgType)
+ .originator(entityId)
+ .customerId(customerId)
+ .copyMetaData(metaData)
+ .dataType(TbMsgDataType.JSON)
+ .data(msgData)
+ .build();
edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, entityId, tbMsg, new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java
index e453cbc26f..c4a57e4a74 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java
@@ -190,8 +190,13 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements
ObjectNode data = JacksonUtil.newObjectNode();
data.put("method", deviceRpcCallMsg.getRequestMsg().getMethod());
data.put("params", deviceRpcCallMsg.getRequestMsg().getParams());
- TbMsg tbMsg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST, deviceId, null, metaData,
- TbMsgDataType.JSON, JacksonUtil.toString(data));
+ TbMsg tbMsg = TbMsg.newMsg()
+ .type(TbMsgType.TO_SERVER_RPC_REQUEST)
+ .originator(deviceId)
+ .copyMetaData(metaData)
+ .dataType(TbMsgDataType.JSON)
+ .data(JacksonUtil.toString(data))
+ .build();
edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
index d94d4d8939..488fc3d8b9 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
@@ -31,6 +31,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants;
@@ -208,7 +209,15 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList());
metaData.putValue("ts", tsKv.getTs() + "");
var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId);
- TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.POST_TELEMETRY_REQUEST, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null);
+ TbMsg tbMsg = TbMsg.newMsg()
+ .queueName(defaultQueueAndRuleChain.getKey())
+ .type(TbMsgType.POST_TELEMETRY_REQUEST)
+ .originator(entityId)
+ .customerId(customerId)
+ .copyMetaData(metaData)
+ .data(gson.toJson(json))
+ .ruleChainId(defaultQueueAndRuleChain.getValue())
+ .build();
edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
@@ -252,7 +261,15 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
SettableFuture futureToSet = SettableFuture.create();
JsonObject json = JsonUtils.getJsonObject(msg.getKvList());
var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId);
- TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null);
+ TbMsg tbMsg = TbMsg.newMsg()
+ .queueName(defaultQueueAndRuleChain.getKey())
+ .type(TbMsgType.POST_ATTRIBUTES_REQUEST)
+ .originator(entityId)
+ .customerId(customerId)
+ .copyMetaData(metaData)
+ .data(gson.toJson(json))
+ .ruleChainId(defaultQueueAndRuleChain.getValue())
+ .build();
edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
@@ -277,16 +294,36 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
JsonObject json = JsonUtils.getJsonObject(msg.getKvList());
List attributes = new ArrayList<>(JsonConverter.convertToAttributes(json));
String scope = metaData.getValue("scope");
- tsSubService.saveAndNotify(tenantId, entityId, AttributeScope.valueOf(scope), attributes, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId);
- TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.ATTRIBUTES_UPDATED, entityId,
- customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null);
- edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() {
+ tsSubService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(entityId)
+ .scope(AttributeScope.valueOf(scope))
+ .entries(attributes)
+ .callback(new FutureCallback<>() {
@Override
- public void onSuccess(TbQueueMsgMetadata metadata) {
- futureToSet.set(null);
+ public void onSuccess(@Nullable Void tmp) {
+ var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId);
+ TbMsg tbMsg = TbMsg.newMsg()
+ .queueName(defaultQueueAndRuleChain.getKey())
+ .type(TbMsgType.ATTRIBUTES_UPDATED)
+ .originator(entityId)
+ .customerId(customerId)
+ .copyMetaData(metaData)
+ .data(gson.toJson(json))
+ .ruleChainId(defaultQueueAndRuleChain.getValue())
+ .build();
+ edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() {
+ @Override
+ public void onSuccess(TbQueueMsgMetadata metadata) {
+ futureToSet.set(null);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t);
+ futureToSet.setException(t);
+ }
+ });
}
@Override
@@ -294,15 +331,8 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t);
futureToSet.setException(t);
}
- });
- }
-
- @Override
- public void onFailure(Throwable t) {
- log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t);
- futureToSet.setException(t);
- }
- });
+ })
+ .build());
return futureToSet;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
index bce599c0e3..80368c085f 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
@@ -250,8 +250,14 @@ public class EntityStateSourcingListener {
private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) {
String data = JacksonUtil.toString(JacksonUtil.valueToTree(assignedDevice));
if (data != null) {
- TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(),
- assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data);
+ TbMsg tbMsg = TbMsg.newMsg()
+ .type(TbMsgType.ENTITY_ASSIGNED_FROM_TENANT)
+ .originator(assignedDevice.getId())
+ .customerId(assignedDevice.getCustomerId())
+ .copyMetaData(getMetaDataForAssignedFrom(currentTenant))
+ .dataType(TbMsgDataType.JSON)
+ .data(data)
+ .build();
tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null);
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java
index 21eec71388..2adb6822dc 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.service.entitiy.dashboard;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
@@ -30,6 +31,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.service.install.ProjectInfo;
import org.thingsboard.server.service.sync.GitSyncService;
import org.thingsboard.server.service.sync.vc.GitRepository.FileType;
import org.thingsboard.server.service.sync.vc.GitRepository.RepoFile;
@@ -51,10 +53,11 @@ public class DashboardSyncService {
private final ImageService imageService;
private final WidgetsBundleService widgetsBundleService;
private final PartitionService partitionService;
+ private final ProjectInfo projectInfo;
@Value("${transport.gateway.dashboard.sync.repository_url:}")
private String repoUrl;
- @Value("${transport.gateway.dashboard.sync.branch:main}")
+ @Value("${transport.gateway.dashboard.sync.branch:}")
private String branch;
@Value("${transport.gateway.dashboard.sync.fetch_frequency:24}")
private int fetchFrequencyHours;
@@ -64,6 +67,9 @@ public class DashboardSyncService {
@AfterStartUp(order = AfterStartUp.REGULAR_SERVICE)
public void init() throws Exception {
+ if (StringUtils.isBlank(branch)) {
+ branch = "release/" + projectInfo.getProjectVersion();
+ }
gitSyncService.registerSync(REPO_KEY, repoUrl, branch, TimeUnit.HOURS.toMillis(fetchFrequencyHours), this::update);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java
index 82877283cd..2c384aa493 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java
@@ -25,6 +25,10 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ConcurrentReferenceHashMap;
+import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
+import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
@@ -55,6 +59,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
@@ -273,36 +278,41 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
return Futures.transform(getAttrFuture, attributeKvEntries -> {
List attributes;
if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) {
- attributes =
- attributeKvEntries.stream()
- .filter(attributeKvEntry -> {
- long startTime = entityView.getStartTimeMs();
- long endTime = entityView.getEndTimeMs();
- long lastUpdateTs = attributeKvEntry.getLastUpdateTs();
- return startTime == 0 && endTime == 0 ||
- (endTime == 0 && startTime < lastUpdateTs) ||
- (startTime == 0 && endTime > lastUpdateTs) ||
- (startTime < lastUpdateTs && endTime > lastUpdateTs);
- }).collect(Collectors.toList());
- tsSubService.saveAndNotify(entityView.getTenantId(), entityId, scope, attributes, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- try {
- logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, null);
- } catch (ThingsboardException e) {
- log.error("Failed to log attribute updates", e);
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- try {
- logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, t);
- } catch (ThingsboardException e) {
- log.error("Failed to log attribute updates", e);
- }
- }
- });
+ attributes = attributeKvEntries.stream()
+ .filter(attributeKvEntry -> {
+ long startTime = entityView.getStartTimeMs();
+ long endTime = entityView.getEndTimeMs();
+ long lastUpdateTs = attributeKvEntry.getLastUpdateTs();
+ return startTime == 0 && endTime == 0 ||
+ (endTime == 0 && startTime < lastUpdateTs) ||
+ (startTime == 0 && endTime > lastUpdateTs) ||
+ (startTime < lastUpdateTs && endTime > lastUpdateTs);
+ }).collect(Collectors.toList());
+ tsSubService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(entityView.getTenantId())
+ .entityId(entityId)
+ .scope(scope)
+ .entries(attributes)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ try {
+ logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, null);
+ } catch (ThingsboardException e) {
+ log.error("Failed to log attribute updates", e);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ try {
+ logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, t);
+ } catch (ThingsboardException e) {
+ log.error("Failed to log attribute updates", e);
+ }
+ }
+ })
+ .build());
}
return null;
}, MoreExecutors.directExecutor());
@@ -334,15 +344,22 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
}, MoreExecutors.directExecutor());
return Futures.transform(latestFuture, latestValues -> {
if (latestValues != null && !latestValues.isEmpty()) {
- tsSubService.saveLatestAndNotify(entityView.getTenantId(), entityId, latestValues, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- }
+ tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(entityView.getTenantId())
+ .entityId(entityId)
+ .entries(latestValues)
+ .strategy(TimeseriesSaveRequest.Strategy.LATEST_AND_WS)
+ .callback(new FutureCallback() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ }
- @Override
- public void onFailure(Throwable t) {
- }
- });
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}][{}] Failed to save entity view latest timeseries: {}", tenantId, entityView.getId(), latestValues, t);
+ }
+ })
+ .build());
}
return null;
}, MoreExecutors.directExecutor());
@@ -352,27 +369,33 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
EntityViewId entityId = entityView.getId();
SettableFuture resultFuture = SettableFuture.create();
if (keys != null && !keys.isEmpty()) {
- tsSubService.deleteAndNotify(entityView.getTenantId(), entityId, scope, keys, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- try {
- logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, null);
- } catch (ThingsboardException e) {
- log.error("Failed to log attribute delete", e);
- }
- resultFuture.set(tmp);
- }
+ tsSubService.deleteAttributes(AttributesDeleteRequest.builder()
+ .tenantId(entityView.getTenantId())
+ .entityId(entityId)
+ .scope(scope)
+ .keys(keys)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ try {
+ logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, null);
+ } catch (ThingsboardException e) {
+ log.error("Failed to log attribute delete", e);
+ }
+ resultFuture.set(tmp);
+ }
- @Override
- public void onFailure(Throwable t) {
- try {
- logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, t);
- } catch (ThingsboardException e) {
- log.error("Failed to log attribute delete", e);
- }
- resultFuture.setException(t);
- }
- });
+ @Override
+ public void onFailure(Throwable t) {
+ try {
+ logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, t);
+ } catch (ThingsboardException e) {
+ log.error("Failed to log attribute delete", e);
+ }
+ resultFuture.setException(t);
+ }
+ })
+ .build());
} else {
resultFuture.set(null);
}
@@ -382,51 +405,32 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
private ListenableFuture deleteLatestFromEntityView(EntityView entityView, List keys, User user) {
EntityViewId entityId = entityView.getId();
SettableFuture resultFuture = SettableFuture.create();
- if (keys != null && !keys.isEmpty()) {
- tsSubService.deleteLatest(entityView.getTenantId(), entityId, keys, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- try {
- logTimeseriesDeleted(entityView.getTenantId(), user, entityId, keys, null);
- } catch (ThingsboardException e) {
- log.error("Failed to log timeseries delete", e);
- }
- resultFuture.set(tmp);
- }
-
- @Override
- public void onFailure(Throwable t) {
- try {
- logTimeseriesDeleted(entityView.getTenantId(), user, entityId, keys, t);
- } catch (ThingsboardException e) {
- log.error("Failed to log timeseries delete", e);
- }
- resultFuture.setException(t);
- }
- });
- } else {
- tsSubService.deleteAllLatest(entityView.getTenantId(), entityId, new FutureCallback>() {
- @Override
- public void onSuccess(@Nullable Collection keys) {
- try {
- logTimeseriesDeleted(entityView.getTenantId(), user, entityId, new ArrayList<>(keys), null);
- } catch (ThingsboardException e) {
- log.error("Failed to log timeseries delete", e);
+ tsSubService.deleteTimeseries(TimeseriesDeleteRequest.builder()
+ .tenantId(entityView.getTenantId())
+ .entityId(entityId)
+ .keys(keys)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable List result) {
+ try {
+ logTimeseriesDeleted(entityView.getTenantId(), user, entityId, result, null);
+ } catch (ThingsboardException e) {
+ log.error("Failed to log timeseries delete", e);
+ }
+ resultFuture.set(null);
}
- resultFuture.set(null);
- }
- @Override
- public void onFailure(Throwable t) {
- try {
- logTimeseriesDeleted(entityView.getTenantId(), user, entityId, Collections.emptyList(), t);
- } catch (ThingsboardException e) {
- log.error("Failed to log timeseries delete", e);
+ @Override
+ public void onFailure(Throwable t) {
+ try {
+ logTimeseriesDeleted(entityView.getTenantId(), user, entityId, Optional.ofNullable(keys).orElse(Collections.emptyList()), t);
+ } catch (ThingsboardException e) {
+ log.error("Failed to log timeseries delete", e);
+ }
+ resultFuture.setException(t);
}
- resultFuture.setException(t);
- }
- });
- }
+ })
+ .build());
return resultFuture;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java
index b414bfb13a..229e21a96d 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java
@@ -17,6 +17,8 @@ package org.thingsboard.server.service.install;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.info.BuildProperties;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -31,12 +33,11 @@ import java.util.List;
@RequiredArgsConstructor
public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSettingsService {
- private static final String CURRENT_PRODUCT = "CE";
// This list should include all versions which are compatible for the upgrade.
// The compatibility cycle usually breaks when we have some scripts written in Java that may not work after new release.
private static final List SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("3.9.0");
- private final BuildProperties buildProperties;
+ private final ProjectInfo projectInfo;
private final JdbcTemplate jdbcTemplate;
private String packageSchemaVersion;
@@ -50,8 +51,8 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti
}
String product = getProductFromDb();
- if (!CURRENT_PRODUCT.equals(product)) {
- onSchemaSettingsError(String.format("Upgrade failed: can't upgrade ThingsBoard %s database using ThingsBoard %s.", product, CURRENT_PRODUCT));
+ if (!projectInfo.getProductType().equals(product)) {
+ onSchemaSettingsError(String.format("Upgrade failed: can't upgrade ThingsBoard %s database using ThingsBoard %s.", product, projectInfo.getProductType()));
}
String dbSchemaVersion = getDbSchemaVersion();
@@ -70,7 +71,7 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti
public void createSchemaSettings() {
Long schemaVersion = getSchemaVersionFromDb();
if (schemaVersion == null) {
- jdbcTemplate.execute("INSERT INTO tb_schema_settings (schema_version, product) VALUES (" + getPackageSchemaVersionForDb() + ", '" + CURRENT_PRODUCT + "')");
+ jdbcTemplate.execute("INSERT INTO tb_schema_settings (schema_version, product) VALUES (" + getPackageSchemaVersionForDb() + ", '" + projectInfo.getProductType() + "')");
}
}
@@ -82,7 +83,7 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti
@Override
public String getPackageSchemaVersion() {
if (packageSchemaVersion == null) {
- packageSchemaVersion = buildProperties.getVersion().replaceAll("[^\\d.]", "");
+ packageSchemaVersion = projectInfo.getProjectVersion();
}
return packageSchemaVersion;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/install/ProjectInfo.java b/application/src/main/java/org/thingsboard/server/service/install/ProjectInfo.java
new file mode 100644
index 0000000000..eddd4c8ccf
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/install/ProjectInfo.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright © 2016-2024 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 lombok.RequiredArgsConstructor;
+import org.springframework.boot.info.BuildProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class ProjectInfo {
+
+ private final BuildProperties buildProperties;
+
+ public String getProjectVersion() {
+ return buildProperties.getVersion().replaceAll("[^\\d.]", "");
+ }
+
+ public String getProductType() {
+ return "CE";
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
index 54ca38ad78..3aedad3ddb 100644
--- a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
@@ -20,7 +20,10 @@ import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
+import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants;
@@ -54,7 +57,6 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -128,7 +130,7 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
// Device was updated and new firmware is different from previous firmware.
send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis(), FIRMWARE);
}
- } else if (oldFirmwareId != null){
+ } else if (oldFirmwareId != null) {
// Device was updated and new firmware is not set.
remove(device, FIRMWARE);
}
@@ -155,7 +157,7 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
// Device was updated and new firmware is different from previous firmware.
send(device.getTenantId(), device.getId(), newSoftwareId, System.currentTimeMillis(), SOFTWARE);
}
- } else if (oldSoftwareId != null){
+ } else if (oldSoftwareId != null) {
// Device was updated and new firmware is not set.
remove(device, SOFTWARE);
}
@@ -261,17 +263,22 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts)));
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name())));
- telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- log.trace("[{}] Success save firmware status!", deviceId);
- }
+ telemetryService.saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(deviceId)
+ .entries(telemetry)
+ .callback(new FutureCallback() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ log.trace("[{}] Success save firmware status!", deviceId);
+ }
- @Override
- public void onFailure(Throwable t) {
- log.error("[{}] Failed to save firmware status!", deviceId, t);
- }
- });
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}] Failed to save firmware status!", deviceId, t);
+ }
+ })
+ .build());
}
@@ -282,19 +289,24 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(otaPackageType, STATE), OtaPackageUpdateStatus.INITIATED.name()));
- telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- log.trace("[{}] Success save telemetry with target {} for device!", deviceId, otaPackage);
- updateAttributes(device, otaPackage, ts, tenantId, deviceId, otaPackageType);
- }
+ telemetryService.saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(deviceId)
+ .entry(status)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ log.trace("[{}] Success save telemetry with target {} for device!", deviceId, otaPackage);
+ updateAttributes(device, otaPackage, ts, tenantId, deviceId, otaPackageType);
+ }
- @Override
- public void onFailure(Throwable t) {
- log.error("[{}] Failed to save telemetry with target {} for device!", deviceId, otaPackage, t);
- updateAttributes(device, otaPackage, ts, tenantId, deviceId, otaPackageType);
- }
- });
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}] Failed to save telemetry with target {} for device!", deviceId, otaPackage, t);
+ updateAttributes(device, otaPackage, ts, tenantId, deviceId, otaPackageType);
+ }
+ })
+ .build());
}
private void updateAttributes(Device device, OtaPackageInfo otaPackage, long ts, TenantId tenantId, DeviceId deviceId, OtaPackageType otaPackageType) {
@@ -336,17 +348,23 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
remove(device, otaPackageType, attrToRemove);
- telemetryService.saveAndNotify(tenantId, deviceId, AttributeScope.SHARED_SCOPE, attributes, new FutureCallback<>() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- log.trace("[{}] Success save attributes with target firmware!", deviceId);
- }
+ telemetryService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(deviceId)
+ .scope(AttributeScope.SHARED_SCOPE)
+ .entries(attributes)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ log.trace("[{}] Success save attributes with target firmware!", deviceId);
+ }
- @Override
- public void onFailure(Throwable t) {
- log.error("[{}] Failed to save attributes with target firmware!", deviceId, t);
- }
- });
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}] Failed to save attributes with target firmware!", deviceId, t);
+ }
+ })
+ .build());
}
private void remove(Device device, OtaPackageType otaPackageType) {
@@ -354,8 +372,12 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
}
private void remove(Device device, OtaPackageType otaPackageType, List attributesKeys) {
- telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), AttributeScope.SHARED_SCOPE, attributesKeys,
- new FutureCallback<>() {
+ telemetryService.deleteAttributes(AttributesDeleteRequest.builder()
+ .tenantId(device.getTenantId())
+ .entityId(device.getId())
+ .scope(AttributeScope.SHARED_SCOPE)
+ .keys(attributesKeys)
+ .callback(new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType);
@@ -366,6 +388,8 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
public void onFailure(Throwable t) {
log.error("[{}] Failed to remove target {} attributes!", device.getId(), otaPackageType, t);
}
- });
+ })
+ .build());
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
index 301f8e8838..92ad0e446a 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
@@ -290,11 +290,16 @@ public class DefaultTbClusterService implements TbClusterService {
boolean isQueueTransform = targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName());
if (isRuleChainTransform && isQueueTransform) {
- tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName);
+ tbMsg = tbMsg.transform()
+ .queueName(targetQueueName)
+ .ruleChainId(targetRuleChainId)
+ .build();
} else if (isRuleChainTransform) {
- tbMsg = TbMsg.transformMsgRuleChainId(tbMsg, targetRuleChainId);
+ tbMsg = tbMsg.transform()
+ .ruleChainId(targetRuleChainId)
+ .build();
} else if (isQueueTransform) {
- tbMsg = TbMsg.transformMsgQueueName(tbMsg, targetQueueName);
+ tbMsg = tbMsg.transform(targetQueueName);
}
}
return tbMsg;
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java
index e8cce43aa2..5ee741fd1f 100644
--- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java
@@ -183,7 +183,14 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService {
entityNode.put(DataConstants.ADDITIONAL_INFO, msg.getAdditionalInfo());
try {
- TbMsg tbMsg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), Optional.ofNullable(currentUser).map(User::getCustomerId).orElse(null), metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode));
+ TbMsg tbMsg = TbMsg.newMsg()
+ .type(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE)
+ .originator(msg.getDeviceId())
+ .customerId(Optional.ofNullable(currentUser).map(User::getCustomerId).orElse(null))
+ .copyMetaData(metaData)
+ .dataType(TbMsgDataType.JSON)
+ .data(JacksonUtil.toString(entityNode))
+ .build();
clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java
index 133818592e..8b4941323f 100644
--- a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java
@@ -63,7 +63,12 @@ public class TbRpcService {
}
private void pushRpcMsgToRuleEngine(TenantId tenantId, Rpc rpc) {
- TbMsg msg = TbMsg.newMsg(TbMsgType.valueOf("RPC_" + rpc.getStatus().name()), rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc));
+ TbMsg msg = TbMsg.newMsg()
+ .type(TbMsgType.valueOf("RPC_" + rpc.getStatus().name()))
+ .originator(rpc.getDeviceId())
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(JacksonUtil.toString(rpc))
+ .build();
tbClusterService.pushMsgToRuleEngine(tenantId, rpc.getDeviceId(), msg, null);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
index a022d6a5b1..c756e50de4 100644
--- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
+++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
@@ -147,6 +147,10 @@ public class RuleNodeJsScriptEngine extends RuleNodeScriptEngine ListenableFuture wrongResultType(Object result) {
diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
index 6025e874b9..c58cb4fd5a 100644
--- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
@@ -38,6 +38,8 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.AttributeScope;
@@ -51,6 +53,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
+import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
@@ -857,7 +860,14 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService(deviceId, key, value));
- } else {
- tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, AttributeScope.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value));
- }
+ save(deviceId, new LongDataEntry(key, value), getCurrentTimeMillis());
}
private void save(DeviceId deviceId, String key, boolean value) {
+ save(deviceId, new BooleanDataEntry(key, value), getCurrentTimeMillis());
+ }
+
+ private void save(DeviceId deviceId, KvEntry kvEntry, long ts) {
if (persistToTelemetry) {
- tsSubService.saveAndNotifyInternal(
- TenantId.SYS_TENANT_ID, deviceId,
- Collections.singletonList(new BasicTsKvEntry(getCurrentTimeMillis(), new BooleanDataEntry(key, value))),
- telemetryTtl, new TelemetrySaveCallback<>(deviceId, key, value));
+ tsSubService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
+ .tenantId(TenantId.SYS_TENANT_ID)
+ .entityId(deviceId)
+ .entry(new BasicTsKvEntry(ts, kvEntry))
+ .ttl(telemetryTtl)
+ .callback(new TelemetrySaveCallback<>(deviceId, kvEntry))
+ .build());
} else {
- tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, AttributeScope.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value));
+ tsSubService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(TenantId.SYS_TENANT_ID)
+ .entityId(deviceId)
+ .scope(AttributeScope.SERVER_SCOPE)
+ .entry(new BaseAttributeKvEntry(ts, kvEntry))
+ .callback(new TelemetrySaveCallback<>(deviceId, kvEntry))
+ .build());
}
}
@@ -892,23 +908,21 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService implements FutureCallback {
private final DeviceId deviceId;
- private final String key;
- private final Object value;
+ private final KvEntry kvEntry;
- TelemetrySaveCallback(DeviceId deviceId, String key, Object value) {
+ TelemetrySaveCallback(DeviceId deviceId, KvEntry kvEntry) {
this.deviceId = deviceId;
- this.key = key;
- this.value = value;
+ this.kvEntry = kvEntry;
}
@Override
public void onSuccess(@Nullable T result) {
- log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value);
+ log.trace("[{}] Successfully updated entry {}", deviceId, kvEntry);
}
@Override
public void onFailure(Throwable t) {
- log.warn("[{}] Failed to update attribute [{}] with value [{}]", deviceId, key, value, t);
+ log.warn("[{}] Failed to update entry {}", deviceId, kvEntry, t);
}
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java b/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
index e0c78e0afa..91766c0f5d 100644
--- a/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
+++ b/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
@@ -22,6 +22,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.id.QueueStatsId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
@@ -37,7 +38,6 @@ import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.queue.TbRuleEngineConsumerStats;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -53,9 +53,9 @@ import java.util.stream.Collectors;
public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsService {
public static final String RULE_ENGINE_EXCEPTION = "ruleEngineException";
- public static final FutureCallback CALLBACK = new FutureCallback() {
+ public static final FutureCallback CALLBACK = new FutureCallback() {
@Override
- public void onSuccess(@Nullable Integer result) {
+ public void onSuccess(@Nullable Void result) {
}
@@ -89,7 +89,13 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
if (!tsList.isEmpty()) {
long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getQueueStatsTtlDays);
ttl = TimeUnit.DAYS.toSeconds(ttl);
- tsService.saveAndNotifyInternal(tenantId, queueStatsId, tsList, ttl, CALLBACK);
+ tsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(queueStatsId)
+ .entries(tsList)
+ .ttl(ttl)
+ .callback(CALLBACK)
+ .build());
}
}
} catch (Exception e) {
@@ -103,7 +109,13 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
TsKvEntry tsKv = new BasicTsKvEntry(e.getTs(), new JsonDataEntry(RULE_ENGINE_EXCEPTION, e.toJsonString(maxErrorMessageLength)));
long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getRuleEngineExceptionsTtlDays);
ttl = TimeUnit.DAYS.toSeconds(ttl);
- tsService.saveAndNotifyInternal(tenantId, getQueueStatsId(tenantId, queueName), Collections.singletonList(tsKv), ttl, CALLBACK);
+ tsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(getQueueStatsId(tenantId, queueName))
+ .entry(tsKv)
+ .ttl(ttl)
+ .callback(CALLBACK)
+ .build());
} catch (Exception e2) {
if (!"Asset is referencing to non-existent tenant!".equalsIgnoreCase(e2.getMessage())) {
log.debug("[{}] Failed to store the statistics", tenantId, e2);
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
index 50d8b9c371..3edb7c9be0 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
@@ -32,6 +32,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType;
@@ -206,20 +208,27 @@ public abstract class AbstractBulkImportService {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
long tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays());
- tsSubscriptionService.saveAndNotify(tenantId, user.getCustomerId(), entityId, timeseries, tenantTtl, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null,
- ActionType.TIMESERIES_UPDATED, null, timeseries);
- }
-
- @Override
- public void onFailure(Throwable t) {
- entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null,
- ActionType.TIMESERIES_UPDATED, BaseController.toException(t), timeseries);
- throw new RuntimeException(t);
- }
- });
+ tsSubscriptionService.saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .customerId(user.getCustomerId())
+ .entityId(entityId)
+ .entries(timeseries)
+ .ttl(tenantTtl)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null,
+ ActionType.TIMESERIES_UPDATED, null, timeseries);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null,
+ ActionType.TIMESERIES_UPDATED, BaseController.toException(t), timeseries);
+ throw new RuntimeException(t);
+ }
+ })
+ .build());
});
}
@@ -229,23 +238,27 @@ public abstract class AbstractBulkImportService attributes = new ArrayList<>(JsonConverter.convertToAttributes(kvsEntry.getValue()));
accessValidator.validateEntityAndCallback(user, Operation.WRITE_ATTRIBUTES, entity.getId(), (result, tenantId, entityId) -> {
- tsSubscriptionService.saveAndNotify(tenantId, entityId, AttributeScope.valueOf(scope), attributes, new FutureCallback<>() {
-
- @Override
- public void onSuccess(Void unused) {
- entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
- null, ActionType.ATTRIBUTES_UPDATED, null, AttributeScope.valueOf(scope), attributes);
- }
-
- @Override
- public void onFailure(Throwable throwable) {
- entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
- null, ActionType.ATTRIBUTES_UPDATED, BaseController.toException(throwable),
- AttributeScope.valueOf(scope), attributes);
- throw new RuntimeException(throwable);
- }
-
- });
+ tsSubscriptionService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(entityId)
+ .scope(AttributeScope.valueOf(scope))
+ .entries(attributes)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(Void unused) {
+ entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
+ null, ActionType.ATTRIBUTES_UPDATED, null, AttributeScope.valueOf(scope), attributes);
+ }
+
+ @Override
+ public void onFailure(Throwable throwable) {
+ entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
+ null, ActionType.ATTRIBUTES_UPDATED, BaseController.toException(throwable),
+ AttributeScope.valueOf(scope), attributes);
+ throw new RuntimeException(throwable);
+ }
+ })
+ .build());
});
}
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
index f88844bd09..0d9c67823a 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
@@ -24,6 +24,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
@@ -257,16 +258,22 @@ public abstract class BaseEntityImportService() {
- @Override
- public void onSuccess(@Nullable Void unused) {
- }
+ tsSubService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(user.getTenantId())
+ .entityId(entity.getId())
+ .scope(scope)
+ .entries(attributeKvEntries)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void unused) {
+ }
- @Override
- public void onFailure(Throwable thr) {
- log.error("Failed to import attributes for {} {}", entity.getId().getEntityType(), entity.getId(), thr);
- }
- });
+ @Override
+ public void onFailure(Throwable thr) {
+ log.error("Failed to import attributes for {} {}", entity.getId().getEntityType(), entity.getId(), thr);
+ }
+ })
+ .build());
});
});
}
diff --git a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java
index 4016352f2d..42d468575a 100644
--- a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java
+++ b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java
@@ -27,6 +27,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.SmsService;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.FeaturesInfo;
@@ -71,9 +72,9 @@ import static org.thingsboard.common.util.SystemUtil.getTotalMemory;
@Slf4j
public class DefaultSystemInfoService extends TbApplicationEventListener implements SystemInfoService {
- public static final FutureCallback CALLBACK = new FutureCallback<>() {
+ public static final FutureCallback CALLBACK = new FutureCallback<>() {
@Override
- public void onSuccess(@Nullable Integer result) {
+ public void onSuccess(@Nullable Void result) {
}
@Override
@@ -200,7 +201,13 @@ public class DefaultSystemInfoService extends TbApplicationEventListener telemetry) {
ApiUsageState apiUsageState = apiUsageStateClient.getApiUsageState(TenantId.SYS_TENANT_ID);
- telemetryService.saveAndNotifyInternal(TenantId.SYS_TENANT_ID, apiUsageState.getId(), telemetry, systemInfoTtlSeconds, CALLBACK);
+ telemetryService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
+ .tenantId(TenantId.SYS_TENANT_ID)
+ .entityId(apiUsageState.getId())
+ .entries(telemetry)
+ .ttl(systemInfoTtlSeconds)
+ .callback(CALLBACK)
+ .build());
}
private List getSystemData(ServiceInfo serviceInfo) {
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
index 5513c41929..cab2f18dc7 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
@@ -19,29 +19,28 @@ 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.SettableFuture;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
+import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
+import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
+import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
+import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
-import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
-import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
-import org.thingsboard.server.common.data.kv.BooleanDataEntry;
-import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
-import org.thingsboard.server.common.data.kv.DoubleDataEntry;
-import org.thingsboard.server.common.data.kv.LongDataEntry;
-import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
import org.thingsboard.server.common.msg.queue.TbCallback;
@@ -54,8 +53,6 @@ import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -64,13 +61,14 @@ import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.function.Consumer;
/**
* Created by ashvayka on 27.03.18.
*/
@Service
@Slf4j
-public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionService implements TelemetrySubscriptionService {
+public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionService implements TelemetrySubscriptionService, RuleEngineTelemetryService {
private final AttributesService attrService;
private final TimeseriesService tsService;
@@ -115,79 +113,100 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}
@Override
- public ListenableFuture saveAndNotify(TenantId tenantId, EntityId entityId, TsKvEntry ts) {
- SettableFuture future = SettableFuture.create();
- saveAndNotify(tenantId, entityId, Collections.singletonList(ts), new VoidFutureCallback(future));
- return future;
+ public void saveTimeseries(TimeseriesSaveRequest request) {
+ TenantId tenantId = request.getTenantId();
+ EntityId entityId = request.getEntityId();
+ checkInternalEntity(entityId);
+ boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null;
+ if (sysTenant || !request.getStrategy().saveTimeseries() || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) {
+ KvUtils.validate(request.getEntries(), valueNoXssValidation);
+ ListenableFuture future = saveTimeseriesInternal(request);
+ if (request.getStrategy().saveTimeseries()) {
+ FutureCallback callback = getApiUsageCallback(tenantId, request.getCustomerId(), sysTenant, request.getCallback());
+ Futures.addCallback(future, callback, tsCallBackExecutor);
+ }
+ } else {
+ request.getCallback().onFailure(new RuntimeException("DB storage writes are disabled due to API limits!"));
+ }
}
@Override
- public void saveAndNotify(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback) {
- saveAndNotify(tenantId, null, entityId, ts, 0L, callback);
- }
+ public ListenableFuture saveTimeseriesInternal(TimeseriesSaveRequest request) {
+ TenantId tenantId = request.getTenantId();
+ EntityId entityId = request.getEntityId();
+ TimeseriesSaveRequest.Strategy strategy = request.getStrategy();
+ ListenableFuture saveFuture;
+ if (strategy.saveTimeseries() && strategy.saveLatest()) {
+ saveFuture = tsService.save(tenantId, entityId, request.getEntries(), request.getTtl());
+ } else if (strategy.saveLatest()) {
+ saveFuture = Futures.transform(tsService.saveLatest(tenantId, entityId, request.getEntries()), result -> 0, MoreExecutors.directExecutor());
+ } else if (strategy.saveTimeseries()) {
+ saveFuture = tsService.saveWithoutLatest(tenantId, entityId, request.getEntries(), request.getTtl());
+ } else {
+ saveFuture = Futures.immediateFuture(0);
+ }
- @Override
- public void saveAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List ts, long ttl, FutureCallback callback) {
- doSaveAndNotify(tenantId, customerId, entityId, ts, ttl, callback, true);
+ addMainCallback(saveFuture, request.getCallback());
+ if (strategy.sendWsUpdate()) {
+ addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, request.getEntries()));
+ }
+ if (strategy.saveLatest()) {
+ copyLatestToEntityViews(tenantId, entityId, request.getEntries());
+ }
+ return saveFuture;
}
@Override
- public void saveWithoutLatestAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List ts, long ttl, FutureCallback callback) {
- doSaveAndNotify(tenantId, customerId, entityId, ts, ttl, callback, false);
+ public void saveAttributes(AttributesSaveRequest request) {
+ checkInternalEntity(request.getEntityId());
+ saveAttributesInternal(request);
}
- private void doSaveAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List ts, long ttl, FutureCallback callback, boolean saveLatest) {
- checkInternalEntity(entityId);
- boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null;
- if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) {
- KvUtils.validate(ts, valueNoXssValidation);
- if (saveLatest) {
- saveAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
- } else {
- saveWithoutLatestAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
- }
- } else {
- callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!"));
- }
+ @Override
+ public void saveAttributesInternal(AttributesSaveRequest request) {
+ log.trace("Executing saveInternal [{}]", request);
+ ListenableFuture> saveFuture = attrService.save(request.getTenantId(), request.getEntityId(), request.getScope(), request.getEntries());
+ addMainCallback(saveFuture, request.getCallback());
+ addWsCallback(saveFuture, success -> onAttributesUpdate(request.getTenantId(), request.getEntityId(), request.getScope().name(), request.getEntries(), request.isNotifyDevice()));
}
- private FutureCallback getCallback(TenantId tenantId, CustomerId customerId, boolean sysTenant, FutureCallback callback) {
- return new FutureCallback<>() {
- @Override
- public void onSuccess(Integer result) {
- if (!sysTenant && result != null && result > 0) {
- apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, result);
- }
- callback.onSuccess(null);
- }
-
- @Override
- public void onFailure(Throwable t) {
- callback.onFailure(t);
- }
- };
+ @Override
+ public void deleteAttributes(AttributesDeleteRequest request) {
+ checkInternalEntity(request.getEntityId());
+ deleteAttributesInternal(request);
}
@Override
- public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback) {
- saveAndNotifyInternal(tenantId, entityId, ts, 0L, callback);
+ public void deleteAttributesInternal(AttributesDeleteRequest request) {
+ ListenableFuture> deleteFuture = attrService.removeAll(request.getTenantId(), request.getEntityId(), request.getScope(), request.getKeys());
+ addMainCallback(deleteFuture, request.getCallback());
+ addWsCallback(deleteFuture, success -> onAttributesDelete(request.getTenantId(), request.getEntityId(), request.getScope().name(), request.getKeys(), request.isNotifyDevice()));
}
@Override
- public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) {
- ListenableFuture saveFuture = tsService.save(tenantId, entityId, ts, ttl);
- addMainCallback(saveFuture, callback);
- addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
- addEntityViewCallback(tenantId, entityId, ts);
+ public void deleteTimeseries(TimeseriesDeleteRequest request) {
+ checkInternalEntity(request.getEntityId());
+ deleteTimeseriesInternal(request);
}
- private void saveWithoutLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) {
- ListenableFuture saveFuture = tsService.saveWithoutLatest(tenantId, entityId, ts, ttl);
- addMainCallback(saveFuture, callback);
- addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
+ @Override
+ public void deleteTimeseriesInternal(TimeseriesDeleteRequest request) {
+ if (CollectionUtils.isNotEmpty(request.getKeys())) {
+ ListenableFuture> deleteFuture;
+ if (request.getDeleteHistoryQueries() == null) {
+ deleteFuture = tsService.removeLatest(request.getTenantId(), request.getEntityId(), request.getKeys());
+ } else {
+ deleteFuture = tsService.remove(request.getTenantId(), request.getEntityId(), request.getDeleteHistoryQueries());
+ addWsCallback(deleteFuture, result -> onTimeSeriesDelete(request.getTenantId(), request.getEntityId(), request.getKeys(), result));
+ }
+ addMainCallback(deleteFuture, __ -> request.getCallback().onSuccess(request.getKeys()), request.getCallback()::onFailure);
+ } else {
+ ListenableFuture> deleteFuture = tsService.removeAllLatest(request.getTenantId(), request.getEntityId());
+ addMainCallback(deleteFuture, request.getCallback()::onSuccess, request.getCallback()::onFailure);
+ }
}
- private void addEntityViewCallback(TenantId tenantId, EntityId entityId, List ts) {
+ private void copyLatestToEntityViews(TenantId tenantId, EntityId entityId, List ts) {
if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) {
Futures.addCallback(this.tbEntityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId),
new FutureCallback<>() {
@@ -214,15 +233,21 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}
}
if (!entityViewLatest.isEmpty()) {
- saveLatestAndNotify(tenantId, entityView.getId(), entityViewLatest, new FutureCallback<>() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- }
- });
+ saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(entityView.getId())
+ .entries(entityViewLatest)
+ .strategy(TimeseriesSaveRequest.Strategy.LATEST_AND_WS)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {}
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}][{}] Failed to save entity view latest timeseries: {}", tenantId, entityView.getId(), entityViewLatest, t);
+ }
+ })
+ .build());
}
}
}
@@ -236,233 +261,6 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}
}
- @Override
- public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List attributes, FutureCallback callback) {
- saveAndNotify(tenantId, entityId, scope, attributes, true, callback);
- }
-
- @Override
- public void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes, FutureCallback callback) {
- saveAndNotify(tenantId, entityId, scope, attributes, true, callback);
- }
-
- @Override
- public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback) {
- checkInternalEntity(entityId);
- saveAndNotifyInternal(tenantId, entityId, scope, attributes, notifyDevice, callback);
- }
-
- @Override
- public void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes, boolean notifyDevice, FutureCallback callback) {
- checkInternalEntity(entityId);
- saveAndNotifyInternal(tenantId, entityId, scope, attributes, notifyDevice, callback);
- }
-
- @Override
- public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback) {
- ListenableFuture> saveFuture = attrService.save(tenantId, entityId, scope, attributes);
- addVoidCallback(saveFuture, callback);
- addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice));
- }
-
- @Override
- public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes, boolean notifyDevice, FutureCallback callback) {
- ListenableFuture> saveFuture = attrService.save(tenantId, entityId, scope, attributes);
- addVoidCallback(saveFuture, callback);
- addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope.name(), attributes, notifyDevice));
- }
-
- @Override
- public void saveLatestAndNotify(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback) {
- checkInternalEntity(entityId);
- saveLatestAndNotifyInternal(tenantId, entityId, ts, callback);
- }
-
- @Override
- public void saveLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback) {
- ListenableFuture> saveFuture = tsService.saveLatest(tenantId, entityId, ts);
- addVoidCallback(saveFuture, callback);
- addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
- }
-
- @Override
- public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List keys, FutureCallback callback) {
- checkInternalEntity(entityId);
- deleteAndNotifyInternal(tenantId, entityId, scope, keys, false, callback);
- }
-
- @Override
- public void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List keys, FutureCallback callback) {
- checkInternalEntity(entityId);
- deleteAndNotifyInternal(tenantId, entityId, scope, keys, false, callback);
- }
-
- @Override
- public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, FutureCallback callback) {
- checkInternalEntity(entityId);
- deleteAndNotifyInternal(tenantId, entityId, scope, keys, notifyDevice, callback);
- }
-
- @Override
- public void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List keys, boolean notifyDevice, FutureCallback callback) {
- checkInternalEntity(entityId);
- deleteAndNotifyInternal(tenantId, entityId, scope, keys, notifyDevice, callback);
- }
-
- @Override
- public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, FutureCallback callback) {
- ListenableFuture> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys);
- addVoidCallback(deleteFuture, callback);
- addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice));
- }
-
- @Override
- public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List keys, boolean notifyDevice, FutureCallback callback) {
- ListenableFuture> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys);
- addVoidCallback(deleteFuture, callback);
- addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope.name(), keys, notifyDevice));
- }
-
- @Override
- public void deleteLatest(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback) {
- checkInternalEntity(entityId);
- deleteLatestInternal(tenantId, entityId, keys, callback);
- }
-
- @Override
- public void deleteLatestInternal(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback) {
- ListenableFuture> deleteFuture = tsService.removeLatest(tenantId, entityId, keys);
- addVoidCallback(deleteFuture, callback);
- }
-
- @Override
- public void deleteAllLatest(TenantId tenantId, EntityId entityId, FutureCallback> callback) {
- ListenableFuture> deleteFuture = tsService.removeAllLatest(tenantId, entityId);
- Futures.addCallback(deleteFuture, new FutureCallback>() {
- @Override
- public void onSuccess(@Nullable Collection result) {
- callback.onSuccess(result);
- }
-
- @Override
- public void onFailure(Throwable t) {
- callback.onFailure(t);
- }
- }, tsCallBackExecutor);
- }
-
- @Override
- public void deleteTimeseriesAndNotify(TenantId tenantId, EntityId entityId, List keys, List deleteTsKvQueries, FutureCallback callback) {
- ListenableFuture> deleteFuture = tsService.remove(tenantId, entityId, deleteTsKvQueries);
- addVoidCallback(deleteFuture, callback);
- addWsCallback(deleteFuture, list -> onTimeSeriesDelete(tenantId, entityId, keys, list));
- }
-
- @Override
- public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback callback) {
- saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value)
- , System.currentTimeMillis())), callback);
- }
-
-
- @Override
- public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value, FutureCallback callback) {
- saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value)
- , System.currentTimeMillis())), callback);
- }
-
- @Override
- public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value, FutureCallback callback) {
- saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(key, value)
- , System.currentTimeMillis())), callback);
- }
-
- @Override
- public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value, FutureCallback callback) {
- saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(key, value)
- , System.currentTimeMillis())), callback);
- }
-
- @Override
- public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value, FutureCallback callback) {
- saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new DoubleDataEntry(key, value)
- , System.currentTimeMillis())), callback);
- }
-
- @Override
- public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value, FutureCallback callback) {
- saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new DoubleDataEntry(key, value)
- , System.currentTimeMillis())), callback);
- }
-
- @Override
- public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value, FutureCallback callback) {
- saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value)
- , System.currentTimeMillis())), callback);
- }
-
- @Override
- public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value, FutureCallback callback) {
- saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value)
- , System.currentTimeMillis())), callback);
- }
-
- @Override
- public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value) {
- SettableFuture future = SettableFuture.create();
- saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
- return future;
- }
-
- @Override
- public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value) {
- SettableFuture future = SettableFuture.create();
- saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
- return future;
- }
-
- @Override
- public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value) {
- SettableFuture future = SettableFuture.create();
- saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
- return future;
- }
-
- @Override
- public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value) {
- SettableFuture future = SettableFuture.create();
- saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
- return future;
- }
-
- @Override
- public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value) {
- SettableFuture future = SettableFuture.create();
- saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
- return future;
- }
-
- @Override
- public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value) {
- SettableFuture future = SettableFuture.create();
- saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
- return future;
- }
-
- @Override
- public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value) {
- SettableFuture future = SettableFuture.create();
- saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
- return future;
- }
-
- @Override
- public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value) {
- SettableFuture future = SettableFuture.create();
- saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
- return future;
- }
-
private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice) {
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY);
@@ -509,33 +307,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
});
}
- private void addVoidCallback(ListenableFuture saveFuture, final FutureCallback callback) {
+ private void addMainCallback(ListenableFuture saveFuture, final FutureCallback callback) {
if (callback == null) return;
- Futures.addCallback(saveFuture, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable S result) {
- callback.onSuccess(null);
- }
-
- @Override
- public void onFailure(Throwable t) {
- callback.onFailure(t);
- }
- }, tsCallBackExecutor);
+ addMainCallback(saveFuture, result -> callback.onSuccess(null), callback::onFailure);
}
- private void addMainCallback(ListenableFuture saveFuture, final FutureCallback callback) {
- Futures.addCallback(saveFuture, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable S result) {
- callback.onSuccess(result);
- }
-
- @Override
- public void onFailure(Throwable t) {
- callback.onFailure(t);
- }
- }, tsCallBackExecutor);
+ private void addMainCallback(ListenableFuture saveFuture, Consumer onSuccess, Consumer onFailure) {
+ DonAsynchron.withCallback(saveFuture, onSuccess, onFailure, tsCallBackExecutor);
}
private void checkInternalEntity(EntityId entityId) {
@@ -544,22 +322,21 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}
}
- private static class VoidFutureCallback implements FutureCallback {
- private final SettableFuture future;
-
- public VoidFutureCallback(SettableFuture future) {
- this.future = future;
- }
-
- @Override
- public void onSuccess(Void result) {
- future.set(null);
- }
+ private FutureCallback getApiUsageCallback(TenantId tenantId, CustomerId customerId, boolean sysTenant, FutureCallback callback) {
+ return new FutureCallback<>() {
+ @Override
+ public void onSuccess(Integer result) {
+ if (!sysTenant && result != null && result > 0) {
+ apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, result);
+ }
+ callback.onSuccess(null);
+ }
- @Override
- public void onFailure(Throwable t) {
- future.setException(t);
- }
+ @Override
+ public void onFailure(Throwable t) {
+ callback.onFailure(t);
+ }
+ };
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java
index a6dc6d1aae..8e45b84a75 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java
@@ -15,37 +15,24 @@
*/
package org.thingsboard.server.service.telemetry;
-import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
-import org.thingsboard.server.common.data.AttributeScope;
-import org.thingsboard.server.common.data.id.EntityId;
-import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.kv.AttributeKvEntry;
-import org.thingsboard.server.common.data.kv.TsKvEntry;
-
-import java.util.List;
+import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
/**
* Created by ashvayka on 27.03.18.
*/
public interface InternalTelemetryService extends RuleEngineTelemetryService {
- void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback);
-
- void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback);
-
- @Deprecated(since = "3.7.0")
- void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback);
-
- void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes, boolean notifyDevice, FutureCallback callback);
-
- void saveLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback);
+ ListenableFuture saveTimeseriesInternal(TimeseriesSaveRequest request);
- @Deprecated(since = "3.7.0")
- void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, FutureCallback callback);
+ void saveAttributesInternal(AttributesSaveRequest request);
- void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List keys, boolean notifyDevice, FutureCallback callback);
+ void deleteTimeseriesInternal(TimeseriesDeleteRequest request);
- void deleteLatestInternal(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback);
+ void deleteAttributesInternal(AttributesDeleteRequest request);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
index 51d37e291a..09fc1be1c8 100644
--- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
+++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
@@ -362,7 +362,14 @@ public class DefaultTransportApiService implements TransportApiService {
DeviceId deviceId = device.getId();
JsonNode entityNode = JacksonUtil.valueToTree(device);
- TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, customerId, metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode));
+ TbMsg tbMsg = TbMsg.newMsg()
+ .type(TbMsgType.ENTITY_CREATED)
+ .originator(deviceId)
+ .customerId(customerId)
+ .copyMetaData(metaData)
+ .dataType(TbMsgDataType.JSON)
+ .data(JacksonUtil.toString(entityNode))
+ .build();
tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, null);
} else {
JsonNode deviceAdditionalInfo = device.getAdditionalInfo();
diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
index dd80eba6bc..75a459c283 100644
--- a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
+++ b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.service.ttl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@@ -66,7 +67,7 @@ public class AlarmsCleanUpService {
try {
cleanUp(tenantId);
} catch (Exception e) {
- log.warn("Failed to clean up alarms by ttl for tenant {}", tenantId, e);
+ getLogger().warn("Failed to clean up alarms by ttl for tenant {}", tenantId, e);
}
}
}
@@ -105,8 +106,13 @@ public class AlarmsCleanUpService {
alarmService.delAlarmTypes(tenantId, typesToRemove);
if (totalRemoved > 0) {
- log.info("Removed {} outdated alarm(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime));
+ getLogger().info("Removed {} outdated alarm(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime));
}
}
+ // wrapper for tests to spy on static logger
+ Logger getLogger() {
+ return log;
+ }
+
}
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 4301f0f211..368367bbab 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -206,7 +206,7 @@ ui:
# Help parameters
help:
# Base URL for UI help assets
- base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.9}"
+ base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-4.0}"
# Database telemetry parameters
database:
@@ -1271,7 +1271,7 @@ transport:
# URL of gateways dashboard repository
repository_url: "${TB_GATEWAY_DASHBOARD_SYNC_REPOSITORY_URL:https://github.com/thingsboard/gateway-management-extensions-dist.git}"
# Branch of gateways dashboard repository to work with
- branch: "${TB_GATEWAY_DASHBOARD_SYNC_BRANCH:main}"
+ branch: "${TB_GATEWAY_DASHBOARD_SYNC_BRANCH:}"
# Fetch frequency in hours for gateways dashboard repository
fetch_frequency: "${TB_GATEWAY_DASHBOARD_SYNC_FETCH_FREQUENCY:24}"
@@ -1493,9 +1493,7 @@ swagger:
# Queue configuration parameters
queue:
- # in-memory or kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
- # aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
- type: "${TB_QUEUE_TYPE:in-memory}"
+ type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka)
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
in_memory:
stats:
@@ -1624,122 +1622,6 @@ queue:
print-interval-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}"
# Time to wait for the stats-loading requests to Kafka to finish
kafka-response-timeout-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_RESPONSE_TIMEOUT_MS:1000}"
- aws_sqs:
- # Use the default credentials provider for AWS SQS
- use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}"
- # Access key ID from AWS IAM user
- access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}"
- # Secret access key from AWS IAM user
- secret_access_key: "${TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY:YOUR_SECRET}"
- # Region from AWS account
- region: "${TB_QUEUE_AWS_SQS_REGION:YOUR_REGION}"
- # Number of threads per each AWS SQS queue in consumer
- threads_per_topic: "${TB_QUEUE_AWS_SQS_THREADS_PER_TOPIC:1}"
- # Thread pool size for aws_sqs queue producer executor provider. Default value equals to AmazonSQSAsyncClient.DEFAULT_THREAD_POOL_SIZE
- producer_thread_pool_size: "${TB_QUEUE_AWS_SQS_EXECUTOR_THREAD_POOL_SIZE:50}"
- queue-properties:
- # AWS SQS queue properties. VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
- rule-engine: "${TB_QUEUE_AWS_SQS_RE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
- # AWS SQS queue properties. VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
- core: "${TB_QUEUE_AWS_SQS_CORE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
- # AWS SQS queue properties. VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
- transport-api: "${TB_QUEUE_AWS_SQS_TA_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
- # AWS SQS queue properties. VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
- notifications: "${TB_QUEUE_AWS_SQS_NOTIFICATIONS_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
- # VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800
- js-executor: "${TB_QUEUE_AWS_SQS_JE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
- # VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
- ota-updates: "${TB_QUEUE_AWS_SQS_OTA_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
- # VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
- version-control: "${TB_QUEUE_AWS_SQS_VC_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
- # VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
- edge: "${TB_QUEUE_AWS_SQS_EDGE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
- pubsub:
- # Project ID from Google Cloud
- project_id: "${TB_QUEUE_PUBSUB_PROJECT_ID:YOUR_PROJECT_ID}"
- # API Credentials in JSON format
- service_account: "${TB_QUEUE_PUBSUB_SERVICE_ACCOUNT:YOUR_SERVICE_ACCOUNT}"
- # Message size for PubSub queue.Value in bytes
- max_msg_size: "${TB_QUEUE_PUBSUB_MAX_MSG_SIZE:1048576}"
- # Number of messages per consumer
- max_messages: "${TB_QUEUE_PUBSUB_MAX_MESSAGES:1000}"
- # Thread pool size for pubsub queue executor provider. If set to 0 - default pubsub executor provider value will be used (5 * number of available processors)
- executor_thread_pool_size: "${TB_QUEUE_PUBSUB_EXECUTOR_THREAD_POOL_SIZE:0}"
- queue-properties:
- # Pub/Sub properties for Rule Engine subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
- rule-engine: "${TB_QUEUE_PUBSUB_RE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
- # Pub/Sub properties for Core subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
- core: "${TB_QUEUE_PUBSUB_CORE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
- # Pub/Sub properties for Transport API subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
- transport-api: "${TB_QUEUE_PUBSUB_TA_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
- # Pub/Sub properties for Version Control subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
- notifications: "${TB_QUEUE_PUBSUB_NOTIFICATIONS_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
- # PubSub queue properties
- js-executor: "${TB_QUEUE_PUBSUB_JE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
- # Pub/Sub properties for Transport Api subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
- version-control: "${TB_QUEUE_PUBSUB_VC_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
- # Pub/Sub properties for Edge subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
- edge: "${TB_QUEUE_PUBSUB_EDGE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
- service_bus:
- # Azure namespace
- namespace_name: "${TB_QUEUE_SERVICE_BUS_NAMESPACE_NAME:YOUR_NAMESPACE_NAME}"
- # Azure Service Bus Shared Access Signatures key name
- sas_key_name: "${TB_QUEUE_SERVICE_BUS_SAS_KEY_NAME:YOUR_SAS_KEY_NAME}"
- # Azure Service Bus Shared Access Signatures key
- sas_key: "${TB_QUEUE_SERVICE_BUS_SAS_KEY:YOUR_SAS_KEY}"
- # Number of messages per a consumer
- max_messages: "${TB_QUEUE_SERVICE_BUS_MAX_MESSAGES:1000}"
- queue-properties:
- # Azure Service Bus properties for Rule Engine queues
- rule-engine: "${TB_QUEUE_SERVICE_BUS_RE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
- # Azure Service Bus properties for Core queues
- core: "${TB_QUEUE_SERVICE_BUS_CORE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
- # Azure Service Bus properties for Transport Api queues
- transport-api: "${TB_QUEUE_SERVICE_BUS_TA_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
- # Azure Service Bus properties for Notification queues
- notifications: "${TB_QUEUE_SERVICE_BUS_NOTIFICATIONS_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
- # Azure Service Bus queue properties
- js-executor: "${TB_QUEUE_SERVICE_BUS_JE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
- # Azure Service Bus properties for Version Control queues
- version-control: "${TB_QUEUE_SERVICE_BUS_VC_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
- # Azure Service Bus properties for Edge queues
- edge: "${TB_QUEUE_SERVICE_BUS_EDGE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
- rabbitmq:
- # By default empty
- exchange_name: "${TB_QUEUE_RABBIT_MQ_EXCHANGE_NAME:}"
- # RabbitMQ host used to establish connection
- host: "${TB_QUEUE_RABBIT_MQ_HOST:localhost}"
- # RabbitMQ host used to establish a connection
- port: "${TB_QUEUE_RABBIT_MQ_PORT:5672}"
- # Virtual hosts provide logical grouping and separation of resources
- virtual_host: "${TB_QUEUE_RABBIT_MQ_VIRTUAL_HOST:/}"
- # Username for RabbitMQ user account
- username: "${TB_QUEUE_RABBIT_MQ_USERNAME:YOUR_USERNAME}"
- # User password for RabbitMQ user account
- password: "${TB_QUEUE_RABBIT_MQ_PASSWORD:YOUR_PASSWORD}"
- # Network connection between clients and RabbitMQ nodes can fail. RabbitMQ Java client supports automatic recovery of connections and topology (queues, exchanges, bindings, and consumers)
- automatic_recovery_enabled: "${TB_QUEUE_RABBIT_MQ_AUTOMATIC_RECOVERY_ENABLED:false}"
- # The connection timeout for the RabbitMQ connection factory
- connection_timeout: "${TB_QUEUE_RABBIT_MQ_CONNECTION_TIMEOUT:60000}"
- # RabbitMQ has a timeout for connection handshake. When clients run in heavily constrained environments, it may be necessary to increase the timeout
- handshake_timeout: "${TB_QUEUE_RABBIT_MQ_HANDSHAKE_TIMEOUT:10000}"
- # The maximum number of messages returned in a single call of doPoll() method
- max_poll_messages: "${TB_QUEUE_RABBIT_MQ_MAX_POLL_MESSAGES:1}"
- queue-properties:
- # RabbitMQ properties for Rule Engine queues
- rule-engine: "${TB_QUEUE_RABBIT_MQ_RE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
- # RabbitMQ properties for Core queues
- core: "${TB_QUEUE_RABBIT_MQ_CORE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
- # RabbitMQ properties for Transport API queues
- transport-api: "${TB_QUEUE_RABBIT_MQ_TA_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
- # RabbitMQ properties for Notification queues
- notifications: "${TB_QUEUE_RABBIT_MQ_NOTIFICATIONS_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
- # RabbitMQ queue properties
- js-executor: "${TB_QUEUE_RABBIT_MQ_JE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
- # RabbitMQ properties for Version Control queues
- version-control: "${TB_QUEUE_RABBIT_MQ_VC_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
- # RabbitMQ properties for Edge queues
- edge: "${TB_QUEUE_RABBIT_MQ_EDGE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
partitions:
hash_function_name: "${TB_QUEUE_PARTITIONS_HASH_FUNCTION_NAME:murmur3_128}" # murmur3_32, murmur3_128 or sha256
transport_api:
@@ -1760,7 +1642,7 @@ queue:
# Interval in milliseconds to poll api response from transport microservices
response_poll_interval: "${TB_QUEUE_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}"
core:
- # Default topic name of Kafka, RabbitMQ, etc. queue
+ # Default topic name
topic: "${TB_QUEUE_CORE_TOPIC:tb_core}"
# Interval in milliseconds to poll messages by Core microservices
poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}"
@@ -1777,7 +1659,7 @@ queue:
pack-interval-ms: "${TB_QUEUE_CORE_OTA_PACK_INTERVAL_MS:60000}"
# The size of OTA updates notifications fetched from the queue. The queue stores pairs of firmware and device ids
pack-size: "${TB_QUEUE_CORE_OTA_PACK_SIZE:100}"
- # Stats topic name for queue Kafka, RabbitMQ, etc.
+ # Stats topic name
usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}"
stats:
# Enable/disable statistics for Core microservices
@@ -1808,7 +1690,7 @@ queue:
print-interval-ms: "${TB_HOUSEKEEPER_STATS_PRINT_INTERVAL_MS:60000}"
vc:
- # Default topic name for Kafka, RabbitMQ, etc.
+ # Default topic name
topic: "${TB_QUEUE_VC_TOPIC:tb_version_control}"
# Number of partitions to associate with this queue. Used for scaling the number of messages that can be processed in parallel
partitions: "${TB_QUEUE_VC_PARTITIONS:10}"
@@ -1818,7 +1700,7 @@ queue:
pack-processing-timeout: "${TB_QUEUE_VC_PACK_PROCESSING_TIMEOUT_MS:180000}"
# Timeout for a request to VC-executor (for a request for the version of the entity, for a commit charge, etc.)
request-timeout: "${TB_QUEUE_VC_REQUEST_TIMEOUT:180000}"
- # Queue settings for Kafka, RabbitMQ, etc. Limit for single message size
+ # Limit for single queue message size
msg-chunk-size: "${TB_QUEUE_VC_MSG_CHUNK_SIZE:250000}"
js:
# JS Eval request topic
@@ -1859,7 +1741,7 @@ queue:
# Interval in milliseconds to poll messages
poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
edge:
- # Default topic name for Kafka, RabbitMQ, etc.
+ # Default topic name
topic: "${TB_QUEUE_EDGE_TOPIC:tb_edge}"
# Amount of partitions used by Edge services
partitions: "${TB_QUEUE_EDGE_PARTITIONS:10}"
diff --git a/application/src/test/java/org/thingsboard/server/actors/rule/DefaultTbContextTest.java b/application/src/test/java/org/thingsboard/server/actors/rule/DefaultTbContextTest.java
index 21a4a45c6f..29d96347ea 100644
--- a/application/src/test/java/org/thingsboard/server/actors/rule/DefaultTbContextTest.java
+++ b/application/src/test/java/org/thingsboard/server/actors/rule/DefaultTbContextTest.java
@@ -928,15 +928,32 @@ class DefaultTbContextTest {
}
private TbMsg getTbMsgWithCallback(TbMsgCallback callback) {
- return TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TENANT_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING, callback);
+ return TbMsg.newMsg()
+ .type(TbMsgType.POST_TELEMETRY_REQUEST)
+ .originator(TENANT_ID)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(TbMsg.EMPTY_STRING)
+ .callback(callback)
+ .build();
}
private TbMsg getTbMsgWithQueueName() {
- return TbMsg.newMsg(DataConstants.MAIN_QUEUE_NAME, TbMsgType.POST_TELEMETRY_REQUEST, TENANT_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING);
+ return TbMsg.newMsg()
+ .queueName(DataConstants.MAIN_QUEUE_NAME)
+ .type(TbMsgType.POST_TELEMETRY_REQUEST)
+ .originator(TENANT_ID)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(TbMsg.EMPTY_STRING)
+ .build();
}
private TbMsg getTbMsg() {
- return TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TENANT_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING);
+ return TbMsg.newMsg()
+ .type(TbMsgType.POST_TELEMETRY_REQUEST)
+ .originator(TENANT_ID)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(TbMsg.EMPTY_STRING)
+ .build();
}
private static long getUntilTime() {
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
index aef4772053..5649caee5a 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
@@ -149,10 +149,6 @@ import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import java.io.IOException;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.VarHandle;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -1053,33 +1049,6 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
throw new AssertionError("Unexpected status " + mvcResult.getResponse().getStatus());
}
- protected static T getFieldValue(Object target, String fieldName) throws Exception {
- Field field = target.getClass().getDeclaredField(fieldName);
- field.setAccessible(true);
- return (T) field.get(target);
- }
-
- protected static void setStaticFieldValue(Class> targetCls, String fieldName, Object value) throws Exception {
- Field field = targetCls.getDeclaredField(fieldName);
- field.setAccessible(true);
- field.set(null, value);
- }
-
- protected static void setStaticFinalFieldValue(Class> targetCls, String fieldName, Object value) throws Exception {
- Field field = targetCls.getDeclaredField(fieldName);
- field.setAccessible(true);
- // Get the VarHandle for the 'modifiers' field in the Field class
- MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
- VarHandle modifiersHandle = lookup.findVarHandle(Field.class, "modifiers", int.class);
-
- // Remove the final modifier from the field
- int currentModifiers = field.getModifiers();
- modifiersHandle.set(field, currentModifiers & ~Modifier.FINAL);
-
- // Set the new value
- field.set(null, value);
- }
-
protected int getDeviceActorSubscriptionCount(DeviceId deviceId, FeatureType featureType) {
DeviceActorMessageProcessor processor = getDeviceActorProcessor(deviceId);
Map subscriptions = (Map) ReflectionTestUtils.getField(processor, getMapName(featureType));
diff --git a/application/src/test/java/org/thingsboard/server/controller/RuleEngineControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/RuleEngineControllerTest.java
index 9c9322dc38..91dbb9714d 100644
--- a/application/src/test/java/org/thingsboard/server/controller/RuleEngineControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/RuleEngineControllerTest.java
@@ -62,7 +62,12 @@ public class RuleEngineControllerTest extends AbstractControllerTest {
@Test
public void testHandleRuleEngineRequestWithMsgOriginatorUser() throws Exception {
loginSysAdmin();
- TbMsg responseMsg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, currentUserId, TbMsgMetaData.EMPTY, RESPONSE_BODY);
+ TbMsg responseMsg = TbMsg.newMsg()
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(currentUserId)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(RESPONSE_BODY)
+ .build();
mockRestApiCallToRuleEngine(responseMsg);
JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/", REQUEST_BODY, new TypeReference<>() {
@@ -86,7 +91,12 @@ public class RuleEngineControllerTest extends AbstractControllerTest {
loginTenantAdmin();
Device device = createDevice("Test", "123");
DeviceId deviceId = device.getId();
- TbMsg responseMsg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, RESPONSE_BODY);
+ TbMsg responseMsg = TbMsg.newMsg()
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(deviceId)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(RESPONSE_BODY)
+ .build();
mockRestApiCallToRuleEngine(responseMsg);
JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/DEVICE/" + deviceId.getId(), REQUEST_BODY, new TypeReference<>() {
@@ -110,7 +120,12 @@ public class RuleEngineControllerTest extends AbstractControllerTest {
loginTenantAdmin();
Device device = createDevice("Test", "123");
DeviceId deviceId = device.getId();
- TbMsg responseMsg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, RESPONSE_BODY);
+ TbMsg responseMsg = TbMsg.newMsg()
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(deviceId)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(RESPONSE_BODY)
+ .build();
mockRestApiCallToRuleEngine(responseMsg);
JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/DEVICE/" + deviceId.getId() + "/15000", REQUEST_BODY, new TypeReference<>() {
@@ -156,7 +171,13 @@ public class RuleEngineControllerTest extends AbstractControllerTest {
loginTenantAdmin();
Device device = createDevice("Test", "123");
DeviceId deviceId = device.getId();
- TbMsg responseMsg = TbMsg.newMsg(DataConstants.HP_QUEUE_NAME, TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, RESPONSE_BODY);
+ TbMsg responseMsg = TbMsg.newMsg()
+ .queueName(DataConstants.HP_QUEUE_NAME)
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(deviceId)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(RESPONSE_BODY)
+ .build();
mockRestApiCallToRuleEngine(responseMsg);
JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/DEVICE/" + deviceId.getId() + "/HighPriority/1000", REQUEST_BODY, new TypeReference<>() {
@@ -195,7 +216,13 @@ public class RuleEngineControllerTest extends AbstractControllerTest {
assignDeviceToCustomer(deviceId, customerId);
loginCustomerUser();
- TbMsg responseMsg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, deviceId, customerId, TbMsgMetaData.EMPTY, RESPONSE_BODY);
+ TbMsg responseMsg = TbMsg.newMsg()
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(deviceId)
+ .customerId(customerId)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(RESPONSE_BODY)
+ .build();
mockRestApiCallToRuleEngine(responseMsg);
JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/DEVICE/" + deviceId.getId(), REQUEST_BODY, new TypeReference<>() {
diff --git a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
index c8cc72c5b5..e78cd67ba3 100644
--- a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
@@ -743,7 +743,12 @@ public class TenantControllerTest extends AbstractControllerTest {
}
private TbMsg publishTbMsg(TenantId tenantId, TopicPartitionInfo tpi) {
- TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, tenantId, TbMsgMetaData.EMPTY, "{\"test\":1}");
+ TbMsg tbMsg = TbMsg.newMsg()
+ .type(TbMsgType.POST_TELEMETRY_REQUEST)
+ .originator(tenantId)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data("{\"test\":1}")
+ .build();
TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
diff --git a/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java b/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java
index e3695d7efb..b64c87ccbd 100644
--- a/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java
@@ -29,6 +29,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestPropertySource;
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
@@ -804,19 +806,24 @@ public class WebsocketApiTest extends AbstractControllerTest {
private void sendTelemetry(Device device, List tsData) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
- tsService.saveAndNotify(device.getTenantId(), null, device.getId(), tsData, 0, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void result) {
- log.debug("sendTelemetry callback onSuccess");
- latch.countDown();
- }
-
- @Override
- public void onFailure(Throwable t) {
- log.error("Failed to send telemetry", t);
- latch.countDown();
- }
- });
+ tsService.saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(device.getTenantId())
+ .entityId(device.getId())
+ .entries(tsData)
+ .callback(new FutureCallback() {
+ @Override
+ public void onSuccess(@Nullable Void result) {
+ log.debug("sendTelemetry callback onSuccess");
+ latch.countDown();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("Failed to send telemetry", t);
+ latch.countDown();
+ }
+ })
+ .build());
assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).as("await sendTelemetry callback");
}
@@ -826,19 +833,26 @@ public class WebsocketApiTest extends AbstractControllerTest {
private void sendAttributes(TenantId tenantId, EntityId entityId, TbAttributeSubscriptionScope scope, List attrData) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
- tsService.saveAndNotify(tenantId, entityId, scope.getAttributeScope(), attrData, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void result) {
- log.debug("sendAttributes callback onSuccess");
- latch.countDown();
- }
-
- @Override
- public void onFailure(Throwable t) {
- log.error("Failed to sendAttributes", t);
- latch.countDown();
- }
- });
+ tsService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(entityId)
+ .scope(scope.getAttributeScope())
+ .entries(attrData)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void result) {
+ log.debug("sendAttributes callback onSuccess");
+ latch.countDown();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("Failed to sendAttributes", t);
+ latch.countDown();
+ }
+ })
+ .build());
assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).as("await sendAttributes callback").isTrue();
}
+
}
diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java
index a085f3a821..1202c764d7 100644
--- a/application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java
+++ b/application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java
@@ -359,7 +359,7 @@ public class DeviceProfileEdgeTest extends AbstractEdgeTest {
transportConfiguration.setBootstrapServerUpdateEnable(true);
TelemetryMappingConfiguration observeAttrConfiguration =
- JacksonUtil.fromString(AbstractLwM2MIntegrationTest.OBSERVE_ATTRIBUTES_WITH_PARAMS, TelemetryMappingConfiguration.class);
+ JacksonUtil.fromString(AbstractLwM2MIntegrationTest.TELEMETRY_WITHOUT_OBSERVE, TelemetryMappingConfiguration.class);
transportConfiguration.setObserveAttr(observeAttrConfiguration);
List bootstrap = new ArrayList<>();
diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
index fba5f2187f..1ec1ed4fb6 100644
--- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
@@ -184,7 +184,13 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class);
Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true);
- TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, tbMsgCallback);
+ TbMsg tbMsg = TbMsg.newMsg()
+ .type(TbMsgType.POST_TELEMETRY_REQUEST)
+ .originator(device.getId())
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(TbMsg.EMPTY_JSON_OBJECT)
+ .callback(tbMsgCallback)
+ .build();
QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null);
// Pushing Message to the system
actorSystem.tell(qMsg);
@@ -309,7 +315,13 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class);
Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true);
- TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, tbMsgCallback);
+ TbMsg tbMsg = TbMsg.newMsg()
+ .type(TbMsgType.POST_TELEMETRY_REQUEST)
+ .originator(device.getId())
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(TbMsg.EMPTY_JSON_OBJECT)
+ .callback(tbMsgCallback)
+ .build();
QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null);
// Pushing Message to the system
actorSystem.tell(qMsg);
diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
index d8bd02ec7f..1c67689814 100644
--- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
@@ -142,7 +142,13 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
log.warn("attr updated");
TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class);
Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true);
- TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, tbMsgCallback);
+ TbMsg tbMsg = TbMsg.newMsg()
+ .type(TbMsgType.POST_TELEMETRY_REQUEST)
+ .originator(device.getId())
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(TbMsg.EMPTY_JSON_OBJECT)
+ .callback(tbMsgCallback)
+ .build();
QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(tenantId, tbMsg, null, null);
// Pushing Message to the system
log.warn("before tell tbMsgCallback");
diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewServiceTest.java
new file mode 100644
index 0000000000..aa6bfde935
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewServiceTest.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright © 2016-2024 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.entitiy.entityview;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
+import org.thingsboard.server.common.data.EntityView;
+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.TenantId;
+import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
+import org.thingsboard.server.common.data.kv.DoubleDataEntry;
+import org.thingsboard.server.common.data.kv.TsKvEntry;
+import org.thingsboard.server.common.data.objects.AttributesEntityView;
+import org.thingsboard.server.common.data.objects.TelemetryEntityView;
+import org.thingsboard.server.dao.attributes.AttributesService;
+import org.thingsboard.server.dao.entityview.EntityViewService;
+import org.thingsboard.server.dao.timeseries.TimeseriesService;
+import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
+
+import java.util.List;
+import java.util.UUID;
+
+import static com.google.common.util.concurrent.Futures.immediateFuture;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
+
+@ExtendWith(MockitoExtension.class)
+class DefaultTbEntityViewServiceTest {
+
+ final TenantId tenantId = TenantId.fromUUID(UUID.fromString("f09c8180-686c-11ef-9471-a71d33080e9c"));
+ final EntityId entityId = DeviceId.fromString("782aaab0-c7a8-11ef-a668-79582e785d5f");
+
+ @Mock
+ EntityViewService entityViewService;
+ @Mock
+ AttributesService attributesService;
+ @Mock
+ TelemetrySubscriptionService tsSubService;
+ @Mock
+ TimeseriesService tsService;
+
+ DefaultTbEntityViewService defaultTbEntityViewService;
+
+ @BeforeEach
+ void setup() {
+ defaultTbEntityViewService = new DefaultTbEntityViewService(entityViewService, attributesService, tsSubService, tsService);
+ }
+
+ @Test
+ void shouldNotSaveTimeseriesWhenCopyingLatestToEntityView() throws Exception {
+ // GIVEN
+ var entityView = new EntityView(new EntityViewId(UUID.randomUUID()));
+ entityView.setTenantId(tenantId);
+ entityView.setEntityId(entityId);
+ entityView.setKeys(new TelemetryEntityView(List.of("temperature"), new AttributesEntityView()));
+
+ List latest = List.of(new BasicTsKvEntry(123L, new DoubleDataEntry("temperature", 22.3)));
+
+ given(tsService.findAll(eq(tenantId), eq(entityId), anyList())).willReturn(immediateFuture(latest));
+
+ // WHEN
+ defaultTbEntityViewService.updateEntityViewAttributes(tenantId, entityView, null, null);
+
+ // THEN
+ var captor = ArgumentCaptor.forClass(TimeseriesSaveRequest.class);
+ then(tsSubService).should().saveTimeseries(captor.capture());
+
+ var expectedCopyLatestRequest = TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(entityView.getId())
+ .entries(latest)
+ .ttl(0L)
+ .strategy(TimeseriesSaveRequest.Strategy.LATEST_AND_WS)
+ .build();
+
+ var actualCopyLatestRequest = captor.getValue();
+
+ assertThat(actualCopyLatestRequest)
+ .usingRecursiveComparison()
+ .ignoringFields("callback")
+ .isEqualTo(expectedCopyLatestRequest);
+ }
+
+}
diff --git a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java
index 25fe589a08..4dae5a6427 100644
--- a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java
@@ -291,7 +291,13 @@ public class DefaultTbClusterServiceTest {
TbQueueCallback callback = mock(TbQueueCallback.class);
TenantId tenantId = TenantId.fromUUID(UUID.fromString("3c8bd350-1239-4a3b-b9c3-4dd76f8e20f1"));
- TbMsg requestMsg = TbMsg.newMsg(DataConstants.HP_QUEUE_NAME, TbMsgType.REST_API_REQUEST, tenantId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT);
+ TbMsg requestMsg = TbMsg.newMsg()
+ .queueName(DataConstants.HP_QUEUE_NAME)
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(tenantId)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(TbMsg.EMPTY_JSON_OBJECT)
+ .build();
when(producerProvider.getRuleEngineMsgProducer()).thenReturn(tbREQueueProducer);
@@ -305,7 +311,12 @@ public class DefaultTbClusterServiceTest {
public void testPushMsgToRuleEngineWithTenantIdIsNullUuidAndEntityIsDevice() {
TenantId tenantId = TenantId.SYS_TENANT_ID;
DeviceId deviceId = new DeviceId(UUID.fromString("aa6d112d-2914-4a22-a9e3-bee33edbdb14"));
- TbMsg requestMsg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT);
+ TbMsg requestMsg = TbMsg.newMsg()
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(deviceId)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(TbMsg.EMPTY_JSON_OBJECT)
+ .build();
TbQueueCallback callback = mock(TbQueueCallback.class);
clusterService.pushMsgToRuleEngine(tenantId, deviceId, requestMsg, false, callback);
@@ -321,7 +332,13 @@ public class DefaultTbClusterServiceTest {
TenantId tenantId = TenantId.fromUUID(UUID.fromString("3c8bd350-1239-4a3b-b9c3-4dd76f8e20f1"));
DeviceId deviceId = new DeviceId(UUID.fromString("adbb9d41-3367-40fd-9e74-7dd7cc5d30cf"));
DeviceProfile deviceProfile = new DeviceProfile(new DeviceProfileId(UUID.fromString("552f5d6d-0b2b-43e1-a7d2-a51cb2a96927")));
- TbMsg requestMsg = TbMsg.newMsg(DataConstants.HP_QUEUE_NAME, TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT);
+ TbMsg requestMsg = TbMsg.newMsg()
+ .queueName(DataConstants.HP_QUEUE_NAME)
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(deviceId)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(TbMsg.EMPTY_JSON_OBJECT)
+ .build();
when(deviceProfileCache.get(any(TenantId.class), any(DeviceId.class))).thenReturn(deviceProfile);
when(producerProvider.getRuleEngineMsgProducer()).thenReturn(tbREQueueProducer);
@@ -341,7 +358,12 @@ public class DefaultTbClusterServiceTest {
DeviceId deviceId = new DeviceId(UUID.fromString("016c2abb-f46f-49f9-a83d-4d28b803cfe6"));
DeviceProfile deviceProfile = new DeviceProfile(new DeviceProfileId(UUID.fromString("dc5766e2-1a32-4022-859b-743050097ab7")));
deviceProfile.setDefaultQueueName(DataConstants.MAIN_QUEUE_NAME);
- TbMsg requestMsg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT);
+ TbMsg requestMsg = TbMsg.newMsg()
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(deviceId)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(TbMsg.EMPTY_JSON_OBJECT)
+ .build();
when(deviceProfileCache.get(any(TenantId.class), any(DeviceId.class))).thenReturn(deviceProfile);
when(producerProvider.getRuleEngineMsgProducer()).thenReturn(tbREQueueProducer);
@@ -349,7 +371,7 @@ public class DefaultTbClusterServiceTest {
clusterService.pushMsgToRuleEngine(tenantId, deviceId, requestMsg, false, callback);
verify(producerProvider).getRuleEngineMsgProducer();
- TbMsg expectedMsg = TbMsg.transformMsgQueueName(requestMsg, DataConstants.MAIN_QUEUE_NAME);
+ TbMsg expectedMsg = requestMsg.transform(DataConstants.MAIN_QUEUE_NAME);
ArgumentCaptor actualMsg = ArgumentCaptor.forClass(TbMsg.class);
verify(ruleEngineProducerService).sendToRuleEngine(eq(tbREQueueProducer), eq(tenantId), actualMsg.capture(), eq(callback));
assertThat(actualMsg.getValue()).usingRecursiveComparison().ignoringFields("ctx").isEqualTo(expectedMsg);
@@ -375,12 +397,12 @@ public class DefaultTbClusterServiceTest {
device.setDeviceProfileId(deviceProfileId);
// device updated
- TbMsg tbMsg = TbMsg.builder().internalType(TbMsgType.ENTITY_UPDATED).build();
+ TbMsg tbMsg = TbMsg.newMsg().type(TbMsgType.ENTITY_UPDATED).build();
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, deviceId, tbMsg);
verify(deviceProfileCache, times(1)).get(tenantId, deviceId);
// device deleted
- tbMsg = TbMsg.builder().internalType(TbMsgType.ENTITY_DELETED).data(JacksonUtil.toString(device)).build();
+ tbMsg = TbMsg.newMsg().type(TbMsgType.ENTITY_DELETED).data(JacksonUtil.toString(device)).build();
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, deviceId, tbMsg);
verify(deviceProfileCache, times(1)).get(tenantId, deviceProfileId);
}
@@ -395,12 +417,12 @@ public class DefaultTbClusterServiceTest {
asset.setAssetProfileId(assetProfileId);
// asset updated
- TbMsg tbMsg = TbMsg.builder().internalType(TbMsgType.ENTITY_UPDATED).build();
+ TbMsg tbMsg = TbMsg.newMsg().type(TbMsgType.ENTITY_UPDATED).build();
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, assetId, tbMsg);
verify(assetProfileCache, times(1)).get(tenantId, assetId);
// asset deleted
- tbMsg = TbMsg.builder().internalType(TbMsgType.ENTITY_DELETED).data(JacksonUtil.toString(asset)).build();
+ tbMsg = TbMsg.newMsg().type(TbMsgType.ENTITY_DELETED).data(JacksonUtil.toString(asset)).build();
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, assetId, tbMsg);
verify(assetProfileCache, times(1)).get(tenantId, assetProfileId);
}
diff --git a/application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManagerTest.java b/application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManagerTest.java
index 66e3de13d1..0b6e70b84c 100644
--- a/application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManagerTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManagerTest.java
@@ -782,7 +782,12 @@ public class TbRuleEngineQueueConsumerManagerTest {
}
public void setUpTestMsg() {
- testMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, new DeviceId(UUID.randomUUID()), new TbMsgMetaData(), "{}");
+ testMsg = TbMsg.newMsg()
+ .type(TbMsgType.POST_TELEMETRY_REQUEST)
+ .originator(new DeviceId(UUID.randomUUID()))
+ .copyMetaData(new TbMsgMetaData())
+ .data("{}")
+ .build();
}
}
diff --git a/application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineStrategyTest.java b/application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineStrategyTest.java
index 098c622e31..a24582099e 100644
--- a/application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineStrategyTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineStrategyTest.java
@@ -248,7 +248,7 @@ public class TbRuleEngineStrategyTest {
}
private static TbMsg createRandomMsg() {
- return TbMsg.builder()
+ return TbMsg.newMsg()
.id(UUID.randomUUID())
.type("test type")
.originator(deviceId)
diff --git a/application/src/test/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcServiceTest.java b/application/src/test/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcServiceTest.java
index 2cab5f991a..be079017c3 100644
--- a/application/src/test/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcServiceTest.java
@@ -46,7 +46,12 @@ class DefaultTbRuleEngineRpcServiceTest {
String serviceId = "tb-core-0";
UUID requestId = UUID.fromString("f64a20df-eb1e-46a3-ba6f-0b3ae053ee0a");
DeviceId deviceId = new DeviceId(UUID.fromString("1d9f771a-7cdc-4ac7-838c-ba193d05a012"));
- TbMsg msg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT);
+ TbMsg msg = TbMsg.newMsg()
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(deviceId)
+ .copyMetaData(TbMsgMetaData.EMPTY)
+ .data(TbMsg.EMPTY_JSON_OBJECT)
+ .build();
var restApiCallResponseMsgProto = TransportProtos.RestApiCallResponseMsgProto.newBuilder()
.setRequestIdMSB(requestId.getMostSignificantBits())
.setRequestIdLSB(requestId.getLeastSignificantBits())
diff --git a/application/src/test/java/org/thingsboard/server/service/ruleengine/DefaultRuleEngineCallServiceTest.java b/application/src/test/java/org/thingsboard/server/service/ruleengine/DefaultRuleEngineCallServiceTest.java
index c49149bd07..a4112ef8d6 100644
--- a/application/src/test/java/org/thingsboard/server/service/ruleengine/DefaultRuleEngineCallServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/ruleengine/DefaultRuleEngineCallServiceTest.java
@@ -85,7 +85,13 @@ public class DefaultRuleEngineCallServiceTest {
metaData.put("serviceId", "core");
metaData.put("requestUUID", requestId.toString());
metaData.put("expirationTime", Long.toString(expTime));
- TbMsg msg = TbMsg.newMsg(DataConstants.MAIN_QUEUE_NAME, TbMsgType.REST_API_REQUEST, TENANT_ID, new TbMsgMetaData(metaData), "{\"key\":\"value\"}");
+ TbMsg msg = TbMsg.newMsg()
+ .queueName(DataConstants.MAIN_QUEUE_NAME)
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(TENANT_ID)
+ .copyMetaData(new TbMsgMetaData(metaData))
+ .data("{\"key\":\"value\"}")
+ .build();
Consumer anyConsumer = TbMsg::getData;
doAnswer(invocation -> {
@@ -113,7 +119,13 @@ public class DefaultRuleEngineCallServiceTest {
metaData.put("serviceId", "core");
metaData.put("requestUUID", requestId.toString());
metaData.put("expirationTime", Long.toString(expTime));
- TbMsg msg = TbMsg.newMsg(DataConstants.MAIN_QUEUE_NAME, TbMsgType.REST_API_REQUEST, TENANT_ID, new TbMsgMetaData(metaData), "{\"key\":\"value\"}");
+ TbMsg msg = TbMsg.newMsg()
+ .queueName(DataConstants.MAIN_QUEUE_NAME)
+ .type(TbMsgType.REST_API_REQUEST)
+ .originator(TENANT_ID)
+ .copyMetaData(new TbMsgMetaData(metaData))
+ .data("{\"key\":\"value\"}")
+ .build();
Consumer anyConsumer = TbMsg::getData;
doAnswer(invocation -> {
diff --git a/application/src/test/java/org/thingsboard/server/service/script/AbstractTbelInvokeTest.java b/application/src/test/java/org/thingsboard/server/service/script/AbstractTbelInvokeTest.java
index 11b61f64c4..dcd73bac50 100644
--- a/application/src/test/java/org/thingsboard/server/service/script/AbstractTbelInvokeTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/script/AbstractTbelInvokeTest.java
@@ -16,8 +16,10 @@
package org.thingsboard.server.service.script;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.ScriptType;
+import org.thingsboard.script.api.tbel.DefaultTbelInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.controller.AbstractControllerTest;
@@ -28,7 +30,8 @@ import java.util.concurrent.ExecutionException;
import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST;
-public abstract class AbstractTbelInvokeTest extends AbstractControllerTest {
+@SpringBootTest(classes = DefaultTbelInvokeService.class)
+public abstract class AbstractTbelInvokeTest {
@Autowired
protected TbelInvokeService invokeService;
diff --git a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java
index d72dd87992..91eae788c3 100644
--- a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java
@@ -18,7 +18,6 @@ package org.thingsboard.server.service.script;
import org.junit.jupiter.api.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.tbel.TbDate;
-import org.thingsboard.server.dao.service.DaoSqlTest;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -34,7 +33,6 @@ import java.util.concurrent.atomic.AtomicReference;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
-@DaoSqlTest
class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
private String decoderStr;
@@ -1469,6 +1467,26 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
assertEquals(expected, actual);
}
+ // hexToBytes List or Array
+ @Test
+ public void hexToBytes_Test() throws ExecutionException, InterruptedException {
+ msgStr = "{}";
+ decoderStr = """
+ var validInputList = "0x01752B0367FA000500010488FFFFFFFFFFFFFFFF33";
+ var validInputArray = "AABBCCDDEE";
+ return {
+ "hexToBytes": hexToBytes(validInputList),
+ "hexToBytesArray": hexToBytesArray(validInputArray),
+ }
+ """;
+ Object actual = invokeScript(evalScript(decoderStr), msgStr);
+ LinkedHashMap expected = new LinkedHashMap<>();
+ expected.put("hexToBytes", bytesToList(new byte[]{1, 117, 43, 3, 103, -6, 0, 5, 0, 1, 4, -120, -1, -1, -1, -1, -1, -1, -1, -1, 51}));
+ // [-86, -69, -52, -35, -18] == new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0xEE}
+ expected.put("hexToBytesArray", bytesToList(new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0xEE}));
+ assertEquals( expected, actual);
+ }
+
// parseBinaryArray
@Test
public void parseBinaryArray_Test() throws ExecutionException, InterruptedException {
@@ -1761,13 +1779,15 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
return {
"base64ToHex": base64ToHex("Kkk="),
"bytesToBase64": bytesToBase64([42, 73]),
- "base64ToBytes": base64ToBytes("Kkk=")
+ "base64ToBytes": base64ToBytes("Kkk="),
+ "base64ToBytesList": base64ToBytesList("AQIDBAU=")
}
""";
LinkedHashMap expected = new LinkedHashMap<>();
expected.put("base64ToHex", "2A49");
expected.put("bytesToBase64", "Kkk=");
expected.put("base64ToBytes", bytesToList(new byte[]{42, 73}));
+ expected.put("base64ToBytesList", bytesToList(new byte[]{1, 2, 3, 4, 5}));
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expected, actual);
}
diff --git a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java
index 071fdf77fc..62170236e0 100644
--- a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java
@@ -22,9 +22,9 @@ import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.tbel.TbelScript;
-import org.thingsboard.server.dao.service.DaoSqlTest;
import java.io.Serializable;
import java.util.ArrayList;
@@ -38,7 +38,6 @@ import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-@DaoSqlTest
@TestPropertySource(properties = {
"tbel.max_script_body_size=100",
"tbel.max_total_args_size=50",
@@ -120,9 +119,9 @@ class TbelInvokeServiceTest extends AbstractTbelInvokeTest {
scriptsIds.add(scriptId);
}
- Map scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash");
- Map scriptMap = getFieldValue(invokeService, "scriptMap");
- Cache compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");
+ Map scriptIdToHash = (Map) ReflectionTestUtils.getField(invokeService, "scriptIdToHash");
+ Map scriptMap = (Map) ReflectionTestUtils.getField(invokeService, "scriptMap");
+ Cache compiledScriptsCache = (Cache) ReflectionTestUtils.getField(invokeService, "compiledScriptsCache");
String scriptHash = scriptIdToHash.get(scriptsIds.get(0));
@@ -140,9 +139,9 @@ class TbelInvokeServiceTest extends AbstractTbelInvokeTest {
scriptsIds.add(scriptId);
}
- Map scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash");
- Map scriptMap = getFieldValue(invokeService, "scriptMap");
- Cache compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");
+ Map scriptIdToHash = (Map) ReflectionTestUtils.getField(invokeService, "scriptIdToHash");
+ Map scriptMap = (Map) ReflectionTestUtils.getField(invokeService, "scriptMap");
+ Cache compiledScriptsCache = (Cache) ReflectionTestUtils.getField(invokeService, "compiledScriptsCache");
String scriptHash = scriptIdToHash.get(scriptsIds.get(0));
for (int i = 0; i < 9; i++) {
@@ -163,8 +162,8 @@ class TbelInvokeServiceTest extends AbstractTbelInvokeTest {
@Ignore("This test is based on assumption that Caffeine cache is LRU based but in fact it is based on " +
"Tiny LFU which is the cause that the tests fail sometime: https://arxiv.org/pdf/1512.00727.pdf")
public void whenCompiledScriptsCacheIsTooBig_thenRemoveRarelyUsedScripts() throws Exception {
- Map scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash");
- Cache compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");
+ Map scriptIdToHash = (Map) ReflectionTestUtils.getField(invokeService, "scriptIdToHash");
+ Cache compiledScriptsCache = (Cache) ReflectionTestUtils.getField(invokeService, "compiledScriptsCache");
List scriptsIds = new ArrayList<>();
for (int i = 0; i < 110; i++) { // tbel.compiled_scripts_cache_size = 100
diff --git a/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java b/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java
index 02281abac3..079bf8e1e2 100644
--- a/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java
@@ -131,11 +131,13 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest
void saveLatestTsForAssetAndDevice(List devices, Asset asset, int idx) throws ExecutionException, InterruptedException, TimeoutException {
for (Device device : devices) {
- TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST,
- device.getId(),
- getTbMsgMetadata(device.getName(), ts.get(idx)),
- TbMsgDataType.JSON,
- getTbMsgData(msgValue.get(idx)));
+ TbMsg tbMsg = TbMsg.newMsg()
+ .type(TbMsgType.POST_TELEMETRY_REQUEST)
+ .originator(device.getId())
+ .copyMetaData(getTbMsgMetadata(device.getName(), ts.get(idx)))
+ .dataType(TbMsgDataType.JSON)
+ .data(getTbMsgData(msgValue.get(idx)))
+ .build();
saveDeviceTsEntry(device.getId(), tbMsg, msgValue.get(idx));
saveAssetTsEntry(asset, device.getName(), msgValue.get(idx), TbMsgTimeseriesNode.computeTs(tbMsg, configuration.isUseServerTs()));
idx++;
diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java
index 296191d61b..b58d19e0e5 100644
--- a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java
@@ -27,6 +27,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device;
@@ -72,7 +73,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
@@ -82,6 +83,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.thingsboard.server.service.state.DefaultDeviceStateService.ACTIVITY_STATE;
@@ -208,9 +210,12 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceConnect(tenantId, deviceId, lastConnectTime);
// THEN
- then(telemetrySubscriptionService).should().saveAttrAndNotify(
- eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(LAST_CONNECT_TIME), eq(lastConnectTime), any()
- );
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
+ request.getEntries().get(0).getKey().equals(LAST_CONNECT_TIME) &&
+ request.getEntries().get(0).getValue().equals(lastConnectTime)
+ ));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any());
@@ -292,10 +297,12 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceDisconnect(tenantId, deviceId, lastDisconnectTime);
// THEN
- then(telemetrySubscriptionService).should().saveAttrAndNotify(
- eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE),
- eq(LAST_DISCONNECT_TIME), eq(lastDisconnectTime), any()
- );
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
+ request.getEntries().get(0).getKey().equals(LAST_DISCONNECT_TIME) &&
+ request.getEntries().get(0).getValue().equals(lastDisconnectTime)
+ ));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any());
@@ -413,14 +420,18 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceInactivity(tenantId, deviceId, lastInactivityTime);
// THEN
- then(telemetrySubscriptionService).should().saveAttrAndNotify(
- eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE),
- eq(INACTIVITY_ALARM_TIME), eq(lastInactivityTime), any()
- );
- then(telemetrySubscriptionService).should().saveAttrAndNotify(
- eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE),
- eq(ACTIVITY_STATE), eq(false), any()
- );
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
+ request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) &&
+ request.getEntries().get(0).getValue().equals(lastInactivityTime)
+ ));
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
+ request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
+ request.getEntries().get(0).getValue().equals(false)
+ ));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should()
@@ -453,14 +464,17 @@ public class DefaultDeviceStateServiceTest {
service.updateInactivityStateIfExpired(System.currentTimeMillis(), deviceId, deviceStateData);
// THEN
- then(telemetrySubscriptionService).should().saveAttrAndNotify(
- eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE),
- eq(INACTIVITY_ALARM_TIME), anyLong(), any()
- );
- then(telemetrySubscriptionService).should().saveAttrAndNotify(
- eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE),
- eq(ACTIVITY_STATE), eq(false), any()
- );
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
+ request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME)
+ ));
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
+ request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
+ request.getEntries().get(0).getValue().equals(false)
+ ));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should()
@@ -612,7 +626,9 @@ public class DefaultDeviceStateServiceTest {
long newTimeout = System.currentTimeMillis() - deviceState.getLastActivityTime() + increase;
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout);
- verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any());
+ verify(telemetrySubscriptionService, never()).saveAttributes(argThat(request ->
+ request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE)
+ ));
Thread.sleep(defaultTimeout + increase);
service.checkStates();
activityVerify(false);
@@ -651,7 +667,9 @@ public class DefaultDeviceStateServiceTest {
long newTimeout = 1;
Thread.sleep(newTimeout);
- verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any());
+ verify(telemetrySubscriptionService, never()).saveAttributes(argThat(request ->
+ request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE)
+ ));
}
@Test
@@ -672,8 +690,6 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis());
activityVerify(true);
- verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any());
-
long newTimeout = 1;
Thread.sleep(newTimeout);
@@ -713,11 +729,17 @@ public class DefaultDeviceStateServiceTest {
long newTimeout = 1;
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout);
- verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any());
+ verify(telemetrySubscriptionService, never()).saveAttributes(argThat(request ->
+ request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE)
+ ));
}
private void activityVerify(boolean isActive) {
- verify(telemetrySubscriptionService).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), eq(isActive), any());
+ verify(telemetrySubscriptionService).saveAttributes(argThat(request ->
+ request.getEntityId().equals(deviceId) &&
+ request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
+ request.getEntries().get(0).getValue().equals(isActive)
+ ));
}
@Test
@@ -763,21 +785,27 @@ public class DefaultDeviceStateServiceTest {
// THEN
assertThat(deviceState.isActive()).isEqualTo(true);
assertThat(deviceState.getLastActivityTime()).isEqualTo(lastReportedActivity);
- then(telemetrySubscriptionService).should().saveAttrAndNotify(
- any(), eq(deviceId), any(AttributeScope.class), eq(LAST_ACTIVITY_TIME), eq(lastReportedActivity), any()
- );
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getEntityId().equals(deviceId) &&
+ request.getEntries().get(0).getKey().equals(LAST_ACTIVITY_TIME) &&
+ request.getEntries().get(0).getValue().equals(lastReportedActivity)
+ ));
assertThat(deviceState.getLastInactivityAlarmTime()).isEqualTo(expectedInactivityAlarmTime);
if (shouldSetInactivityAlarmTimeToZero) {
- then(telemetrySubscriptionService).should().saveAttrAndNotify(
- any(), eq(deviceId), any(AttributeScope.class), eq(INACTIVITY_ALARM_TIME), eq(0L), any()
- );
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getEntityId().equals(deviceId) &&
+ request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) &&
+ request.getEntries().get(0).getValue().equals(0L)
+ ));
}
if (shouldUpdateActivityStateToActive) {
- then(telemetrySubscriptionService).should().saveAttrAndNotify(
- eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any()
- );
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getEntityId().equals(deviceId) &&
+ request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
+ request.getEntries().get(0).getValue().equals(true)
+ ));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any());
@@ -796,28 +824,28 @@ public class DefaultDeviceStateServiceTest {
private static Stream provideParametersForUpdateActivityState() {
return Stream.of(
- Arguments.of(true, 100, 120, 80, 80, false, false),
+ Arguments.of(true, 100, 120, 80, 80, false, false),
- Arguments.of(true, 100, 120, 100, 100, false, false),
+ Arguments.of(true, 100, 120, 100, 100, false, false),
Arguments.of(false, 100, 120, 110, 110, false, true),
- Arguments.of(true, 100, 100, 80, 80, false, false),
+ Arguments.of(true, 100, 100, 80, 80, false, false),
- Arguments.of(true, 100, 100, 100, 100, false, false),
+ Arguments.of(true, 100, 100, 100, 100, false, false),
- Arguments.of(false, 100, 100, 110, 0, true, true),
+ Arguments.of(false, 100, 100, 110, 0, true, true),
- Arguments.of(false, 100, 110, 110, 0, true, true),
+ Arguments.of(false, 100, 110, 110, 0, true, true),
- Arguments.of(false, 100, 110, 120, 0, true, true),
+ Arguments.of(false, 100, 110, 120, 0, true, true),
- Arguments.of(true, 0, 0, 0, 0, false, false),
+ Arguments.of(true, 0, 0, 0, 0, false, false),
- Arguments.of(false, 0, 0, 0, 0, true, true)
+ Arguments.of(false, 0, 0, 0, 0, true, true)
);
}
@@ -857,9 +885,10 @@ public class DefaultDeviceStateServiceTest {
assertThat(deviceState.getInactivityTimeout()).isEqualTo(newInactivityTimeout);
assertThat(deviceState.isActive()).isEqualTo(expectedActivityState);
if (activityState && !expectedActivityState) {
- then(telemetrySubscriptionService).should().saveAttrAndNotify(
- any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), eq(false), any()
- );
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
+ request.getEntries().get(0).getValue().equals(false)
+ ));
}
}
@@ -954,9 +983,10 @@ public class DefaultDeviceStateServiceTest {
assertThat(state.getLastInactivityAlarmTime()).isEqualTo(expectedLastInactivityAlarmTime);
if (shouldUpdateActivityStateToInactive) {
- then(telemetrySubscriptionService).should().saveAttrAndNotify(
- eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any()
- );
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
+ request.getEntries().get(0).getValue().equals(false)
+ ));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any());
@@ -971,72 +1001,74 @@ public class DefaultDeviceStateServiceTest {
assertThat(actualNotification.getDeviceId()).isEqualTo(deviceId);
assertThat(actualNotification.isActive()).isFalse();
- then(telemetrySubscriptionService).should().saveAttrAndNotify(
- eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE),
- eq(INACTIVITY_ALARM_TIME), eq(expectedLastInactivityAlarmTime), any()
- );
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
+ request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
+ request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) &&
+ request.getEntries().get(0).getValue().equals(expectedLastInactivityAlarmTime)
+ ));
}
}
private static Stream provideParametersForUpdateInactivityStateIfExpired() {
return Stream.of(
- Arguments.of(false, 100, 70, 90, 70, 60, false, 90, false),
+ Arguments.of(false, 100, 70, 90, 70, 60, false, 90, false),
- Arguments.of(false, 100, 40, 50, 70, 10, false, 50, false),
+ Arguments.of(false, 100, 40, 50, 70, 10, false, 50, false),
- Arguments.of(false, 100, 25, 60, 75, 25, false, 60, false),
+ Arguments.of(false, 100, 25, 60, 75, 25, false, 60, false),
- Arguments.of(false, 100, 60, 70, 10, 50, false, 70, false),
+ Arguments.of(false, 100, 60, 70, 10, 50, false, 70, false),
- Arguments.of(false, 100, 10, 15, 90, 10, false, 15, false),
+ Arguments.of(false, 100, 10, 15, 90, 10, false, 15, false),
- Arguments.of(false, 100, 0, 40, 75, 0, false, 40, false),
+ Arguments.of(false, 100, 0, 40, 75, 0, false, 40, false),
- Arguments.of(true, 100, 90, 80, 80, 50, true, 80, false),
+ Arguments.of(true, 100, 90, 80, 80, 50, true, 80, false),
- Arguments.of(true, 100, 95, 90, 10, 50, true, 90, false),
+ Arguments.of(true, 100, 95, 90, 10, 50, true, 90, false),
- Arguments.of(true, 100, 10, 10, 90, 10, false, 100, true),
+ Arguments.of(true, 100, 10, 10, 90, 10, false, 100, true),
- Arguments.of(true, 100, 10, 10, 90, 11, true, 10, false),
+ Arguments.of(true, 100, 10, 10, 90, 11, true, 10, false),
- Arguments.of(true, 100, 15, 10, 85, 5, false, 100, true),
+ Arguments.of(true, 100, 15, 10, 85, 5, false, 100, true),
- Arguments.of(true, 100, 15, 10, 75, 5, false, 100, true),
+ Arguments.of(true, 100, 15, 10, 75, 5, false, 100, true),
- Arguments.of(true, 100, 95, 90, 5, 50, false, 100, true),
+ Arguments.of(true, 100, 95, 90, 5, 50, false, 100, true),
- Arguments.of(true, 100, 0, 0, 101, 0, true, 0, false),
+ Arguments.of(true, 100, 0, 0, 101, 0, true, 0, false),
- Arguments.of(true, 100, 0, 0, 100, 0, false, 100, true),
+ Arguments.of(true, 100, 0, 0, 100, 0, false, 100, true),
- Arguments.of(true, 100, 0, 0, 99, 0, false, 100, true),
+ Arguments.of(true, 100, 0, 0, 99, 0, false, 100, true),
- Arguments.of(true, 100, 0, 0, 120, 10, true, 0, false),
+ Arguments.of(true, 100, 0, 0, 120, 10, true, 0, false),
- Arguments.of(true, 100, 50, 0, 100, 0, true, 0, false),
+ Arguments.of(true, 100, 50, 0, 100, 0, true, 0, false),
- Arguments.of(true, 100, 10, 0, 91, 0, true, 0, false),
+ Arguments.of(true, 100, 10, 0, 91, 0, true, 0, false),
- Arguments.of(true, 100, 90, 0, 10, 0, false, 100, true),
+ Arguments.of(true, 100, 90, 0, 10, 0, false, 100, true),
- Arguments.of(true, 100, 100, 100, 1, 0, true, 100, false),
+ Arguments.of(true, 100, 100, 100, 1, 0, true, 100, false),
- Arguments.of(true, 100, 100, 100, 100, 100, true, 100, false),
+ Arguments.of(true, 100, 100, 100, 100, 100, true, 100, false),
- Arguments.of(false, 100, 59, 60, 30, 10, false, 60, false),
+ Arguments.of(false, 100, 59, 60, 30, 10, false, 60, false),
- Arguments.of(true, 100, 60, 60, 30, 10, false, 100, true),
+ Arguments.of(true, 100, 60, 60, 30, 10, false, 100, true),
- Arguments.of(true, 100, 61, 60, 30, 10, false, 100, true),
+ Arguments.of(true, 100, 61, 60, 30, 10, false, 100, true),
- Arguments.of(true, 0, 0, 0, 1, 0, true, 0, false),
+ Arguments.of(true, 0, 0, 0, 1, 0, true, 0, false),
- Arguments.of(true, 0, 0, 0, 0, 0, false, 0, true),
+ Arguments.of(true, 0, 0, 0, 0, 0, false, 0, true),
- Arguments.of(true, 100, 90, 80, 20, 70, true, 80, false),
+ Arguments.of(true, 100, 90, 80, 20, 70, true, 80, false),
- Arguments.of(true, 100, 80, 90, 30, 70, true, 90, false)
+ Arguments.of(true, 100, 80, 90, 30, 70, true, 90, false)
);
}
@@ -1100,7 +1132,10 @@ public class DefaultDeviceStateServiceTest {
// THEN
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(false);
- then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any());
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
+ request.getEntries().get(0).getValue().equals(false)
+ ));
});
}
@@ -1127,10 +1162,31 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceActivity(tenantId, deviceId, currentTime);
// THEN
+ ArgumentCaptor attributeRequestCaptor = ArgumentCaptor.forClass(AttributesSaveRequest.class);
+ then(telemetrySubscriptionService).should(times(2)).saveAttributes(attributeRequestCaptor.capture());
+
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true);
- then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(LAST_ACTIVITY_TIME), eq(currentTime), any());
- then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any());
+
+ assertThat(attributeRequestCaptor.getAllValues()).hasSize(2)
+ .anySatisfy(request -> {
+ assertThat(request.getTenantId()).isEqualTo(TenantId.SYS_TENANT_ID);
+ assertThat(request.getEntityId()).isEqualTo(deviceId);
+ assertThat(request.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE);
+ assertThat(request.getEntries()).singleElement().satisfies(attributeKvEntry -> {
+ assertThat(attributeKvEntry.getKey()).isEqualTo(LAST_ACTIVITY_TIME);
+ assertThat(attributeKvEntry.getLongValue()).hasValue(currentTime);
+ });
+ })
+ .anySatisfy(request -> {
+ assertThat(request.getTenantId()).isEqualTo(TenantId.SYS_TENANT_ID);
+ assertThat(request.getEntityId()).isEqualTo(deviceId);
+ assertThat(request.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE);
+ assertThat(request.getEntries()).singleElement().satisfies(attributeKvEntry -> {
+ assertThat(attributeKvEntry.getKey()).isEqualTo(ACTIVITY_STATE);
+ assertThat(attributeKvEntry.getBooleanValue()).hasValue(true);
+ });
+ });
});
}
@@ -1174,7 +1230,10 @@ public class DefaultDeviceStateServiceTest {
// THEN
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true);
- then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any());
+ then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
+ request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
+ request.getEntries().get(0).getValue().equals(true)
+ ));
});
}
diff --git a/application/src/test/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionServiceTest.java b/application/src/test/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionServiceTest.java
new file mode 100644
index 0000000000..10fdd85504
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionServiceTest.java
@@ -0,0 +1,373 @@
+/**
+ * Copyright © 2016-2024 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.telemetry;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
+import org.thingsboard.server.cluster.TbClusterService;
+import org.thingsboard.server.common.data.ApiUsageRecordKey;
+import org.thingsboard.server.common.data.ApiUsageState;
+import org.thingsboard.server.common.data.ApiUsageStateValue;
+import org.thingsboard.server.common.data.EntityView;
+import org.thingsboard.server.common.data.id.CustomerId;
+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.TenantId;
+import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
+import org.thingsboard.server.common.data.kv.DoubleDataEntry;
+import org.thingsboard.server.common.data.kv.KvEntry;
+import org.thingsboard.server.common.data.kv.TsKvEntry;
+import org.thingsboard.server.common.data.objects.AttributesEntityView;
+import org.thingsboard.server.common.data.objects.TelemetryEntityView;
+import org.thingsboard.server.common.msg.queue.ServiceType;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
+import org.thingsboard.server.common.stats.TbApiUsageReportClient;
+import org.thingsboard.server.dao.attributes.AttributesService;
+import org.thingsboard.server.dao.timeseries.TimeseriesService;
+import org.thingsboard.server.queue.discovery.PartitionService;
+import org.thingsboard.server.queue.discovery.QueueKey;
+import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
+import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
+import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
+import org.thingsboard.server.service.subscription.SubscriptionManagerService;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+import static com.google.common.util.concurrent.Futures.immediateFuture;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.lenient;
+
+@ExtendWith(MockitoExtension.class)
+class DefaultTelemetrySubscriptionServiceTest {
+
+ final TenantId tenantId = TenantId.fromUUID(UUID.fromString("a00ec470-c6b4-11ef-8c88-63b5533fb5bc"));
+ final CustomerId customerId = new CustomerId(UUID.fromString("7bdc9750-c775-11ef-8e03-ff69ed8da327"));
+ final EntityId entityId = DeviceId.fromString("cc51e450-53e1-11ee-883e-e56b48fd2088");
+
+ final long sampleTtl = 10_000L;
+
+ final List sampleTelemetry = List.of(
+ new BasicTsKvEntry(100L, new DoubleDataEntry("temperature", 65.2)),
+ new BasicTsKvEntry(100L, new DoubleDataEntry("humidity", 33.1))
+ );
+
+ ApiUsageState apiUsageState;
+
+ final TopicPartitionInfo tpi = TopicPartitionInfo.builder()
+ .tenantId(tenantId)
+ .myPartition(true)
+ .build();
+
+ final FutureCallback emptyCallback = new FutureCallback<>() {
+ @Override
+ public void onSuccess(Void result) {}
+
+ @Override
+ public void onFailure(@NonNull Throwable t) {}
+ };
+
+ ExecutorService wsCallBackExecutor;
+ ExecutorService tsCallBackExecutor;
+
+ @Mock
+ TbClusterService clusterService;
+ @Mock
+ PartitionService partitionService;
+ @Mock
+ SubscriptionManagerService subscriptionManagerService;
+ @Mock
+ AttributesService attrService;
+ @Mock
+ TimeseriesService tsService;
+ @Mock
+ TbEntityViewService tbEntityViewService;
+ @Mock
+ TbApiUsageReportClient apiUsageClient;
+ @Mock
+ TbApiUsageStateService apiUsageStateService;
+
+ DefaultTelemetrySubscriptionService telemetryService;
+
+ @BeforeEach
+ void setup() {
+ telemetryService = new DefaultTelemetrySubscriptionService(attrService, tsService, tbEntityViewService, apiUsageClient, apiUsageStateService);
+ ReflectionTestUtils.setField(telemetryService, "clusterService", clusterService);
+ ReflectionTestUtils.setField(telemetryService, "partitionService", partitionService);
+ ReflectionTestUtils.setField(telemetryService, "subscriptionManagerService", Optional.of(subscriptionManagerService));
+
+ wsCallBackExecutor = MoreExecutors.newDirectExecutorService();
+ ReflectionTestUtils.setField(telemetryService, "wsCallBackExecutor", wsCallBackExecutor);
+
+ tsCallBackExecutor = MoreExecutors.newDirectExecutorService();
+ ReflectionTestUtils.setField(telemetryService, "tsCallBackExecutor", tsCallBackExecutor);
+
+ apiUsageState = new ApiUsageState();
+ apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED);
+ lenient().when(apiUsageStateService.getApiUsageState(tenantId)).thenReturn(apiUsageState);
+
+ lenient().when(partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId)).thenReturn(tpi);
+
+ lenient().when(tsService.save(tenantId, entityId, sampleTelemetry, sampleTtl)).thenReturn(immediateFuture(sampleTelemetry.size()));
+ lenient().when(tsService.saveWithoutLatest(tenantId, entityId, sampleTelemetry, sampleTtl)).thenReturn(immediateFuture(sampleTelemetry.size()));
+ lenient().when(tsService.saveLatest(tenantId, entityId, sampleTelemetry)).thenReturn(immediateFuture(listOfNNumbers(sampleTelemetry.size())));
+
+ // mock no entity views
+ lenient().when(tbEntityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId)).thenReturn(immediateFuture(Collections.emptyList()));
+
+ // send partition change event so currentPartitions set is populated
+ telemetryService.onTbApplicationEvent(new PartitionChangeEvent(this, ServiceType.TB_CORE, Map.of(new QueueKey(ServiceType.TB_CORE), Set.of(tpi))));
+ }
+
+ @AfterEach
+ void cleanup() {
+ wsCallBackExecutor.shutdownNow();
+ tsCallBackExecutor.shutdownNow();
+ }
+
+ @Test
+ void shouldReportStorageDataPointsApiUsageWhenTimeSeriesIsSaved() {
+ // GIVEN
+ var request = TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .customerId(customerId)
+ .entityId(entityId)
+ .entries(sampleTelemetry)
+ .ttl(sampleTtl)
+ .strategy(new TimeseriesSaveRequest.Strategy(true, false, false))
+ .callback(emptyCallback)
+ .build();
+
+ // WHEN
+ telemetryService.saveTimeseries(request);
+
+ // THEN
+ then(apiUsageClient).should().report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, sampleTelemetry.size());
+ }
+
+ @Test
+ void shouldNotReportStorageDataPointsApiUsageWhenTimeSeriesIsNotSaved() {
+ // GIVEN
+ var request = TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .customerId(customerId)
+ .entityId(entityId)
+ .entries(sampleTelemetry)
+ .ttl(sampleTtl)
+ .strategy(TimeseriesSaveRequest.Strategy.LATEST_AND_WS)
+ .callback(emptyCallback)
+ .build();
+
+ // WHEN
+ telemetryService.saveTimeseries(request);
+
+ // THEN
+ then(apiUsageClient).shouldHaveNoInteractions();
+ }
+
+ @Test
+ void shouldThrowStorageDisabledWhenTimeSeriesIsSavedAndStorageIsDisabled() {
+ // GIVEN
+ apiUsageState.setDbStorageState(ApiUsageStateValue.DISABLED);
+
+ SettableFuture future = SettableFuture.create();
+ var request = TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .customerId(customerId)
+ .entityId(entityId)
+ .entries(sampleTelemetry)
+ .ttl(sampleTtl)
+ .strategy(TimeseriesSaveRequest.Strategy.SAVE_ALL)
+ .future(future)
+ .build();
+
+ // WHEN
+ telemetryService.saveTimeseries(request);
+
+ // THEN
+ assertThat(future).failsWithin(Duration.ofSeconds(5))
+ .withThrowableOfType(ExecutionException.class)
+ .withCauseInstanceOf(RuntimeException.class)
+ .withMessageContaining("DB storage writes are disabled due to API limits!");
+ }
+
+ @Test
+ void shouldNotThrowStorageDisabledWhenTimeSeriesIsNotSavedAndStorageIsDisabled() {
+ // GIVEN
+ apiUsageState.setDbStorageState(ApiUsageStateValue.DISABLED);
+
+ SettableFuture future = SettableFuture.create();
+ var request = TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .customerId(customerId)
+ .entityId(entityId)
+ .entries(sampleTelemetry)
+ .ttl(sampleTtl)
+ .strategy(TimeseriesSaveRequest.Strategy.LATEST_AND_WS)
+ .future(future)
+ .build();
+
+ // WHEN
+ telemetryService.saveTimeseries(request);
+
+ // THEN
+ assertThat(future).succeedsWithin(Duration.ofSeconds(5));
+ }
+
+ @Test
+ void shouldCopyLatestToEntityViewWhenLatestIsSavedOnMainEntity() {
+ // GIVEN
+ var entityView = new EntityView(new EntityViewId(UUID.randomUUID()));
+ entityView.setTenantId(tenantId);
+ entityView.setCustomerId(customerId);
+ entityView.setEntityId(entityId);
+ entityView.setKeys(new TelemetryEntityView(sampleTelemetry.stream().map(KvEntry::getKey).toList(), new AttributesEntityView()));
+
+ // mock that there is one entity view
+ given(tbEntityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId)).willReturn(immediateFuture(List.of(entityView)));
+ // mock that save latest call for entity view is successful
+ given(tsService.saveLatest(tenantId, entityView.getId(), sampleTelemetry)).willReturn(immediateFuture(listOfNNumbers(sampleTelemetry.size())));
+ // mock TPI for entity view
+ given(partitionService.resolve(ServiceType.TB_CORE, tenantId, entityView.getId())).willReturn(tpi);
+
+ var request = TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .customerId(customerId)
+ .entityId(entityId)
+ .entries(sampleTelemetry)
+ .ttl(sampleTtl)
+ .strategy(new TimeseriesSaveRequest.Strategy(false, true, false))
+ .callback(emptyCallback)
+ .build();
+
+ // WHEN
+ telemetryService.saveTimeseries(request);
+
+ // THEN
+ // should save latest to both the main entity and it's entity view
+ then(tsService).should().saveLatest(tenantId, entityId, sampleTelemetry);
+ then(tsService).should().saveLatest(tenantId, entityView.getId(), sampleTelemetry);
+ then(tsService).shouldHaveNoMoreInteractions();
+
+ // should send WS update only for entity view (WS update for the main entity is disabled in the save request)
+ then(subscriptionManagerService).should().onTimeSeriesUpdate(tenantId, entityView.getId(), sampleTelemetry, TbCallback.EMPTY);
+ then(subscriptionManagerService).shouldHaveNoMoreInteractions();
+ }
+
+ @Test
+ void shouldNotCopyLatestToEntityViewWhenLatestIsNotSavedOnMainEntity() {
+ // GIVEN
+ var request = TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .customerId(customerId)
+ .entityId(entityId)
+ .entries(sampleTelemetry)
+ .ttl(sampleTtl)
+ .strategy(new TimeseriesSaveRequest.Strategy(true, false, false))
+ .callback(emptyCallback)
+ .build();
+
+ // WHEN
+ telemetryService.saveTimeseries(request);
+
+ // THEN
+ // should save only time series for the main entity
+ then(tsService).should().saveWithoutLatest(tenantId, entityId, sampleTelemetry, sampleTtl);
+ then(tsService).shouldHaveNoMoreInteractions();
+
+ // should not send any WS updates
+ then(subscriptionManagerService).shouldHaveNoInteractions();
+ }
+
+ @ParameterizedTest
+ @MethodSource("booleanCombinations")
+ void shouldCallCorrectApiBasedOnBooleanFlagsInTheSaveRequest(boolean saveTimeseries, boolean saveLatest, boolean sendWsUpdate) {
+ // GIVEN
+ var request = TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .customerId(customerId)
+ .entityId(entityId)
+ .entries(sampleTelemetry)
+ .ttl(sampleTtl)
+ .strategy(new TimeseriesSaveRequest.Strategy(saveTimeseries, saveLatest, sendWsUpdate))
+ .callback(emptyCallback)
+ .build();
+
+ // WHEN
+ telemetryService.saveTimeseries(request);
+
+ // THEN
+ if (saveTimeseries && saveLatest) {
+ then(tsService).should().save(tenantId, entityId, sampleTelemetry, sampleTtl);
+ } else if (saveLatest) {
+ then(tsService).should().saveLatest(tenantId, entityId, sampleTelemetry);
+ } else if (saveTimeseries) {
+ then(tsService).should().saveWithoutLatest(tenantId, entityId, sampleTelemetry, sampleTtl);
+ }
+ then(tsService).shouldHaveNoMoreInteractions();
+
+ if (sendWsUpdate) {
+ then(subscriptionManagerService).should().onTimeSeriesUpdate(tenantId, entityId, sampleTelemetry, TbCallback.EMPTY);
+ } else {
+ then(subscriptionManagerService).shouldHaveNoInteractions();
+ }
+ }
+
+ private static Stream booleanCombinations() {
+ return Stream.of(
+ Arguments.of(true, true, true),
+ Arguments.of(true, true, false),
+ Arguments.of(true, false, true),
+ Arguments.of(true, false, false),
+ Arguments.of(false, true, true),
+ Arguments.of(false, true, false),
+ Arguments.of(false, false, true),
+ Arguments.of(false, false, false)
+ );
+ }
+
+ // used to emulate sequence numbers returned by save latest API
+ private static List listOfNNumbers(int N) {
+ return LongStream.range(0, N).boxed().toList();
+ }
+
+}
diff --git a/application/src/test/java/org/thingsboard/server/service/ttl/AlarmsCleanUpServiceTest.java b/application/src/test/java/org/thingsboard/server/service/ttl/AlarmsCleanUpServiceTest.java
index 7b9cb0ed4d..6206523d4d 100644
--- a/application/src/test/java/org/thingsboard/server/service/ttl/AlarmsCleanUpServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/ttl/AlarmsCleanUpServiceTest.java
@@ -15,7 +15,7 @@
*/
package org.thingsboard.server.service.ttl;
-import org.junit.BeforeClass;
+import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.slf4j.Logger;
@@ -40,6 +40,7 @@ import java.util.concurrent.TimeUnit;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
+import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -49,19 +50,19 @@ import static org.mockito.Mockito.verify;
})
public class AlarmsCleanUpServiceTest extends AbstractControllerTest {
- @Autowired
+ @SpyBean
private AlarmsCleanUpService alarmsCleanUpService;
@SpyBean
private AlarmService alarmService;
@Autowired
private AlarmDao alarmDao;
- private static Logger cleanUpServiceLogger;
+ private Logger cleanUpServiceLoggerSpy;
- @BeforeClass
- public static void before() throws Exception {
- cleanUpServiceLogger = Mockito.spy(LoggerFactory.getLogger(AlarmsCleanUpService.class));
- setStaticFinalFieldValue(AlarmsCleanUpService.class, "log", cleanUpServiceLogger);
+ @Before
+ public void beforeEach() throws Exception {
+ cleanUpServiceLoggerSpy = Mockito.spy(LoggerFactory.getLogger(AlarmsCleanUpService.class));
+ willReturn(cleanUpServiceLoggerSpy).given(alarmsCleanUpService).getLogger();
}
@Test
@@ -110,7 +111,7 @@ public class AlarmsCleanUpServiceTest extends AbstractControllerTest {
verify(alarmService, never()).delAlarm(eq(tenantId), eq(freshAlarm), eq(false));
}
- verify(cleanUpServiceLogger).info(startsWith("Removed {} outdated alarm"), eq((long) count), eq(tenantId), any());
+ verify(cleanUpServiceLoggerSpy).info(startsWith("Removed {} outdated alarm"), eq((long) count), eq(tenantId), any());
}
}
diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java
index 33c83b5623..2b0d87527b 100644
--- a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java
@@ -54,6 +54,14 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg
protected final byte[] EMPTY_PAYLOAD = new byte[0];
protected CoapTestClient client;
+ protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
+ " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
+ protected static final String PAYLOAD_VALUES_STR_01 = "{\"key2\":\"value2\", \"key3\":false, \"key4\": 4.0, \"key5\": 5," +
+ " \"key6\": {\"someNumber_02\": 52, \"someArray_02\": [1,2,3,4], \"someNestedObject_02\": {\"key_02\": \"value_02\"}}}";
+
+ protected void processBeforeTest() throws Exception {
+ loginTenantAdmin();
+ }
protected void processAfterTest() throws Exception {
if (client != null) {
diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java
index 112d3e6aa5..534c83bd40 100644
--- a/application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java
@@ -63,8 +63,6 @@ import static org.thingsboard.server.common.data.query.EntityKeyType.SHARED_ATTR
@DaoSqlTest
public class CoapClientIntegrationTest extends AbstractCoapIntegrationTest {
- private static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
- " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
private static final List EXPECTED_KEYS = Arrays.asList("key1", "key2", "key3", "key4", "key5");
private static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}";
diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/security/AbstractCoapSecurityIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/security/AbstractCoapSecurityIntegrationTest.java
new file mode 100644
index 0000000000..8413c6f32b
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/transport/coap/security/AbstractCoapSecurityIntegrationTest.java
@@ -0,0 +1,291 @@
+/**
+ * Copyright © 2016-2024 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.transport.coap.security;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.californium.core.CoapResponse;
+import org.eclipse.californium.core.coap.CoAP;
+import org.junit.Assert;
+import org.springframework.test.context.TestPropertySource;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.server.common.data.CoapDeviceType;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.DeviceProfile;
+import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
+import org.thingsboard.server.common.data.TransportPayloadType;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.DeviceProfileId;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.data.security.DeviceCredentialsType;
+import org.thingsboard.server.common.msg.session.FeatureType;
+import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest;
+import org.thingsboard.server.transport.coap.x509.CertPrivateKey;
+import org.thingsboard.server.transport.coap.x509.CoapClientX509Test;
+import org.thingsboard.server.transport.coap.CoapTestConfigProperties;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.ServerSocket;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@Slf4j
+@TestPropertySource(properties = {
+ "coap.enabled=true",
+ "coap.dtls.enabled=true",
+ "coap.dtls.credentials.pem.cert_file=coap/credentials/server/cert.pem",
+ "device.connectivity.coaps.enabled=true",
+ "service.integrations.supported=ALL",
+ "transport.coap.enabled=true",
+})
+public abstract class AbstractCoapSecurityIntegrationTest extends AbstractCoapIntegrationTest {
+ private static final String COAPS_BASE_URL = "coaps://localhost:5684/api/v1/";
+ protected final String CREDENTIALS_PATH = "coap/credentials/";
+ protected final String CREDENTIALS_PATH_CLIENT = CREDENTIALS_PATH + "client/";
+ protected final String CREDENTIALS_PATH_CLIENT_CERT_PEM = CREDENTIALS_PATH_CLIENT + "cert.pem";
+ protected final String CREDENTIALS_PATH_CLIENT_KEY_PEM = CREDENTIALS_PATH_CLIENT + "key.pem";
+ protected final X509Certificate clientX509CertTrustNo; // client certificate signed by intermediate, rootCA with a good CN ("host name")
+ protected final PrivateKey clientPrivateKeyFromCertTrustNo;
+
+ protected static final String CLIENT_JKS_FOR_TEST = "coapclientTest";
+ protected static final String CLIENT_STORE_PWD = "client_ks_password";
+ protected static final String CLIENT_ALIAS_CERT_TRUST_NO = "client_alias_trust_no";
+
+ protected AbstractCoapSecurityIntegrationTest() {
+
+ try {
+ // Get certificates from key store
+ char[] clientKeyStorePwd = CLIENT_STORE_PWD.toCharArray();
+ KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ try (InputStream clientKeyStoreFile =
+ this.getClass().getClassLoader().
+ getResourceAsStream(CREDENTIALS_PATH + CLIENT_JKS_FOR_TEST + ".jks")) {
+ clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd);
+ }
+ // No trust
+ clientPrivateKeyFromCertTrustNo = (PrivateKey) clientKeyStore.getKey(CLIENT_ALIAS_CERT_TRUST_NO, clientKeyStorePwd);
+ clientX509CertTrustNo = (X509Certificate) clientKeyStore.getCertificate(CLIENT_ALIAS_CERT_TRUST_NO);
+ } catch (GeneralSecurityException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected Device createDeviceWithX509(String deviceName, DeviceProfileId deviceProfileId, X509Certificate clientX509Cert) throws Exception {
+ Device device = new Device();
+ device.setName(deviceName);
+ device.setType(deviceName);
+ device.setDeviceProfileId(deviceProfileId);
+
+ DeviceCredentials deviceCredentials = new DeviceCredentials();
+ deviceCredentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE);
+ String pemFormatCert = CertPrivateKey.convertCertToPEM(clientX509Cert);
+ deviceCredentials.setCredentialsValue(pemFormatCert);
+
+ SaveDeviceWithCredentialsRequest saveRequest = new SaveDeviceWithCredentialsRequest(device, deviceCredentials);
+ Device deviceX509 = readResponse(doPost("/api/device-with-credentials", saveRequest)
+ .andExpect(status().isOk()), Device.class);
+ DeviceCredentials savedDeviceCredentials =
+ doGet("/api/device/" + deviceX509.getId().getId() + "/credentials", DeviceCredentials.class);
+ Assert.assertNotNull(savedDeviceCredentials);
+ Assert.assertNotNull(savedDeviceCredentials.getId());
+ Assert.assertEquals(deviceX509.getId(), savedDeviceCredentials.getDeviceId());
+ Assert.assertEquals(DeviceCredentialsType.X509_CERTIFICATE, savedDeviceCredentials.getCredentialsType());
+ accessToken = savedDeviceCredentials.getCredentialsId();
+ assertNotNull(accessToken);
+ return deviceX509;
+ }
+
+ protected void clientX509FromJksUpdateAttributesTest() throws Exception {
+ CertPrivateKey certPrivateKey = new CertPrivateKey(clientX509CertTrustNo, clientPrivateKeyFromCertTrustNo);
+ CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder()
+ .coapDeviceType(CoapDeviceType.DEFAULT)
+ .transportPayloadType(TransportPayloadType.JSON)
+ .build();
+ DeviceProfile deviceProfile = createCoapDeviceProfile(configProperties);
+ assertNotNull(deviceProfile);
+ CoapClientX509Test clientX509 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey,
+ "CoapX509TrustNo_" + FeatureType.ATTRIBUTES.name(), deviceProfile.getId(), null);
+ clientX509.disconnect();
+ }
+
+ protected void clientX509FromPathUpdateFeatureTypeTest(FeatureType featureType) throws Exception {
+ CertPrivateKey certPrivateKey = new CertPrivateKey(CREDENTIALS_PATH_CLIENT_CERT_PEM, CREDENTIALS_PATH_CLIENT_KEY_PEM);
+ CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder()
+ .coapDeviceType(CoapDeviceType.DEFAULT)
+ .transportPayloadType(TransportPayloadType.JSON)
+ .build();
+ DeviceProfile deviceProfile = createCoapDeviceProfile(configProperties);
+ assertNotNull(deviceProfile);
+ CoapClientX509Test clientX509 = clientX509UpdateTest(featureType, certPrivateKey,
+ "CoapX509TrustNo_" + featureType.name(), deviceProfile.getId(), null);
+ clientX509.disconnect();
+ }
+ protected void twoClientWithSamePortX509FromPathConnectTest() throws Exception {
+ CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder()
+ .coapDeviceType(CoapDeviceType.DEFAULT)
+ .transportPayloadType(TransportPayloadType.JSON)
+ .build();
+ DeviceProfile deviceProfile = createCoapDeviceProfile(configProperties);
+ CertPrivateKey certPrivateKey = new CertPrivateKey(CREDENTIALS_PATH_CLIENT_CERT_PEM, CREDENTIALS_PATH_CLIENT_KEY_PEM);
+ CertPrivateKey certPrivateKey_01 = new CertPrivateKey(CREDENTIALS_PATH_CLIENT + "cert_01.pem",
+ CREDENTIALS_PATH_CLIENT + "key_01.pem");
+ Integer fixedPort = getFreePort();
+ CoapClientX509Test clientX509 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey,
+ "CoapX509TrustNo_" + FeatureType.TELEMETRY.name(), deviceProfile.getId(), fixedPort);
+ clientX509.disconnect();
+ await("Need to make port " + fixedPort + " free")
+ .atMost(40, TimeUnit.SECONDS)
+ .until(() -> isPortAvailable(fixedPort));
+ CoapClientX509Test clientX509_01 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey_01,
+ "CoapX509TrustNo_" + FeatureType.TELEMETRY.name() + "_01", deviceProfile.getId(),
+ fixedPort, PAYLOAD_VALUES_STR_01);
+ clientX509_01.disconnect();
+ }
+
+ private CoapClientX509Test clientX509UpdateTest(FeatureType featureType, CertPrivateKey certPrivateKey,
+ String deviceName, DeviceProfileId deviceProfileId, Integer fixedPort) throws Exception {
+ return clientX509UpdateTest(featureType, certPrivateKey, deviceName, deviceProfileId, fixedPort, null);
+ }
+
+ private CoapClientX509Test clientX509UpdateTest(FeatureType featureType, CertPrivateKey certPrivateKey,
+ String deviceName, DeviceProfileId deviceProfileId, Integer fixedPort, String payload) throws Exception {
+ String payloadValuesStr = payload == null ? PAYLOAD_VALUES_STR : payload;
+ Device deviceX509 = createDeviceWithX509(deviceName, deviceProfileId, certPrivateKey.getCert());
+ CoapClientX509Test clientX509 = new CoapClientX509Test(certPrivateKey, featureType, COAPS_BASE_URL, fixedPort);
+ CoapResponse coapResponseX509 = clientX509.postMethod(payloadValuesStr);
+ assertNotNull(coapResponseX509);
+ assertEquals(CoAP.ResponseCode.CREATED, coapResponseX509.getCode());
+
+ if (FeatureType.ATTRIBUTES.equals(featureType)) {
+ DeviceId deviceId = deviceX509.getId();
+ JsonNode expectedNode = JacksonUtil.toJsonNode(payloadValuesStr);
+ List expectedKeys = getKeysFromNode(expectedNode);
+ List actualKeys = getActualKeysList(deviceId, expectedKeys, "attributes/CLIENT_SCOPE");
+ assertNotNull(actualKeys);
+
+ Set actualKeySet = new HashSet<>(actualKeys);
+ Set expectedKeySet = new HashSet<>(expectedKeys);
+ assertEquals(expectedKeySet, actualKeySet);
+
+ String getAttributesValuesUrl = getAttributesValuesUrl(deviceId, actualKeySet, "attributes/CLIENT_SCOPE");
+ List