Browse Source

Merge remote-tracking branch 'upstream/master' into feature/edge-json-converter-impl

pull/9617/head
Andrii Landiak 3 years ago
parent
commit
bfb2547ad2
  1. 2
      application/pom.xml
  2. 1
      application/src/main/data/json/system/widget_bundles/charts.json
  3. 2
      application/src/main/data/json/system/widget_types/doughnut.json
  4. 2
      application/src/main/data/json/system/widget_types/horizontal_doughnut.json
  5. 31
      application/src/main/data/json/system/widget_types/range_chart.json
  6. 2
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  7. 15
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  8. 9
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  9. 5
      application/src/main/resources/thingsboard.yml
  10. 41
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  11. 4
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  12. 32
      application/src/test/java/org/thingsboard/server/transport/coap/CoapTestClient.java
  13. 309
      application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java
  14. 2
      common/actor/pom.xml
  15. 2
      common/cache/pom.xml
  16. 25
      common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java
  17. 2
      common/cluster-api/pom.xml
  18. 6
      common/cluster-api/src/main/proto/queue.proto
  19. 2
      common/coap-server/pom.xml
  20. 2
      common/dao-api/pom.xml
  21. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java
  22. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java
  23. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
  24. 2
      common/data/pom.xml
  25. 1
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  26. 2
      common/edge-api/pom.xml
  27. 2
      common/message/pom.xml
  28. 2
      common/pom.xml
  29. 2
      common/queue/pom.xml
  30. 2
      common/script/pom.xml
  31. 2
      common/script/remote-js-client/pom.xml
  32. 2
      common/script/script-api/pom.xml
  33. 2
      common/stats/pom.xml
  34. 2
      common/transport/coap/pom.xml
  35. 1
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/AbstractSyncSessionCallback.java
  36. 4
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapOkCallback.java
  37. 2
      common/transport/http/pom.xml
  38. 2
      common/transport/lwm2m/pom.xml
  39. 2
      common/transport/mqtt/pom.xml
  40. 22
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java
  41. 2
      common/transport/pom.xml
  42. 2
      common/transport/snmp/pom.xml
  43. 2
      common/transport/transport-api/pom.xml
  44. 2
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  45. 74
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultEntityLimitsCache.java
  46. 27
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityLimitKey.java
  47. 24
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityLimitsCache.java
  48. 48
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  49. 2
      common/util/pom.xml
  50. 2
      common/version-control/pom.xml
  51. 2
      dao/pom.xml
  52. 24
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java
  53. 24
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  54. 2
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  55. 9
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  56. 35
      dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitException.java
  57. 3
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  58. 3
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  59. 9
      dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java
  60. 39
      dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java
  61. 29
      dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java
  62. 2
      monitoring/pom.xml
  63. 2
      msa/black-box-tests/pom.xml
  64. 2
      msa/js-executor/package.json
  65. 2
      msa/js-executor/pom.xml
  66. 2
      msa/monitoring/pom.xml
  67. 2
      msa/pom.xml
  68. 2
      msa/tb-node/pom.xml
  69. 4
      msa/tb/pom.xml
  70. 2
      msa/transport/coap/pom.xml
  71. 2
      msa/transport/http/pom.xml
  72. 2
      msa/transport/lwm2m/pom.xml
  73. 2
      msa/transport/mqtt/pom.xml
  74. 2
      msa/transport/pom.xml
  75. 2
      msa/transport/snmp/pom.xml
  76. 2
      msa/vc-executor-docker/pom.xml
  77. 2
      msa/vc-executor/pom.xml
  78. 2
      msa/web-ui/package.json
  79. 2
      msa/web-ui/pom.xml
  80. 4
      netty-mqtt/pom.xml
  81. 2
      pom.xml
  82. 2
      rest-client/pom.xml
  83. 2
      rule-engine/pom.xml
  84. 2
      rule-engine/rule-engine-api/pom.xml
  85. 2
      rule-engine/rule-engine-components/pom.xml
  86. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java
  87. 13
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java
  88. 2
      tools/pom.xml
  89. 2
      transport/coap/pom.xml
  90. 4
      transport/coap/src/main/resources/tb-coap-transport.yml
  91. 2
      transport/http/pom.xml
  92. 4
      transport/http/src/main/resources/tb-http-transport.yml
  93. 2
      transport/lwm2m/pom.xml
  94. 4
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
  95. 2
      transport/mqtt/pom.xml
  96. 4
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  97. 2
      transport/pom.xml
  98. 2
      transport/snmp/pom.xml
  99. 4
      transport/snmp/src/main/resources/tb-snmp-transport.yml
  100. 2
      ui-ngx/package.json

2
application/pom.xml

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

1
application/src/main/data/json/system/widget_bundles/charts.json

@ -11,6 +11,7 @@
"widgetTypeFqns": [
"charts.basic_timeseries",
"charts.state_chart",
"range_chart",
"charts.timeseries_bars_flot",
"cards.aggregated_value_card",
"charts.bars",

2
application/src/main/data/json/system/widget_types/doughnut.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/horizontal_doughnut.json

File diff suppressed because one or more lines are too long

31
application/src/main/data/json/system/widget_types/range_chart.json

File diff suppressed because one or more lines are too long

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

@ -266,6 +266,8 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.6.0 to 3.6.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.0");
dataUpdateService.updateData("3.6.0");
case "3.6.1":
log.info("Upgrading ThingsBoard from version 3.6.1 to 3.6.2 ...");
//TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
break;
default:

15
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -175,20 +175,21 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) {
ListenableFuture<Integer> saveFuture = tsService.save(tenantId, entityId, ts, ttl);
addCallbacks(tenantId, entityId, ts, callback, saveFuture);
addMainCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
addEntityViewCallback(tenantId, entityId, ts);
}
private void saveWithoutLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) {
ListenableFuture<Integer> saveFuture = tsService.saveWithoutLatest(tenantId, entityId, ts, ttl);
addCallbacks(tenantId, entityId, ts, callback, saveFuture);
}
private void addCallbacks(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Integer> callback, ListenableFuture<Integer> saveFuture) {
addMainCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
}
private void addEntityViewCallback(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts) {
if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) {
Futures.addCallback(this.tbEntityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId),
new FutureCallback<List<EntityView>>() {
new FutureCallback<>() {
@Override
public void onSuccess(@Nullable List<EntityView> result) {
if (result != null && !result.isEmpty()) {
@ -212,7 +213,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}
}
if (!entityViewLatest.isEmpty()) {
saveLatestAndNotify(tenantId, entityView.getId(), entityViewLatest, new FutureCallback<Void>() {
saveLatestAndNotify(tenantId, entityView.getId(), entityViewLatest, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
}

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

@ -78,6 +78,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
import org.thingsboard.server.dao.exception.EntitiesLimitException;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
@ -164,6 +165,7 @@ public class DefaultTransportApiService implements TransportApiService {
public void init() {
handlerExecutor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(maxCoreHandlerThreads, "transport-api-service-core-handler"));
}
@PreDestroy
public void destroy() {
if (handlerExecutor != null) {
@ -400,6 +402,13 @@ public class DefaultTransportApiService implements TransportApiService {
} catch (JsonProcessingException e) {
log.warn("[{}] Failed to lookup device by gateway id and name: [{}]", gatewayId, requestMsg.getDeviceName(), e);
throw new RuntimeException(e);
} catch (EntitiesLimitException e) {
log.warn("[{}][{}] API limit exception: [{}]", e.getTenantId(), gatewayId, e.getMessage());
return TransportApiResponseMsg.newBuilder()
.setGetOrCreateDeviceResponseMsg(
GetOrCreateDeviceFromGatewayResponseMsg.newBuilder()
.setError(TransportProtos.TransportApiRequestErrorCode.ENTITY_LIMIT))
.build();
} finally {
deviceCreationLock.unlock();
}

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

@ -583,6 +583,9 @@ cache:
rateLimits:
timeToLiveInMinutes: "${CACHE_SPECS_RATE_LIMITS_TTL:120}" # Rate limits cache TTL
maxSize: "${CACHE_SPECS_RATE_LIMITS_MAX_SIZE:200000}" # 0 means the cache is disabled
entityLimits:
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_LIMITS_TTL:5}" # Entity limits cache TTL
maxSize: "${CACHE_SPECS_ENTITY_LIMITS_MAX_SIZE:100000}" # 0 means the cache is disabled
# Spring data parameters
spring.data.redis.repositories.enabled: false # Disable this because it is not required.
@ -875,7 +878,7 @@ transport:
ip_limits_enabled: "${TB_TRANSPORT_IP_RATE_LIMITS_ENABLED:false}"
# Maximum number of connect attempts with invalid credentials
max_wrong_credentials_per_ip: "${TB_TRANSPORT_MAX_WRONG_CREDENTIALS_PER_IP:10}"
# Timeout to expire block IP addresses
# Timeout (in milliseconds) to expire block IP addresses
ip_block_timeout: "${TB_TRANSPORT_IP_BLOCK_TIMEOUT:60000}"
# Local HTTP transport parameters
http:

41
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -25,6 +25,7 @@ import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.awaitility.Awaitility;
import org.hamcrest.Matcher;
import org.hibernate.exception.ConstraintViolationException;
@ -177,6 +178,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
* and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()}
*/
private static final long DEFAULT_TIMEOUT = -1L;
private static final int CLEANUP_TENANT_RETRIES_COUNT = 3;
protected MediaType contentType = MediaType.APPLICATION_JSON;
@ -330,10 +332,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
log.debug("Executing web test teardown");
loginSysAdmin();
doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
deleteTenant(tenantId);
deleteDifferentTenant();
verifyNoTenantsLeft();
tenantProfileService.deleteTenantProfiles(TenantId.SYS_TENANT_ID);
@ -341,7 +341,34 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
log.info("Executed web test teardown");
}
void verifyNoTenantsLeft() throws Exception {
private void verifyNoTenantsLeft() throws Exception {
List<Tenant> loadedTenants = getAllTenants();
if (!loadedTenants.isEmpty()) {
loadedTenants.forEach(tenant -> deleteTenant(tenant.getId()));
loadedTenants = getAllTenants();
}
assertThat(loadedTenants).as("All tenants expected to be deleted, but some tenants left in the database").isEmpty();
}
private void deleteTenant(TenantId tenantId) {
int status = 0;
int retries = 0;
while (status != HttpStatus.SC_OK && retries < CLEANUP_TENANT_RETRIES_COUNT) {
retries++;
try {
status = doDelete("/api/tenant/" + tenantId.getId().toString())
.andReturn().getResponse().getStatus();
if (status != HttpStatus.SC_OK) {
log.warn("Tenant deletion failed, tenantId: {}", tenantId.getId().toString());
Thread.sleep(1000L);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private List<Tenant> getAllTenants() throws Exception {
List<Tenant> loadedTenants = new ArrayList<>();
PageLink pageLink = new PageLink(10);
PageData<Tenant> pageData;
@ -353,8 +380,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
assertThat(loadedTenants).as("All tenants expected to be deleted, but some tenants left in the database").isEmpty();
return loadedTenants;
}
protected void loginSysAdmin() throws Exception {
@ -465,8 +491,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected void deleteDifferentTenant() throws Exception {
if (savedDifferentTenant != null) {
loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedDifferentTenant.getId());
savedDifferentTenant = null;
}
}

4
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

@ -113,15 +113,13 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Test
public void testSubscribingToUnreadNotificationsCount() {
wsClient.subscribeForUnreadNotificationsCount().waitForReply(true);
NotificationTarget notificationTarget = createNotificationTarget(customerUserId);
String notificationText1 = "Notification 1";
submitNotificationRequest(notificationTarget.getId(), notificationText1);
String notificationText2 = "Notification 2";
submitNotificationRequest(notificationTarget.getId(), notificationText2);
wsClient.subscribeForUnreadNotificationsCount();
wsClient.waitForReply(true);
await().atMost(2, TimeUnit.SECONDS)
.until(() -> wsClient.getLastCountUpdate().getTotalUnreadCount() == 2);
}

32
application/src/test/java/org/thingsboard/server/transport/coap/CoapTestClient.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.transport.coap;
import lombok.Getter;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapObserveRelation;
@ -34,7 +35,10 @@ public class CoapTestClient {
private final CoapClient client;
public CoapTestClient(){
@Getter
private CoAP.Type type = CoAP.Type.CON;
public CoapTestClient() {
this.client = createClient();
}
@ -80,9 +84,13 @@ public class CoapTestClient {
return client.setTimeout(CLIENT_REQUEST_TIMEOUT).get();
}
public CoapObserveRelation getObserveRelation(CoapTestCallback callback){
public CoapObserveRelation getObserveRelation(CoapTestCallback callback) {
return getObserveRelation(callback, true);
}
public CoapObserveRelation getObserveRelation(CoapTestCallback callback, boolean confirmable) {
Request request = Request.newGet().setObserve();
request.setType(CoAP.Type.CON);
request.setType(confirmable ? CoAP.Type.CON : CoAP.Type.NON);
return client.observe(request, callback);
}
@ -94,12 +102,28 @@ public class CoapTestClient {
}
public void setURI(String accessToken, FeatureType featureType) {
if (featureType == null){
if (featureType == null) {
featureType = FeatureType.ATTRIBUTES;
}
setURI(getFeatureTokenUrl(accessToken, featureType));
}
public void useCONs() {
if (client == null) {
throw new RuntimeException("Failed to connect! CoapClient is not initialized!");
}
type = CoAP.Type.CON;
client.useCONs();
}
public void useNONs() {
if (client == null) {
throw new RuntimeException("Failed to connect! CoapClient is not initialized!");
}
type = CoAP.Type.NON;
client.useNONs();
}
private CoapClient createClient() {
return new CoapClient();
}

309
application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java

@ -0,0 +1,309 @@
/**
* Copyright © 2016-2023 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.client;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapObserveRelation;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.SingleEntityFilter;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest;
import org.thingsboard.server.transport.coap.CoapTestCallback;
import org.thingsboard.server.transport.coap.CoapTestClient;
import org.thingsboard.server.transport.coap.CoapTestConfigProperties;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertArrayEquals;
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;
import static org.thingsboard.server.common.data.query.EntityKeyType.CLIENT_ATTRIBUTE;
import static org.thingsboard.server.common.data.query.EntityKeyType.SHARED_ATTRIBUTE;
@Slf4j
@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<String> EXPECTED_KEYS = Arrays.asList("key1", "key2", "key3", "key4", "key5");
private static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}";
@Before
public void beforeTest() throws Exception {
CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder()
.deviceName("Test Post Attributes device")
.build();
processBeforeTest(configProperties);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testConfirmableRequests() throws Exception {
boolean confirmable = true;
processAttributesTest(confirmable);
processTwoWayRpcTest(confirmable);
processTestRequestAttributesValuesFromTheServer(confirmable);
}
@Test
public void testNonConfirmableRequests() throws Exception {
boolean confirmable = false;
processAttributesTest(confirmable);
processTwoWayRpcTest(confirmable);
processTestRequestAttributesValuesFromTheServer(confirmable);
}
protected void processAttributesTest(boolean confirmable) throws Exception {
client = createClientForFeatureWithConfirmableParameter(FeatureType.ATTRIBUTES, confirmable);
CoapResponse coapResponse = client.postMethod(PAYLOAD_VALUES_STR.getBytes());
assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode());
assertEquals("CoAP response type is wrong!", client.getType(), coapResponse.advanced().getType());
DeviceId deviceId = savedDevice.getId();
List<String> actualKeys = getActualKeysList(deviceId);
assertNotNull(actualKeys);
Set<String> actualKeySet = new HashSet<>(actualKeys);
Set<String> expectedKeySet = new HashSet<>(EXPECTED_KEYS);
assertEquals(expectedKeySet, actualKeySet);
String attributesValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/attributes/CLIENT_SCOPE?keys=" + String.join(",", actualKeySet);
;
List<Map<String, Object>> values = doGetAsyncTyped(attributesValuesUrl, new TypeReference<>() {
});
assertAttributesValues(values, actualKeySet);
String deleteAttributesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/CLIENT_SCOPE?keys=" + String.join(",", actualKeySet);
doDelete(deleteAttributesUrl);
}
protected void processTwoWayRpcTest(boolean confirmable) throws Exception {
client = createClientForFeatureWithConfirmableParameter(FeatureType.RPC, confirmable);
CoapTestCallback callbackCoap = new TestCoapCallbackForRPC(client);
CoapObserveRelation observeRelation = client.getObserveRelation(callbackCoap, confirmable);
String awaitAlias = "await Two Way Rpc (client.getObserveRelation)";
await(awaitAlias)
.atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.until(() -> CoAP.ResponseCode.VALID.equals(callbackCoap.getResponseCode()) &&
callbackCoap.getObserve() != null &&
0 == callbackCoap.getObserve());
validateCurrentStateNotification(callbackCoap);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString();
int expectedObserveCountAfterGpioRequest1 = callbackCoap.getObserve() + 1;
String actualResult = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
awaitAlias = "await Two Way Rpc (setGpio(method, params, value) first";
await(awaitAlias)
.atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) &&
callbackCoap.getObserve() != null &&
expectedObserveCountAfterGpioRequest1 == callbackCoap.getObserve());
validateTwoWayStateChangedNotification(callbackCoap, actualResult);
int expectedObserveCountAfterGpioRequest2 = callbackCoap.getObserve() + 1;
actualResult = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
awaitAlias = "await Two Way Rpc (setGpio(method, params, value) second";
await(awaitAlias)
.atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) &&
callbackCoap.getObserve() != null &&
expectedObserveCountAfterGpioRequest2 == callbackCoap.getObserve());
validateTwoWayStateChangedNotification(callbackCoap, actualResult);
observeRelation.proactiveCancel();
assertTrue(observeRelation.isCanceled());
}
protected void processTestRequestAttributesValuesFromTheServer(boolean confirmable) throws Exception {
client = createClientForFeatureWithConfirmableParameter(FeatureType.ATTRIBUTES, confirmable);
SingleEntityFilter dtf = new SingleEntityFilter();
dtf.setSingleEntity(savedDevice.getId());
List<EntityKey> csKeys = getEntityKeys(CLIENT_ATTRIBUTE);
List<EntityKey> shKeys = getEntityKeys(SHARED_ATTRIBUTE);
List<EntityKey> keys = new ArrayList<>();
keys.addAll(csKeys);
keys.addAll(shKeys);
getWsClient().subscribeLatestUpdate(keys, dtf);
getWsClient().registerWaitForUpdate(2);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE",
PAYLOAD_VALUES_STR, String.class, status().isOk());
CoapResponse coapResponse = client.postMethod(PAYLOAD_VALUES_STR);
assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode());
String update = getWsClient().waitForUpdate();
assertThat(update).as("ws update received").isNotBlank();
String keysParam = String.join(",", EXPECTED_KEYS);
String featureTokenUrl = CoapTestClient.getFeatureTokenUrl(accessToken, FeatureType.ATTRIBUTES) + "?clientKeys=" + keysParam + "&sharedKeys=" + keysParam;
client.setURI(featureTokenUrl);
CoapResponse response = client.getMethod();
assertEquals("CoAP response type is wrong!", client.getType(), response.advanced().getType());
}
@SuppressWarnings({"unchecked", "rawtypes"})
protected void assertAttributesValues(List<Map<String, Object>> deviceValues, Set<String> keySet) {
for (Map<String, Object> map : deviceValues) {
String key = (String) map.get("key");
Object value = map.get("value");
assertTrue(keySet.contains(key));
switch (key) {
case "key1":
assertEquals("value1", value);
break;
case "key2":
assertEquals(true, value);
break;
case "key3":
assertEquals(3.0, value);
break;
case "key4":
assertEquals(4, value);
break;
case "key5":
assertNotNull(value);
assertEquals(3, ((LinkedHashMap) value).size());
assertEquals(42, ((LinkedHashMap) value).get("someNumber"));
assertEquals(Arrays.asList(1, 2, 3), ((LinkedHashMap) value).get("someArray"));
LinkedHashMap<String, String> someNestedObject = (LinkedHashMap) ((LinkedHashMap) value).get("someNestedObject");
assertEquals("value", someNestedObject.get("key"));
break;
}
}
}
private List<String> getActualKeysList(DeviceId deviceId) throws Exception {
long start = System.currentTimeMillis();
long end = System.currentTimeMillis() + 5000;
List<String> actualKeys = null;
while (start <= end) {
actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {
});
if (actualKeys.size() == EXPECTED_KEYS.size()) {
break;
}
Thread.sleep(100);
start += 100;
}
return actualKeys;
}
private void validateCurrentStateNotification(CoapTestCallback callback) {
assertArrayEquals(EMPTY_PAYLOAD, callback.getPayloadBytes());
}
private void validateTwoWayStateChangedNotification(CoapTestCallback callback, String actualResult) {
assertEquals(DEVICE_RESPONSE, actualResult);
assertNotNull(callback.getPayloadBytes());
}
protected class TestCoapCallbackForRPC extends CoapTestCallback {
private final CoapTestClient client;
@Getter
private boolean wasSuccessful = false;
TestCoapCallbackForRPC(CoapTestClient client) {
this.client = client;
}
@Override
public void onLoad(CoapResponse response) {
payloadBytes = response.getPayload();
responseCode = response.getCode();
observe = response.getOptions().getObserve();
wasSuccessful = client.getType().equals(response.advanced().getType());
if (observe != null) {
if (observe > 0) {
processOnLoadResponse(response, client);
}
}
}
@Override
public void onError() {
log.warn("Command Response Ack Error, No connect");
}
}
protected void processOnLoadResponse(CoapResponse response, CoapTestClient client) {
JsonNode responseJson = JacksonUtil.fromBytes(response.getPayload());
int requestId = responseJson.get("id").asInt();
client.setURI(CoapTestClient.getFeatureTokenUrl(accessToken, FeatureType.RPC, requestId));
client.postMethod(new CoapHandler() {
@Override
public void onLoad(CoapResponse response) {
log.warn("RPC {} command response ack: {}", requestId, response.getCode());
}
@Override
public void onError() {
log.warn("RPC {} command response ack error, no connect", requestId);
}
}, DEVICE_RESPONSE, MediaTypeRegistry.APPLICATION_JSON);
}
private CoapTestClient createClientForFeatureWithConfirmableParameter(FeatureType featureType, boolean confirmable) {
CoapTestClient coapTestClient = new CoapTestClient(accessToken, featureType);
if (confirmable) {
coapTestClient.useCONs();
} else {
coapTestClient.useNONs();
}
return coapTestClient;
}
private List<EntityKey> getEntityKeys(EntityKeyType scope) {
return CoapClientIntegrationTest.EXPECTED_KEYS.stream().map(key -> new EntityKey(scope, key)).collect(Collectors.toList());
}
}

2
common/actor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

25
common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java

@ -47,6 +47,18 @@ public interface TbTransactionalCache<K extends Serializable, V extends Serializ
*/
TbCacheTransaction<K, V> newTransactionForKeys(List<K> keys);
default V getOrFetchFromDB(K key, Supplier<V> dbCall, boolean cacheNullValue, boolean putToCache) {
if (putToCache) {
return getAndPutInTransaction(key, dbCall, cacheNullValue);
} else {
TbCacheValueWrapper<V> cacheValueWrapper = get(key);
if (cacheValueWrapper != null) {
return cacheValueWrapper.get();
}
return dbCall.get();
}
}
default V getAndPutInTransaction(K key, Supplier<V> dbCall, boolean cacheNullValue) {
TbCacheValueWrapper<V> cacheValueWrapper = get(key);
if (cacheValueWrapper != null) {
@ -69,6 +81,19 @@ public interface TbTransactionalCache<K extends Serializable, V extends Serializ
}
}
default <R> R getOrFetchFromDB(K key, Supplier<R> dbCall, Function<V, R> cacheValueToResult, Function<R, V> dbValueToCacheValue, boolean cacheNullValue, boolean putToCache) {
if (putToCache) {
return getAndPutInTransaction(key, dbCall, cacheValueToResult, dbValueToCacheValue, cacheNullValue);
} else {
TbCacheValueWrapper<V> cacheValueWrapper = get(key);
if (cacheValueWrapper != null) {
var cacheValue = cacheValueWrapper.get();
return cacheValue == null ? null : cacheValueToResult.apply(cacheValue);
}
return dbCall.get();
}
}
default <R> R getAndPutInTransaction(K key, Supplier<R> dbCall, Function<V, R> cacheValueToResult, Function<R, V> dbValueToCacheValue, boolean cacheNullValue) {
TbCacheValueWrapper<V> cacheValueWrapper = get(key);
if (cacheValueWrapper != null) {

2
common/cluster-api/pom.xml

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

6
common/cluster-api/src/main/proto/queue.proto

@ -255,6 +255,12 @@ message GetOrCreateDeviceFromGatewayRequestMsg {
message GetOrCreateDeviceFromGatewayResponseMsg {
DeviceInfoProto deviceInfo = 1;
bytes profileBody = 2;
TransportApiRequestErrorCode error = 3;
}
enum TransportApiRequestErrorCode {
UNKNOWN_TRANSPORT_API_ERROR = 0;
ENTITY_LIMIT = 1;
}
message GetEntityProfileRequestMsg {

2
common/coap-server/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

4
common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java

@ -27,8 +27,12 @@ public interface AssetProfileService extends EntityDaoService {
AssetProfile findAssetProfileById(TenantId tenantId, AssetProfileId assetProfileId);
AssetProfile findAssetProfileById(TenantId tenantId, AssetProfileId assetProfileId, boolean putInCache);
AssetProfile findAssetProfileByName(TenantId tenantId, String profileName);
AssetProfile findAssetProfileByName(TenantId tenantId, String profileName, boolean putInCache);
AssetProfileInfo findAssetProfileInfoById(TenantId tenantId, AssetProfileId assetProfileId);
AssetProfile saveAssetProfile(AssetProfile assetProfile, boolean doValidate);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java

@ -27,8 +27,12 @@ public interface DeviceProfileService extends EntityDaoService {
DeviceProfile findDeviceProfileById(TenantId tenantId, DeviceProfileId deviceProfileId);
DeviceProfile findDeviceProfileById(TenantId tenantId, DeviceProfileId deviceProfileId, boolean putInCache);
DeviceProfile findDeviceProfileByName(TenantId tenantId, String profileName);
DeviceProfile findDeviceProfileByName(TenantId tenantId, String profileName, boolean putInCache);
DeviceProfileInfo findDeviceProfileInfoById(TenantId tenantId, DeviceProfileId deviceProfileId);
DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile, boolean doValidate);

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

@ -50,6 +50,8 @@ public interface EntityViewService extends EntityDaoService {
EntityView findEntityViewById(TenantId tenantId, EntityViewId entityViewId);
EntityView findEntityViewById(TenantId tenantId, EntityViewId entityViewId, boolean putInCache);
EntityView findEntityViewByTenantIdAndName(TenantId tenantId, String name);
PageData<EntityView> findEntityViewByTenantId(TenantId tenantId, PageLink pageLink);

2
common/data/pom.xml

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

1
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -49,6 +49,7 @@ public class DataConstants {
public static final String MQTT_TRANSPORT_NAME = "MQTT";
public static final String HTTP_TRANSPORT_NAME = "HTTP";
public static final String SNMP_TRANSPORT_NAME = "SNMP";
public static final String MAXIMUM_NUMBER_OF_DEVICES_REACHED = "Maximum number of devices reached!";
public static final String[] allScopes() {

2
common/edge-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/pom.xml

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

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>script</artifactId>
</parent>
<groupId>org.thingsboard.common.script</groupId>

2
common/stats/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

1
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/AbstractSyncSessionCallback.java

@ -81,6 +81,7 @@ public abstract class AbstractSyncSessionCallback implements SessionMsgListener
protected void respond(Response response) {
response.getOptions().setContentFormat(TbCoapContentFormatUtil.getContentFormat(exchange.getRequestOptions().getContentFormat(), state.getContentFormat()));
response.setConfirmable(exchange.advanced().getRequest().isConfirmable());
exchange.respond(response);
}

4
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapOkCallback.java

@ -34,7 +34,9 @@ public class CoapOkCallback implements TransportServiceCallback<Void> {
@Override
public void onSuccess(Void msg) {
exchange.respond(new Response(onSuccessResponse));
Response response = new Response(onSuccessResponse);
response.setConfirmable(isConRequest());
exchange.respond(response);
}
@Override

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

22
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java

@ -35,6 +35,7 @@ import io.netty.handler.codec.mqtt.MqttPublishMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.transport.TransportService;
@ -215,8 +216,7 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
@Override
public void onFailure(Throwable t) {
log.warn("[{}][{}][{}] Failed to process device connect command: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName, t);
logDeviceCreationError(t, deviceName);
}
}, context.getExecutor());
}
@ -248,7 +248,8 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
return future;
}
try {
transportService.process(GetOrCreateDeviceFromGatewayRequestMsg.newBuilder()
transportService.process(gateway.getTenantId(),
GetOrCreateDeviceFromGatewayRequestMsg.newBuilder()
.setDeviceName(deviceName)
.setDeviceType(deviceType)
.setGatewayIdMSB(gateway.getDeviceId().getId().getMostSignificantBits())
@ -274,9 +275,9 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
}
@Override
public void onError(Throwable e) {
log.warn("[{}][{}][{}] Failed to process device connect command at getDeviceCreationFuture: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName, e);
futureToSet.setException(e);
public void onError(Throwable t) {
logDeviceCreationError(t, deviceName);
futureToSet.setException(t);
deviceFutures.remove(deviceName);
}
});
@ -287,6 +288,15 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
}
}
private void logDeviceCreationError(Throwable t, String deviceName) {
if (DataConstants.MAXIMUM_NUMBER_OF_DEVICES_REACHED.equals(t.getMessage())) {
log.info("[{}][{}][{}] Failed to process device connect command: [{}] due to [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName,
DataConstants.MAXIMUM_NUMBER_OF_DEVICES_REACHED);
} else {
log.warn("[{}][{}][{}] Failed to process device connect command: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName, t);
}
}
protected abstract T newDeviceSessionCtx(GetOrCreateDeviceFromGatewayResponse msg);
protected int getMsgId(MqttPublishMessage mqttMsg) {

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java

@ -98,7 +98,7 @@ public interface TransportService {
void process(ValidateDeviceLwM2MCredentialsRequestMsg msg,
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
void process(GetOrCreateDeviceFromGatewayRequestMsg msg,
void process(TenantId tenantId, GetOrCreateDeviceFromGatewayRequestMsg msg,
TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback);
void process(ProvisionDeviceRequestMsg msg,

74
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultEntityLimitsCache.java

@ -0,0 +1,74 @@
/**
* Copyright © 2016-2023 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.common.transport.limits;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.queue.util.TbTransportComponent;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@Service
@TbTransportComponent
@Slf4j
public class DefaultEntityLimitsCache implements EntityLimitsCache {
private static final int DEVIATION = 10;
private final Cache<EntityLimitKey, Boolean> cache;
public DefaultEntityLimitsCache(@Value("${cache.entityLimits.timeToLiveInMinutes:5}") int ttl,
@Value("${cache.entityLimits.maxSize:100000}") int maxSize) {
// We use the 'random' expiration time to avoid peak loads.
long mainPart = (TimeUnit.MINUTES.toNanos(ttl) / 100) * (100 - DEVIATION);
long randomPart = (TimeUnit.MINUTES.toNanos(ttl) / 100) * DEVIATION;
cache = Caffeine.newBuilder()
.expireAfter(new Expiry<EntityLimitKey, Boolean>() {
@Override
public long expireAfterCreate(@NotNull EntityLimitKey key, @NotNull Boolean value, long currentTime) {
return mainPart + (long) (randomPart * ThreadLocalRandom.current().nextDouble());
}
@Override
public long expireAfterUpdate(@NotNull EntityLimitKey key, @NotNull Boolean value, long currentTime, long currentDuration) {
return currentDuration;
}
@Override
public long expireAfterRead(@NotNull EntityLimitKey key, @NotNull Boolean value, long currentTime, long currentDuration) {
return currentDuration;
}
})
.maximumSize(maxSize)
.build();
}
@Override
public boolean get(EntityLimitKey key) {
var result = cache.getIfPresent(key);
return result != null ? result : false;
}
@Override
public void put(EntityLimitKey key, boolean value) {
cache.put(key, value);
}
}

27
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityLimitKey.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2023 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.common.transport.limits;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
@Data
public class EntityLimitKey {
private final TenantId tenantId;
private final String deviceName;
}

24
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityLimitsCache.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2023 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.common.transport.limits;
public interface EntityLimitsCache {
boolean get(EntityLimitKey key);
void put(EntityLimitKey key, boolean value);
}

48
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -76,6 +76,8 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.limits.EntityLimitKey;
import org.thingsboard.server.common.transport.limits.EntityLimitsCache;
import org.thingsboard.server.common.transport.limits.TransportRateLimitService;
import org.thingsboard.server.common.transport.util.JsonUtils;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -161,6 +163,7 @@ public class DefaultTransportService implements TransportService {
@Value("${transport.stats.enabled:false}")
private boolean statsEnabled;
@Autowired
@Lazy
private TbApiUsageReportClient apiUsageClient;
@ -184,6 +187,8 @@ public class DefaultTransportService implements TransportService {
private final TransportResourceCache transportResourceCache;
private final NotificationRuleProcessor notificationRuleProcessor;
private final EntityLimitsCache entityLimitsCache;
protected TbQueueRequestTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> transportApiRequestTemplate;
protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer;
protected TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> tbCoreMsgProducer;
@ -212,7 +217,8 @@ public class DefaultTransportService implements TransportService {
TransportTenantProfileCache tenantProfileCache,
TransportRateLimitService rateLimitService,
DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler, TransportResourceCache transportResourceCache,
ApplicationEventPublisher eventPublisher, NotificationRuleProcessor notificationRuleProcessor) {
ApplicationEventPublisher eventPublisher, NotificationRuleProcessor notificationRuleProcessor,
EntityLimitsCache entityLimitsCache) {
this.partitionService = partitionService;
this.serviceInfoProvider = serviceInfoProvider;
this.queueProvider = queueProvider;
@ -227,6 +233,7 @@ public class DefaultTransportService implements TransportService {
this.transportResourceCache = transportResourceCache;
this.eventPublisher = eventPublisher;
this.notificationRuleProcessor = notificationRuleProcessor;
this.entityLimitsCache = entityLimitsCache;
}
@PostConstruct
@ -249,7 +256,7 @@ public class DefaultTransportService implements TransportService {
}
@AfterStartUp(order = AfterStartUp.TRANSPORT_SERVICE)
private void start() {
public void start() {
mainConsumerExecutor.execute(() -> {
while (!stopped) {
try {
@ -473,24 +480,33 @@ public class DefaultTransportService implements TransportService {
AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
}
@Override
public void process(TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback) {
public void process(TenantId tenantId, TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback) {
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(requestMsg).build());
log.trace("Processing msg: {}", requestMsg);
ListenableFuture<GetOrCreateDeviceFromGatewayResponse> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> {
TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg msg = tmp.getValue().getGetOrCreateDeviceResponseMsg();
GetOrCreateDeviceFromGatewayResponse.GetOrCreateDeviceFromGatewayResponseBuilder result = GetOrCreateDeviceFromGatewayResponse.builder();
if (msg.hasDeviceInfo()) {
TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo());
result.deviceInfo(tdi);
ByteString profileBody = msg.getProfileBody();
if (profileBody != null && !profileBody.isEmpty()) {
result.deviceProfile(deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody));
var key = new EntityLimitKey(tenantId, StringUtils.truncate(requestMsg.getDeviceName(), 256));
if (entityLimitsCache.get(key)) {
transportCallbackExecutor.submit(() -> callback.onError(new RuntimeException(DataConstants.MAXIMUM_NUMBER_OF_DEVICES_REACHED)));
} else {
ListenableFuture<GetOrCreateDeviceFromGatewayResponse> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> {
TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg msg = tmp.getValue().getGetOrCreateDeviceResponseMsg();
GetOrCreateDeviceFromGatewayResponse.GetOrCreateDeviceFromGatewayResponseBuilder result = GetOrCreateDeviceFromGatewayResponse.builder();
if (msg.hasDeviceInfo()) {
TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo());
result.deviceInfo(tdi);
ByteString profileBody = msg.getProfileBody();
if (!profileBody.isEmpty()) {
result.deviceProfile(deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody));
}
} else if (TransportProtos.TransportApiRequestErrorCode.ENTITY_LIMIT.equals(msg.getError())) {
entityLimitsCache.put(key, true);
throw new RuntimeException(DataConstants.MAXIMUM_NUMBER_OF_DEVICES_REACHED);
}
}
return result.build();
}, MoreExecutors.directExecutor());
AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
return result.build();
}, MoreExecutors.directExecutor());
AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
}
}
@Override

2
common/util/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
dao/pom.xml

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

24
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java

@ -90,18 +90,28 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService<AssetPr
@Override
public AssetProfile findAssetProfileById(TenantId tenantId, AssetProfileId assetProfileId) {
return findAssetProfileById(tenantId, assetProfileId, true);
}
@Override
public AssetProfile findAssetProfileById(TenantId tenantId, AssetProfileId assetProfileId, boolean putInCache) {
log.trace("Executing findAssetProfileById [{}]", assetProfileId);
Validator.validateId(assetProfileId, INCORRECT_ASSET_PROFILE_ID + assetProfileId);
return cache.getAndPutInTransaction(AssetProfileCacheKey.fromId(assetProfileId),
() -> assetProfileDao.findById(tenantId, assetProfileId.getId()), true);
return cache.getOrFetchFromDB(AssetProfileCacheKey.fromId(assetProfileId),
() -> assetProfileDao.findById(tenantId, assetProfileId.getId()), true, putInCache);
}
@Override
public AssetProfile findAssetProfileByName(TenantId tenantId, String profileName) {
return findAssetProfileByName(tenantId, profileName, true);
}
@Override
public AssetProfile findAssetProfileByName(TenantId tenantId, String profileName, boolean putInCache) {
log.trace("Executing findAssetProfileByName [{}][{}]", tenantId, profileName);
Validator.validateString(profileName, INCORRECT_ASSET_PROFILE_NAME + profileName);
return cache.getAndPutInTransaction(AssetProfileCacheKey.fromName(tenantId, profileName),
() -> assetProfileDao.findByName(tenantId, profileName), false);
return cache.getOrFetchFromDB(AssetProfileCacheKey.fromName(tenantId, profileName),
() -> assetProfileDao.findByName(tenantId, profileName), false, putInCache);
}
@Override
@ -127,7 +137,7 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService<AssetPr
if (doValidate) {
oldAssetProfile = assetProfileValidator.validate(assetProfile, AssetProfile::getTenantId);
} else if (assetProfile.getId() != null) {
oldAssetProfile = findAssetProfileById(assetProfile.getTenantId(), assetProfile.getId());
oldAssetProfile = findAssetProfileById(assetProfile.getTenantId(), assetProfile.getId(), false);
}
AssetProfile savedAssetProfile;
try {
@ -208,13 +218,13 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService<AssetPr
@Override
public AssetProfile findOrCreateAssetProfile(TenantId tenantId, String name) {
log.trace("Executing findOrCreateAssetProfile");
AssetProfile assetProfile = findAssetProfileByName(tenantId, name);
AssetProfile assetProfile = findAssetProfileByName(tenantId, name, false);
if (assetProfile == null) {
try {
assetProfile = this.doCreateDefaultAssetProfile(tenantId, name, name.equals("default"));
} catch (DataValidationException e) {
if (ASSET_PROFILE_WITH_SUCH_NAME_ALREADY_EXISTS.equals(e.getMessage())) {
assetProfile = findAssetProfileByName(tenantId, name);
assetProfile = findAssetProfileByName(tenantId, name, false);
} else {
throw e;
}

24
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -113,18 +113,28 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
@Override
public DeviceProfile findDeviceProfileById(TenantId tenantId, DeviceProfileId deviceProfileId) {
return findDeviceProfileById(tenantId, deviceProfileId, true);
}
@Override
public DeviceProfile findDeviceProfileById(TenantId tenantId, DeviceProfileId deviceProfileId, boolean putInCache) {
log.trace("Executing findDeviceProfileById [{}]", deviceProfileId);
validateId(deviceProfileId, INCORRECT_DEVICE_PROFILE_ID + deviceProfileId);
return cache.getAndPutInTransaction(DeviceProfileCacheKey.fromId(deviceProfileId),
() -> deviceProfileDao.findById(tenantId, deviceProfileId.getId()), true);
return cache.getOrFetchFromDB(DeviceProfileCacheKey.fromId(deviceProfileId),
() -> deviceProfileDao.findById(tenantId, deviceProfileId.getId()), true, putInCache);
}
@Override
public DeviceProfile findDeviceProfileByName(TenantId tenantId, String profileName) {
return findDeviceProfileByName(tenantId, profileName, true);
}
@Override
public DeviceProfile findDeviceProfileByName(TenantId tenantId, String profileName, boolean putInCache) {
log.trace("Executing findDeviceProfileByName [{}][{}]", tenantId, profileName);
validateString(profileName, INCORRECT_DEVICE_PROFILE_NAME + profileName);
return cache.getAndPutInTransaction(DeviceProfileCacheKey.fromName(tenantId, profileName),
() -> deviceProfileDao.findByName(tenantId, profileName), true);
return cache.getOrFetchFromDB(DeviceProfileCacheKey.fromName(tenantId, profileName),
() -> deviceProfileDao.findByName(tenantId, profileName), true, putInCache);
}
@Override
@ -164,7 +174,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
if (doValidate) {
oldDeviceProfile = deviceProfileValidator.validate(deviceProfile, DeviceProfile::getTenantId);
} else if (deviceProfile.getId() != null) {
oldDeviceProfile = findDeviceProfileById(deviceProfile.getTenantId(), deviceProfile.getId());
oldDeviceProfile = findDeviceProfileById(deviceProfile.getTenantId(), deviceProfile.getId(), false);
}
DeviceProfile savedDeviceProfile;
try {
@ -252,13 +262,13 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
@Override
public DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String name) {
log.trace("Executing findOrCreateDefaultDeviceProfile");
DeviceProfile deviceProfile = findDeviceProfileByName(tenantId, name);
DeviceProfile deviceProfile = findDeviceProfileByName(tenantId, name, false);
if (deviceProfile == null) {
try {
deviceProfile = this.doCreateDefaultDeviceProfile(tenantId, name, name.equals("default"));
} catch (DataValidationException e) {
if (DEVICE_PROFILE_WITH_SUCH_NAME_ALREADY_EXISTS.equals(e.getMessage())) {
deviceProfile = findDeviceProfileByName(tenantId, name);
deviceProfile = findDeviceProfileByName(tenantId, name, false);
} else {
throw e;
}

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

@ -223,7 +223,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService<DeviceCacheKe
}
device.setDeviceProfileId(new DeviceProfileId(deviceProfile.getId().getId()));
} else {
deviceProfile = this.deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
deviceProfile = this.deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId(), false);
if (deviceProfile == null) {
throw new DataValidationException("Device is referencing non existing device profile!");
}

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

@ -163,11 +163,16 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService<EntityVie
@Override
public EntityView findEntityViewById(TenantId tenantId, EntityViewId entityViewId) {
return findEntityViewById(tenantId, entityViewId, true);
}
@Override
public EntityView findEntityViewById(TenantId tenantId, EntityViewId entityViewId, boolean putInCache) {
log.trace("Executing findEntityViewById [{}]", entityViewId);
validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId);
return cache.getAndPutInTransaction(EntityViewCacheKey.byId(entityViewId),
return cache.getOrFetchFromDB(EntityViewCacheKey.byId(entityViewId),
() -> entityViewDao.findById(tenantId, entityViewId.getId())
, EntityViewCacheValue::getEntityView, v -> new EntityViewCacheValue(v, null), true);
, EntityViewCacheValue::getEntityView, v -> new EntityViewCacheValue(v, null), true, putInCache);
}
@Override

35
dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitException.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2023 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.exception;
import lombok.Getter;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
public class EntitiesLimitException extends DataValidationException {
private static final long serialVersionUID = -9211462514373279196L;
@Getter
private final TenantId tenantId;
@Getter
private final EntityType entityType;
public EntitiesLimitException(TenantId tenantId, EntityType entityType) {
super(entityType.getNormalName() + "s limit reached");
this.tenantId = tenantId;
this.entityType = entityType;
}
}

3
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -155,7 +155,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
if (ruleChain == null) {
return RuleChainUpdateResult.failed();
}
RuleChainDataValidator.validateMetaData(ruleChainMetaData);
RuleChainDataValidator.validateMetaDataFieldsAndConnections(ruleChainMetaData);
List<RuleNode> nodes = ruleChainMetaData.getNodes();
List<RuleNode> toAddOrUpdate = new ArrayList<>();
@ -194,6 +194,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
for (RuleNode node : toAddOrUpdate) {
node.setRuleChainId(ruleChainId);
node = ruleNodeUpdater.apply(node);
RuleChainDataValidator.validateRuleNode(node);
RuleNode savedNode = ruleNodeDao.save(tenantId, node);
relations.add(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));

3
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.TenantEntityWithDataDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.EntitiesLimitException;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import java.util.HashSet;
@ -117,7 +118,7 @@ public abstract class DataValidator<D extends BaseData<?>> {
protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
EntityType entityType) {
if (!apiLimitService.checkEntitiesLimit(tenantId, entityType)) {
throw new DataValidationException(entityType.getNormalName() + "s limit reached");
throw new EntitiesLimitException(tenantId, entityType);
}
}

9
dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java

@ -87,15 +87,18 @@ public class RuleChainDataValidator extends DataValidator<RuleChain> {
}
public static List<Throwable> validateMetaData(RuleChainMetaData ruleChainMetaData) {
ConstraintValidator.validateFields(ruleChainMetaData);
List<Throwable> throwables = ruleChainMetaData.getNodes().stream()
validateMetaDataFieldsAndConnections(ruleChainMetaData);
return ruleChainMetaData.getNodes().stream()
.map(RuleChainDataValidator::validateRuleNode)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public static void validateMetaDataFieldsAndConnections(RuleChainMetaData ruleChainMetaData) {
ConstraintValidator.validateFields(ruleChainMetaData);
if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) {
validateCircles(ruleChainMetaData.getConnections());
}
return throwables;
}
public static Throwable validateRuleNode(RuleNode ruleNode) {

39
dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java

@ -20,16 +20,25 @@ import org.junit.Assert;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.asset.AssetDao;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.exception.DataValidationException;
@ -37,6 +46,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -46,7 +56,13 @@ public class AssetServiceTest extends AbstractServiceTest {
@Autowired
AssetService assetService;
@Autowired
AssetDao assetDao;
@Autowired
CustomerService customerService;
@Autowired
private AssetProfileService assetProfileService;
@Autowired
private PlatformTransactionManager platformTransactionManager;
private IdComparator<Asset> idComparator = new IdComparator<>();
@ -75,6 +91,29 @@ public class AssetServiceTest extends AbstractServiceTest {
assetService.deleteAsset(tenantId, savedAsset.getId());
}
@Test
public void testShouldNotPutInCacheRolledbackAssetProfile() {
AssetProfile assetProfile = new AssetProfile();
assetProfile.setName(StringUtils.randomAlphabetic(10));
assetProfile.setTenantId(tenantId);
Asset asset = new Asset();
asset.setName("My asset" + StringUtils.randomAlphabetic(15));
asset.setType(assetProfile.getName());
asset.setTenantId(tenantId);
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = platformTransactionManager.getTransaction(def);
try {
assetProfileService.saveAssetProfile(assetProfile);
assetService.saveAsset(asset);
} finally {
platformTransactionManager.rollback(status);
}
AssetProfile assetProfileByName = assetProfileService.findAssetProfileByName(tenantId, assetProfile.getName());
Assert.assertNull(assetProfileByName);
}
@Test
public void testSaveAssetWithEmptyName() {
Asset asset = new Asset();

29
dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java

@ -22,6 +22,9 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
@ -32,6 +35,8 @@ import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
@ -72,6 +77,8 @@ public class DeviceServiceTest extends AbstractServiceTest {
OtaPackageService otaPackageService;
@Autowired
TenantProfileService tenantProfileService;
@Autowired
private PlatformTransactionManager platformTransactionManager;
private IdComparator<Device> idComparator = new IdComparator<>();
private TenantId anotherTenantId;
@ -305,6 +312,28 @@ public class DeviceServiceTest extends AbstractServiceTest {
});
}
@Test
public void testShouldNotPutInCacheRolledbackDeviceProfile() {
DeviceProfile deviceProfile = createDeviceProfile(tenantId, "New device Profile" + StringUtils.randomAlphabetic(5));
Device device = new Device();
device.setType(deviceProfile.getName());
device.setTenantId(tenantId);
device.setName("My device"+ StringUtils.randomAlphabetic(5));
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = platformTransactionManager.getTransaction(def);
try {
deviceProfileService.saveDeviceProfile(deviceProfile);
deviceService.saveDevice(device);
} finally {
platformTransactionManager.rollback(status);
}
DeviceProfile deviceProfileByName = deviceProfileService.findDeviceProfileByName(tenantId, deviceProfile.getName());
Assert.assertNull(deviceProfileByName);
}
@Test
public void testAssignDeviceToNonExistentCustomer() {
Device device = new Device();

2
monitoring/pom.xml

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

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

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

2
msa/js-executor/package.json

@ -1,7 +1,7 @@
{
"name": "thingsboard-js-executor",
"private": true,
"version": "3.6.1",
"version": "3.6.2",
"description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.ts",
"bin": "server.js",

2
msa/js-executor/pom.xml

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

2
msa/monitoring/pom.xml

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

2
msa/pom.xml

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

2
msa/tb-node/pom.xml

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

4
msa/tb/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>
@ -38,7 +38,7 @@
<tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name>
<tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<pkg.upgradeVersion>3.6.1</pkg.upgradeVersion>
<pkg.upgradeVersion>3.6.2</pkg.upgradeVersion>
</properties>
<dependencies>

2
msa/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/web-ui/package.json

@ -1,7 +1,7 @@
{
"name": "thingsboard-web-ui",
"private": true,
"version": "3.6.1",
"version": "3.6.2",
"description": "ThingsBoard Web UI Microservice",
"main": "server.ts",
"bin": "server.js",

2
msa/web-ui/pom.xml

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

4
netty-mqtt/pom.xml

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

2
pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.thingsboard</groupId>
<artifactId>thingsboard</artifactId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Thingsboard</name>

2
rest-client/pom.xml

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

2
rule-engine/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java

@ -89,7 +89,9 @@ public class TbCopyKeysNode implements TbNode {
String keyData = entry.getKey();
if (checkKey(keyData)) {
msgChanged = true;
metaData.putValue(keyData, JacksonUtil.toString(entry.getValue()));
String value = entry.getValue().isTextual() ?
entry.getValue().asText() : JacksonUtil.toString(entry.getValue());
metaData.putValue(keyData, value);
}
}
}

13
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java

@ -96,10 +96,12 @@ public class TbCopyKeysNodeTest {
@Test
void givenMsgFromMsg_whenOnMsg_thenVerifyOutput() throws Exception {
config.setFromMetadata(false);
config.setKeys(Set.of(".*Key$"));
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
node.init(ctx, nodeConfiguration);
String data = "{\"DigitData\":22.5,\"TempDataValue\":10.5}";
String data = "{\"nullKey\":null,\"stringKey\":\"value1\",\"booleanKey\":true,\"doubleKey\":42.0,\"longKey\":73," +
"\"jsonKey\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}";
node.onMsg(ctx, getTbMsg(deviceId, data));
ArgumentCaptor<TbMsg> newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
@ -110,8 +112,13 @@ public class TbCopyKeysNodeTest {
assertThat(newMsg).isNotNull();
Map<String, String> metaDataMap = newMsg.getMetaData().getData();
assertThat(metaDataMap.containsKey("DigitData")).isEqualTo(true);
assertThat(metaDataMap.containsKey("TempDataValue")).isEqualTo(true);
assertThat(metaDataMap.get("nullKey")).isEqualTo("null");
assertThat(metaDataMap.get("stringKey")).isEqualTo("value1");
assertThat(metaDataMap.get("booleanKey")).isEqualTo("true");
assertThat(metaDataMap.get("doubleKey")).isEqualTo("42.0");
assertThat(metaDataMap.get("longKey")).isEqualTo("73");
assertThat(metaDataMap.get("jsonKey"))
.isEqualTo("{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}");
}
@Test

2
tools/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

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

@ -49,6 +49,10 @@ zk:
cache:
# caffeine or redis
type: "${CACHE_TYPE:redis}"
# Deliberately placed outside the 'specs' group above
entityLimits:
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_LIMITS_TTL:5}" # Entity limits cache TTL
maxSize: "${CACHE_SPECS_ENTITY_LIMITS_MAX_SIZE:100000}" # 0 means the cache is disabled
# Redis configuration parameters
redis:

2
transport/http/pom.xml

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

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

@ -79,6 +79,10 @@ zk:
cache:
# caffeine or redis
type: "${CACHE_TYPE:redis}"
# Deliberately placed outside the 'specs' group above
entityLimits:
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_LIMITS_TTL:5}" # Entity limits cache TTL
maxSize: "${CACHE_SPECS_ENTITY_LIMITS_MAX_SIZE:100000}" # 0 means the cache is disabled
# Redis configuration parameters
redis:

2
transport/lwm2m/pom.xml

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

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

@ -49,6 +49,10 @@ zk:
cache:
# caffeine or redis
type: "${CACHE_TYPE:redis}"
# Deliberately placed outside the 'specs' group above
entityLimits:
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_LIMITS_TTL:5}" # Entity limits cache TTL
maxSize: "${CACHE_SPECS_ENTITY_LIMITS_MAX_SIZE:100000}" # 0 means the cache is disabled
# Redis configuration parameters
redis:

2
transport/mqtt/pom.xml

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

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

@ -49,6 +49,10 @@ zk:
cache:
# caffeine or redis
type: "${CACHE_TYPE:redis}"
# Deliberately placed outside the 'specs' group above
entityLimits:
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_LIMITS_TTL:5}" # Entity limits cache TTL
maxSize: "${CACHE_SPECS_ENTITY_LIMITS_MAX_SIZE:100000}" # 0 means the cache is disabled
# Redis configuration parameters
redis:

2
transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.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>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>

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

@ -49,6 +49,10 @@ zk:
cache:
# caffeine or redis
type: "${CACHE_TYPE:redis}"
# Deliberately placed outside the 'specs' group above
entityLimits:
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_LIMITS_TTL:5}" # Entity limits cache TTL
maxSize: "${CACHE_SPECS_ENTITY_LIMITS_MAX_SIZE:100000}" # 0 means the cache is disabled
# Redis configuration parameters
redis:

2
ui-ngx/package.json

@ -1,6 +1,6 @@
{
"name": "thingsboard",
"version": "3.6.1",
"version": "3.6.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",

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

Loading…
Cancel
Save