Browse Source

Merge branch 'lts-4.2' into fix/sync-toast-destroy-and-data-reset

pull/15071/head
Maksym Tsymbarov 5 months ago
committed by GitHub
parent
commit
0f362ee440
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      .github/release.yml
  2. 2
      application/pom.xml
  3. 16
      application/src/main/java/org/thingsboard/server/service/sync/DefaultGitSyncService.java
  4. 6
      application/src/main/resources/thingsboard.yml
  5. 34
      application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java
  6. 52
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java
  7. 60
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java
  8. 15
      application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/AbstractMqttV5ClientSparkplugTest.java
  9. 2
      common/actor/pom.xml
  10. 2
      common/cache/pom.xml
  11. 2
      common/cluster-api/pom.xml
  12. 2
      common/coap-server/pom.xml
  13. 2
      common/dao-api/pom.xml
  14. 32
      common/dao-api/src/main/java/org/thingsboard/server/dao/nosql/ResultSetSizeLimitExceededException.java
  15. 24
      common/dao-api/src/main/java/org/thingsboard/server/dao/nosql/TbResultSet.java
  16. 140
      common/dao-api/src/test/java/org/thingsboard/server/dao/nosql/TbResultSetTest.java
  17. 2
      common/data/pom.xml
  18. 2
      common/discovery-api/pom.xml
  19. 2
      common/edge-api/pom.xml
  20. 4
      common/edge-api/src/main/proto/edge.proto
  21. 2
      common/edqs/pom.xml
  22. 2
      common/message/pom.xml
  23. 4
      common/message/src/test/java/org/thingsboard/server/common/msg/tools/TbRateLimitsTest.java
  24. 2
      common/pom.xml
  25. 2
      common/proto/pom.xml
  26. 4
      common/proto/src/main/java/org/thingsboard/server/common/adaptor/ProtoConverter.java
  27. 2
      common/queue/pom.xml
  28. 2
      common/script/pom.xml
  29. 2
      common/script/remote-js-client/pom.xml
  30. 2
      common/script/script-api/pom.xml
  31. 8
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
  32. 95
      common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java
  33. 2
      common/stats/pom.xml
  34. 2
      common/transport/coap/pom.xml
  35. 2
      common/transport/http/pom.xml
  36. 2
      common/transport/lwm2m/pom.xml
  37. 2
      common/transport/mqtt/pom.xml
  38. 2
      common/transport/pom.xml
  39. 2
      common/transport/snmp/pom.xml
  40. 2
      common/transport/transport-api/pom.xml
  41. 2
      common/util/pom.xml
  42. 2
      common/version-control/pom.xml
  43. 35
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java
  44. 2
      dao/pom.xml
  45. 3
      dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractAsyncDao.java
  46. 92
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
  47. 2
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java
  48. 4
      dao/src/main/java/org/thingsboard/server/dao/timeseries/SimpleListenableFuture.java
  49. 42
      dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java
  50. 2
      edqs/pom.xml
  51. 2
      edqs/src/main/resources/edqs.yml
  52. 2
      monitoring/pom.xml
  53. 2
      msa/black-box-tests/pom.xml
  54. 2
      msa/edqs/pom.xml
  55. 5
      msa/js-executor/package.json
  56. 2
      msa/js-executor/pom.xml
  57. 30
      msa/js-executor/yarn.lock
  58. 2
      msa/monitoring/pom.xml
  59. 4
      msa/pom.xml
  60. 2
      msa/tb-node/pom.xml
  61. 2
      msa/tb/pom.xml
  62. 2
      msa/transport/coap/pom.xml
  63. 2
      msa/transport/http/pom.xml
  64. 2
      msa/transport/lwm2m/pom.xml
  65. 2
      msa/transport/mqtt/pom.xml
  66. 2
      msa/transport/pom.xml
  67. 2
      msa/transport/snmp/pom.xml
  68. 2
      msa/vc-executor-docker/pom.xml
  69. 2
      msa/vc-executor/pom.xml
  70. 2
      msa/vc-executor/src/main/resources/tb-vc-executor.yml
  71. 5
      msa/web-ui/package.json
  72. 2
      msa/web-ui/pom.xml
  73. 30
      msa/web-ui/yarn.lock
  74. 4
      netty-mqtt/pom.xml
  75. 30
      pom.xml
  76. 2
      rest-client/pom.xml
  77. 34
      rest-client/src/main/resources/logback.xml
  78. 2
      rule-engine/pom.xml
  79. 2
      rule-engine/rule-engine-api/pom.xml
  80. 2
      rule-engine/rule-engine-components/pom.xml
  81. 2
      tools/pom.xml
  82. 2
      transport/coap/pom.xml
  83. 2
      transport/coap/src/main/resources/tb-coap-transport.yml
  84. 2
      transport/http/pom.xml
  85. 2
      transport/http/src/main/resources/tb-http-transport.yml
  86. 2
      transport/lwm2m/pom.xml
  87. 2
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
  88. 2
      transport/mqtt/pom.xml
  89. 2
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  90. 2
      transport/pom.xml
  91. 2
      transport/snmp/pom.xml
  92. 2
      transport/snmp/src/main/resources/tb-snmp-transport.yml
  93. 19
      ui-ngx/package.json
  94. 2
      ui-ngx/patches/@angular+build+20.3.16.patch
  95. 31
      ui-ngx/patches/ngx-hm-carousel+19.0.0.patch
  96. 2
      ui-ngx/pom.xml
  97. 2
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.scss
  98. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.scss
  99. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings.component.ts
  100. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings.component.ts

4
.github/release.yml

@ -19,6 +19,10 @@ changelog:
labels:
- Ignore for release
categories:
- title: 'Security'
labels:
- 'Security'
- title: 'Major Core & Rule Engine'
labels:
- 'Major Core'

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>

16
application/src/main/java/org/thingsboard/server/service/sync/DefaultGitSyncService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.sync;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.revwalk.RevCommit;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardExecutors;
@ -47,6 +48,8 @@ public class DefaultGitSyncService implements GitSyncService {
private final Map<String, GitRepository> repositories = new ConcurrentHashMap<>();
private final Map<String, Runnable> updateListeners = new ConcurrentHashMap<>();
private RevCommit lastCommit;
@Override
public void registerSync(String key, String repoUri, String branch, long fetchFrequencyMs, Runnable onUpdate) {
RepositorySettings settings = new RepositorySettings();
@ -84,7 +87,7 @@ public class DefaultGitSyncService implements GitSyncService {
@Override
public List<RepoFile> listFiles(String key, String path, int depth, FileType type) {
GitRepository repository = getRepository(key);
return repository.listFilesAtCommit(getBranchRef(repository), path, depth).stream()
return repository.listFilesAtCommit(lastCommit, path, depth).stream()
.filter(file -> type == null || file.type() == type)
.toList();
}
@ -93,7 +96,7 @@ public class DefaultGitSyncService implements GitSyncService {
@Override
public byte[] getFileContent(String key, String path) {
GitRepository repository = getRepository(key);
return repository.getFileContentAtCommit(path, getBranchRef(repository));
return repository.getFileContentAtCommit(path, lastCommit);
}
@Override
@ -137,6 +140,15 @@ public class DefaultGitSyncService implements GitSyncService {
}
private void onUpdate(String key) {
GitRepository repository = getRepository(key);
String branchRef = getBranchRef(repository);
try {
lastCommit = repository.resolveCommit(branchRef);
} catch (Throwable e) {
log.error("[{}] Failed to resolve commit for ref {}", key, branchRef, e);
return;
}
Runnable listener = updateListeners.get(key);
if (listener != null) {
log.debug("[{}] Handling repository update", key);

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

@ -208,7 +208,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-4.2.1}"
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-4.2.2}"
# Database telemetry parameters
database:
@ -330,6 +330,8 @@ cassandra:
set_null_values_enabled: "${CASSANDRA_QUERY_SET_NULL_VALUES_ENABLED:true}"
# log one of cassandra queries with specified frequency (0 - logging is disabled)
print_queries_freq: "${CASSANDRA_QUERY_PRINT_FREQ:0}"
# Maximum total size in bytes of a Cassandra query result set across all pages. Default is 50MB. 0 means unlimited
max_result_set_size_in_bytes: "${CASSANDRA_QUERY_MAX_RESULT_SET_SIZE_IN_BYTES:52428800}"
tenant_rate_limits:
# Whether to print rate-limited tenant names when printing Cassandra query queue statistic
print_tenant_names: "${CASSANDRA_QUERY_TENANT_RATE_LIMITS_PRINT_TENANT_NAMES:false}"
@ -2011,7 +2013,7 @@ management:
web:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
include: "${METRICS_ENDPOINTS_EXPOSE:info}"
health:
elasticsearch:
# Enable the org.springframework.boot.actuate.elasticsearch.ElasticsearchRestClientHealthIndicator.doHealthCheck

34
application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.script;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.tbel.TbDate;
@ -779,7 +780,7 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
assertEquals(expected.toString(), actual.toString());
}
@Test
@Test
public void setsCreateNewSetFromCreateSetTbMethod_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["B", "A", "C", "A"]}
@ -800,7 +801,7 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
assertEquals(expected.toString(), actual.toString());
}
@Test
@Test
public void setsForeachForLoop_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["A", "B", "C"]}
@ -975,13 +976,13 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
ArrayList<Object> listSortDesc = new ArrayList<>(List.of("hello", "C", "B", "A", 34567, 34));
Set<Object> expectedDesc = new LinkedHashSet<>(listSortDesc);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>)actual).get("set1").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>)actual).get("set1_asc").toString());
assertEquals(expectedDesc.toString(), ((LinkedHashMap<?, ?>)actual).get("set1_desc").toString());
assertEquals(expected.toString(), ((LinkedHashMap<?, ?>)actual).get("set2").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>)actual).get("set3").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>)actual).get("set3_asc").toString());
assertEquals(expectedDesc.toString(), ((LinkedHashMap<?, ?>)actual).get("set3_desc").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>) actual).get("set1").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>) actual).get("set1_asc").toString());
assertEquals(expectedDesc.toString(), ((LinkedHashMap<?, ?>) actual).get("set1_desc").toString());
assertEquals(expected.toString(), ((LinkedHashMap<?, ?>) actual).get("set2").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>) actual).get("set3").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>) actual).get("set3_asc").toString());
assertEquals(expectedDesc.toString(), ((LinkedHashMap<?, ?>) actual).get("set3_desc").toString());
}
@Test
@ -1002,9 +1003,9 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
List<Object> listOrigin = new ArrayList<>(List.of("C", "B", "A", 34567, "B", "C", "hello", 34));
Set<Object> expectedSet = new LinkedHashSet<>(listOrigin);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expectedSet.toString(), ((LinkedHashMap<?, ?>)actual).get("set1").toString());
assertEquals(true, ((LinkedHashMap<?, ?>)actual).get("result1"));
assertEquals(false, ((LinkedHashMap<?, ?>)actual).get("result2"));
assertEquals(expectedSet.toString(), ((LinkedHashMap<?, ?>) actual).get("set1").toString());
assertEquals(true, ((LinkedHashMap<?, ?>) actual).get("result1"));
assertEquals(false, ((LinkedHashMap<?, ?>) actual).get("result2"));
}
@Test
@ -1025,9 +1026,9 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
Set<Object> expectedSet = new LinkedHashSet<>(listOrigin);
List<Object> expectedToList = new ArrayList<>(expectedSet);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(listOrigin.toString(), ((LinkedHashMap<?, ?>)actual).get("list").toString());
assertEquals(expectedSet.toString(), ((LinkedHashMap<?, ?>)actual).get("set1").toString());
assertEquals(expectedToList.toString(), ((LinkedHashMap<?, ?>)actual).get("tolist").toString());
assertEquals(listOrigin.toString(), ((LinkedHashMap<?, ?>) actual).get("list").toString());
assertEquals(expectedSet.toString(), ((LinkedHashMap<?, ?>) actual).get("set1").toString());
assertEquals(expectedToList.toString(), ((LinkedHashMap<?, ?>) actual).get("tolist").toString());
}
@Test
@ -1713,7 +1714,7 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
assertEquals(expected, actual);
}
@Test
@RepeatedTest(value = 3, name = "{displayName} {currentRepetition}/{totalRepetitions}")
public void parseBytes_Test() throws ExecutionException, InterruptedException {
byte[] bytesExecutionArrayList = new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD};
msgStr = "{}";
@ -2821,5 +2822,6 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
}
return list;
}
}

52
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java

@ -140,45 +140,61 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
}
private void startDownloading() {
long delay = 0;
// Step 1: state = 1
scheduler.schedule(() -> {
try {
state.set(1);
fireResourceChange(3);
Thread.sleep(100);
state.set(2);
fireResourceChange(3);
} catch (Exception e) {
}
}, 100, TimeUnit.MILLISECONDS);
state.set(1);
fireResourceChange(3);
log.info("Downloading started: state=[{}]", state.get());
}, delay, TimeUnit.MILLISECONDS);
delay += 100; // next step after 100 ms
// Step 2: state = 2
scheduler.schedule(() -> {
state.set(2);
fireResourceChange(3);
log.info("Downloading in progress: state=[{}]", state.get());
}, delay, TimeUnit.MILLISECONDS);
}
private void startUpdating(LwM2mServer identity) {
scheduler.schedule(() -> {
try {
// Update state + result
state.set(3);
fireResourceChange(3);
Thread.sleep(100);
updateResult.set(1);
fireResourceChange(5);
this.pkgName = TITLE;
fireResourceChange(6);
this.pkgVersion = TARGET_FW_VERSION;
fireResourceChange(7);
if (this.leshanClient != null) {
log.info("Stop/reboot LwM2M client {}", this.leshanClient.getEndpoint(identity));
this.leshanClient.stop(false);
log.info("Start after update fw LwM2M client {}", this.leshanClient.getEndpoint(identity));
this.leshanClient.start();
this.pkgName = this.pkgNameDef;
this.pkgVersion = this.pkgVersionDef;
// Delayed reset pkgName/pkgVersion, after reboot + registration
scheduler.schedule(() -> {
this.pkgName = this.pkgNameDef;
fireResourceChange(6);
this.pkgVersion = this.pkgVersionDef;
fireResourceChange(7);
log.info("FW resources updating to new values: pkgName=[{}], pkgVersion=[{}]",
this.pkgName, this.pkgVersion);
}, 15, TimeUnit.SECONDS); // 15 sec — safe timing
}
} catch (Exception e) {
log.error("Error during firmware update", e);
}
}, 100, TimeUnit.MILLISECONDS);
}, 0, TimeUnit.SECONDS); // start immediately, without further delay
}
protected void setLeshanClient(LeshanClient leshanClient) {
this.leshanClient = leshanClient;
}
}

60
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java

@ -33,6 +33,8 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.thingsboard.server.controller.AbstractWebTest.TIMEOUT;
@Slf4j
public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
@ -85,10 +87,7 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId);
switch (resourceId) {
case 2:
startDownloading();
return WriteResponse.success();
case 3:
case 2, 3:
startDownloading();
return WriteResponse.success();
default:
@ -123,25 +122,34 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
}
private void startDownloading() {
long delay = 0;
// Step 1: start downloading
scheduler.schedule(() -> {
try {
state.set(1);
updateResult.set(1);
fireResourceChange(7);
fireResourceChange(9);
Thread.sleep(100);
state.set(2);
fireResourceChange(7);
Thread.sleep(100);
state.set(3);
fireResourceChange(7);
Thread.sleep(100);
updateResult.set(3);
fireResourceChange(9);
} catch (Exception e) {
}
}, 100, TimeUnit.MILLISECONDS);
state.set(1);
updateResult.set(1);
fireResourceChange(7);
fireResourceChange(9);
}, delay, TimeUnit.MILLISECONDS);
delay += 100;
// Step 2: downloading in progress
scheduler.schedule(() -> {
state.set(2);
fireResourceChange(7);
}, delay, TimeUnit.MILLISECONDS);
delay += 100;
// Step 3: downloading finished
scheduler.schedule(() -> {
state.set(3);
fireResourceChange(7);
updateResult.set(3);
fireResourceChange(9);
}, delay, TimeUnit.MILLISECONDS);
}
private void startUpdating() {
@ -150,7 +158,13 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
updateResult.set(2);
fireResourceChange(7);
fireResourceChange(9);
// Optional: delayed log about FW update
scheduler.schedule(() -> {
log.info("FW resources updating to new values: state=[{}], updateResult=[{}]",
state.get(), updateResult.get());
}, 500, TimeUnit.MILLISECONDS);
}, 100, TimeUnit.MILLISECONDS);
}
}

15
application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/AbstractMqttV5ClientSparkplugTest.java

@ -159,14 +159,17 @@ public abstract class AbstractMqttV5ClientSparkplugTest extends AbstractMqttInte
protected List<Device> connectClientWithCorrectAccessTokenWithNDEATHCreatedDevices(int cntDevices, long ts) throws Exception {
List<Device> devices = new ArrayList<>();
clientWithCorrectNodeAccessTokenWithNDEATH();
MetricDataType metricDataType = Int32;
String key = "Node Metric int32";
String keyInt = "Node Metric int32";
int valueDeviceInt32 = 1024;
SparkplugBProto.Payload.Metric metric = createMetric(valueDeviceInt32, ts, key, metricDataType, -1L);
SparkplugBProto.Payload.Metric metricInt = createMetric(valueDeviceInt32, ts, keyInt, Int32, -1L);
String keyStringEmpty = "Node Metric String Empty";
String valueDeviceStringEmpty = "";
SparkplugBProto.Payload.Metric metricStringEmpty = createMetric(valueDeviceStringEmpty, ts, keyStringEmpty, MetricDataType.String, -1L);
SparkplugBProto.Payload.Builder payloadBirthNode = SparkplugBProto.Payload.newBuilder()
.setTimestamp(ts)
.setSeq(getBdSeqNum());
payloadBirthNode.addMetrics(metric);
payloadBirthNode.addMetrics(metricInt);
payloadBirthNode.addMetrics(metricStringEmpty);
payloadBirthNode.setTimestamp(ts);
if (client.isConnected()) {
client.publish(TOPIC_ROOT_SPB_V_1_0 + "/" + groupId + "/" + SparkplugMessageType.NBIRTH.name() + "/" + edgeNode,
@ -174,14 +177,14 @@ public abstract class AbstractMqttV5ClientSparkplugTest extends AbstractMqttInte
}
valueDeviceInt32 = 4024;
metric = createMetric(valueDeviceInt32, ts, metricBirthName_Int32, metricBirthDataType_Int32, -1L);
metricInt = createMetric(valueDeviceInt32, ts, metricBirthName_Int32, metricBirthDataType_Int32, -1L);
for (int i = 0; i < cntDevices; i++) {
SparkplugBProto.Payload.Builder payloadBirthDevice = SparkplugBProto.Payload.newBuilder()
.setTimestamp(ts)
.setSeq(getSeqNum());
String deviceName = deviceId + "_" + i;
payloadBirthDevice.addMetrics(metric);
payloadBirthDevice.addMetrics(metricInt);
if (client.isConnected()) {
client.publish(TOPIC_ROOT_SPB_V_1_0 + "/" + groupId + "/" + SparkplugMessageType.DBIRTH.name() + "/" + edgeNode + "/" + deviceName,
payloadBirthDevice.build().toByteArray(), 0, false);

2
common/actor/pom.xml

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

2
common/cache/pom.xml

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

2
common/cluster-api/pom.xml

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

2
common/coap-server/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/dao-api/pom.xml

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

32
common/dao-api/src/main/java/org/thingsboard/server/dao/nosql/ResultSetSizeLimitExceededException.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2026 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.nosql;
import lombok.Getter;
@Getter
public class ResultSetSizeLimitExceededException extends IllegalArgumentException {
private final long limitBytes;
private final long actualBytes;
public ResultSetSizeLimitExceededException(long limitBytes, long actualBytes) {
super("Result set size exceeds the maximum allowed limit. Please narrow your query");
this.limitBytes = limitBytes;
this.actualBytes = actualBytes;
}
}

24
common/dao-api/src/main/java/org/thingsboard/server/dao/nosql/TbResultSet.java

@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
public class TbResultSet implements AsyncResultSet {
@ -89,9 +90,14 @@ public class TbResultSet implements AsyncResultSet {
}
public ListenableFuture<List<Row>> allRows(Executor executor) {
return allRows(executor, 0);
}
public ListenableFuture<List<Row>> allRows(Executor executor, long maxResultSetSizeBytes) {
List<Row> allRows = new ArrayList<>();
SettableFuture<List<Row>> resultFuture = SettableFuture.create();
this.processRows(originalStatement, delegate, allRows, resultFuture, executor);
AtomicLong accumulatedBytes = new AtomicLong(0);
this.processRows(originalStatement, delegate, allRows, resultFuture, executor, maxResultSetSizeBytes, accumulatedBytes);
return resultFuture;
}
@ -99,7 +105,19 @@ public class TbResultSet implements AsyncResultSet {
AsyncResultSet resultSet,
List<Row> allRows,
SettableFuture<List<Row>> resultFuture,
Executor executor) {
Executor executor,
long maxResultSetSizeBytes,
AtomicLong accumulatedBytes) {
if (maxResultSetSizeBytes > 0) {
int pageSizeInBytes = resultSet.getExecutionInfo().getResponseSizeInBytes();
if (pageSizeInBytes > 0) {
accumulatedBytes.addAndGet(pageSizeInBytes);
}
if (accumulatedBytes.get() > maxResultSetSizeBytes) {
resultFuture.setException(new ResultSetSizeLimitExceededException(maxResultSetSizeBytes, accumulatedBytes.get()));
return;
}
}
allRows.addAll(loadRows(resultSet));
if (resultSet.hasMorePages()) {
ByteBuffer nextPagingState = resultSet.getExecutionInfo().getPagingState();
@ -110,7 +128,7 @@ public class TbResultSet implements AsyncResultSet {
@Override
public void onSuccess(@Nullable TbResultSet result) {
processRows(nextStatement, result,
allRows, resultFuture, executor);
allRows, resultFuture, executor, maxResultSetSizeBytes, accumulatedBytes);
}
@Override

140
common/dao-api/src/test/java/org/thingsboard/server/dao/nosql/TbResultSetTest.java

@ -0,0 +1,140 @@
/**
* Copyright © 2016-2026 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.nosql;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.ColumnDefinitions;
import com.datastax.oss.driver.api.core.cql.ExecutionInfo;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.cql.Statement;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import org.junit.jupiter.api.Test;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class TbResultSetTest {
@Test
void allRows_withinLimit_returnsAllRows() throws Exception {
Row row = mock(Row.class);
AsyncResultSet asyncResultSet = createMockResultSet(List.of(row), false, 1000);
Statement<?> statement = mock(Statement.class);
TbResultSet tbResultSet = new TbResultSet(statement, asyncResultSet, s -> null);
ListenableFuture<List<Row>> future = tbResultSet.allRows(MoreExecutors.directExecutor(), 5000);
List<Row> result = future.get();
assertThat(result).hasSize(1);
assertThat(result.get(0)).isSameAs(row);
}
@Test
void allRows_exceedsLimitOnFirstPage_failsWithException() {
Row row = mock(Row.class);
AsyncResultSet asyncResultSet = createMockResultSet(List.of(row), false, 6000);
Statement<?> statement = mock(Statement.class);
TbResultSet tbResultSet = new TbResultSet(statement, asyncResultSet, s -> null);
ListenableFuture<List<Row>> future = tbResultSet.allRows(MoreExecutors.directExecutor(), 5000);
assertThatThrownBy(future::get)
.isInstanceOf(ExecutionException.class)
.hasCauseInstanceOf(ResultSetSizeLimitExceededException.class);
}
@Test
void allRows_exceedsLimitOnSecondPage_failsAfterSecondPage() {
Row row1 = mock(Row.class);
Row row2 = mock(Row.class);
Statement<?> statement = mock(Statement.class);
doReturn(statement).when(statement).setPagingState((ByteBuffer) null);
AsyncResultSet page2 = createMockResultSet(List.of(row2), false, 3000);
TbResultSet tbResultSetPage2 = new TbResultSet(statement, page2, s -> null);
SettableFuture<TbResultSet> page2Future = SettableFuture.create();
page2Future.set(tbResultSetPage2);
TbResultSetFuture tbPage2Future = new TbResultSetFuture(page2Future);
ExecutionInfo page1ExecInfo = mock(ExecutionInfo.class);
when(page1ExecInfo.getResponseSizeInBytes()).thenReturn(3000);
when(page1ExecInfo.getPagingState()).thenReturn(null);
AsyncResultSet page1 = createMockResultSet(List.of(row1), true, 3000);
when(page1.getExecutionInfo()).thenReturn(page1ExecInfo);
Function<Statement, TbResultSetFuture> executeAsync = s -> tbPage2Future;
TbResultSet tbResultSet = new TbResultSet(statement, page1, executeAsync);
ListenableFuture<List<Row>> future = tbResultSet.allRows(MoreExecutors.directExecutor(), 5000);
assertThatThrownBy(future::get)
.isInstanceOf(ExecutionException.class)
.hasCauseInstanceOf(ResultSetSizeLimitExceededException.class);
}
@Test
void allRows_unlimitedWithZero_returnsAllRowsRegardlessOfSize() throws Exception {
Row row = mock(Row.class);
AsyncResultSet asyncResultSet = createMockResultSet(List.of(row), false, 999999);
Statement<?> statement = mock(Statement.class);
TbResultSet tbResultSet = new TbResultSet(statement, asyncResultSet, s -> null);
ListenableFuture<List<Row>> future = tbResultSet.allRows(MoreExecutors.directExecutor(), 0);
List<Row> result = future.get();
assertThat(result).hasSize(1);
}
@Test
void allRows_noLimitOverload_returnsAllRows() throws Exception {
Row row = mock(Row.class);
AsyncResultSet asyncResultSet = createMockResultSet(List.of(row), false, 999999);
Statement<?> statement = mock(Statement.class);
TbResultSet tbResultSet = new TbResultSet(statement, asyncResultSet, s -> null);
ListenableFuture<List<Row>> future = tbResultSet.allRows(MoreExecutors.directExecutor());
List<Row> result = future.get();
assertThat(result).hasSize(1);
}
private AsyncResultSet createMockResultSet(List<Row> rows, boolean hasMorePages, int responseSizeInBytes) {
AsyncResultSet resultSet = mock(AsyncResultSet.class);
ExecutionInfo executionInfo = mock(ExecutionInfo.class);
ColumnDefinitions columnDefs = mock(ColumnDefinitions.class);
when(executionInfo.getResponseSizeInBytes()).thenReturn(responseSizeInBytes);
when(executionInfo.getPagingState()).thenReturn(null);
when(resultSet.getExecutionInfo()).thenReturn(executionInfo);
when(resultSet.getColumnDefinitions()).thenReturn(columnDefs);
when(resultSet.currentPage()).thenReturn(rows);
when(resultSet.hasMorePages()).thenReturn(hasMorePages);
when(resultSet.remaining()).thenReturn(rows.size());
return resultSet;
}
}

2
common/data/pom.xml

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

2
common/discovery-api/pom.xml

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

2
common/edge-api/pom.xml

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

4
common/edge-api/src/main/proto/edge.proto

@ -45,9 +45,9 @@ enum EdgeVersion {
V_4_1_0 = 11;
V_4_2_0 = 12;
V_4_2_1_2 = 14;
V_4_2_1_3 = 420;
V_4_2_2 = 4220;
V_LATEST = 999;
V_LATEST = 99999;
}
/**

2
common/edqs/pom.xml

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

2
common/message/pom.xml

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

4
common/message/src/test/java/org/thingsboard/server/common/msg/tools/TbRateLimitsTest.java

@ -42,7 +42,7 @@ public class TbRateLimitsTest {
assertThat(rateLimits.tryConsume()).as("new token is available").isFalse();
int expectedRefillTime = (int) (((double) period / capacity) * 1000);
int gap = 500;
int gap = 1000;
for (int i = 0; i < capacity; i++) {
await("token refill for rate limit " + rateLimitConfig)
@ -71,7 +71,7 @@ public class TbRateLimitsTest {
assertThat(rateLimits.tryConsume()).as("new token is available").isFalse();
int expectedRefillTime = period * 1000;
int gap = 500;
int gap = 1000;
await("tokens refill for rate limit " + rateLimitConfig)
.pollInterval(new FixedPollInterval(10, TimeUnit.MILLISECONDS))

2
common/pom.xml

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

2
common/proto/pom.xml

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

4
common/proto/src/main/java/org/thingsboard/server/common/adaptor/ProtoConverter.java

@ -156,11 +156,7 @@ public class ProtoConverter {
case BOOLEAN_V:
case LONG_V:
case DOUBLE_V:
break;
case STRING_V:
if (StringUtils.isEmpty(keyValueProto.getStringV())) {
throw new IllegalArgumentException("Value is empty for key: " + key + "!");
}
break;
case JSON_V:
try {

2
common/queue/pom.xml

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

2
common/script/pom.xml

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

2
common/script/remote-js-client/pom.xml

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

2
common/script/script-api/pom.xml

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

8
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java

@ -898,11 +898,11 @@ public class TbUtils {
public static int parseBytesToInt(byte[] data, int offset, int length, boolean bigEndian) {
validationNumberByLength(data, offset, length, BYTES_LEN_INT_MAX);
var bb = ByteBuffer.allocate(4);
var bb = ByteBuffer.allocate(BYTES_LEN_INT_MAX);
if (!bigEndian) {
bb.order(ByteOrder.LITTLE_ENDIAN);
}
bb.position(bigEndian ? 4 - length : 0);
bb.position(bigEndian ? BYTES_LEN_INT_MAX - length : 0);
bb.put(data, offset, length);
bb.position(0);
return bb.getInt();
@ -923,11 +923,11 @@ public class TbUtils {
public static long parseBytesToUnsignedInt(byte[] data, int offset, int length, boolean bigEndian) {
validationNumberByLength(data, offset, length, BYTES_LEN_INT_MAX);
ByteBuffer bb = ByteBuffer.allocate(8);
ByteBuffer bb = ByteBuffer.allocate(BYTES_LEN_LONG_MAX);
if (!bigEndian) {
bb.order(ByteOrder.LITTLE_ENDIAN);
}
bb.position(bigEndian ? 8 - length : 0);
bb.position(bigEndian ? BYTES_LEN_LONG_MAX - length : 0);
bb.put(data, offset, length);
bb.position(0);

95
common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java

@ -155,6 +155,101 @@ public class TbUtilsTest {
Assertions.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 3, false));
}
@Test
public void parseBytesToInt_doesNotChangeInputData() {
byte[] data = new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD};
byte[] copy = data.clone();
TbUtils.parseBytesToInt(data, 0, 4, true);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesToInt(data, 0, 4, false);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesToUnsignedInt(data, 0, 4, true);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesToUnsignedInt(data, 0, 4, false);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesToLong(data, 0, 4, true);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesToLong(data, 0, 4, false);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesToFloat(data, 0, 4, true);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesToFloat(data, 0, 4, false);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesIntToFloat(data, 0, 4, true);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesIntToFloat(data, 0, 4, false);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesToDouble(data, 0, 4, true);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesToDouble(data, 0, 4, false);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesLongToDouble(data, 0, 4, true);
Assertions.assertArrayEquals(copy, data);
TbUtils.parseBytesLongToDouble(data, 0, 4, false);
Assertions.assertArrayEquals(copy, data);
List<Byte> listData = toList(new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD});
List<Byte> listCopy = new ArrayList<>(listData);
TbUtils.parseBytesToInt(listData, 0, 4, true);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesToInt(listData, 0, 4, false);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesToUnsignedInt(listData, 0, 4, true);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesToUnsignedInt(listData, 0, 4, false);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesToLong(listData, 0, 4, true);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesToLong(listData, 0, 4, false);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesToFloat(listData, 0, 4, true);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesToFloat(listData, 0, 4, false);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesIntToFloat(listData, 0, 4, true);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesIntToFloat(listData, 0, 4, false);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesToDouble(listData, 0, 4, true);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesToDouble(listData, 0, 4, false);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesLongToDouble(listData, 0, 4, true);
Assertions.assertEquals(listCopy, listData);
TbUtils.parseBytesLongToDouble(listData, 0, 4, false);
Assertions.assertEquals(listCopy, listData);
}
@Test
public void compare_parseBytesToInt_and_parseBytesToUnsignedInt() {
byte[] data = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
// 4 bytes: parseBytesToInt returns -1, parseBytesToUnsignedInt returns 4294967295L
Assertions.assertEquals(-1, TbUtils.parseBytesToInt(data, 0, 4, true));
Assertions.assertEquals(4294967295L, TbUtils.parseBytesToUnsignedInt(data, 0, 4, true));
// 2 bytes (0xFFFF): both return 65535 (no sign extension for parseBytesToInt when length < 4)
Assertions.assertEquals(65535, TbUtils.parseBytesToInt(data, 0, 2, true));
Assertions.assertEquals(65535L, TbUtils.parseBytesToUnsignedInt(data, 0, 2, true));
// 2 bytes with high bit set (0x8000)
byte[] data2 = new byte[]{(byte) 0x80, (byte) 0x00};
Assertions.assertEquals(32768, TbUtils.parseBytesToInt(data2, 0, 2, true));
Assertions.assertEquals(32768L, TbUtils.parseBytesToUnsignedInt(data2, 0, 2, true));
}
@Test
public void toFlatMap() {
ExecutionHashMap<String, Object> inputMap = new ExecutionHashMap<>(16, ctx);

2
common/stats/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/transport/coap/pom.xml

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

2
common/transport/http/pom.xml

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

2
common/transport/lwm2m/pom.xml

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

2
common/transport/mqtt/pom.xml

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

2
common/transport/pom.xml

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

2
common/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard.common</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>

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

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

2
common/util/pom.xml

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

2
common/version-control/pom.xml

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

35
common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java

@ -277,13 +277,17 @@ public class GitRepository {
return listFilesAtCommit(commitId, path, -1).stream().map(RepoFile::path).toList();
}
@SneakyThrows
public List<RepoFile> listFilesAtCommit(String commitId, String path, int depth) {
log.debug("Executing listFilesAtCommit [{}][{}][{}]", settings.getRepositoryUri(), commitId, path);
RevCommit commit = resolveCommit(commitId);
return listFilesAtCommit(commit, path, depth);
}
@SneakyThrows
public List<RepoFile> listFilesAtCommit(RevCommit commit, String path, int depth) {
log.debug("Executing listFilesAtCommit [{}][{}][{}]", settings.getRepositoryUri(), commit, path);
List<RepoFile> files = new ArrayList<>();
RevCommit revCommit = resolveCommit(commitId);
try (TreeWalk treeWalk = new TreeWalk(git.getRepository())) {
treeWalk.reset(revCommit.getTree().getId());
treeWalk.reset(commit.getTree().getId());
if (StringUtils.isNotEmpty(path)) {
treeWalk.setFilter(PathFilter.create(path));
}
@ -301,11 +305,15 @@ public class GitRepository {
return files;
}
@SneakyThrows
public byte[] getFileContentAtCommit(String file, String commitId) {
log.debug("Executing getFileContentAtCommit [{}][{}][{}]", settings.getRepositoryUri(), commitId, file);
RevCommit revCommit = resolveCommit(commitId);
try (TreeWalk treeWalk = TreeWalk.forPath(git.getRepository(), file, revCommit.getTree())) {
RevCommit commit = resolveCommit(commitId);
return getFileContentAtCommit(file, commit);
}
@SneakyThrows
public byte[] getFileContentAtCommit(String file, RevCommit commit) {
log.debug("Executing getFileContentAtCommit [{}][{}][{}]", settings.getRepositoryUri(), commit, file);
try (TreeWalk treeWalk = TreeWalk.forPath(git.getRepository(), file, commit.getTree())) {
if (treeWalk == null) {
throw new IllegalArgumentException("File not found");
}
@ -373,9 +381,9 @@ public class GitRepository {
for (RemoteRefUpdate update : pushResult.getRemoteUpdates()) {
RemoteRefUpdate.Status status = update.getStatus();
if (status == REJECTED_NONFASTFORWARD || status == REJECTED_NODELETE ||
status == REJECTED_REMOTE_CHANGED || status == REJECTED_OTHER_REASON) {
status == REJECTED_REMOTE_CHANGED || status == REJECTED_OTHER_REASON) {
throw new RuntimeException("Remote repository answered with error: " +
Optional.ofNullable(update.getMessage()).orElseGet(status::name));
Optional.ofNullable(update.getMessage()).orElseGet(status::name));
}
}
});
@ -450,7 +458,8 @@ public class GitRepository {
revCommit.getFullMessage(), revCommit.getAuthorIdent().getName(), revCommit.getAuthorIdent().getEmailAddress());
}
private RevCommit resolveCommit(String id) throws IOException {
@SneakyThrows
public RevCommit resolveCommit(String id) {
return git.getRepository().parseCommit(resolve(id));
}
@ -481,8 +490,8 @@ public class GitRepository {
private static final Function<PageLink, Comparator<RevCommit>> revCommitComparatorFunction = pageLink -> {
SortOrder sortOrder = pageLink.getSortOrder();
if (sortOrder != null
&& sortOrder.getProperty().equals("timestamp")
&& SortOrder.Direction.ASC.equals(sortOrder.getDirection())) {
&& sortOrder.getProperty().equals("timestamp")
&& SortOrder.Direction.ASC.equals(sortOrder.getDirection())) {
return Comparator.comparingInt(RevCommit::getCommitTime);
}
return null;

2
dao/pom.xml

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

3
dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractAsyncDao.java

@ -37,6 +37,9 @@ public abstract class CassandraAbstractAsyncDao extends CassandraAbstractDao {
@Value("${cassandra.query.result_processing_threads:50}")
private int threadPoolSize;
@Value("${cassandra.query.max_result_set_size_in_bytes:52428800}")
protected long maxResultSetSizeBytes;
@PostConstruct
public void startExecutor() {
readResultsProcessingExecutor = ThingsBoardExecutors.newWorkStealingPool(threadPoolSize, "cassandra-callback");

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

@ -52,6 +52,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntryAggWrapper;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.nosql.ResultSetSizeLimitExceededException;
import org.thingsboard.server.dao.nosql.TbResultSet;
import org.thingsboard.server.dao.nosql.TbResultSetFuture;
import org.thingsboard.server.dao.sqlts.AggregationTimeseriesDao;
@ -256,7 +257,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), minPartition, maxPartition, t);
log.error("[{}][{}][{}] Failed to fetch partitions for interval {}-{}", tenantId, entityId.getEntityType(), entityId.getId(), minPartition, maxPartition, t);
resultFuture.setException(t);
}
}, readResultsProcessingExecutor);
return resultFuture;
@ -330,7 +332,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), toPartitionTs(query.getStartTs()), toPartitionTs(query.getEndTs()), t);
log.error("[{}][{}][{}] Failed to fetch partitions for interval {}-{}", tenantId, entityId.getEntityType(), entityId.getId(), toPartitionTs(query.getStartTs()), toPartitionTs(query.getEndTs()), t);
resultFuture.setException(t);
}
}, readResultsProcessingExecutor);
@ -372,8 +375,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
cursor.addData(convertResultToTsKvEntryList(Collections.emptyList()));
findAllAsyncSequentiallyWithLimit(tenantId, cursor, resultFuture);
} else {
Futures.addCallback(result.allRows(readResultsProcessingExecutor), new FutureCallback<List<Row>>() {
Futures.addCallback(result.allRows(readResultsProcessingExecutor, maxResultSetSizeBytes), new FutureCallback<List<Row>>() {
@Override
public void onSuccess(@Nullable List<Row> result) {
cursor.addData(convertResultToTsKvEntryList(result == null ? Collections.emptyList() : result));
@ -382,7 +384,13 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to fetch data for query {}-{}", stmt, t);
if (t instanceof ResultSetSizeLimitExceededException e) {
log.warn("[{}][{}][{}] Result set size limit exceeded for key [{}], query [{}]: {} bytes, limit {} bytes",
tenantId, cursor.getEntityType(), cursor.getEntityId(), cursor.getKey(), stmt.getPreparedStatement().getQuery(), e.getActualBytes(), e.getLimitBytes());
} else {
log.error("[{}][{}][{}] Failed to fetch data for key [{}], query [{}]", tenantId, cursor.getEntityType(), cursor.getEntityId(), cursor.getKey(), stmt.getPreparedStatement().getQuery(), t);
}
resultFuture.setException(t);
}
}, readResultsProcessingExecutor);
@ -392,7 +400,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to fetch data for query {}-{}", stmt, t);
log.error("[{}][{}][{}] Failed to fetch data for key [{}], query [{}]", tenantId, cursor.getEntityType(), cursor.getEntityId(), cursor.getKey(), stmt.getPreparedStatement().getQuery(), t);
resultFuture.setException(t);
}
}, readResultsProcessingExecutor);
}
@ -425,7 +434,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
}
if (!isUseTsKeyValuePartitioningOnRead()) {
final long estimatedPartitionCount = estimatePartitionCount(minPartition, maxPartition);
if (estimatedPartitionCount <= useTsKeyValuePartitioningOnReadMaxEstimatedPartitionCount) {
if (estimatedPartitionCount <= useTsKeyValuePartitioningOnReadMaxEstimatedPartitionCount) {
return Futures.immediateFuture(calculatePartitions(minPartition, maxPartition, (int) estimatedPartitionCount));
}
}
@ -446,7 +455,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
}
List<Long> calculatePartitions(long minPartition, long maxPartition) {
return calculatePartitions(minPartition, maxPartition, 0);
return calculatePartitions(minPartition, maxPartition, 0);
}
List<Long> calculatePartitions(long minPartition, long maxPartition, int estimatedPartitionCount) {
@ -533,6 +542,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
public void onFailure(Throwable t) {
}
}
private long computeTtl(long ttl) {
@ -569,7 +579,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to delete data for query {}-{}", stmt, t);
log.error("[{}][{}][{}] Failed to delete data for key [{}], query [{}]", tenantId, cursor.getEntityType(), cursor.getEntityId(), cursor.getKey(), stmt.getPreparedStatement().getQuery(), t);
resultFuture.setException(t);
}
}, readResultsProcessingExecutor);
}
@ -581,12 +592,12 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
try {
if (deleteStmt == null) {
deleteStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_CF +
" WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.TS_COLUMN + " >= ? "
+ "AND " + ModelConstants.TS_COLUMN + " < ?");
" WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.TS_COLUMN + " >= ? "
+ "AND " + ModelConstants.TS_COLUMN + " < ?");
}
} finally {
stmtCreationLock.unlock();
@ -661,13 +672,13 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
private String getPreparedStatementQuery(DataType type) {
return INSERT_INTO + ModelConstants.TS_KV_CF +
"(" + ModelConstants.ENTITY_TYPE_COLUMN +
"," + ModelConstants.ENTITY_ID_COLUMN +
"," + ModelConstants.KEY_COLUMN +
"," + ModelConstants.PARTITION_COLUMN +
"," + ModelConstants.TS_COLUMN +
"," + getColumnName(type) + ")" +
" VALUES(?, ?, ?, ?, ?, ?)";
"(" + ModelConstants.ENTITY_TYPE_COLUMN +
"," + ModelConstants.ENTITY_ID_COLUMN +
"," + ModelConstants.KEY_COLUMN +
"," + ModelConstants.PARTITION_COLUMN +
"," + ModelConstants.TS_COLUMN +
"," + getColumnName(type) + ")" +
" VALUES(?, ?, ?, ?, ?, ?)";
}
private String getPreparedStatementQueryWithTtl(DataType type) {
@ -680,11 +691,11 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
try {
if (partitionInsertStmt == null) {
partitionInsertStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_PARTITIONS_CF +
"(" + ModelConstants.ENTITY_TYPE_COLUMN +
"," + ModelConstants.ENTITY_ID_COLUMN +
"," + ModelConstants.PARTITION_COLUMN +
"," + ModelConstants.KEY_COLUMN + ")" +
" VALUES(?, ?, ?, ?)");
"(" + ModelConstants.ENTITY_TYPE_COLUMN +
"," + ModelConstants.ENTITY_ID_COLUMN +
"," + ModelConstants.PARTITION_COLUMN +
"," + ModelConstants.KEY_COLUMN + ")" +
" VALUES(?, ?, ?, ?)");
}
} finally {
stmtCreationLock.unlock();
@ -699,11 +710,11 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
try {
if (partitionInsertTtlStmt == null) {
partitionInsertTtlStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_PARTITIONS_CF +
"(" + ModelConstants.ENTITY_TYPE_COLUMN +
"," + ModelConstants.ENTITY_ID_COLUMN +
"," + ModelConstants.PARTITION_COLUMN +
"," + ModelConstants.KEY_COLUMN + ")" +
" VALUES(?, ?, ?, ?) USING TTL ?");
"(" + ModelConstants.ENTITY_TYPE_COLUMN +
"," + ModelConstants.ENTITY_ID_COLUMN +
"," + ModelConstants.PARTITION_COLUMN +
"," + ModelConstants.KEY_COLUMN + ")" +
" VALUES(?, ?, ?, ?) USING TTL ?");
}
} finally {
stmtCreationLock.unlock();
@ -809,16 +820,17 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
fetchStmts[type.ordinal()] = fetchStmts[Aggregation.SUM.ordinal()];
} else {
fetchStmts[type.ordinal()] = prepare(SELECT_PREFIX +
String.join(", ", ModelConstants.getFetchColumnNames(type)) + " FROM " + ModelConstants.TS_KV_CF
+ " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.TS_COLUMN + " >= ? "
+ "AND " + ModelConstants.TS_COLUMN + " < ?"
+ (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " " + orderBy + " LIMIT ?" : ""));
String.join(", ", ModelConstants.getFetchColumnNames(type)) + " FROM " + ModelConstants.TS_KV_CF
+ " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.TS_COLUMN + " >= ? "
+ "AND " + ModelConstants.TS_COLUMN + " < ?"
+ (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " " + orderBy + " LIMIT ?" : ""));
}
}
return fetchStmts;
}
}

2
dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java

@ -184,7 +184,7 @@ public class CassandraBaseTimeseriesLatestDao extends AbstractCassandraBaseTimes
}
private ListenableFuture<List<TsKvEntry>> convertAsyncResultSetToTsKvEntryList(TbResultSet rs) {
return Futures.transform(rs.allRows(readResultsProcessingExecutor),
return Futures.transform(rs.allRows(readResultsProcessingExecutor, maxResultSetSizeBytes),
rows -> this.convertResultToTsKvEntryList(rows), readResultsProcessingExecutor);
}

4
dao/src/main/java/org/thingsboard/server/dao/timeseries/SimpleListenableFuture.java

@ -26,4 +26,8 @@ public class SimpleListenableFuture<V> extends AbstractFuture<V> {
return super.set(value);
}
public boolean setException(Throwable throwable) {
return super.setException(throwable);
}
}

42
dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java

@ -16,7 +16,10 @@
package org.thingsboard.server.dao.service.timeseries.nosql;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
@ -27,9 +30,12 @@ import org.thingsboard.server.common.data.kv.JsonDataEntry;
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.dao.nosql.ResultSetSizeLimitExceededException;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.dao.service.timeseries.BaseTimeseriesServiceTest;
import org.thingsboard.server.dao.timeseries.CassandraBaseTimeseriesDao;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
@ -37,6 +43,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@ -44,6 +51,9 @@ import static org.junit.Assert.assertTrue;
@DaoNoSqlTest
public class TimeseriesServiceNoSqlTest extends BaseTimeseriesServiceTest {
@Autowired
private CassandraBaseTimeseriesDao cassandraBaseTimeseriesDao;
@Test
public void shouldSaveEntryOfEachTypeWithTtl() throws ExecutionException, InterruptedException, TimeoutException {
long ttlInSec = TimeUnit.SECONDS.toSeconds(3);
@ -94,4 +104,36 @@ public class TimeseriesServiceNoSqlTest extends BaseTimeseriesServiceTest {
double expectedValue = (doubleValue + longValue)/ 2;
assertThat(listWithAgg.get(0).getDoubleValue().get()).isEqualTo(expectedValue);
}
@Test
public void testResultSetSizeLimitExceeded() throws Exception {
DeviceId deviceId = new DeviceId(Uuids.timeBased());
String value = "x".repeat(500);
List<TsKvEntry> entries = new ArrayList<>();
for (int i = 0; i < 5; i++) {
entries.add(new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(i + 1), new StringDataEntry("bigKey", value)));
}
tsService.save(tenantId, deviceId, entries, 0).get(MAX_TIMEOUT, TimeUnit.SECONDS);
long originalLimit = (long) ReflectionTestUtils.getField(cassandraBaseTimeseriesDao, "maxResultSetSizeBytes");
try {
// Set a very low limit to trigger the exception
ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "maxResultSetSizeBytes", 1024L);
assertThatThrownBy(() -> tsService.findAll(tenantId, deviceId, Collections.singletonList(
new BaseReadTsKvQuery("bigKey", 0L, TimeUnit.MINUTES.toMillis(6), 1000, 10, Aggregation.NONE)
)).get(MAX_TIMEOUT, TimeUnit.SECONDS))
.isInstanceOf(ExecutionException.class)
.satisfies(e -> assertThat(ExceptionUtils.getRootCause(e)).isInstanceOf(ResultSetSizeLimitExceededException.class));
} finally {
ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "maxResultSetSizeBytes", originalLimit);
}
// Verify query succeeds with original limit restored
List<TsKvEntry> result = tsService.findAll(tenantId, deviceId, Collections.singletonList(
new BaseReadTsKvQuery("bigKey", 0L, TimeUnit.MINUTES.toMillis(6), 1000, 10, Aggregation.NONE)
)).get(MAX_TIMEOUT, TimeUnit.SECONDS);
assertThat(result).hasSize(5);
}
}

2
edqs/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>edqs</artifactId>

2
edqs/src/main/resources/edqs.yml

@ -210,7 +210,7 @@ management:
web:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
include: "${METRICS_ENDPOINTS_EXPOSE:info}"
health:
elasticsearch:
# Enable the org.springframework.boot.actuate.elasticsearch.ElasticsearchRestClientHealthIndicator.doHealthCheck

2
monitoring/pom.xml

@ -21,7 +21,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>

2
msa/black-box-tests/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/edqs/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

5
msa/js-executor/package.json

@ -1,7 +1,7 @@
{
"name": "thingsboard-js-executor",
"private": true,
"version": "4.2.1.3",
"version": "4.2.2",
"description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.ts",
"bin": "server.js",
@ -41,6 +41,9 @@
"ts-node": "^10.9.2",
"typescript": "5.9.2"
},
"resolutions": {
"@yao-pkg/pkg/tar": ">=7.5.8"
},
"pkg": {
"assets": [
"node_modules/config/**/*.*"

2
msa/js-executor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

30
msa/js-executor/yarn.lock

@ -1036,9 +1036,9 @@ mimic-response@^3.1.0:
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
version "3.1.3"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.3.tgz#6a5cba9b31f503887018f579c89f81f61162e624"
integrity sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==
dependencies:
brace-expansion "^1.1.7"
@ -1052,10 +1052,10 @@ minipass@^7.0.4, minipass@^7.1.2:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
minizlib@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.2.tgz#f33d638eb279f664439aa38dc5f91607468cb574"
integrity sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==
minizlib@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.1.0.tgz#6ad76c3a8f10227c9b51d1c9ac8e30b27f5a251c"
integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==
dependencies:
minipass "^7.1.2"
@ -1064,11 +1064,6 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
moment@^2.29.1:
version "2.30.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
@ -1553,16 +1548,15 @@ tar-stream@^2.1.4:
inherits "^2.0.3"
readable-stream "^3.1.1"
tar@^7.4.3:
version "7.4.3"
resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571"
integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==
tar@>=7.5.8, tar@^7.4.3:
version "7.5.9"
resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.9.tgz#817ac12a54bc4362c51340875b8985d7dc9724b8"
integrity sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==
dependencies:
"@isaacs/fs-minipass" "^4.0.0"
chownr "^3.0.0"
minipass "^7.1.2"
minizlib "^3.0.1"
mkdirp "^3.0.1"
minizlib "^3.1.0"
yallist "^5.0.0"
text-hex@1.0.x:

2
msa/monitoring/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>

4
msa/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>msa</artifactId>
@ -133,7 +133,7 @@
</property>
</activation>
<properties>
<docker.lts.tag>4.2.1-latest</docker.lts.tag>
<docker.lts.tag>4.2.2-latest</docker.lts.tag>
<docker.skip.latest.tag>true</docker.skip.latest.tag>
</properties>
<build>

2
msa/tb-node/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/tb/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard.msa</groupId>
<artifactId>transport</artifactId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/vc-executor-docker/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/vc-executor/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/vc-executor/src/main/resources/tb-vc-executor.yml

@ -232,7 +232,7 @@ management:
web:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
include: "${METRICS_ENDPOINTS_EXPOSE:info}"
# Service common properties
service:

5
msa/web-ui/package.json

@ -1,7 +1,7 @@
{
"name": "thingsboard-web-ui",
"private": true,
"version": "4.2.1.3",
"version": "4.2.2",
"description": "ThingsBoard Web UI Microservice",
"main": "server.ts",
"bin": "server.js",
@ -44,6 +44,9 @@
"ts-node": "^10.9.2",
"typescript": "5.9.2"
},
"resolutions": {
"@yao-pkg/pkg/tar": ">=7.5.8"
},
"pkg": {
"assets": [
"node_modules/config/**/*.*"

2
msa/web-ui/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

30
msa/web-ui/yarn.lock

@ -1098,9 +1098,9 @@ mimic-response@^3.1.0:
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
version "3.1.3"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.3.tgz#6a5cba9b31f503887018f579c89f81f61162e624"
integrity sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==
dependencies:
brace-expansion "^1.1.7"
@ -1114,10 +1114,10 @@ minipass@^7.0.4, minipass@^7.1.2:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
minizlib@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.2.tgz#f33d638eb279f664439aa38dc5f91607468cb574"
integrity sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==
minizlib@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.1.0.tgz#6ad76c3a8f10227c9b51d1c9ac8e30b27f5a251c"
integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==
dependencies:
minipass "^7.1.2"
@ -1126,11 +1126,6 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
moment@^2.29.1:
version "2.30.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
@ -1635,16 +1630,15 @@ tar-stream@^2.1.4:
inherits "^2.0.3"
readable-stream "^3.1.1"
tar@^7.4.3:
version "7.4.3"
resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571"
integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==
tar@>=7.5.8, tar@^7.4.3:
version "7.5.9"
resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.9.tgz#817ac12a54bc4362c51340875b8985d7dc9724b8"
integrity sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==
dependencies:
"@isaacs/fs-minipass" "^4.0.0"
chownr "^3.0.0"
minipass "^7.1.2"
minizlib "^3.0.1"
mkdirp "^3.0.1"
minizlib "^3.1.0"
yallist "^5.0.0"
text-hex@1.0.x:

4
netty-mqtt/pom.xml

@ -19,11 +19,11 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>netty-mqtt</artifactId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Netty MQTT Client</name>

30
pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.thingsboard</groupId>
<artifactId>thingsboard</artifactId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Thingsboard</name>
@ -38,7 +38,8 @@
<pkg.implementationTitle>${project.name}</pkg.implementationTitle>
<pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<spring-boot.version>3.4.10</spring-boot.version>
<spring-boot.version>3.4.13</spring-boot.version>
<tomcat.version>10.1.52</tomcat.version> <!-- to fix CVE-2026-24734 and CVE-2025-66614. TODO: remove when fixed in spring-boot-dependencies -->
<javax.xml.bind-api.version>2.4.0-b180830.0359</javax.xml.bind-api.version>
<jedis.version>5.1.5</jedis.version>
<jjwt.version>0.12.5</jjwt.version>
@ -64,7 +65,7 @@
<zookeeper.version>3.9.3</zookeeper.version>
<protobuf.version>3.25.5</protobuf.version> <!-- A Major v4 does not support by the pubsub yet-->
<grpc.version>1.76.0</grpc.version>
<tbel.version>1.2.8</tbel.version>
<tbel.version>1.2.9</tbel.version>
<lombok.version>1.18.38</lombok.version>
<paho.client.version>1.2.5</paho.client.version>
<paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version>
@ -147,7 +148,6 @@
<firebase-admin.version>9.2.0</firebase-admin.version>
<snappy.version>1.1.10.5</snappy.version>
<rocksdbjni.version>9.10.0</rocksdbjni.version>
<netty.version>4.1.128.Final</netty.version> <!-- to fix CVEs. TODO: remove when fixed in spring-boot-dependencies -->
</properties>
<modules>
@ -837,6 +837,7 @@
<exclude>**/test/resources/lwm2m/**</exclude>
<exclude>**/resources/lwm2m/models/**</exclude>
<exclude>src/main/data/resources/**</exclude>
<exclude>.claude/**</exclude>
</excludes>
<mapping>
<proto>JAVADOC_STYLE</proto>
@ -899,13 +900,24 @@
<dependencyManagement>
<dependencies>
<!-- Temporary Tomcat version override -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>${netty.version}</version>
<type>pom</type>
<scope>import</scope>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>${tomcat.version}</version>
</dependency>
<!-- End of Tomcat version override -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>

2
rest-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>rest-client</artifactId>

34
rest-client/src/main/resources/logback.xml

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright © 2016-2026 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE configuration>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.thingsboard.server" level="INFO" />
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

2
rule-engine/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>rule-engine</artifactId>

2
rule-engine/rule-engine-api/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>

2
rule-engine/rule-engine-components/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>

2
tools/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>tools</artifactId>

2
transport/coap/pom.xml

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

2
transport/coap/src/main/resources/tb-coap-transport.yml

@ -435,7 +435,7 @@ management:
web:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
include: "${METRICS_ENDPOINTS_EXPOSE:info}"
# Notification system parameters
notification_system:

2
transport/http/pom.xml

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

2
transport/http/src/main/resources/tb-http-transport.yml

@ -384,7 +384,7 @@ management:
web:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
include: "${METRICS_ENDPOINTS_EXPOSE:info}"
# Notification system parameters
notification_system:

2
transport/lwm2m/pom.xml

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

2
transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

@ -485,7 +485,7 @@ management:
web:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
include: "${METRICS_ENDPOINTS_EXPOSE:info}"
# Notification system parameters
notification_system:

2
transport/mqtt/pom.xml

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

2
transport/mqtt/src/main/resources/tb-mqtt-transport.yml

@ -418,7 +418,7 @@ management:
web:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
include: "${METRICS_ENDPOINTS_EXPOSE:info}"
# Notification system parameters
notification_system:

2
transport/pom.xml

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

2
transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>

2
transport/snmp/src/main/resources/tb-snmp-transport.yml

@ -373,7 +373,7 @@ management:
web:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
include: "${METRICS_ENDPOINTS_EXPOSE:info}"
# Notification system parameters
notification_system:

19
ui-ngx/package.json

@ -1,6 +1,6 @@
{
"name": "thingsboard",
"version": "4.2.1.3",
"version": "4.2.2",
"scripts": {
"ng": "ng",
"start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open",
@ -94,11 +94,11 @@
},
"devDependencies": {
"@angular-builders/custom-esbuild": "20.0.0",
"@angular-devkit/build-angular": "20.3.15",
"@angular-devkit/core": "20.3.15",
"@angular-devkit/schematics": "20.3.15",
"@angular/build": "20.3.15",
"@angular/cli": "20.3.15",
"@angular-devkit/build-angular": "20.3.16",
"@angular-devkit/core": "20.3.16",
"@angular-devkit/schematics": "20.3.16",
"@angular/build": "20.3.16",
"@angular/cli": "20.3.16",
"@angular/compiler-cli": "20.3.16",
"@angular/language-service": "20.3.16",
"@types/ace-diff": "^2.1.4",
@ -121,7 +121,7 @@
"angular-eslint": "~20.7.0",
"autoprefixer": "^10.4.23",
"directory-tree": "^3.5.2",
"eslint": "~9.39.2",
"eslint": "9.39.3",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsdoc": "^62.4.1",
"eslint-plugin-prefer-arrow": "^1.2.3",
@ -139,6 +139,9 @@
"ace-builds": "1.43.6",
"tinymce": "6.8.6",
"@babel/core": "7.28.3",
"esbuild": "0.25.9"
"esbuild": "0.25.9",
"rollup": "4.52.3",
"jquery.terminal/**/form-data": ">=4.0.4",
"js-beautify/**/minimatch": "^9.0.6"
}
}

2
ui-ngx/patches/@angular+build+20.3.15.patch → ui-ngx/patches/@angular+build+20.3.16.patch

@ -12,7 +12,7 @@ index 53168ea..f07c80a 100755
// remove important annotations, such as /* @__PURE__ */ and comments like /* vite-ignore */.
removeComments: false,
diff --git a/node_modules/@angular/build/src/tools/esbuild/angular/compiler-plugin.js b/node_modules/@angular/build/src/tools/esbuild/angular/compiler-plugin.js
index ee68408..ac15cbf 100755
index ee68408..2667b84 100755
--- a/node_modules/@angular/build/src/tools/esbuild/angular/compiler-plugin.js
+++ b/node_modules/@angular/build/src/tools/esbuild/angular/compiler-plugin.js
@@ -90,7 +90,7 @@ function createCompilerPlugin(pluginOptions, compilationOrFactory, stylesheetBun

31
ui-ngx/patches/ngx-hm-carousel+19.0.0.patch

@ -0,0 +1,31 @@
diff --git a/node_modules/ngx-hm-carousel/fesm2022/ngx-hm-carousel.mjs b/node_modules/ngx-hm-carousel/fesm2022/ngx-hm-carousel.mjs
index 117f782..70e49a7 100644
--- a/node_modules/ngx-hm-carousel/fesm2022/ngx-hm-carousel.mjs
+++ b/node_modules/ngx-hm-carousel/fesm2022/ngx-hm-carousel.mjs
@@ -1,5 +1,5 @@
import * as i0 from '@angular/core';
-import { Directive, inject, ViewContainerRef, TemplateRef, input, PLATFORM_ID, DestroyRef, Renderer2, NgZone, ChangeDetectorRef, viewChild, ElementRef, contentChildren, contentChild, computed, signal, effect, forwardRef, Component, ChangeDetectionStrategy } from '@angular/core';
+import { Directive, inject, ViewContainerRef, TemplateRef, input, PLATFORM_ID, DestroyRef, Renderer2, NgZone, ChangeDetectorRef, viewChild, ElementRef, contentChildren, contentChild, computed, signal, effect, forwardRef, Component, ChangeDetectionStrategy, afterNextRender } from '@angular/core';
import { DOCUMENT, isPlatformBrowser, NgTemplateOutlet, AsyncPipe } from '@angular/common';
import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
@@ -340,7 +340,7 @@ class NgxHmCarouselComponent {
});
});
});
- const effectRef = effect(() => {
+ afterNextRender(() => {
this.rootElm = this.container().nativeElement;
this.containerElm = this.rootElm.children[0];
this.init();
@@ -365,10 +365,6 @@ class NgxHmCarouselComponent {
])
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe();
- // only exec once
- effectRef.destroy();
- }, {
- allowSignalWrites: true,
});
}
ngOnDestroy() {

2
ui-ngx/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>4.2.1.3-SNAPSHOT</version>
<version>4.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>

2
ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.scss

@ -43,7 +43,7 @@
}
}
> .mat-expansion-panel-content {
.mat-expansion-panel-content {
> .mat-expansion-panel-body {
padding: 0;
}

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.scss

@ -44,7 +44,7 @@
display: none;
}
}
> .mat-expansion-panel-content {
.mat-expansion-panel-content {
> .mat-expansion-panel-body {
padding: 0 16px 16px !important;
}

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings.component.ts

@ -82,11 +82,12 @@ export class MarkerImageSettingsComponent implements ControlValueAccessor {
renderer: this.renderer,
componentType: MarkerImageSettingsPanelComponent,
hostView: this.viewContainerRef,
preferredPlacement: 'left',
preferredPlacement: 'leftOnly',
context: {
markerImageSettings: this.modelValue,
},
isModal: true
isModal: true,
overlayStyle: {padding: '10px'}
});
markerImageSettingsPanelPopover.tbComponentRef.instance.popover = markerImageSettingsPanelPopover;
markerImageSettingsPanelPopover.tbComponentRef.instance.markerImageSettingsApplied.subscribe((markerImageSettings) => {

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings.component.ts

@ -82,11 +82,12 @@ export class ShapeFillImageSettingsComponent implements ControlValueAccessor {
renderer: this.renderer,
componentType: ShapeFillImageSettingsPanelComponent,
hostView: this.viewContainerRef,
preferredPlacement: 'left',
preferredPlacement: 'leftOnly',
context: {
shapeFillImageSettings: this.modelValue,
},
isModal: true
isModal: true,
overlayStyle: {padding: '10px'}
}).tbComponentRef.instance.shapeFillImageSettingsApplied.subscribe((shapeFillImageSettings) => {
this.modelValue = shapeFillImageSettings;
this.propagateChange(this.modelValue);

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

Loading…
Cancel
Save