Merge remote-tracking branch 'upstream/master' into feature/edge-json-converter-impl
This commit is contained in:
		
						commit
						bfb2547ad2
					
				@ -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>
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@
 | 
			
		||||
  "widgetTypeFqns": [
 | 
			
		||||
    "charts.basic_timeseries",
 | 
			
		||||
    "charts.state_chart",
 | 
			
		||||
    "range_chart",
 | 
			
		||||
    "charts.timeseries_bars_flot",
 | 
			
		||||
    "cards.aggregated_value_card",
 | 
			
		||||
    "charts.bars",
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
                                            }
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								common/cache/pom.xml
									
									
									
									
										vendored
									
									
								
							@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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() {
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -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!");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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));
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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",
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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",
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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
									
									
									
									
									
								
							
							
						
						
									
										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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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 have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user