From 2b7df84ae9f3e77040f2e537072a95fc65d30aaf Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 18 Oct 2019 08:30:53 +0300 Subject: [PATCH] Claiming devices using claimData attribute (#2105) * Claiming devices using claimData attribute * Fixed license header --- .../server/dao/device/ClaimDataInfo.java | 30 ++++++++++ .../dao/device/ClaimDevicesService.java | 3 +- .../dao/device/ClaimDevicesServiceImpl.java | 55 +++++++++++++++---- 3 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/ClaimDataInfo.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDataInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDataInfo.java new file mode 100644 index 0000000000..3b3194dbbf --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDataInfo.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2019 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.device; + +import lombok.Data; +import org.thingsboard.server.dao.device.claim.ClaimData; + +import java.util.List; + +@Data +public class ClaimDataInfo { + + private final boolean fromCache; + private final List key; + private final ClaimData data; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java b/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java index eb8c800d1e..a5f2dbcab1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java @@ -23,12 +23,13 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.device.claim.ClaimResult; import java.util.List; +import java.util.concurrent.ExecutionException; public interface ClaimDevicesService { ListenableFuture registerClaimingInfo(TenantId tenantId, DeviceId deviceId, String secretKey, long durationMs); - ListenableFuture claimDevice(Device device, CustomerId customerId, String secretKey); + ListenableFuture claimDevice(Device device, CustomerId customerId, String secretKey) throws ExecutionException, InterruptedException; ListenableFuture> reClaimDevice(TenantId tenantId, Device device); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesServiceImpl.java index 9000c91823..6955cd761c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesServiceImpl.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.device; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -23,6 +24,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.CustomerId; @@ -37,9 +39,12 @@ import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.model.ModelConstants; +import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.ExecutionException; import static org.thingsboard.server.common.data.CacheConstants.CLAIM_DEVICES_CACHE; @@ -48,6 +53,8 @@ import static org.thingsboard.server.common.data.CacheConstants.CLAIM_DEVICES_CA public class ClaimDevicesServiceImpl implements ClaimDevicesService { private static final String CLAIM_ATTRIBUTE_NAME = "claimingAllowed"; + private static final String CLAIM_DATA_ATTRIBUTE_NAME = "claimingData"; + private static final ObjectMapper mapper = new ObjectMapper(); @Autowired private DeviceService deviceService; @@ -95,24 +102,45 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { }); } - @Override - public ListenableFuture claimDevice(Device device, CustomerId customerId, String secretKey) { + private ClaimDataInfo getClaimData(Cache cache, Device device) throws ExecutionException, InterruptedException { List key = constructCacheKey(device.getId()); + ClaimData claimDataFromCache = cache.get(key, ClaimData.class); + if (claimDataFromCache != null) { + return new ClaimDataInfo(true, key, claimDataFromCache); + } else { + Optional claimDataAttr = attributesService.find(device.getTenantId(), device.getId(), + DataConstants.SERVER_SCOPE, CLAIM_DATA_ATTRIBUTE_NAME).get(); + if (claimDataAttr.isPresent()) { + try { + ClaimData claimDataFromAttribute = mapper.readValue(claimDataAttr.get().getValueAsString(), ClaimData.class); + return new ClaimDataInfo(false, key, claimDataFromAttribute); + } catch (IOException e) { + log.warn("Failed to read Claim Data [{}] from attribute!", claimDataAttr, e); + } + } + } + return null; + } + + @Override + public ListenableFuture claimDevice(Device device, CustomerId customerId, String secretKey) throws ExecutionException, InterruptedException { Cache cache = cacheManager.getCache(CLAIM_DEVICES_CACHE); - ClaimData claimData = cache.get(key, ClaimData.class); + ClaimDataInfo claimData = getClaimData(cache, device); if (claimData != null) { long currTs = System.currentTimeMillis(); - if (currTs > claimData.getExpirationTime() || !secretKey.equals(claimData.getSecretKey())) { + if (currTs > claimData.getData().getExpirationTime() || !secretKeyIsEmptyOrEqual(secretKey, claimData.getData().getSecretKey())) { log.warn("The claiming timeout occurred or wrong 'secretKey' provided for the device [{}]", device.getName()); - cache.evict(key); + if (claimData.isFromCache()) { + cache.evict(claimData.getKey()); + } return Futures.immediateFuture(new ClaimResult(null, ClaimResponse.FAILURE)); } else { if (device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { device.setCustomerId(customerId); Device savedDevice = deviceService.saveDevice(device); - return Futures.transform(removeClaimingSavedData(cache, key, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS)); + return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS)); } - return Futures.transform(removeClaimingSavedData(cache, key, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED)); + return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED)); } } else { log.warn("Failed to find the device's claiming message![{}]", device.getName()); @@ -124,6 +152,10 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { } } + private boolean secretKeyIsEmptyOrEqual(String secretKeyA, String secretKeyB) { + return (StringUtils.isEmpty(secretKeyA) && StringUtils.isEmpty(secretKeyB)) || secretKeyA.equals(secretKeyB); + } + @Override public ListenableFuture> reClaimDevice(TenantId tenantId, Device device) { if (!device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { @@ -159,13 +191,12 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { return systemDurationMs; } - private ListenableFuture> removeClaimingSavedData(Cache cache, List key, Device device) { - cache.evict(key); - if (isAllowedClaimingByDefault) { - return Futures.immediateFuture(null); + private ListenableFuture> removeClaimingSavedData(Cache cache, ClaimDataInfo data, Device device) { + if (data.isFromCache()) { + cache.evict(data.getKey()); } return attributesService.removeAll(device.getTenantId(), - device.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList(CLAIM_ATTRIBUTE_NAME)); + device.getId(), DataConstants.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME)); } private void cacheEviction(DeviceId deviceId) {