Handle Device service transactional methods exceptions (fix exception handling for devices with same name)
This commit is contained in:
parent
0d7adb73eb
commit
b67f454ba0
@ -30,7 +30,6 @@ import org.springframework.cache.annotation.Cacheable;
|
|||||||
import org.springframework.cache.annotation.Caching;
|
import org.springframework.cache.annotation.Caching;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.thingsboard.common.util.JacksonUtil;
|
import org.thingsboard.common.util.JacksonUtil;
|
||||||
@ -82,6 +81,7 @@ import org.thingsboard.server.dao.service.DataValidator;
|
|||||||
import org.thingsboard.server.dao.service.PaginatedRemover;
|
import org.thingsboard.server.dao.service.PaginatedRemover;
|
||||||
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
|
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
|
||||||
import org.thingsboard.server.dao.tenant.TenantDao;
|
import org.thingsboard.server.dao.tenant.TenantDao;
|
||||||
|
import org.thingsboard.server.dao.tx.TransactionHandler;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -141,6 +141,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
|||||||
@Autowired
|
@Autowired
|
||||||
private OtaPackageService otaPackageService;
|
private OtaPackageService otaPackageService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TransactionHandler transactionHandler;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) {
|
public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) {
|
||||||
log.trace("Executing findDeviceInfoById [{}]", deviceId);
|
log.trace("Executing findDeviceInfoById [{}]", deviceId);
|
||||||
@ -184,10 +187,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
|||||||
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}"),
|
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}"),
|
||||||
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.id}")
|
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.id}")
|
||||||
})
|
})
|
||||||
@Transactional
|
|
||||||
@Override
|
@Override
|
||||||
public Device saveDeviceWithAccessToken(Device device, String accessToken) {
|
public Device saveDeviceWithAccessToken(Device device, String accessToken) {
|
||||||
return doSaveDevice(device, accessToken, true);
|
try {
|
||||||
|
return transactionHandler.runInTransaction(() -> doSaveDevice(device, accessToken, true));
|
||||||
|
} catch (Exception t) {
|
||||||
|
throw handleDeviceSaveException(device, t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Caching(evict= {
|
@Caching(evict= {
|
||||||
@ -212,26 +218,32 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
|||||||
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}"),
|
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}"),
|
||||||
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.id}")
|
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.id}")
|
||||||
})
|
})
|
||||||
@Transactional
|
|
||||||
@Override
|
@Override
|
||||||
public Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials) {
|
public Device saveDeviceWithCredentials(Device toSave, DeviceCredentials deviceCredentials) {
|
||||||
if (device.getId() == null) {
|
try {
|
||||||
Device deviceWithName = this.findDeviceByTenantIdAndName(device.getTenantId(), device.getName());
|
return transactionHandler.runInTransaction(() -> {
|
||||||
device = deviceWithName == null ? device : deviceWithName.updateDevice(device);
|
Device device = toSave;
|
||||||
|
if (device.getId() == null) {
|
||||||
|
Device deviceWithName = this.findDeviceByTenantIdAndName(device.getTenantId(), device.getName());
|
||||||
|
device = deviceWithName == null ? device : deviceWithName.updateDevice(device);
|
||||||
|
}
|
||||||
|
Device savedDevice = this.saveDeviceWithoutCredentials(device, true);
|
||||||
|
deviceCredentials.setDeviceId(savedDevice.getId());
|
||||||
|
if (device.getId() == null) {
|
||||||
|
deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials);
|
||||||
|
} else {
|
||||||
|
DeviceCredentials foundDeviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), savedDevice.getId());
|
||||||
|
if (foundDeviceCredentials == null) {
|
||||||
|
deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials);
|
||||||
|
} else {
|
||||||
|
deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), deviceCredentials);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return savedDevice;
|
||||||
|
});
|
||||||
|
} catch (Exception t) {
|
||||||
|
throw handleDeviceSaveException(toSave, t);
|
||||||
}
|
}
|
||||||
Device savedDevice = this.saveDeviceWithoutCredentials(device, true);
|
|
||||||
deviceCredentials.setDeviceId(savedDevice.getId());
|
|
||||||
if (device.getId() == null) {
|
|
||||||
deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials);
|
|
||||||
} else {
|
|
||||||
DeviceCredentials foundDeviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), savedDevice.getId());
|
|
||||||
if (foundDeviceCredentials == null) {
|
|
||||||
deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials);
|
|
||||||
} else {
|
|
||||||
deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), deviceCredentials);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return savedDevice;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Device doSaveDevice(Device device, String accessToken, boolean doValidate) {
|
private Device doSaveDevice(Device device, String accessToken, boolean doValidate) {
|
||||||
@ -270,15 +282,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
|||||||
device.setDeviceData(syncDeviceData(deviceProfile, device.getDeviceData()));
|
device.setDeviceData(syncDeviceData(deviceProfile, device.getDeviceData()));
|
||||||
return deviceDao.save(device.getTenantId(), device);
|
return deviceDao.save(device.getTenantId(), device);
|
||||||
} catch (Exception t) {
|
} catch (Exception t) {
|
||||||
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
|
throw handleDeviceSaveException(device, t);
|
||||||
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) {
|
|
||||||
// remove device from cache in case null value cached in the distributed redis.
|
|
||||||
removeDeviceFromCacheByName(device.getTenantId(), device.getName());
|
|
||||||
removeDeviceFromCacheById(device.getTenantId(), device.getId());
|
|
||||||
throw new DataValidationException("Device with such name already exists!");
|
|
||||||
} else {
|
|
||||||
throw t;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,16 +368,17 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void removeDeviceFromCacheByName(TenantId tenantId, String name) {
|
private void removeDeviceFromCacheByName(TenantId tenantId, String name) {
|
||||||
Cache cache = cacheManager.getCache(DEVICE_CACHE);
|
if (tenantId != null && !StringUtils.isEmpty(name)) {
|
||||||
cache.evict(Arrays.asList(tenantId, name));
|
Cache cache = cacheManager.getCache(DEVICE_CACHE);
|
||||||
|
cache.evict(Arrays.asList(tenantId, name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeDeviceFromCacheById(TenantId tenantId, DeviceId deviceId) {
|
private void removeDeviceFromCacheById(TenantId tenantId, DeviceId deviceId) {
|
||||||
if (deviceId == null) {
|
if (tenantId != null && deviceId != null) {
|
||||||
return;
|
Cache cache = cacheManager.getCache(DEVICE_CACHE);
|
||||||
|
cache.evict(Arrays.asList(tenantId, deviceId));
|
||||||
}
|
}
|
||||||
Cache cache = cacheManager.getCache(DEVICE_CACHE);
|
|
||||||
cache.evict(Arrays.asList(tenantId, deviceId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -560,84 +565,93 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
|||||||
}, MoreExecutors.directExecutor());
|
}, MoreExecutors.directExecutor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
@Override
|
||||||
public Device assignDeviceToTenant(TenantId tenantId, Device device) {
|
public Device assignDeviceToTenant(TenantId tenantId, Device device) {
|
||||||
log.trace("Executing assignDeviceToTenant [{}][{}]", tenantId, device);
|
log.trace("Executing assignDeviceToTenant [{}][{}]", tenantId, device);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), device.getId()).get();
|
return transactionHandler.runInTransaction(() -> {
|
||||||
if (!CollectionUtils.isEmpty(entityViews)) {
|
try {
|
||||||
throw new DataValidationException("Can't assign device that has entity views to another tenant!");
|
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), device.getId()).get();
|
||||||
}
|
if (!CollectionUtils.isEmpty(entityViews)) {
|
||||||
} catch (ExecutionException | InterruptedException e) {
|
throw new DataValidationException("Can't assign device that has entity views to another tenant!");
|
||||||
log.error("Exception while finding entity views for deviceId [{}]", device.getId(), e);
|
}
|
||||||
throw new RuntimeException("Exception while finding entity views for deviceId [" + device.getId() + "]", e);
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
log.error("Exception while finding entity views for deviceId [{}]", device.getId(), e);
|
||||||
|
throw new RuntimeException("Exception while finding entity views for deviceId [" + device.getId() + "]", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
eventService.removeEvents(device.getTenantId(), device.getId());
|
||||||
|
|
||||||
|
relationService.removeRelations(device.getTenantId(), device.getId());
|
||||||
|
|
||||||
|
TenantId oldTenantId = device.getTenantId();
|
||||||
|
|
||||||
|
device.setTenantId(tenantId);
|
||||||
|
device.setCustomerId(null);
|
||||||
|
Device savedDevice = doSaveDevice(device, null, true);
|
||||||
|
|
||||||
|
// explicitly remove device with previous tenant id from cache
|
||||||
|
// result device object will have different tenant id and will not remove entity from cache
|
||||||
|
removeDeviceFromCacheByName(oldTenantId, device.getName());
|
||||||
|
removeDeviceFromCacheById(oldTenantId, device.getId());
|
||||||
|
|
||||||
|
return savedDevice;
|
||||||
|
});
|
||||||
|
} catch (Exception t) {
|
||||||
|
throw handleDeviceSaveException(device, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
eventService.removeEvents(device.getTenantId(), device.getId());
|
|
||||||
|
|
||||||
relationService.removeRelations(device.getTenantId(), device.getId());
|
|
||||||
|
|
||||||
TenantId oldTenantId = device.getTenantId();
|
|
||||||
|
|
||||||
device.setTenantId(tenantId);
|
|
||||||
device.setCustomerId(null);
|
|
||||||
Device savedDevice = doSaveDevice(device, null, true);
|
|
||||||
|
|
||||||
// explicitly remove device with previous tenant id from cache
|
|
||||||
// result device object will have different tenant id and will not remove entity from cache
|
|
||||||
removeDeviceFromCacheByName(oldTenantId, device.getName());
|
|
||||||
removeDeviceFromCacheById(oldTenantId, device.getId());
|
|
||||||
|
|
||||||
return savedDevice;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#profile.tenantId, #provisionRequest.deviceName}")
|
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#profile.tenantId, #provisionRequest.deviceName}")
|
||||||
@Transactional
|
|
||||||
public Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
|
public Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
|
||||||
Device device = new Device();
|
Device device = new Device();
|
||||||
device.setName(provisionRequest.getDeviceName());
|
try {
|
||||||
device.setType(profile.getName());
|
return transactionHandler.runInTransaction(() -> {
|
||||||
device.setTenantId(profile.getTenantId());
|
device.setName(provisionRequest.getDeviceName());
|
||||||
Device savedDevice = saveDevice(device);
|
device.setType(profile.getName());
|
||||||
if (!StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken()) ||
|
device.setTenantId(profile.getTenantId());
|
||||||
!StringUtils.isEmpty(provisionRequest.getCredentialsData().getX509CertHash()) ||
|
Device savedDevice = saveDevice(device);
|
||||||
!StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) ||
|
if (!StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken()) ||
|
||||||
!StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) ||
|
!StringUtils.isEmpty(provisionRequest.getCredentialsData().getX509CertHash()) ||
|
||||||
!StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) {
|
!StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) ||
|
||||||
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId());
|
!StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) ||
|
||||||
if (deviceCredentials == null) {
|
!StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) {
|
||||||
deviceCredentials = new DeviceCredentials();
|
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId());
|
||||||
}
|
if (deviceCredentials == null) {
|
||||||
deviceCredentials.setDeviceId(savedDevice.getId());
|
deviceCredentials = new DeviceCredentials();
|
||||||
deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType());
|
}
|
||||||
switch (provisionRequest.getCredentialsType()) {
|
deviceCredentials.setDeviceId(savedDevice.getId());
|
||||||
case ACCESS_TOKEN:
|
deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType());
|
||||||
deviceCredentials.setCredentialsId(provisionRequest.getCredentialsData().getToken());
|
switch (provisionRequest.getCredentialsType()) {
|
||||||
break;
|
case ACCESS_TOKEN:
|
||||||
case MQTT_BASIC:
|
deviceCredentials.setCredentialsId(provisionRequest.getCredentialsData().getToken());
|
||||||
BasicMqttCredentials mqttCredentials = new BasicMqttCredentials();
|
break;
|
||||||
mqttCredentials.setClientId(provisionRequest.getCredentialsData().getClientId());
|
case MQTT_BASIC:
|
||||||
mqttCredentials.setUserName(provisionRequest.getCredentialsData().getUsername());
|
BasicMqttCredentials mqttCredentials = new BasicMqttCredentials();
|
||||||
mqttCredentials.setPassword(provisionRequest.getCredentialsData().getPassword());
|
mqttCredentials.setClientId(provisionRequest.getCredentialsData().getClientId());
|
||||||
deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials));
|
mqttCredentials.setUserName(provisionRequest.getCredentialsData().getUsername());
|
||||||
break;
|
mqttCredentials.setPassword(provisionRequest.getCredentialsData().getPassword());
|
||||||
case X509_CERTIFICATE:
|
deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials));
|
||||||
deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getX509CertHash());
|
break;
|
||||||
break;
|
case X509_CERTIFICATE:
|
||||||
case LWM2M_CREDENTIALS:
|
deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getX509CertHash());
|
||||||
break;
|
break;
|
||||||
}
|
case LWM2M_CREDENTIALS:
|
||||||
try {
|
break;
|
||||||
deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials);
|
}
|
||||||
} catch (Exception e) {
|
try {
|
||||||
throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
|
deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials);
|
||||||
}
|
} catch (Exception e) {
|
||||||
|
throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeDeviceFromCacheById(savedDevice.getTenantId(), savedDevice.getId()); // eviction by name is described as annotation @CacheEvict above
|
||||||
|
return savedDevice;
|
||||||
|
});
|
||||||
|
} catch (Exception t) {
|
||||||
|
throw handleDeviceSaveException(device, t);
|
||||||
}
|
}
|
||||||
removeDeviceFromCacheById(savedDevice.getTenantId(), savedDevice.getId()); // eviction by name is described as annotation @CacheEvict above
|
|
||||||
return savedDevice;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -818,4 +832,20 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
|||||||
unassignDeviceFromCustomer(tenantId, new DeviceId(entity.getUuidId()));
|
unassignDeviceFromCustomer(tenantId, new DeviceId(entity.getUuidId()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private RuntimeException handleDeviceSaveException(Device device, Exception t) {
|
||||||
|
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
|
||||||
|
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) {
|
||||||
|
// remove device from cache in case null value cached in the distributed redis.
|
||||||
|
if (device != null) {
|
||||||
|
removeDeviceFromCacheByName(device.getTenantId(), device.getName());
|
||||||
|
removeDeviceFromCacheById(device.getTenantId(), device.getId());
|
||||||
|
}
|
||||||
|
return new DataValidationException("Device with such name already exists!");
|
||||||
|
} else if (t instanceof RuntimeException) {
|
||||||
|
return (RuntimeException)t;
|
||||||
|
} else {
|
||||||
|
return new RuntimeException("Failed to save device!", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2021 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.tx;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TransactionHandler {
|
||||||
|
|
||||||
|
@Transactional(propagation = Propagation.REQUIRED)
|
||||||
|
public <T> T runInTransaction(Supplier<T> supplier) {
|
||||||
|
return supplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
|
public <T> T runInNewTransaction(Supplier<T> supplier) {
|
||||||
|
return supplier.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user