diff --git a/application/src/main/data/upgrade/3.7.0/schema_update.sql b/application/src/main/data/upgrade/3.7.0/schema_update.sql index a52eb73783..afbd86fee7 100644 --- a/application/src/main/data/upgrade/3.7.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.7.0/schema_update.sql @@ -19,14 +19,34 @@ CREATE SEQUENCE IF NOT EXISTS attribute_kv_version_seq cache 1; CREATE SEQUENCE IF NOT EXISTS ts_kv_latest_version_seq cache 1; -ALTER TABLE attribute_kv ADD COLUMN version bigint default 0; -ALTER TABLE ts_kv_latest ADD COLUMN version bigint default 0; +ALTER TABLE attribute_kv ADD COLUMN IF NOT EXISTS version bigint default 0; +ALTER TABLE ts_kv_latest ADD COLUMN IF NOT EXISTS version bigint default 0; -- KV VERSIONING UPDATE END -- RELATION VERSIONING UPDATE START CREATE SEQUENCE IF NOT EXISTS relation_version_seq cache 1; -ALTER TABLE relation ADD COLUMN version bigint default 0; +ALTER TABLE relation ADD COLUMN IF NOT EXISTS version bigint default 0; -- RELATION VERSIONING UPDATE END + + +-- ENTITIES VERSIONING UPDATE START + +ALTER TABLE device ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE device_profile ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE device_credentials ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE asset ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE asset_profile ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE entity_view ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE customer ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE edge ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE rule_chain ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE dashboard ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE widget_type ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE widgets_bundle ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; +ALTER TABLE tenant ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1; + +-- ENTITIES VERSIONING UPDATE END diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index b723603e8a..29b9db10ab 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -65,6 +65,7 @@ import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeInfo; +import org.thingsboard.server.common.data.exception.EntityVersionMismatchException; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AlarmCommentId; @@ -390,6 +391,8 @@ public abstract class BaseController { } else { return new ThingsboardException("Database error", ThingsboardErrorCode.GENERAL); } + } else if (exception instanceof EntityVersionMismatchException) { + return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.VERSION_CONFLICT); } return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.GENERAL); } diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java index fd73a424a3..256302f30e 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java @@ -92,6 +92,7 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand errorCodeToStatusMap.put(ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS); errorCodeToStatusMap.put(ThingsboardErrorCode.TOO_MANY_UPDATES, HttpStatus.TOO_MANY_REQUESTS); errorCodeToStatusMap.put(ThingsboardErrorCode.SUBSCRIPTION_VIOLATION, HttpStatus.FORBIDDEN); + errorCodeToStatusMap.put(ThingsboardErrorCode.VERSION_CONFLICT, HttpStatus.CONFLICT); } private static ThingsboardErrorCode statusToErrorCode(HttpStatus status) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 3de4c95cc3..0eb128d0b7 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -182,7 +182,8 @@ public class EdgeEventSourcingListener { return false; } if (oldEntity != null) { - User oldUser = (User) oldEntity; + user = JacksonUtil.clone(user); + User oldUser = JacksonUtil.clone((User) oldEntity); cleanUpUserAdditionalInfo(oldUser); cleanUpUserAdditionalInfo(user); return !user.equals(oldUser); @@ -226,6 +227,7 @@ public class EdgeEventSourcingListener { user.setAdditionalInfo(additionalInfo); } } + user.setVersion(null); } private EdgeEventType getEdgeEventTypeForEntityEvent(Object entity) { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java index b05ece9c61..d527492d98 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java @@ -183,7 +183,7 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T try { DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials)); logEntityActionService.logEntityAction(tenantId, deviceId, device, device.getCustomerId(), - actionType, user, deviceCredentials); + actionType, user, result); return result; } catch (Exception e) { logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java index 77996a8b7f..26c0771c20 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java @@ -22,6 +22,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.HasVersion; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -74,6 +75,9 @@ public class DefaultEntityExportService loadedAuditLogs = new ArrayList<>(); diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 512dd434dc..cf46d0f7fd 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -192,7 +192,7 @@ public class DeviceControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); savedDevice.setName("My new device"); - doPost("/api/device", savedDevice, Device.class); + savedDevice = doPost("/api/device", savedDevice, Device.class); testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED); @@ -247,7 +247,7 @@ public class DeviceControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); savedDevice.setName("My new device"); - doPost("/api/device", savedDevice, Device.class); + savedDevice = doPost("/api/device", savedDevice, Device.class); testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED); @@ -749,8 +749,7 @@ public class DeviceControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); - doPost("/api/device/credentials", deviceCredentials) - .andExpect(status().isOk()); + deviceCredentials = doPost("/api/device/credentials", deviceCredentials, DeviceCredentials.class); testNotifyEntityMsgToEdgePushMsgToCoreOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.CREDENTIALS_UPDATED, deviceCredentials); @@ -1586,10 +1585,33 @@ public class DeviceControllerTest extends AbstractControllerTest { Assert.assertEquals(newAttributeValue, actualAttribute.get("value")); } + @Test + public void testSaveDeviceWithOutdatedVersion() throws Exception { + Device device = createDevice("Device v1.0"); + assertThat(device.getVersion()).isOne(); + + device.setName("Device v2.0"); + device = doPost("/api/device", device, Device.class); + assertThat(device.getVersion()).isEqualTo(2); + + device.setName("Device v1.1"); + device.setVersion(1L); + String response = doPost("/api/device", device).andExpect(status().isConflict()) + .andReturn().getResponse().getContentAsString(); + assertThat(JacksonUtil.toJsonNode(response).get("message").asText()) + .containsIgnoringCase("already changed by someone else"); + + device.setVersion(null); // overriding entity + device = doPost("/api/device", device, Device.class); + assertThat(device.getName()).isEqualTo("Device v1.1"); + assertThat(device.getVersion()).isEqualTo(3); + } + private Device createDevice(String name) { Device device = new Device(); device.setName(name); device.setType("default"); return doPost("/api/device", device, Device.class); } + } diff --git a/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java index 827ea250ed..9e70a00f2f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java @@ -176,7 +176,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { savedView.setName("New test entity view"); - doPost("/api/entityView", savedView, EntityView.class); + savedView = doPost("/api/entityView", savedView, EntityView.class); foundEntityView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); assertEquals(savedView, foundEntityView); diff --git a/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java index a990f55224..949dd4a781 100644 --- a/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java @@ -120,7 +120,7 @@ public class RuleChainControllerTest extends AbstractControllerTest { ActionType.ADDED); savedRuleChain.setName("New RuleChain"); - doPost("/api/ruleChain", savedRuleChain, RuleChain.class); + savedRuleChain = doPost("/api/ruleChain", savedRuleChain, RuleChain.class); RuleChain foundRuleChain = doGet("/api/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class); Assert.assertEquals(savedRuleChain.getName(), foundRuleChain.getName()); diff --git a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java index 14e597378c..e1beda28d8 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java @@ -149,7 +149,7 @@ public class TenantControllerTest extends AbstractControllerTest { testBroadcastEntityStateChangeEventTimeManyTimeTenant(savedTenant, ComponentLifecycleEvent.CREATED, 1); savedTenant.setTitle("My new tenant"); - saveTenant(savedTenant); + savedTenant = saveTenant(savedTenant); Tenant foundTenant = doGet("/api/tenant/" + savedTenant.getId().getId().toString(), Tenant.class); Assert.assertEquals(foundTenant.getTitle(), savedTenant.getTitle()); @@ -470,7 +470,7 @@ public class TenantControllerTest extends AbstractControllerTest { tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); tenant.setTenantProfileId(tenantProfile.getId()); - saveTenant(tenant); + tenant = saveTenant(tenant); login(username, password); @@ -500,7 +500,7 @@ public class TenantControllerTest extends AbstractControllerTest { tenantProfile2 = doPost("/api/tenantProfile", tenantProfile2, TenantProfile.class); tenant.setTenantProfileId(tenantProfile2.getId()); - saveTenant(tenant); + tenant = saveTenant(tenant); login(username, password); @@ -542,7 +542,7 @@ public class TenantControllerTest extends AbstractControllerTest { loginSysAdmin(); tenant.setTenantProfileId(null); - saveTenant(tenant); + tenant = saveTenant(tenant); login(username, password); for (Queue queue : foundTenantQueues) { diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java index c5f34e4f47..071d6b1391 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java @@ -204,8 +204,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest { Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); deviceCredentials.setCredentialsId("access_token"); - doPost("/api/device/credentials", deviceCredentials) - .andExpect(status().isOk()); + deviceCredentials = doPost("/api/device/credentials", deviceCredentials, DeviceCredentials.class); Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof DeviceCredentialsUpdateMsg); @@ -218,8 +217,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest { deviceCredentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); deviceCredentials.setCredentialsId(null); deviceCredentials.setCredentialsValue("-----BEGIN RSA PRIVATE KEY-----"); - doPost("/api/device/credentials", deviceCredentials) - .andExpect(status().isOk()); + deviceCredentials = doPost("/api/device/credentials", deviceCredentials, DeviceCredentials.class); Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof DeviceCredentialsUpdateMsg); diff --git a/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java index 503d87cf6f..93fea90c5a 100644 --- a/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java @@ -47,7 +47,7 @@ public class UserEdgeTest extends AbstractEdgeTest { @Test public void testCreateUpdateDeleteTenantUser() throws Exception { // create user - edgeImitator.expectMessageAmount(3); + edgeImitator.expectMessageAmount(4); User newTenantAdmin = new User(); newTenantAdmin.setAuthority(Authority.TENANT_ADMIN); newTenantAdmin.setTenantId(tenantId); @@ -55,7 +55,7 @@ public class UserEdgeTest extends AbstractEdgeTest { newTenantAdmin.setFirstName("Boris"); newTenantAdmin.setLastName("Johnson"); User savedTenantAdmin = createUser(newTenantAdmin, "tenant"); - Assert.assertTrue(edgeImitator.waitForMessages()); // wait 3 messages - user update msg and x2 user credentials update msgs + Assert.assertTrue(edgeImitator.waitForMessages()); // wait 4 messages - x2 user update msg and x2 user credentials update msgs Optional userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); Assert.assertTrue(userUpdateMsgOpt.isPresent()); UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get(); @@ -131,7 +131,7 @@ public class UserEdgeTest extends AbstractEdgeTest { Assert.assertTrue(edgeImitator.waitForMessages()); // create user - edgeImitator.expectMessageAmount(3); + edgeImitator.expectMessageAmount(4); User customerUser = new User(); customerUser.setAuthority(Authority.CUSTOMER_USER); customerUser.setTenantId(tenantId); @@ -140,7 +140,7 @@ public class UserEdgeTest extends AbstractEdgeTest { customerUser.setFirstName("John"); customerUser.setLastName("Edwards"); User savedCustomerUser = createUser(customerUser, "customer"); - Assert.assertTrue(edgeImitator.waitForMessages()); // wait 3 messages - user update msg and x2 user credentials update msgs + Assert.assertTrue(edgeImitator.waitForMessages()); // wait 4 messages - x2 user update msg and x2 user credentials update msgs Optional userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); Assert.assertTrue(userUpdateMsgOpt.isPresent()); UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java index c7027ed42a..4f24c58916 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java @@ -59,7 +59,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); - createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); + createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); awaitObserveReadAll(0, device.getId().getId().toString()); device.setFirmwareId(createFirmware().getId()); @@ -145,12 +145,14 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { return tsKvEntries; } - private boolean predicateForStatuses (List ts) { - List statuses = ts.stream().sorted(Comparator - .comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString) + private boolean predicateForStatuses(List ts) { + List statuses = ts.stream() + .sorted(Comparator.comparingLong(TsKvEntry::getTs)) + .map(KvEntry::getValueAsString) .map(OtaPackageUpdateStatus::valueOf) .collect(Collectors.toList()); log.warn("{}", statuses); return statuses.containsAll(expectedStatuses); } + } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/VersionedCaffeineTbCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/VersionedCaffeineTbCache.java index 9f74e413ba..f9c22ecc32 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/VersionedCaffeineTbCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/VersionedCaffeineTbCache.java @@ -39,14 +39,14 @@ public abstract class VersionedCaffeineTbCache versionValuePair = doGet(key); @@ -85,7 +85,7 @@ public abstract class VersionedCaffeineTbCache wrapValue(V value, Long version) { diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/VersionedRedisTbCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/VersionedRedisTbCache.java index 7c1e8dc9a6..6ef3918a68 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/VersionedRedisTbCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/VersionedRedisTbCache.java @@ -46,20 +46,11 @@ public abstract class VersionedRedisTbCacheI8", currentVersionBytes) if newVersion > currentVersion then setNewValue() end @@ -68,7 +59,7 @@ public abstract class VersionedRedisTbCache valueSerializer) { super(cacheName, cacheSpecsMap, connectionFactory, configuration, valueSerializer); @@ -168,14 +159,4 @@ public abstract class VersionedRedisTbCache { +public class DeviceCaffeineCache extends VersionedCaffeineTbCache { public DeviceCaffeineCache(CacheManager cacheManager) { super(cacheManager, CacheConstants.DEVICE_CACHE); diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java index 03eea82f09..6e338a175a 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java @@ -21,9 +21,9 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.SerializationException; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; -import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.cache.TbRedisSerializer; +import org.thingsboard.server.cache.VersionedRedisTbCache; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.util.ProtoUtils; @@ -31,7 +31,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("DeviceCache") -public class DeviceRedisCache extends RedisTbTransactionalCache { +public class DeviceRedisCache extends VersionedRedisTbCache { public DeviceRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { super(CacheConstants.DEVICE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java index 7972a75340..07b91294f7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @EqualsAndHashCode(callSuper = true) -public class Customer extends ContactBased implements HasTenantId, ExportableEntity, HasTitle { +public class Customer extends ContactBased implements HasTenantId, ExportableEntity, HasTitle, HasVersion { private static final long serialVersionUID = -1599722990298929275L; @@ -42,6 +42,8 @@ public class Customer extends ContactBased implements HasTenantId, E @Getter @Setter private CustomerId externalId; + @Getter @Setter + private Long version; public Customer() { super(); @@ -56,6 +58,7 @@ public class Customer extends ContactBased implements HasTenantId, E this.tenantId = customer.getTenantId(); this.title = customer.getTitle(); this.externalId = customer.getExternalId(); + this.version = customer.getVersion(); } public TenantId getTenantId() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java index 81c7c7683b..a6cc81a51a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java @@ -18,6 +18,8 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; +import lombok.Getter; +import lombok.Setter; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; @@ -29,7 +31,7 @@ import java.util.Objects; import java.util.Set; @Schema -public class DashboardInfo extends BaseData implements HasName, HasTenantId, HasTitle, HasImage { +public class DashboardInfo extends BaseData implements HasName, HasTenantId, HasTitle, HasImage, HasVersion { private static final long serialVersionUID = -9080404114760433799L; @@ -43,6 +45,9 @@ public class DashboardInfo extends BaseData implements HasName, Has private boolean mobileHide; private Integer mobileOrder; + @Getter @Setter + private Long version; + public DashboardInfo() { super(); } @@ -59,6 +64,7 @@ public class DashboardInfo extends BaseData implements HasName, Has this.assignedCustomers = dashboardInfo.getAssignedCustomers(); this.mobileHide = dashboardInfo.isMobileHide(); this.mobileOrder = dashboardInfo.getMobileOrder(); + this.version = dashboardInfo.getVersion(); } @Schema(description = "JSON object with the dashboard Id. " + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index e7c37d3ed1..f6478f4a0b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -22,6 +22,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.id.CustomerId; @@ -38,8 +39,9 @@ import java.util.Optional; @Schema @EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) @Slf4j -public class Device extends BaseDataWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity { +public class Device extends BaseDataWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, HasVersion, ExportableEntity { private static final long serialVersionUID = 2807343040519543363L; @@ -65,6 +67,8 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL @Getter @Setter private DeviceId externalId; + @Getter @Setter + private Long version; public Device() { super(); @@ -86,6 +90,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL this.firmwareId = device.getFirmwareId(); this.softwareId = device.getSoftwareId(); this.externalId = device.getExternalId(); + this.version = device.getVersion(); } public Device updateDevice(Device device) { @@ -100,6 +105,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL this.setSoftwareId(device.getSoftwareId()); Optional.ofNullable(device.getAdditionalInfo()).ifPresent(this::setAdditionalInfo); this.setExternalId(device.getExternalId()); + this.setVersion(device.getVersion()); return this; } @@ -225,33 +231,4 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL return super.getAdditionalInfo(); } - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Device [tenantId="); - builder.append(tenantId); - builder.append(", customerId="); - builder.append(customerId); - builder.append(", name="); - builder.append(name); - builder.append(", type="); - builder.append(type); - builder.append(", label="); - builder.append(label); - builder.append(", deviceProfileId="); - builder.append(deviceProfileId); - builder.append(", deviceData="); - builder.append(firmwareId); - builder.append(", firmwareId="); - builder.append(deviceData); - builder.append(", additionalInfo="); - builder.append(getAdditionalInfo()); - builder.append(", createdTime="); - builder.append(createdTime); - builder.append(", id="); - builder.append(id); - builder.append("]"); - return builder.toString(); - } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java index c324f96ac2..6a583afbd1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java @@ -18,11 +18,13 @@ package org.thingsboard.server.common.data; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.ToString; import org.thingsboard.server.common.data.id.DeviceId; @Schema @Data @EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) public class DeviceInfo extends Device { private static final long serialVersionUID = -3004579925090663691L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 6791a460ed..df297ba85e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -42,7 +42,7 @@ import java.io.IOException; @ToString(exclude = {"image", "profileDataBytes"}) @EqualsAndHashCode(callSuper = true) @Slf4j -public class DeviceProfile extends BaseData implements HasName, HasTenantId, HasOtaPackage, HasRuleEngineProfile, ExportableEntity, HasImage, HasDefaultOption { +public class DeviceProfile extends BaseData implements HasName, HasTenantId, HasOtaPackage, HasRuleEngineProfile, ExportableEntity, HasImage, HasDefaultOption, HasVersion { private static final long serialVersionUID = 6998485460273302018L; @@ -97,6 +97,7 @@ public class DeviceProfile extends BaseData implements HasName, private RuleChainId defaultEdgeRuleChainId; private DeviceProfileId externalId; + private Long version; public DeviceProfile() { super(); @@ -122,6 +123,7 @@ public class DeviceProfile extends BaseData implements HasName, this.softwareId = deviceProfile.getSoftwareId(); this.defaultEdgeRuleChainId = deviceProfile.getDefaultEdgeRuleChainId(); this.externalId = deviceProfile.getExternalId(); + this.version = deviceProfile.getVersion(); } @Schema(description = "JSON object with the device profile Id. " + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java index 344045134e..8c7a89a220 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java @@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @AllArgsConstructor @EqualsAndHashCode(callSuper = true) public class EntityView extends BaseDataWithAdditionalInfo - implements HasName, HasTenantId, HasCustomerId, ExportableEntity { + implements HasName, HasTenantId, HasCustomerId, HasVersion, ExportableEntity { private static final long serialVersionUID = 5582010124562018986L; @@ -60,6 +60,7 @@ public class EntityView extends BaseDataWithAdditionalInfo private long endTimeMs; private EntityViewId externalId; + private Long version; public EntityView() { super(); @@ -80,6 +81,7 @@ public class EntityView extends BaseDataWithAdditionalInfo this.startTimeMs = entityView.getStartTimeMs(); this.endTimeMs = entityView.getEndTimeMs(); this.externalId = entityView.getExternalId(); + this.version = entityView.getVersion(); } @Schema(description = "JSON object with Customer Id. Use 'assignEntityViewToCustomer' to change the Customer Id.", accessMode = Schema.AccessMode.READ_ONLY) @@ -102,7 +104,7 @@ public class EntityView extends BaseDataWithAdditionalInfo @Schema(description = "JSON object with the Entity View Id. " + "Specify this field to update the Entity View. " + "Referencing non-existing Entity View Id will cause error. " + - "Omit this field to create new Entity View." ) + "Omit this field to create new Entity View.") @Override public EntityViewId getId() { return super.getId(); @@ -114,7 +116,7 @@ public class EntityView extends BaseDataWithAdditionalInfo return super.getCreatedTime(); } - @Schema(description = "Additional parameters of the device",implementation = com.fasterxml.jackson.databind.JsonNode.class) + @Schema(description = "Additional parameters of the device", implementation = com.fasterxml.jackson.databind.JsonNode.class) @Override public JsonNode getAdditionalInfo() { return super.getAdditionalInfo(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasVersion.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasVersion.java index b9af6f93c9..db916d2195 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/HasVersion.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasVersion.java @@ -16,5 +16,10 @@ package org.thingsboard.server.common.data; public interface HasVersion { + Long getVersion(); + + default void setVersion(Long version) { + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java index b7fade3e09..0c0c5c760a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java @@ -20,6 +20,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.validation.Length; @@ -27,7 +29,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @Schema @EqualsAndHashCode(callSuper = true) -public class Tenant extends ContactBased implements HasTenantId, HasTitle { +public class Tenant extends ContactBased implements HasTenantId, HasTitle, HasVersion { private static final long serialVersionUID = 8057243243859922101L; @@ -43,6 +45,9 @@ public class Tenant extends ContactBased implements HasTenantId, HasTi @Schema(description = "JSON object with Tenant Profile Id") private TenantProfileId tenantProfileId; + @Getter @Setter + private Long version; + public Tenant() { super(); } @@ -56,6 +61,7 @@ public class Tenant extends ContactBased implements HasTenantId, HasTi this.title = tenant.getTitle(); this.region = tenant.getRegion(); this.tenantProfileId = tenant.getTenantProfileId(); + this.version = tenant.getVersion(); } public String getTitle() { @@ -98,7 +104,7 @@ public class Tenant extends ContactBased implements HasTenantId, HasTi @Schema(description = "JSON object with the tenant Id. " + "Specify this field to update the tenant. " + "Referencing non-existing tenant Id will cause error. " + - "Omit this field to create new tenant." ) + "Omit this field to create new tenant.") @Override public TenantId getId() { return super.getId(); @@ -158,7 +164,7 @@ public class Tenant extends ContactBased implements HasTenantId, HasTi return super.getEmail(); } - @Schema(description = "Additional parameters of the device",implementation = com.fasterxml.jackson.databind.JsonNode.class) + @Schema(description = "Additional parameters of the device", implementation = com.fasterxml.jackson.databind.JsonNode.class) @Override public JsonNode getAdditionalInfo() { return super.getAdditionalInfo(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java index 678b7cdac6..7b616d2e54 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/User.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/User.java @@ -20,6 +20,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -33,7 +35,7 @@ import static org.apache.commons.lang3.StringUtils.isNotEmpty; @Schema @EqualsAndHashCode(callSuper = true) -public class User extends BaseDataWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId, NotificationRecipient { +public class User extends BaseDataWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId, NotificationRecipient, HasVersion { private static final long serialVersionUID = 8250339805336035966L; @@ -50,6 +52,9 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, @NoXss private String phone; + @Getter @Setter + private Long version; + public User() { super(); } @@ -67,6 +72,7 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, this.firstName = user.getFirstName(); this.lastName = user.getLastName(); this.phone = user.getPhone(); + this.version = user.getVersion(); } @@ -222,4 +228,5 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, public boolean isCustomerUser() { return !isSystemAdmin() && !isTenantAdmin(); } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java index 16518bc618..4b79752135 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasLabel; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.HasVersion; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.CustomerId; @@ -36,7 +37,7 @@ import java.util.Optional; @Schema @EqualsAndHashCode(callSuper = true) -public class Asset extends BaseDataWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, ExportableEntity { +public class Asset extends BaseDataWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, HasVersion, ExportableEntity { private static final long serialVersionUID = 2807343040519543363L; @@ -56,6 +57,8 @@ public class Asset extends BaseDataWithAdditionalInfo implements HasLab @Getter @Setter private AssetId externalId; + @Getter @Setter + private Long version; public Asset() { super(); @@ -74,6 +77,7 @@ public class Asset extends BaseDataWithAdditionalInfo implements HasLab this.label = asset.getLabel(); this.assetProfileId = asset.getAssetProfileId(); this.externalId = asset.getExternalId(); + this.version = asset.getVersion(); } public void update(Asset asset) { @@ -85,6 +89,7 @@ public class Asset extends BaseDataWithAdditionalInfo implements HasLab this.assetProfileId = asset.getAssetProfileId(); Optional.ofNullable(asset.getAdditionalInfo()).ifPresent(this::setAdditionalInfo); this.externalId = asset.getExternalId(); + this.version = asset.getVersion(); } @Schema(description = "JSON object with the asset Id. " + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java index 91563c0f2d..890c497d4b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.HasImage; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasRuleEngineProfile; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.HasVersion; import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.RuleChainId; @@ -39,7 +40,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @ToString(exclude = {"image"}) @EqualsAndHashCode(callSuper = true) @Slf4j -public class AssetProfile extends BaseData implements HasName, HasTenantId, HasRuleEngineProfile, ExportableEntity, HasImage, HasDefaultOption { +public class AssetProfile extends BaseData implements HasName, HasTenantId, HasRuleEngineProfile, ExportableEntity, HasImage, HasDefaultOption, HasVersion { private static final long serialVersionUID = 6998485460273302018L; @@ -74,6 +75,7 @@ public class AssetProfile extends BaseData implements HasName, H private RuleChainId defaultEdgeRuleChainId; private AssetProfileId externalId; + private Long version; public AssetProfile() { super(); @@ -95,6 +97,7 @@ public class AssetProfile extends BaseData implements HasName, H this.defaultQueueName = assetProfile.getDefaultQueueName(); this.defaultEdgeRuleChainId = assetProfile.getDefaultEdgeRuleChainId(); this.externalId = assetProfile.getExternalId(); + this.version = assetProfile.getVersion(); } @Schema(description = "JSON object with the asset profile Id. " + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java index 67d32d6e89..ec8ade7f69 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java @@ -17,12 +17,14 @@ package org.thingsboard.server.common.data.edge; import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.thingsboard.server.common.data.BaseDataWithAdditionalInfo; import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasLabel; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.HasVersion; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; @@ -34,7 +36,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @EqualsAndHashCode(callSuper = true) @ToString @Setter -public class Edge extends BaseDataWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId { +public class Edge extends BaseDataWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, HasVersion { private static final long serialVersionUID = 4934987555236873728L; @@ -57,6 +59,9 @@ public class Edge extends BaseDataWithAdditionalInfo implements HasLabel @Length(fieldName = "secret") private String secret; + @Getter + private Long version; + public Edge() { super(); } @@ -75,6 +80,7 @@ public class Edge extends BaseDataWithAdditionalInfo implements HasLabel this.name = edge.getName(); this.routingKey = edge.getRoutingKey(); this.secret = edge.getSecret(); + this.version = edge.getVersion(); } public void update(Edge edge) { @@ -86,6 +92,7 @@ public class Edge extends BaseDataWithAdditionalInfo implements HasLabel this.name = edge.getName(); this.routingKey = edge.getRoutingKey(); this.secret = edge.getSecret(); + this.version = edge.getVersion(); } @Schema(description = "JSON object with the Edge Id. " + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/exception/EntityVersionMismatchException.java b/common/data/src/main/java/org/thingsboard/server/common/data/exception/EntityVersionMismatchException.java new file mode 100644 index 0000000000..310c6bccf7 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/exception/EntityVersionMismatchException.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2024 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.data.exception; + +public class EntityVersionMismatchException extends RuntimeException { + + public EntityVersionMismatchException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/exception/ThingsboardErrorCode.java b/common/data/src/main/java/org/thingsboard/server/common/data/exception/ThingsboardErrorCode.java index 0f789a5822..43423e9eef 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/exception/ThingsboardErrorCode.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/exception/ThingsboardErrorCode.java @@ -29,6 +29,7 @@ public enum ThingsboardErrorCode { ITEM_NOT_FOUND(32), TOO_MANY_REQUESTS(33), TOO_MANY_UPDATES(34), + VERSION_CONFLICT(35), SUBSCRIPTION_VIOLATION(40), PASSWORD_VIOLATION(45); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java index 5448fcd259..0f7e793878 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.HasDefaultOption; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.HasVersion; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; @@ -36,7 +37,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @Data @EqualsAndHashCode(callSuper = true) @Slf4j -public class RuleChain extends BaseDataWithAdditionalInfo implements HasName, HasTenantId, ExportableEntity, HasDefaultOption { +public class RuleChain extends BaseDataWithAdditionalInfo implements HasName, HasTenantId, ExportableEntity, HasDefaultOption, HasVersion { private static final long serialVersionUID = -5656679015121935465L; @@ -58,6 +59,7 @@ public class RuleChain extends BaseDataWithAdditionalInfo implement private transient JsonNode configuration; private RuleChainId externalId; + private Long version; @JsonIgnore private byte[] configurationBytes; @@ -79,6 +81,7 @@ public class RuleChain extends BaseDataWithAdditionalInfo implement this.root = ruleChain.isRoot(); this.setConfiguration(ruleChain.getConfiguration()); this.setExternalId(ruleChain.getExternalId()); + this.version = ruleChain.getVersion(); } @Override @@ -89,7 +92,7 @@ public class RuleChain extends BaseDataWithAdditionalInfo implement @Schema(description = "JSON object with the Rule Chain Id. " + "Specify this field to update the Rule Chain. " + "Referencing non-existing Rule Chain Id will cause error. " + - "Omit this field to create new rule chain." ) + "Omit this field to create new rule chain.") @Override public RuleChainId getId() { return super.getId(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java index e269f0438d..094582f647 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java @@ -17,20 +17,26 @@ package org.thingsboard.server.common.data.security; import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasVersion; import org.thingsboard.server.common.data.id.DeviceCredentialsId; import org.thingsboard.server.common.data.id.DeviceId; @Schema @EqualsAndHashCode(callSuper = true) -public class DeviceCredentials extends BaseData implements DeviceCredentialsFilter { +public class DeviceCredentials extends BaseData implements DeviceCredentialsFilter, HasVersion { private static final long serialVersionUID = -7869261127032877765L; private DeviceId deviceId; private DeviceCredentialsType credentialsType; private String credentialsId; private String credentialsValue; - + + @Getter @Setter + private Long version; + public DeviceCredentials() { super(); } @@ -45,6 +51,7 @@ public class DeviceCredentials extends BaseData implements this.credentialsType = deviceCredentials.getCredentialsType(); this.credentialsId = deviceCredentials.getCredentialsId(); this.credentialsValue = deviceCredentials.getCredentialsValue(); + this.version = deviceCredentials.getVersion(); } @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "The Id is automatically generated during device creation. " + @@ -111,4 +118,5 @@ public class DeviceCredentials extends BaseData implements + credentialsId + ", credentialsValue=" + credentialsValue + ", createdTime=" + createdTime + ", id=" + id + "]"; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java index 4ec88fd92b..1b5459ec1a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java @@ -60,6 +60,6 @@ import java.lang.annotation.Target; @Type(name = "NOTIFICATION_RULE", value = NotificationRule.class), @Type(name = "TB_RESOURCE", value = TbResource.class) }) -@JsonIgnoreProperties(value = {"tenantId", "createdTime"}, ignoreUnknown = true) +@JsonIgnoreProperties(value = {"tenantId", "createdTime", "version"}, ignoreUnknown = true) public @interface JsonTbEntity { } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java index ec922f2362..3285fd4ca0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; public class DeviceExportData extends EntityExportData { @JsonProperty(index = 3) - @JsonIgnoreProperties({"id", "deviceId", "createdTime"}) + @JsonIgnoreProperties({"id", "deviceId", "createdTime", "version"}) private DeviceCredentials credentials; @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java index 2336c283a6..7f60ea5bb7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java @@ -95,4 +95,5 @@ public class EntityExportData> { public boolean hasRelations() { return relations != null; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java index 5bf721a2f4..6e1198d840 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java @@ -17,16 +17,19 @@ package org.thingsboard.server.common.data.widget; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.HasVersion; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @Data -public class BaseWidgetType extends BaseData implements HasName, HasTenantId { +@EqualsAndHashCode(callSuper = true) +public class BaseWidgetType extends BaseData implements HasName, HasTenantId, HasVersion { private static final long serialVersionUID = 8388684344603660756L; @@ -44,6 +47,8 @@ public class BaseWidgetType extends BaseData implements HasName, H @Schema(description = "Whether widget type is deprecated.", example = "true") private boolean deprecated; + private Long version; + public BaseWidgetType() { super(); } @@ -58,12 +63,13 @@ public class BaseWidgetType extends BaseData implements HasName, H this.fqn = widgetType.getFqn(); this.name = widgetType.getName(); this.deprecated = widgetType.isDeprecated(); + this.version = widgetType.getVersion(); } @Schema(description = "JSON object with the Widget Type Id. " + "Specify this field to update the Widget Type. " + "Referencing non-existing Widget Type Id will cause error. " + - "Omit this field to create new Widget Type." ) + "Omit this field to create new Widget Type.") @Override public WidgetTypeId getId() { return super.getId(); @@ -74,4 +80,5 @@ public class BaseWidgetType extends BaseData implements HasName, H public long getCreatedTime() { return super.getCreatedTime(); } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java index f29fe8fec5..55bd1cf27a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java @@ -18,8 +18,7 @@ package org.thingsboard.server.common.data.widget; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import lombok.Getter; -import lombok.Setter; +import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.HasImage; import org.thingsboard.server.common.data.HasName; @@ -29,7 +28,8 @@ import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @Data -@JsonPropertyOrder({ "fqn", "name", "deprecated", "image", "description", "descriptor", "externalId" }) +@EqualsAndHashCode(callSuper = true) +@JsonPropertyOrder({"fqn", "name", "deprecated", "image", "description", "descriptor", "externalId"}) public class WidgetTypeDetails extends WidgetType implements HasName, HasTenantId, HasImage, ExportableEntity { @Schema(description = "Relative or external image URL. Replaced with image data URL (Base64) in case of relative URL and 'inlineImages' option enabled.") @@ -42,8 +42,6 @@ public class WidgetTypeDetails extends WidgetType implements HasName, HasTenantI @Schema(description = "Tags of the widget type") private String[] tags; - @Getter - @Setter private WidgetTypeId externalId; public WidgetTypeDetails() { @@ -65,4 +63,5 @@ public class WidgetTypeDetails extends WidgetType implements HasName, HasTenantI this.tags = widgetTypeDetails.getTags(); this.externalId = widgetTypeDetails.getExternalId(); } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java index 7e75f5e58f..e8e964a033 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.HasImage; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTitle; +import org.thingsboard.server.common.data.HasVersion; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.validation.Length; @@ -33,7 +34,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @Schema @EqualsAndHashCode(callSuper = true) -public class WidgetsBundle extends BaseData implements HasName, HasTenantId, ExportableEntity, HasTitle, HasImage { +public class WidgetsBundle extends BaseData implements HasName, HasTenantId, ExportableEntity, HasTitle, HasImage, HasVersion { private static final long serialVersionUID = -7627368878362410489L; @@ -76,6 +77,9 @@ public class WidgetsBundle extends BaseData implements HasName, @Getter @Setter private WidgetsBundleId externalId; + @Getter + @Setter + private Long version; public WidgetsBundle() { super(); @@ -94,6 +98,7 @@ public class WidgetsBundle extends BaseData implements HasName, this.description = widgetsBundle.getDescription(); this.order = widgetsBundle.getOrder(); this.externalId = widgetsBundle.getExternalId(); + this.version = widgetsBundle.getVersion(); } @Schema(description = "JSON object with the Widget Bundle Id. " + diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/MaxPayloadSizeExceededException.java b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/MaxPayloadSizeExceededException.java index 8195bf19a7..a9a206a4df 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/MaxPayloadSizeExceededException.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/MaxPayloadSizeExceededException.java @@ -20,4 +20,5 @@ public class MaxPayloadSizeExceededException extends RuntimeException { public MaxPayloadSizeExceededException() { super("Payload size exceeds the limit"); } + } diff --git a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java index 20d5021492..b0574da7b1 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java @@ -17,9 +17,7 @@ package org.thingsboard.server.common.util; import com.fasterxml.jackson.core.JsonProcessingException; import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.Nullable; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageStateValue; @@ -569,6 +567,9 @@ public class ProtoUtils { if (isNotNull(device.getDeviceDataBytes())) { builder.setDeviceData(ByteString.copyFrom(device.getDeviceDataBytes())); } + if (isNotNull(device.getVersion())) { + builder.setVersion(device.getVersion()); + } return builder.build(); } @@ -600,6 +601,9 @@ public class ProtoUtils { if (proto.hasDeviceData()) { device.setDeviceDataBytes(proto.getDeviceData().toByteArray()); } + if (proto.hasVersion()) { + device.setVersion(proto.getVersion()); + } return device; } @@ -655,6 +659,9 @@ public class ProtoUtils { builder.setDefaultEdgeRuleChainIdMSB(getMsb(deviceProfile.getDefaultEdgeRuleChainId())) .setDefaultEdgeRuleChainIdLSB(getLsb(deviceProfile.getDefaultEdgeRuleChainId())); } + if (isNotNull(deviceProfile.getVersion())) { + builder.setVersion(deviceProfile.getVersion()); + } return builder.build(); } @@ -700,6 +707,9 @@ public class ProtoUtils { if (proto.hasDefaultEdgeRuleChainIdMSB() && proto.hasDefaultEdgeRuleChainIdLSB()) { deviceProfile.setDefaultEdgeRuleChainId(getEntityId(proto.getDefaultEdgeRuleChainIdMSB(), proto.getDefaultEdgeRuleChainIdLSB(), RuleChainId::new)); } + if (proto.hasVersion()) { + deviceProfile.setVersion(proto.getVersion()); + } return deviceProfile; } @@ -742,6 +752,9 @@ public class ProtoUtils { if (isNotNull(tenant.getAdditionalInfo())) { builder.setAdditionalInfo(JacksonUtil.toString(tenant.getAdditionalInfo())); } + if (isNotNull(tenant.getVersion())) { + builder.setVersion(tenant.getVersion()); + } return builder.build(); } @@ -781,6 +794,9 @@ public class ProtoUtils { if (proto.hasAdditionalInfo()) { tenant.setAdditionalInfo(JacksonUtil.toJsonNode(proto.getAdditionalInfo())); } + if (proto.hasVersion()) { + tenant.setVersion(proto.getVersion()); + } return tenant; } @@ -989,6 +1005,9 @@ public class ProtoUtils { if (deviceCredentials.getCredentialsValue() != null) { builder.setCredentialsValue(deviceCredentials.getCredentialsValue()); } + if (deviceCredentials.getVersion() != null) { + builder.setVersion(deviceCredentials.getVersion()); + } return builder.build(); } @@ -1000,6 +1019,7 @@ public class ProtoUtils { deviceCredentials.setCredentialsId(proto.getCredentialsId()); deviceCredentials.setCredentialsType(DeviceCredentialsType.valueOf(proto.getCredentialsType().name())); deviceCredentials.setCredentialsValue(proto.hasCredentialsValue() ? proto.getCredentialsValue() : null); + deviceCredentials.setVersion(proto.hasVersion() ? proto.getVersion() : null); return deviceCredentials; } diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index 2167c00873..800250d1cc 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -217,6 +217,7 @@ message DeviceProto { optional int64 softwareIdLSB = 18; optional int64 externalIdMSB = 19; optional int64 externalIdLSB = 20; + optional int64 version = 21; } message DeviceProfileProto { @@ -247,6 +248,7 @@ message DeviceProfileProto { optional int64 defaultEdgeRuleChainIdLSB = 25; optional int64 externalIdMSB = 26; optional int64 externalIdLSB = 27; + optional int64 version = 28; } message TenantProto { @@ -266,6 +268,7 @@ message TenantProto { optional string phone = 14; optional string email = 15; optional string additionalInfo = 16; + optional int64 version = 17; } message TenantProfileProto { @@ -676,6 +679,7 @@ message DeviceCredentialsProto { CredentialsType credentialsType = 6; string credentialsId = 7; optional string credentialsValue = 8; + optional int64 version = 9; } message CredentialsDataProto { diff --git a/dao/src/main/java/org/thingsboard/server/dao/Dao.java b/dao/src/main/java/org/thingsboard/server/dao/Dao.java index e7290cdced..e912872496 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/Dao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/Dao.java @@ -39,7 +39,7 @@ public interface Dao { T saveAndFlush(TenantId tenantId, T t); - boolean removeById(TenantId tenantId, UUID id); + void removeById(TenantId tenantId, UUID id); void removeAllByIds(Collection ids); diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCacheKey.java index 0283fdb961..18e45d08fb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCacheKey.java @@ -38,15 +38,15 @@ public class AssetProfileCacheKey implements Serializable { this.defaultProfile = defaultProfile; } - public static AssetProfileCacheKey fromName(TenantId tenantId, String name) { + public static AssetProfileCacheKey forName(TenantId tenantId, String name) { return new AssetProfileCacheKey(tenantId, name, null, false); } - public static AssetProfileCacheKey fromId(AssetProfileId id) { + public static AssetProfileCacheKey forId(AssetProfileId id) { return new AssetProfileCacheKey(null, null, id, false); } - public static AssetProfileCacheKey defaultProfile(TenantId tenantId) { + public static AssetProfileCacheKey forDefaultProfile(TenantId tenantId) { return new AssetProfileCacheKey(tenantId, null, null, true); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCaffeineCache.java index db812732e8..b37f84e649 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCaffeineCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCaffeineCache.java @@ -18,13 +18,13 @@ package org.thingsboard.server.dao.asset; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; -import org.thingsboard.server.cache.CaffeineTbTransactionalCache; +import org.thingsboard.server.cache.VersionedCaffeineTbCache; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.asset.AssetProfile; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) @Service("AssetProfileCache") -public class AssetProfileCaffeineCache extends CaffeineTbTransactionalCache { +public class AssetProfileCaffeineCache extends VersionedCaffeineTbCache { public AssetProfileCaffeineCache(CacheManager cacheManager) { super(cacheManager, CacheConstants.ASSET_PROFILE_CACHE); diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileEvictEvent.java index a08ad2ad32..0cb1d35bf5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileEvictEvent.java @@ -15,11 +15,16 @@ */ package org.thingsboard.server.dao.asset; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.TenantId; @Data +@RequiredArgsConstructor +@AllArgsConstructor public class AssetProfileEvictEvent { private final TenantId tenantId; @@ -27,5 +32,6 @@ public class AssetProfileEvictEvent { private final String oldName; private final AssetProfileId assetProfileId; private final boolean defaultProfile; + private AssetProfile savedAssetProfile; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java index 625bc16d43..cd2ee7a9a8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java @@ -19,17 +19,18 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; -import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.cache.TbJsonRedisSerializer; +import org.thingsboard.server.cache.VersionedRedisTbCache; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.asset.AssetProfile; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("AssetProfileCache") -public class AssetProfileRedisCache extends RedisTbTransactionalCache { +public class AssetProfileRedisCache extends VersionedRedisTbCache { public AssetProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { super(CacheConstants.ASSET_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(AssetProfile.class)); } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java index 6848c58d5c..95786f0a6a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java @@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.id.HasId; 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.entity.AbstractCachedEntityService; +import org.thingsboard.server.dao.entity.CachedVersionedEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; @@ -53,7 +53,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @Service("AssetProfileDaoService") @Slf4j -public class AssetProfileServiceImpl extends AbstractCachedEntityService implements AssetProfileService { +public class AssetProfileServiceImpl extends CachedVersionedEntityService implements AssetProfileService { private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; @@ -81,18 +81,20 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService keys = new ArrayList<>(2); - keys.add(AssetProfileCacheKey.fromName(event.getTenantId(), event.getNewName())); - if (event.getAssetProfileId() != null) { - keys.add(AssetProfileCacheKey.fromId(event.getAssetProfileId())); + List toEvict = new ArrayList<>(2); + toEvict.add(AssetProfileCacheKey.forName(event.getTenantId(), event.getNewName())); + if (event.getSavedAssetProfile() != null) { + cache.put(AssetProfileCacheKey.forId(event.getSavedAssetProfile().getId()), event.getSavedAssetProfile()); + } else if (event.getAssetProfileId() != null) { + toEvict.add(AssetProfileCacheKey.forId(event.getAssetProfileId())); } if (event.isDefaultProfile()) { - keys.add(AssetProfileCacheKey.defaultProfile(event.getTenantId())); + toEvict.add(AssetProfileCacheKey.forDefaultProfile(event.getTenantId())); } if (StringUtils.isNotEmpty(event.getOldName()) && !event.getOldName().equals(event.getNewName())) { - keys.add(AssetProfileCacheKey.fromName(event.getTenantId(), event.getOldName())); + toEvict.add(AssetProfileCacheKey.forName(event.getTenantId(), event.getOldName())); } - cache.evict(keys); + cache.evict(toEvict); } @Override @@ -104,8 +106,8 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService INCORRECT_ASSET_PROFILE_ID + id); - return cache.getOrFetchFromDB(AssetProfileCacheKey.fromId(assetProfileId), - () -> assetProfileDao.findById(tenantId, assetProfileId.getId()), true, putInCache); + return cache.get(AssetProfileCacheKey.forId(assetProfileId), + () -> assetProfileDao.findById(tenantId, assetProfileId.getId()), putInCache); } @Override @@ -117,7 +119,7 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService INCORRECT_ASSET_PROFILE_NAME + s); - return cache.getOrFetchFromDB(AssetProfileCacheKey.fromName(tenantId, profileName), + return cache.getOrFetchFromDB(AssetProfileCacheKey.forName(tenantId, profileName), () -> assetProfileDao.findByName(tenantId, profileName), false, putInCache); } @@ -147,7 +149,7 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService INCORRECT_TENANT_ID + id); - return cache.getAndPutInTransaction(AssetProfileCacheKey.defaultProfile(tenantId), + return cache.getAndPutInTransaction(AssetProfileCacheKey.forDefaultProfile(tenantId), () -> assetProfileDao.findDefaultAssetProfile(tenantId), true); } @@ -353,4 +355,5 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService implements DeviceCredentialsService { - @Autowired - private DeviceCredentialsDao deviceCredentialsDao; - - @Autowired - private DataValidator credentialsValidator; + private final DeviceCredentialsDao deviceCredentialsDao; + private final DeviceCredentialsDataValidator credentialsValidator; @TransactionalEventListener(classes = DeviceCredentialsEvictEvent.class) @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java index eca228891d..6df8f9907e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java @@ -41,19 +41,19 @@ public class DeviceProfileCacheKey implements Serializable { this.provisionDeviceKey = provisionDeviceKey; } - public static DeviceProfileCacheKey fromName(TenantId tenantId, String name) { + public static DeviceProfileCacheKey forName(TenantId tenantId, String name) { return new DeviceProfileCacheKey(tenantId, name, null, false, null); } - public static DeviceProfileCacheKey fromId(DeviceProfileId id) { + public static DeviceProfileCacheKey forId(DeviceProfileId id) { return new DeviceProfileCacheKey(null, null, id, false, null); } - public static DeviceProfileCacheKey defaultProfile(TenantId tenantId) { + public static DeviceProfileCacheKey forDefaultProfile(TenantId tenantId) { return new DeviceProfileCacheKey(tenantId, null, null, true, null); } - public static DeviceProfileCacheKey fromProvisionDeviceKey(String provisionDeviceKey) { + public static DeviceProfileCacheKey forProvisionKey(String provisionDeviceKey) { return new DeviceProfileCacheKey(null, null, null, false, provisionDeviceKey); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCaffeineCache.java index d9bb2fec33..8343c8ba40 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCaffeineCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCaffeineCache.java @@ -18,13 +18,13 @@ package org.thingsboard.server.dao.device; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; -import org.thingsboard.server.cache.CaffeineTbTransactionalCache; +import org.thingsboard.server.cache.VersionedCaffeineTbCache; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.DeviceProfile; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) @Service("DeviceProfileCache") -public class DeviceProfileCaffeineCache extends CaffeineTbTransactionalCache { +public class DeviceProfileCaffeineCache extends VersionedCaffeineTbCache { public DeviceProfileCaffeineCache(CacheManager cacheManager) { super(cacheManager, CacheConstants.DEVICE_PROFILE_CACHE); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java index 2b5fc0a644..9de496566b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java @@ -15,11 +15,16 @@ */ package org.thingsboard.server.dao.device; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; @Data +@RequiredArgsConstructor +@AllArgsConstructor public class DeviceProfileEvictEvent { private final TenantId tenantId; @@ -28,5 +33,6 @@ public class DeviceProfileEvictEvent { private final DeviceProfileId deviceProfileId; private final boolean defaultProfile; private final String provisionDeviceKey; + private DeviceProfile savedDeviceProfile; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java index eafcc5d166..15d16d4012 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java @@ -21,9 +21,9 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.SerializationException; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; -import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.cache.TbRedisSerializer; +import org.thingsboard.server.cache.VersionedRedisTbCache; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.util.ProtoUtils; @@ -31,7 +31,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("DeviceProfileCache") -public class DeviceProfileRedisCache extends RedisTbTransactionalCache { +public class DeviceProfileRedisCache extends VersionedRedisTbCache { public DeviceProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { super(CacheConstants.DEVICE_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer() { @@ -50,4 +50,5 @@ public class DeviceProfileRedisCache extends RedisTbTransactionalCache implements DeviceProfileService { +@RequiredArgsConstructor +public class DeviceProfileServiceImpl extends CachedVersionedEntityService implements DeviceProfileService { private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; private static final String INCORRECT_DEVICE_PROFILE_ID = "Incorrect deviceProfileId "; @@ -87,7 +88,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileValidator; + private DeviceProfileDataValidator deviceProfileValidator; @Autowired private ImageService imageService; @@ -95,21 +96,23 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService keys = new ArrayList<>(2); - keys.add(DeviceProfileCacheKey.fromName(event.getTenantId(), event.getNewName())); - if (event.getDeviceProfileId() != null) { - keys.add(DeviceProfileCacheKey.fromId(event.getDeviceProfileId())); + List toEvict = new ArrayList<>(2); + toEvict.add(DeviceProfileCacheKey.forName(event.getTenantId(), event.getNewName())); + if (event.getSavedDeviceProfile() != null) { + cache.put(DeviceProfileCacheKey.forId(event.getSavedDeviceProfile().getId()), event.getSavedDeviceProfile()); + } else if (event.getDeviceProfileId() != null) { + toEvict.add(DeviceProfileCacheKey.forId(event.getDeviceProfileId())); } if (event.isDefaultProfile()) { - keys.add(DeviceProfileCacheKey.defaultProfile(event.getTenantId())); + toEvict.add(DeviceProfileCacheKey.forDefaultProfile(event.getTenantId())); } if (StringUtils.isNotEmpty(event.getOldName()) && !event.getOldName().equals(event.getNewName())) { - keys.add(DeviceProfileCacheKey.fromName(event.getTenantId(), event.getOldName())); + toEvict.add(DeviceProfileCacheKey.forName(event.getTenantId(), event.getOldName())); } if (StringUtils.isNotEmpty(event.getProvisionDeviceKey())) { - keys.add(DeviceProfileCacheKey.fromProvisionDeviceKey(event.getProvisionDeviceKey())); + toEvict.add(DeviceProfileCacheKey.forProvisionKey(event.getProvisionDeviceKey())); } - cache.evict(keys); + cache.evict(toEvict); } @Override @@ -121,8 +124,8 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService INCORRECT_DEVICE_PROFILE_ID + id); - return cache.getOrFetchFromDB(DeviceProfileCacheKey.fromId(deviceProfileId), - () -> deviceProfileDao.findById(tenantId, deviceProfileId.getId()), true, putInCache); + return cache.get(DeviceProfileCacheKey.forId(deviceProfileId), + () -> deviceProfileDao.findById(tenantId, deviceProfileId.getId()), putInCache); } @Override @@ -134,7 +137,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService INCORRECT_DEVICE_PROFILE_NAME + pn); - return cache.getOrFetchFromDB(DeviceProfileCacheKey.fromName(tenantId, profileName), + return cache.getOrFetchFromDB(DeviceProfileCacheKey.forName(tenantId, profileName), () -> deviceProfileDao.findByName(tenantId, profileName), true, putInCache); } @@ -142,7 +145,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService INCORRECT_PROVISION_DEVICE_KEY + dk); - return cache.getAndPutInTransaction(DeviceProfileCacheKey.fromProvisionDeviceKey(provisionDeviceKey), + return cache.getAndPutInTransaction(DeviceProfileCacheKey.forProvisionKey(provisionDeviceKey), () -> deviceProfileDao.findByProvisionDeviceKey(provisionDeviceKey), false); } @@ -179,7 +182,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService INCORRECT_TENANT_ID + id); - return cache.getAndPutInTransaction(DeviceProfileCacheKey.defaultProfile(tenantId), + return cache.getAndPutInTransaction(DeviceProfileCacheKey.forDefaultProfile(tenantId), () -> deviceProfileDao.findDefaultDeviceProfile(tenantId), true); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 5b770845c1..2c80354923 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -18,8 +18,8 @@ package org.thingsboard.server.dao.device; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; @@ -68,7 +68,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; -import org.thingsboard.server.dao.entity.AbstractCachedEntityService; +import org.thingsboard.server.dao.entity.CachedVersionedEntityService; import org.thingsboard.server.dao.entity.EntityCountService; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; @@ -76,8 +76,8 @@ import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; -import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.service.validator.DeviceDataValidator; import org.thingsboard.server.dao.sql.JpaExecutorService; import org.thingsboard.server.dao.tenant.TenantService; @@ -94,38 +94,23 @@ import static org.thingsboard.server.dao.service.Validator.validateString; @Service("DeviceDaoService") @Slf4j -public class DeviceServiceImpl extends AbstractCachedEntityService implements DeviceService { +@RequiredArgsConstructor +public class DeviceServiceImpl extends CachedVersionedEntityService implements DeviceService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_DEVICE_PROFILE_ID = "Incorrect deviceProfileId "; - public static final String INCORRECT_PAGE_LINK = "Incorrect page link "; public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId "; public static final String INCORRECT_DEVICE_ID = "Incorrect deviceId "; public static final String INCORRECT_EDGE_ID = "Incorrect edgeId "; - @Autowired - private DeviceDao deviceDao; - - @Autowired - private DeviceCredentialsService deviceCredentialsService; - - @Autowired - private DeviceProfileService deviceProfileService; - - @Autowired - private EventService eventService; - - @Autowired - private TenantService tenantService; - - @Autowired - private DataValidator deviceValidator; - - @Autowired - private EntityCountService countService; - - @Autowired - private JpaExecutorService executor; + private final DeviceDao deviceDao; + private final DeviceCredentialsService deviceCredentialsService; + private final DeviceProfileService deviceProfileService; + private final EventService eventService; + private final TenantService tenantService; + private final DeviceDataValidator deviceValidator; + private final EntityCountService countService; + private final JpaExecutorService executor; @Override public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) { @@ -139,11 +124,11 @@ public class DeviceServiceImpl extends AbstractCachedEntityService INCORRECT_DEVICE_ID + id); if (TenantId.SYS_TENANT_ID.equals(tenantId)) { - return cache.getAndPutInTransaction(new DeviceCacheKey(deviceId), - () -> deviceDao.findById(tenantId, deviceId.getId()), true); + return cache.get(new DeviceCacheKey(deviceId), + () -> deviceDao.findById(tenantId, deviceId.getId())); } else { - return cache.getAndPutInTransaction(new DeviceCacheKey(tenantId, deviceId), - () -> deviceDao.findDeviceByTenantIdAndId(tenantId, deviceId.getId()), true); + return cache.get(new DeviceCacheKey(tenantId, deviceId), + () -> deviceDao.findDeviceByTenantIdAndId(tenantId, deviceId.getId())); } } @@ -251,6 +236,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService keys = new ArrayList<>(3); - keys.add(new DeviceCacheKey(event.getTenantId(), event.getNewName())); - if (event.getDeviceId() != null) { - keys.add(new DeviceCacheKey(event.getDeviceId())); - keys.add(new DeviceCacheKey(event.getTenantId(), event.getDeviceId())); - } + List toEvict = new ArrayList<>(3); + toEvict.add(new DeviceCacheKey(event.getTenantId(), event.getNewName())); if (StringUtils.isNotEmpty(event.getOldName()) && !event.getOldName().equals(event.getNewName())) { - keys.add(new DeviceCacheKey(event.getTenantId(), event.getOldName())); + toEvict.add(new DeviceCacheKey(event.getTenantId(), event.getOldName())); + } + Device savedDevice = event.getSavedDevice(); + if (savedDevice != null) { + cache.put(new DeviceCacheKey(event.getDeviceId()), savedDevice); + cache.put(new DeviceCacheKey(event.getTenantId(), event.getDeviceId()), savedDevice); + } else { + toEvict.add(new DeviceCacheKey(event.getDeviceId())); + toEvict.add(new DeviceCacheKey(event.getTenantId(), event.getDeviceId())); } - cache.evict(keys); + cache.evict(toEvict); } private DeviceData syncDeviceData(DeviceProfile deviceProfile, DeviceData deviceData) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index d4d3d2fb37..c03bc0a238 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -246,8 +246,8 @@ public class EdgeServiceImpl extends AbstractCachedEntityService extends AbstractCachedEntityService { + + @Autowired + protected VersionedTbCache cache; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewCacheValue.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewCacheValue.java index 271d604e31..e02182b498 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewCacheValue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewCacheValue.java @@ -19,6 +19,7 @@ import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.HasVersion; import java.io.Serializable; import java.util.List; @@ -26,11 +27,16 @@ import java.util.List; @Getter @EqualsAndHashCode @Builder -public class EntityViewCacheValue implements Serializable { +public class EntityViewCacheValue implements Serializable, HasVersion { private static final long serialVersionUID = 1959004642076413174L; private final EntityView entityView; private final List entityViews; + @Override + public Long getVersion() { + return entityView != null ? entityView.getVersion() : 0; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewCaffeineCache.java index 7ee5a1a725..0aec41493b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewCaffeineCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewCaffeineCache.java @@ -18,12 +18,12 @@ package org.thingsboard.server.dao.entityview; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; -import org.thingsboard.server.cache.CaffeineTbTransactionalCache; +import org.thingsboard.server.cache.VersionedCaffeineTbCache; import org.thingsboard.server.common.data.CacheConstants; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) @Service("EntityViewCache") -public class EntityViewCaffeineCache extends CaffeineTbTransactionalCache { +public class EntityViewCaffeineCache extends VersionedCaffeineTbCache { public EntityViewCaffeineCache(CacheManager cacheManager) { super(cacheManager, CacheConstants.ENTITY_VIEW_CACHE); diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewEvictEvent.java index aac8167d50..2e81084a9e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewEvictEvent.java @@ -15,21 +15,25 @@ */ package org.thingsboard.server.dao.entityview; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.RequiredArgsConstructor; +import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; @Data @RequiredArgsConstructor +@AllArgsConstructor class EntityViewEvictEvent { private final TenantId tenantId; - private final EntityViewId id; + private final EntityViewId entityViewId; private final EntityId newEntityId; private final EntityId oldEntityId; private final String newName; private final String oldName; + private EntityView savedEntityView; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java index 8ce7f84b75..480540a78b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java @@ -19,14 +19,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; -import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.cache.TbJsonRedisSerializer; +import org.thingsboard.server.cache.VersionedRedisTbCache; import org.thingsboard.server.common.data.CacheConstants; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("EntityViewCache") -public class EntityViewRedisCache extends RedisTbTransactionalCache { +public class EntityViewRedisCache extends VersionedRedisTbCache { public EntityViewRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { super(CacheConstants.ENTITY_VIEW_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(EntityViewCacheValue.class)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java index d396fc3cfd..cbe6883b46 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java @@ -44,13 +44,13 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; -import org.thingsboard.server.dao.entity.AbstractCachedEntityService; +import org.thingsboard.server.dao.entity.CachedVersionedEntityService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.service.validator.EntityViewDataValidator; import org.thingsboard.server.dao.sql.JpaExecutorService; import java.util.ArrayList; @@ -69,7 +69,7 @@ import static org.thingsboard.server.dao.service.Validator.validateString; */ @Service("EntityViewDaoService") @Slf4j -public class EntityViewServiceImpl extends AbstractCachedEntityService implements EntityViewService { +public class EntityViewServiceImpl extends CachedVersionedEntityService implements EntityViewService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId "; @@ -80,7 +80,7 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService entityViewValidator; + private EntityViewDataValidator entityViewValidator; @Autowired protected JpaExecutorService service; @@ -88,17 +88,21 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService keys = new ArrayList<>(5); - keys.add(EntityViewCacheKey.byName(event.getTenantId(), event.getNewName())); - keys.add(EntityViewCacheKey.byId(event.getId())); - keys.add(EntityViewCacheKey.byEntityId(event.getTenantId(), event.getNewEntityId())); + List toEvict = new ArrayList<>(5); + toEvict.add(EntityViewCacheKey.byName(event.getTenantId(), event.getNewName())); + if (event.getSavedEntityView() != null) { + cache.put(EntityViewCacheKey.byId(event.getSavedEntityView().getId()), new EntityViewCacheValue(event.getSavedEntityView(), null)); + } else if (event.getEntityViewId() != null) { + toEvict.add(EntityViewCacheKey.byId(event.getEntityViewId())); + } + toEvict.add(EntityViewCacheKey.byEntityId(event.getTenantId(), event.getNewEntityId())); if (event.getOldEntityId() != null && !event.getOldEntityId().equals(event.getNewEntityId())) { - keys.add(EntityViewCacheKey.byEntityId(event.getTenantId(), event.getOldEntityId())); + toEvict.add(EntityViewCacheKey.byEntityId(event.getTenantId(), event.getOldEntityId())); } if (StringUtils.isNotEmpty(event.getOldName()) && !event.getOldName().equals(event.getNewName())) { - keys.add(EntityViewCacheKey.byName(event.getTenantId(), event.getOldName())); + toEvict.add(EntityViewCacheKey.byName(event.getTenantId(), event.getOldName())); } - cache.evict(keys); + cache.evict(toEvict); } @Override @@ -113,11 +117,11 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService INCORRECT_ENTITY_VIEW_ID + id); - return cache.getOrFetchFromDB(EntityViewCacheKey.byId(entityViewId), - () -> entityViewDao.findById(tenantId, entityViewId.getId()) - , EntityViewCacheValue::getEntityView, v -> new EntityViewCacheValue(v, null), true, putInCache); + EntityViewCacheValue value = cache.get(EntityViewCacheKey.byId(entityViewId), () -> { + EntityView entityView = entityViewDao.findById(tenantId, entityViewId.getId()); + return new EntityViewCacheValue(entityView, null); + }, putInCache); + return value != null ? value.getEntityView() : null; } @Override @@ -233,7 +239,7 @@ public class EntityViewServiceImpl extends AbstractCachedEntityServiceINCORRECT_TENANT_ID + id); + validateId(tenantId, id -> INCORRECT_TENANT_ID + id); validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id); validatePageLink(pageLink); return entityViewDao.findEntityViewsByTenantIdAndCustomerId(tenantId.getId(), diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java index 02e1a15543..9f363ace96 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java @@ -22,6 +22,7 @@ import jakarta.persistence.MappedSuperclass; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; @@ -48,6 +49,19 @@ public abstract class BaseSqlEntity implements BaseEntity { @Column(name = ModelConstants.CREATED_TIME_PROPERTY, updatable = false) protected long createdTime; + public BaseSqlEntity() { + } + + public BaseSqlEntity(BaseData domain) { + this.id = domain.getUuidId(); + this.createdTime = domain.getCreatedTime(); + } + + public BaseSqlEntity(BaseSqlEntity entity) { + this.id = entity.id; + this.createdTime = entity.createdTime; + } + @Override public UUID getUuid() { return id; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedEntity.java index 6b98348de9..900fe2ed7f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedEntity.java @@ -15,6 +15,47 @@ */ package org.thingsboard.server.dao.model; -public interface BaseVersionedEntity { - long getVersion(); +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.Version; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasVersion; + +@Data +@EqualsAndHashCode(callSuper = true) +@MappedSuperclass +public abstract class BaseVersionedEntity extends BaseSqlEntity implements HasVersion { + + @Getter @Setter + @Version + @Column(name = ModelConstants.VERSION_PROPERTY) + protected Long version; + + public BaseVersionedEntity() { + super(); + } + + public BaseVersionedEntity(D domain) { + super(domain); + this.version = domain.getVersion(); + } + + public BaseVersionedEntity(BaseVersionedEntity entity) { + super(entity); + this.version = entity.version; + } + + @Override + public String toString() { + return "BaseVersionedEntity{" + + "id=" + id + + ", createdTime=" + createdTime + + ", version=" + version + + '}'; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 643c099829..43f8ce507b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -49,6 +49,7 @@ public class ModelConstants { public static final String SEARCH_TEXT_PROPERTY = "search_text"; public static final String ADDITIONAL_INFO_PROPERTY = "additional_info"; public static final String ENTITY_TYPE_PROPERTY = "entity_type"; + public static final String VERSION_PROPERTY = "version"; public static final String ENTITY_TYPE_COLUMN = ENTITY_TYPE_PROPERTY; public static final String TENANT_ID_COLUMN = "tenant_id"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java index 50fe1f7636..30c53c30e1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java @@ -26,7 +26,6 @@ import org.thingsboard.server.common.data.alarm.AlarmCommentType; import org.thingsboard.server.common.data.id.AlarmCommentId; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -40,7 +39,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COMMENT_TYPE @Data @EqualsAndHashCode(callSuper = true) @MappedSuperclass -public abstract class AbstractAlarmCommentEntity extends BaseSqlEntity implements BaseEntity { +public abstract class AbstractAlarmCommentEntity extends BaseSqlEntity { @Column(name = ALARM_COMMENT_ALARM_ID, columnDefinition = "uuid") private UUID alarmId; @@ -94,4 +93,5 @@ public abstract class AbstractAlarmCommentEntity extends alarmComment.setComment(comment); return alarmComment; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java index 4c488b3917..716e794511 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.AssetId; 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.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -42,7 +42,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.EXTERNAL_ID_PROPER @Data @EqualsAndHashCode(callSuper = true) @MappedSuperclass -public abstract class AbstractAssetEntity extends BaseSqlEntity { +public abstract class AbstractAssetEntity extends BaseVersionedEntity { @Column(name = ASSET_TENANT_ID_PROPERTY) private UUID tenantId; @@ -73,11 +73,8 @@ public abstract class AbstractAssetEntity extends BaseSqlEntity super(); } - public AbstractAssetEntity(Asset asset) { - if (asset.getId() != null) { - this.setUuid(asset.getId().getId()); - } - this.setCreatedTime(asset.getCreatedTime()); + public AbstractAssetEntity(T asset) { + super(asset); if (asset.getTenantId() != null) { this.tenantId = asset.getTenantId().getId(); } @@ -97,8 +94,7 @@ public abstract class AbstractAssetEntity extends BaseSqlEntity } public AbstractAssetEntity(AssetEntity assetEntity) { - this.setId(assetEntity.getId()); - this.setCreatedTime(assetEntity.getCreatedTime()); + super(assetEntity); this.tenantId = assetEntity.getTenantId(); this.customerId = assetEntity.getCustomerId(); this.assetProfileId = assetEntity.getAssetProfileId(); @@ -112,6 +108,7 @@ public abstract class AbstractAssetEntity extends BaseSqlEntity protected Asset toAsset() { Asset asset = new Asset(new AssetId(id)); asset.setCreatedTime(createdTime); + asset.setVersion(version); if (tenantId != null) { asset.setTenantId(TenantId.fromUUID(tenantId)); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java index 1c0f47d062..3ce6d171dc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -41,7 +41,7 @@ import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @MappedSuperclass -public abstract class AbstractDeviceEntity extends BaseSqlEntity { +public abstract class AbstractDeviceEntity extends BaseVersionedEntity { @Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY, columnDefinition = "uuid") private UUID tenantId; @@ -83,11 +83,8 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti super(); } - public AbstractDeviceEntity(Device device) { - if (device.getId() != null) { - this.setUuid(device.getUuidId()); - } - this.setCreatedTime(device.getCreatedTime()); + public AbstractDeviceEntity(T device) { + super(device); if (device.getTenantId() != null) { this.tenantId = device.getTenantId().getId(); } @@ -113,9 +110,8 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti } } - public AbstractDeviceEntity(DeviceEntity deviceEntity) { - this.setId(deviceEntity.getId()); - this.setCreatedTime(deviceEntity.getCreatedTime()); + public AbstractDeviceEntity(AbstractDeviceEntity deviceEntity) { + super(deviceEntity); this.tenantId = deviceEntity.getTenantId(); this.customerId = deviceEntity.getCustomerId(); this.deviceProfileId = deviceEntity.getDeviceProfileId(); @@ -132,6 +128,7 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti protected Device toDevice() { Device device = new Device(new DeviceId(getUuid())); device.setCreatedTime(createdTime); + device.setVersion(version); if (tenantId != null) { device.setTenantId(TenantId.fromUUID(tenantId)); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEdgeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEdgeEntity.java index 19907c8182..f02b33b65f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEdgeEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEdgeEntity.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -44,7 +44,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TYPE_PROPERTY @Data @EqualsAndHashCode(callSuper = true) @MappedSuperclass -public abstract class AbstractEdgeEntity extends BaseSqlEntity { +public abstract class AbstractEdgeEntity extends BaseVersionedEntity { @Column(name = EDGE_TENANT_ID_PROPERTY, columnDefinition = "uuid") private UUID tenantId; @@ -78,11 +78,8 @@ public abstract class AbstractEdgeEntity extends BaseSqlEntity extends BaseSqlEntity extends BaseSqlEntity extends BaseSqlEntity extends BaseSqlEntity { +public abstract class AbstractEntityViewEntity extends BaseVersionedEntity { @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY) private UUID entityId; @@ -89,11 +89,8 @@ public abstract class AbstractEntityViewEntity extends Bas super(); } - public AbstractEntityViewEntity(EntityView entityView) { - if (entityView.getId() != null) { - this.setUuid(entityView.getId().getId()); - } - this.setCreatedTime(entityView.getCreatedTime()); + public AbstractEntityViewEntity(T entityView) { + super(entityView); if (entityView.getEntityId() != null) { this.entityId = entityView.getEntityId().getId(); this.entityType = entityView.getEntityId().getEntityType(); @@ -120,8 +117,7 @@ public abstract class AbstractEntityViewEntity extends Bas } public AbstractEntityViewEntity(EntityViewEntity entityViewEntity) { - this.setId(entityViewEntity.getId()); - this.setCreatedTime(entityViewEntity.getCreatedTime()); + super(entityViewEntity); this.entityId = entityViewEntity.getEntityId(); this.entityType = entityViewEntity.getEntityType(); this.tenantId = entityViewEntity.getTenantId(); @@ -138,6 +134,7 @@ public abstract class AbstractEntityViewEntity extends Bas protected EntityView toEntityView() { EntityView entityView = new EntityView(new EntityViewId(getUuid())); entityView.setCreatedTime(createdTime); + entityView.setVersion(version); if (entityId != null) { entityView.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType.name(), entityId)); @@ -163,4 +160,5 @@ public abstract class AbstractEntityViewEntity extends Bas } return entityView; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTenantEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTenantEntity.java index 845a196569..61fed6097e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTenantEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTenantEntity.java @@ -24,7 +24,7 @@ import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -33,7 +33,7 @@ import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @MappedSuperclass -public abstract class AbstractTenantEntity extends BaseSqlEntity { +public abstract class AbstractTenantEntity extends BaseVersionedEntity { @Column(name = ModelConstants.TENANT_TITLE_PROPERTY) private String title; @@ -76,11 +76,8 @@ public abstract class AbstractTenantEntity extends BaseSqlEnti super(); } - public AbstractTenantEntity(Tenant tenant) { - if (tenant.getId() != null) { - this.setUuid(tenant.getId().getId()); - } - this.setCreatedTime(tenant.getCreatedTime()); + public AbstractTenantEntity(T tenant) { + super(tenant); this.title = tenant.getTitle(); this.region = tenant.getRegion(); this.country = tenant.getCountry(); @@ -98,8 +95,7 @@ public abstract class AbstractTenantEntity extends BaseSqlEnti } public AbstractTenantEntity(TenantEntity tenantEntity) { - this.setId(tenantEntity.getId()); - this.setCreatedTime(tenantEntity.getCreatedTime()); + super(tenantEntity); this.title = tenantEntity.getTitle(); this.region = tenantEntity.getRegion(); this.country = tenantEntity.getCountry(); @@ -117,6 +113,7 @@ public abstract class AbstractTenantEntity extends BaseSqlEnti protected Tenant toTenant() { Tenant tenant = new Tenant(TenantId.fromUUID(this.getUuid())); tenant.setCreatedTime(createdTime); + tenant.setVersion(version); tenant.setTitle(title); tenant.setRegion(region); tenant.setCountry(country); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java index 7342018bd6..6e6ee4fed8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java @@ -22,7 +22,7 @@ import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.widget.BaseWidgetType; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import java.util.UUID; @@ -30,7 +30,7 @@ import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @MappedSuperclass -public abstract class AbstractWidgetTypeEntity extends BaseSqlEntity { +public abstract class AbstractWidgetTypeEntity extends BaseVersionedEntity { @Column(name = ModelConstants.WIDGET_TYPE_TENANT_ID_PROPERTY) private UUID tenantId; @@ -48,11 +48,8 @@ public abstract class AbstractWidgetTypeEntity extends super(); } - public AbstractWidgetTypeEntity(BaseWidgetType widgetType) { - if (widgetType.getId() != null) { - this.setUuid(widgetType.getId().getId()); - } - this.setCreatedTime(widgetType.getCreatedTime()); + public AbstractWidgetTypeEntity(T widgetType) { + super(widgetType); if (widgetType.getTenantId() != null) { this.tenantId = widgetType.getTenantId().getId(); } @@ -62,8 +59,7 @@ public abstract class AbstractWidgetTypeEntity extends } public AbstractWidgetTypeEntity(AbstractWidgetTypeEntity widgetTypeEntity) { - this.setId(widgetTypeEntity.getId()); - this.setCreatedTime(widgetTypeEntity.getCreatedTime()); + super(widgetTypeEntity); this.tenantId = widgetTypeEntity.getTenantId(); this.fqn = widgetTypeEntity.getFqn(); this.name = widgetTypeEntity.getName(); @@ -73,6 +69,7 @@ public abstract class AbstractWidgetTypeEntity extends protected BaseWidgetType toBaseWidgetType() { BaseWidgetType widgetType = new BaseWidgetType(new WidgetTypeId(getUuid())); widgetType.setCreatedTime(createdTime); + widgetType.setVersion(version); if (tenantId != null) { widgetType.setTenantId(TenantId.fromUUID(tenantId)); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java index 264f41c226..73264a15df 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; import java.util.HashMap; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetProfileEntity.java index ab9132e8cc..536da5b1ec 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetProfileEntity.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import java.util.UUID; @@ -34,7 +34,7 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.ASSET_PROFILE_TABLE_NAME) -public final class AssetProfileEntity extends BaseSqlEntity { +public final class AssetProfileEntity extends BaseVersionedEntity { @Column(name = ModelConstants.ASSET_PROFILE_TENANT_ID_PROPERTY) private UUID tenantId; @@ -71,13 +71,10 @@ public final class AssetProfileEntity extends BaseSqlEntity { } public AssetProfileEntity(AssetProfile assetProfile) { - if (assetProfile.getId() != null) { - this.setUuid(assetProfile.getId().getId()); - } + super(assetProfile); if (assetProfile.getTenantId() != null) { this.tenantId = assetProfile.getTenantId().getId(); } - this.setCreatedTime(assetProfile.getCreatedTime()); this.name = assetProfile.getName(); this.image = assetProfile.getImage(); this.description = assetProfile.getDescription(); @@ -101,6 +98,7 @@ public final class AssetProfileEntity extends BaseSqlEntity { public AssetProfile toData() { AssetProfile assetProfile = new AssetProfile(new AssetProfileId(this.getUuid())); assetProfile.setCreatedTime(createdTime); + assetProfile.setVersion(version); if (tenantId != null) { assetProfile.setTenantId(TenantId.fromUUID(tenantId)); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/CustomerEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/CustomerEntity.java index 569200e566..526f2a057a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/CustomerEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/CustomerEntity.java @@ -25,7 +25,7 @@ import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -35,7 +35,7 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.CUSTOMER_TABLE_NAME) -public final class CustomerEntity extends BaseSqlEntity { +public final class CustomerEntity extends BaseVersionedEntity { @Column(name = ModelConstants.CUSTOMER_TENANT_ID_PROPERTY) private UUID tenantId; @@ -82,10 +82,7 @@ public final class CustomerEntity extends BaseSqlEntity { } public CustomerEntity(Customer customer) { - if (customer.getId() != null) { - this.setUuid(customer.getId().getId()); - } - this.setCreatedTime(customer.getCreatedTime()); + super(customer); this.tenantId = customer.getTenantId().getId(); this.title = customer.getTitle(); this.country = customer.getCountry(); @@ -107,6 +104,7 @@ public final class CustomerEntity extends BaseSqlEntity { public Customer toData() { Customer customer = new Customer(new CustomerId(this.getUuid())); customer.setCreatedTime(createdTime); + customer.setVersion(version); customer.setTenantId(TenantId.fromUUID(tenantId)); customer.setTitle(title); customer.setCountry(country); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java index e5f4723699..c9e074ac7e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.ShortCustomerInfo; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -42,7 +42,7 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.DASHBOARD_TABLE_NAME) -public final class DashboardEntity extends BaseSqlEntity { +public final class DashboardEntity extends BaseVersionedEntity { private static final JavaType assignedCustomersType = JacksonUtil.constructCollectionType(HashSet.class, ShortCustomerInfo.class); @@ -77,10 +77,7 @@ public final class DashboardEntity extends BaseSqlEntity { } public DashboardEntity(Dashboard dashboard) { - if (dashboard.getId() != null) { - this.setUuid(dashboard.getId().getId()); - } - this.setCreatedTime(dashboard.getCreatedTime()); + super(dashboard); if (dashboard.getTenantId() != null) { this.tenantId = dashboard.getTenantId().getId(); } @@ -105,6 +102,7 @@ public final class DashboardEntity extends BaseSqlEntity { public Dashboard toData() { Dashboard dashboard = new Dashboard(new DashboardId(this.getUuid())); dashboard.setCreatedTime(this.getCreatedTime()); + dashboard.setVersion(version); if (tenantId != null) { dashboard.setTenantId(TenantId.fromUUID(tenantId)); } @@ -125,4 +123,5 @@ public final class DashboardEntity extends BaseSqlEntity { } return dashboard; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java index 067ad635df..93a1b1ad27 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.ShortCustomerInfo; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import java.util.HashSet; @@ -39,7 +39,7 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.DASHBOARD_TABLE_NAME) -public class DashboardInfoEntity extends BaseSqlEntity { +public class DashboardInfoEntity extends BaseVersionedEntity { private static final JavaType assignedCustomersType = JacksonUtil.constructCollectionType(HashSet.class, ShortCustomerInfo.class); @@ -67,10 +67,7 @@ public class DashboardInfoEntity extends BaseSqlEntity { } public DashboardInfoEntity(DashboardInfo dashboardInfo) { - if (dashboardInfo.getId() != null) { - this.setUuid(dashboardInfo.getId().getId()); - } - this.setCreatedTime(dashboardInfo.getCreatedTime()); + super(dashboardInfo); if (dashboardInfo.getTenantId() != null) { this.tenantId = dashboardInfo.getTenantId().getId(); } @@ -91,6 +88,7 @@ public class DashboardInfoEntity extends BaseSqlEntity { public DashboardInfo toData() { DashboardInfo dashboardInfo = new DashboardInfo(new DashboardId(this.getUuid())); dashboardInfo.setCreatedTime(createdTime); + dashboardInfo.setVersion(version); if (tenantId != null) { dashboardInfo.setTenantId(TenantId.fromUUID(tenantId)); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceCredentialsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceCredentialsEntity.java index 0b8b882304..edcaa745c7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceCredentialsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceCredentialsEntity.java @@ -26,8 +26,7 @@ import org.thingsboard.server.common.data.id.DeviceCredentialsId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.dao.model.BaseEntity; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import java.util.UUID; @@ -36,7 +35,7 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.DEVICE_CREDENTIALS_TABLE_NAME) -public final class DeviceCredentialsEntity extends BaseSqlEntity implements BaseEntity { +public final class DeviceCredentialsEntity extends BaseVersionedEntity { @Column(name = ModelConstants.DEVICE_CREDENTIALS_DEVICE_ID_PROPERTY) private UUID deviceId; @@ -56,10 +55,7 @@ public final class DeviceCredentialsEntity extends BaseSqlEntity { +public final class DeviceProfileEntity extends BaseVersionedEntity { @Column(name = ModelConstants.DEVICE_PROFILE_TENANT_ID_PROPERTY) private UUID tenantId; @@ -111,13 +111,10 @@ public final class DeviceProfileEntity extends BaseSqlEntity { } public DeviceProfileEntity(DeviceProfile deviceProfile) { - if (deviceProfile.getId() != null) { - this.setUuid(deviceProfile.getId().getId()); - } + super(deviceProfile); if (deviceProfile.getTenantId() != null) { this.tenantId = deviceProfile.getTenantId().getId(); } - this.setCreatedTime(deviceProfile.getCreatedTime()); this.name = deviceProfile.getName(); this.type = deviceProfile.getType(); this.image = deviceProfile.getImage(); @@ -152,6 +149,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity { public DeviceProfile toData() { DeviceProfile deviceProfile = new DeviceProfile(new DeviceProfileId(this.getUuid())); deviceProfile.setCreatedTime(createdTime); + deviceProfile.setVersion(version); if (tenantId != null) { deviceProfile.setTenantId(TenantId.fromUUID(tenantId)); } @@ -187,4 +185,5 @@ public final class DeviceProfileEntity extends BaseSqlEntity { return deviceProfile; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java index 8e012ed0d1..6504eca4fb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -40,7 +40,7 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.RULE_CHAIN_TABLE_NAME) -public class RuleChainEntity extends BaseSqlEntity { +public class RuleChainEntity extends BaseVersionedEntity { @Column(name = ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY) private UUID tenantId; @@ -76,10 +76,7 @@ public class RuleChainEntity extends BaseSqlEntity { } public RuleChainEntity(RuleChain ruleChain) { - if (ruleChain.getId() != null) { - this.setUuid(ruleChain.getUuidId()); - } - this.setCreatedTime(ruleChain.getCreatedTime()); + super(ruleChain); this.tenantId = DaoUtil.getId(ruleChain.getTenantId()); this.name = ruleChain.getName(); this.type = ruleChain.getType(); @@ -99,6 +96,7 @@ public class RuleChainEntity extends BaseSqlEntity { public RuleChain toData() { RuleChain ruleChain = new RuleChain(new RuleChainId(this.getUuid())); ruleChain.setCreatedTime(createdTime); + ruleChain.setVersion(version); ruleChain.setTenantId(TenantId.fromUUID(tenantId)); ruleChain.setName(name); ruleChain.setType(type); @@ -114,4 +112,5 @@ public class RuleChainEntity extends BaseSqlEntity { } return ruleChain; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java index abbf64fb08..87e901e930 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -42,7 +42,7 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.USER_PG_HIBERNATE_TABLE_NAME) -public class UserEntity extends BaseSqlEntity { +public class UserEntity extends BaseVersionedEntity { @Column(name = ModelConstants.USER_TENANT_ID_PROPERTY) private UUID tenantId; @@ -74,10 +74,7 @@ public class UserEntity extends BaseSqlEntity { } public UserEntity(User user) { - if (user.getId() != null) { - this.setUuid(user.getId().getId()); - } - this.setCreatedTime(user.getCreatedTime()); + super(user); this.authority = user.getAuthority(); if (user.getTenantId() != null) { this.tenantId = user.getTenantId().getId(); @@ -96,6 +93,7 @@ public class UserEntity extends BaseSqlEntity { public User toData() { User user = new User(new UserId(this.getUuid())); user.setCreatedTime(createdTime); + user.setVersion(version); user.setAuthority(authority); if (tenantId != null) { user.setTenantId(TenantId.fromUUID(tenantId)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java index b9866363e0..5e2766b98d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java @@ -24,7 +24,7 @@ import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import java.util.UUID; @@ -33,7 +33,7 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.WIDGETS_BUNDLE_TABLE_NAME) -public final class WidgetsBundleEntity extends BaseSqlEntity { +public final class WidgetsBundleEntity extends BaseVersionedEntity { @Column(name = ModelConstants.WIDGETS_BUNDLE_TENANT_ID_PROPERTY) private UUID tenantId; @@ -61,10 +61,7 @@ public final class WidgetsBundleEntity extends BaseSqlEntity { } public WidgetsBundleEntity(WidgetsBundle widgetsBundle) { - if (widgetsBundle.getId() != null) { - this.setUuid(widgetsBundle.getId().getId()); - } - this.setCreatedTime(widgetsBundle.getCreatedTime()); + super(widgetsBundle); if (widgetsBundle.getTenantId() != null) { this.tenantId = widgetsBundle.getTenantId().getId(); } @@ -82,6 +79,7 @@ public final class WidgetsBundleEntity extends BaseSqlEntity { public WidgetsBundle toData() { WidgetsBundle widgetsBundle = new WidgetsBundle(new WidgetsBundleId(id)); widgetsBundle.setCreatedTime(createdTime); + widgetsBundle.setVersion(version); if (tenantId != null) { widgetsBundle.setTenantId(TenantId.fromUUID(tenantId)); } @@ -95,4 +93,5 @@ public final class WidgetsBundleEntity extends BaseSqlEntity { } return widgetsBundle; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java index 5109a4e93f..8cf4a6fe42 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.service.validator; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.StringUtils; @@ -32,7 +33,7 @@ public class DeviceCredentialsDataValidator extends DataValidator, D> @Autowired protected JdbcTemplate jdbcTemplate; + @PersistenceContext + private EntityManager entityManager; + protected abstract Class getEntityClass(); protected abstract JpaRepository getRepository(); @@ -68,12 +76,37 @@ public abstract class JpaAbstractDao, D> entity.setUuid(uuid); entity.setCreatedTime(Uuids.unixTimestamp(uuid)); } - entity = doSave(entity, isNew); + try { + entity = doSave(entity, isNew); + } catch (OptimisticLockException e) { + throw new EntityVersionMismatchException((getEntityType() != null ? getEntityType().getNormalName() : "Entity") + " was already changed by someone else", e); + } return DaoUtil.getData(entity); } protected E doSave(E entity, boolean isNew) { - return getRepository().save(entity); + if (isNew) { + if (entity instanceof HasVersion versionedEntity) { + versionedEntity.setVersion(1L); + } + entityManager.persist(entity); + } else { + if (entity instanceof HasVersion versionedEntity) { + if (versionedEntity.getVersion() == null) { + HasVersion existingEntity = entityManager.find(versionedEntity.getClass(), entity.getUuid()); + if (existingEntity != null) { + versionedEntity.setVersion(existingEntity.getVersion()); // manually resetting the version to latest to allow force overwrite of the entity + } else { + return doSave(entity, true); + } + } + entity = entityManager.merge(entity); + entityManager.flush(); + } else { + entity = entityManager.merge(entity); + } + } + return entity; } @Override @@ -111,16 +144,18 @@ public abstract class JpaAbstractDao, D> @Override @Transactional - public boolean removeById(TenantId tenantId, UUID id) { - getRepository().deleteById(id); + public void removeById(TenantId tenantId, UUID id) { + JpaRepository repository = getRepository(); + repository.deleteById(id); + repository.flush(); log.debug("Remove request: {}", id); - return !getRepository().existsById(id); } @Transactional public void removeAllByIds(Collection ids) { JpaRepository repository = getRepository(); ids.forEach(repository::deleteById); + repository.flush(); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaPartitionedAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaPartitionedAbstractDao.java index 38cf02047f..6afb8b4ede 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaPartitionedAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaPartitionedAbstractDao.java @@ -15,26 +15,16 @@ */ package org.thingsboard.server.dao.sql; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.util.SqlDao; @SqlDao public abstract class JpaPartitionedAbstractDao, D> extends JpaAbstractDao { - @PersistenceContext - private EntityManager entityManager; - @Override protected E doSave(E entity, boolean isNew) { createPartition(entity); - if (isNew) { - entityManager.persist(entity); - } else { - entity = entityManager.merge(entity); - } - return entity; + return super.doSave(entity, isNew); } public abstract void createPartition(E entity); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java index 6510d3c415..7571c00b6a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java @@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -52,14 +51,6 @@ public class JpaDeviceCredentialsDao extends JpaAbstractDao implement return DaoUtil.getData(deviceRepository.findDeviceInfoById(deviceId)); } - @Override - @Transactional - public Device saveAndFlush(TenantId tenantId, Device device) { - Device result = this.save(tenantId, device); - deviceRepository.flush(); - return result; - } - @Override public PageData findDevicesByTenantId(UUID tenantId, PageLink pageLink) { if (StringUtils.isEmpty(pageLink.getTextSearch())) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index 34602d81b3..f64ccf4970 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -17,6 +17,9 @@ package org.thingsboard.server.dao.user; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.BooleanNode; +import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.node.LongNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; @@ -411,16 +414,11 @@ public class UserServiceImpl extends AbstractCachedEntityService d.getId().equals(savedDevice.getId()) - && d.getTenantId().equals(tenantId) - && d.getLabel().equals(savedDevice.getLabel()) + && d.getTenantId().equals(tenantId) + && d.getLabel().equals(savedDevice.getLabel()) ) ); @@ -1004,9 +1004,9 @@ public class DeviceServiceTest extends AbstractServiceTest { deviceInfosWithLabel.stream() .anyMatch( d -> d.getId().equals(savedDevice.getId()) - && d.getTenantId().equals(tenantId) - && d.getDeviceProfileName().equals(savedDevice.getType()) - && d.getLabel().equals(savedDevice.getLabel()) + && d.getTenantId().equals(tenantId) + && d.getDeviceProfileName().equals(savedDevice.getType()) + && d.getLabel().equals(savedDevice.getLabel()) ) ); @@ -1072,4 +1072,5 @@ public class DeviceServiceTest extends AbstractServiceTest { ) ); } + } diff --git a/ui-ngx/src/app/core/core.module.ts b/ui-ngx/src/app/core/core.module.ts index 032197057e..4b3fb44b12 100644 --- a/ui-ngx/src/app/core/core.module.ts +++ b/ui-ngx/src/app/core/core.module.ts @@ -41,6 +41,7 @@ import { WINDOW_PROVIDERS } from '@core/services/window.service'; import { HotkeyModule } from 'angular2-hotkeys'; import { TranslateDefaultParser } from '@core/translate/translate-default-parser'; import { TranslateDefaultLoader } from '@core/translate/translate-default-loader'; +import { EntityConflictInterceptor } from '@core/interceptors/entity-conflict.interceptor'; @NgModule({ imports: [ @@ -95,6 +96,11 @@ import { TranslateDefaultLoader } from '@core/translate/translate-default-loader useClass: GlobalHttpInterceptor, multi: true }, + { + provide: HTTP_INTERCEPTORS, + useClass: EntityConflictInterceptor, + multi: true + }, { provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: { diff --git a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts new file mode 100644 index 0000000000..57ee28c779 --- /dev/null +++ b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts @@ -0,0 +1,90 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { Injectable } from '@angular/core'; +import { + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, + HttpStatusCode +} from '@angular/common/http'; +import { Observable, of, throwError } from 'rxjs'; +import { catchError, switchMap } from 'rxjs/operators'; +import { MatDialog } from '@angular/material/dialog'; +import { + EntityConflictDialogComponent +} from '@shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component'; +import { HasId } from '@shared/models/base-data'; +import { HasVersion } from '@shared/models/entity.models'; +import { getInterceptorConfig } from './interceptor.util'; + +@Injectable() +export class EntityConflictInterceptor implements HttpInterceptor { + + constructor( + private dialog: MatDialog, + ) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + if (!request.url.startsWith('/api/')) { + return next.handle(request); + } + + return next.handle(request).pipe( + catchError((error: HttpErrorResponse) => { + if (error.status !== HttpStatusCode.Conflict) { + return throwError(() => error); + } + + return this.handleConflictError(request, next, error); + }) + ); + } + + private handleConflictError( + request: HttpRequest, + next: HttpHandler, + error: HttpErrorResponse + ): Observable> { + if (getInterceptorConfig(request).ignoreVersionConflict) { + return throwError(() => error); + } + + return this.openConflictDialog(request.body, error.error.message).pipe( + switchMap(result => { + if (result) { + return next.handle(this.updateRequestVersion(request)); + } + return of(null); + }) + ); + } + + private updateRequestVersion(request: HttpRequest): HttpRequest { + const body = { ...request.body, version: null }; + return request.clone({ body }); + } + + private openConflictDialog(entity: unknown & HasId & HasVersion, message: string): Observable { + const dialogRef = this.dialog.open(EntityConflictDialogComponent, { + data: { message, entity } + }); + + return dialogRef.afterClosed(); + } +} diff --git a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts index 502df634f3..1cdd0c506e 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -16,10 +16,9 @@ import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; import { Observable } from 'rxjs/internal/Observable'; -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { AuthService } from '@core/auth/auth.service'; import { Constants } from '@shared/models/constants'; -import { InterceptorHttpParams } from './interceptor-http-params'; import { catchError, delay, finalize, mergeMap, switchMap } from 'rxjs/operators'; import { of, throwError } from 'rxjs'; import { InterceptorConfig } from './interceptor-config'; @@ -30,6 +29,7 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { parseHttpErrorMessage } from '@core/utils'; +import { getInterceptorConfig } from './interceptor.util'; const tmpHeaders = {}; @@ -39,22 +39,18 @@ export class GlobalHttpInterceptor implements HttpInterceptor { private AUTH_SCHEME = 'Bearer '; private AUTH_HEADER_NAME = 'X-Authorization'; - private internalUrlPrefixes = [ - '/api/auth/token', - '/api/rpc' - ]; - private activeRequests = 0; - constructor(@Inject(Store) private store: Store, - @Inject(DialogService) private dialogService: DialogService, - @Inject(TranslateService) private translate: TranslateService, - @Inject(AuthService) private authService: AuthService) { - } + constructor( + private store: Store, + private dialogService: DialogService, + private translate: TranslateService, + private authService: AuthService, + ) {} intercept(req: HttpRequest, next: HttpHandler): Observable> { if (req.url.startsWith('/api/')) { - const config = this.getInterceptorConfig(req); + const config = getInterceptorConfig(req); this.updateLoadingState(config, true); let observable$: Observable>; if (this.isTokenBasedAuthEntryPoint(req.url)) { @@ -98,7 +94,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } private handleResponseError(req: HttpRequest, next: HttpHandler, errorResponse: HttpErrorResponse): Observable> { - const config = this.getInterceptorConfig(req); + const config = getInterceptorConfig(req); let unhandled = false; const ignoreErrors = config.ignoreErrors; const resendRequest = config.resendRequest; @@ -171,15 +167,6 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } } - private isInternalUrlPrefix(url: string): boolean { - for (const index in this.internalUrlPrefixes) { - if (url.startsWith(this.internalUrlPrefixes[index])) { - return true; - } - } - return false; - } - private isTokenBasedAuthEntryPoint(url: string): boolean { return url.startsWith('/api/') && !url.startsWith(Constants.entryPoints.login) && @@ -202,19 +189,6 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } } - private getInterceptorConfig(req: HttpRequest): InterceptorConfig { - let config: InterceptorConfig; - if (req.params && req.params instanceof InterceptorHttpParams) { - config = (req.params as InterceptorHttpParams).interceptorConfig; - } else { - config = new InterceptorConfig(false, false); - } - if (this.isInternalUrlPrefix(req.url)) { - config.ignoreLoading = true; - } - return config; - } - private showError(error: string, timeout: number = 0) { setTimeout(() => { this.store.dispatch(new ActionNotificationShow({message: error, type: 'error'})); diff --git a/ui-ngx/src/app/core/interceptors/interceptor-config.ts b/ui-ngx/src/app/core/interceptors/interceptor-config.ts index 5a83d34c6b..62500cbefc 100644 --- a/ui-ngx/src/app/core/interceptors/interceptor-config.ts +++ b/ui-ngx/src/app/core/interceptors/interceptor-config.ts @@ -17,5 +17,6 @@ export class InterceptorConfig { constructor(public ignoreLoading: boolean = false, public ignoreErrors: boolean = false, + public ignoreVersionConflict: boolean = false, public resendRequest: boolean = false) {} } diff --git a/ui-ngx/src/app/core/interceptors/interceptor.util.ts b/ui-ngx/src/app/core/interceptors/interceptor.util.ts new file mode 100644 index 0000000000..ed9fc41868 --- /dev/null +++ b/ui-ngx/src/app/core/interceptors/interceptor.util.ts @@ -0,0 +1,46 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { HttpRequest } from '@angular/common/http'; +import { InterceptorConfig } from '@core/interceptors/interceptor-config'; +import { InterceptorHttpParams } from '@core/interceptors/interceptor-http-params'; + +const internalUrlPrefixes = [ + '/api/auth/token', + '/api/rpc' +]; + +export const getInterceptorConfig = (req: HttpRequest): InterceptorConfig => { + let config: InterceptorConfig; + if (req.params && req.params instanceof InterceptorHttpParams) { + config = (req.params as InterceptorHttpParams).interceptorConfig; + } else { + config = new InterceptorConfig(); + } + if (isInternalUrlPrefix(req.url)) { + config.ignoreLoading = true; + } + return config; +}; + +const isInternalUrlPrefix = (url: string): boolean => { + for (const prefix of internalUrlPrefixes) { + if (url.startsWith(prefix)) { + return true; + } + } + return false; +}; diff --git a/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html new file mode 100644 index 0000000000..502bc3381c --- /dev/null +++ b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html @@ -0,0 +1,53 @@ + + +

{{ 'entity.version-conflict.label' | translate }}

+ + +
+
+
+ {{ data.message }}. + + {{ 'entity.version-conflict.link' | translate: + { entityType: (entityTypeTranslations.get(data.entity.id.entityType).type | translate) } + }} + {{ 'entity.link' | translate }}. + + {{ 'entity.version-conflict.message' | translate }} +
+
+
+ + +
diff --git a/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.scss b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.scss new file mode 100644 index 0000000000..93673a1803 --- /dev/null +++ b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.scss @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2024 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. + */ +$conflict-dialog-width: 700px; + +:host { + .main-label { + padding-left: 8px; + } + + .message-container { + max-width: #{$conflict-dialog-width}; + } +} diff --git a/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts new file mode 100644 index 0000000000..ec8c38e5d4 --- /dev/null +++ b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts @@ -0,0 +1,61 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { SharedModule } from '@shared/shared.module'; +import { ImportExportService } from '@shared/import-export/import-export.service'; +import { CommonModule } from '@angular/common'; +import { entityTypeTranslations } from '@shared/models/entity-type.models'; +import { EntityInfoData } from '@shared/models/entity.models'; + +interface EntityConflictDialogData { + message: string; + entity: EntityInfoData; +} + +@Component({ + selector: 'tb-entity-conflict-dialog', + templateUrl: 'entity-conflict-dialog.component.html', + styleUrls: ['./entity-conflict-dialog.component.scss'], + standalone: true, + imports: [ + CommonModule, + SharedModule, + ], +}) +export class EntityConflictDialogComponent { + readonly entityTypeTranslations = entityTypeTranslations; + + constructor( + @Inject(MAT_DIALOG_DATA) public data: EntityConflictDialogData, + private dialogRef: MatDialogRef, + private importExportService: ImportExportService, + ) {} + + onCancel(): void { + this.dialogRef.close(false); + } + + onConfirm(): void { + this.dialogRef.close(true); + } + + onLinkClick(event: MouseEvent): void { + event.preventDefault(); + this.importExportService.exportEntity(this.data.entity); + } +} diff --git a/ui-ngx/src/app/shared/import-export/import-export.service.ts b/ui-ngx/src/app/shared/import-export/import-export.service.ts index 9902177ebd..6ca925e9e9 100644 --- a/ui-ngx/src/app/shared/import-export/import-export.service.ts +++ b/ui-ngx/src/app/shared/import-export/import-export.service.ts @@ -55,7 +55,7 @@ import { EntityType } from '@shared/models/entity-type.models'; import { UtilsService } from '@core/services/utils.service'; import { WidgetService } from '@core/http/widget.service'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; -import { ImportEntitiesResultInfo, ImportEntityData } from '@shared/models/entity.models'; +import { EntityInfoData, ImportEntitiesResultInfo, ImportEntityData } from '@shared/models/entity.models'; import { RequestConfig } from '@core/http/http-utils'; import { RuleChain, RuleChainImport, RuleChainMetaData, RuleChainType } from '@shared/models/rule-chain.models'; import { RuleChainService } from '@core/http/rule-chain.service'; @@ -380,6 +380,35 @@ export class ImportExportService { }); } + public exportEntity(entityData: EntityInfoData): void { + let preparedData; + switch (entityData.id.entityType) { + case EntityType.DEVICE_PROFILE: + case EntityType.ASSET_PROFILE: + preparedData = this.prepareProfileExport(entityData as DeviceProfile | AssetProfile); + break; + case EntityType.RULE_CHAIN: + this.ruleChainService.getRuleChainMetadata(entityData.id.id) + .pipe( + take(1), + map((ruleChainMetaData) => { + const ruleChainExport: RuleChainImport = { + ruleChain: this.prepareRuleChain(entityData as RuleChain), + metadata: this.prepareRuleChainMetaData(ruleChainMetaData) + }; + return ruleChainExport; + })) + .subscribe(ruleChainData => this.exportToPc(ruleChainData, entityData.name)); + return; + case EntityType.DASHBOARD: + preparedData = this.prepareDashboardExport(entityData as Dashboard); + break; + default: + preparedData = this.prepareExport(entityData); + } + this.exportToPc(preparedData, entityData.name); + } + private exportWidgetsBundleWithWidgetTypes(widgetsBundle: WidgetsBundle) { this.widgetService.exportBundleWidgetTypesDetails(widgetsBundle.id.id).subscribe({ next: (widgetTypesDetails) => { @@ -1108,6 +1137,9 @@ export class ImportExportService { if (isDefined(exportedData.externalId)) { delete exportedData.externalId; } + if (isDefined(exportedData.version)) { + delete exportedData.version; + } return exportedData; } diff --git a/ui-ngx/src/app/shared/models/asset.models.ts b/ui-ngx/src/app/shared/models/asset.models.ts index 6ba8bc8abb..7fc97f48e2 100644 --- a/ui-ngx/src/app/shared/models/asset.models.ts +++ b/ui-ngx/src/app/shared/models/asset.models.ts @@ -22,9 +22,9 @@ import { EntitySearchQuery } from '@shared/models/relation.models'; import { AssetProfileId } from '@shared/models/id/asset-profile-id'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { DashboardId } from '@shared/models/id/dashboard-id'; -import { EntityInfoData, HasTenantId } from '@shared/models/entity.models'; +import { EntityInfoData, HasTenantId, HasVersion } from '@shared/models/entity.models'; -export interface AssetProfile extends BaseData, HasTenantId, ExportableEntity { +export interface AssetProfile extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId?: TenantId; name: string; description?: string; @@ -42,7 +42,7 @@ export interface AssetProfileInfo extends EntityInfoData { defaultDashboardId?: DashboardId; } -export interface Asset extends BaseData, HasTenantId, ExportableEntity { +export interface Asset extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId?: TenantId; customerId?: CustomerId; name: string; diff --git a/ui-ngx/src/app/shared/models/customer.model.ts b/ui-ngx/src/app/shared/models/customer.model.ts index 3484dac633..2e8bf65334 100644 --- a/ui-ngx/src/app/shared/models/customer.model.ts +++ b/ui-ngx/src/app/shared/models/customer.model.ts @@ -18,9 +18,9 @@ import { CustomerId } from '@shared/models/id/customer-id'; import { ContactBased } from '@shared/models/contact-based.model'; import { TenantId } from './id/tenant-id'; import { ExportableEntity } from '@shared/models/base-data'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; -export interface Customer extends ContactBased, HasTenantId, ExportableEntity { +export interface Customer extends ContactBased, HasTenantId, HasVersion, ExportableEntity { tenantId: TenantId; title: string; additionalInfo?: any; diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index 36a14f59c6..fc42ccdc5f 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -23,9 +23,9 @@ import { Timewindow } from '@shared/models/time/time.models'; import { EntityAliases } from './alias.models'; import { Filters } from '@shared/models/query/query.models'; import { MatDialogRef } from '@angular/material/dialog'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; -export interface DashboardInfo extends BaseData, HasTenantId, ExportableEntity { +export interface DashboardInfo extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId?: TenantId; title?: string; image?: string; diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 94d1bc0add..89a887e76d 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -22,7 +22,7 @@ import { DeviceCredentialsId } from '@shared/models/id/device-credentials-id'; import { EntitySearchQuery } from '@shared/models/relation.models'; import { DeviceProfileId } from '@shared/models/id/device-profile-id'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; -import { EntityInfoData, HasTenantId } from '@shared/models/entity.models'; +import { EntityInfoData, HasTenantId, HasVersion } from '@shared/models/entity.models'; import { FilterPredicateValue, KeyFilter } from '@shared/models/query/query.models'; import { TimeUnit } from '@shared/models/time/time.models'; import * as _moment from 'moment'; @@ -584,7 +584,7 @@ export interface DeviceProfileData { provisionConfiguration?: DeviceProvisionConfiguration; } -export interface DeviceProfile extends BaseData, HasTenantId, ExportableEntity { +export interface DeviceProfile extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId?: TenantId; name: string; description?: string; @@ -711,7 +711,7 @@ export interface DeviceData { transportConfiguration: DeviceTransportConfiguration; } -export interface Device extends BaseData, HasTenantId, ExportableEntity { +export interface Device extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId?: TenantId; customerId?: CustomerId; name: string; @@ -801,7 +801,7 @@ export const credentialTypesByTransportType = new Map { +export interface DeviceCredentials extends BaseData, HasTenantId { deviceId: DeviceId; credentialsType: DeviceCredentialsType; credentialsId: string; diff --git a/ui-ngx/src/app/shared/models/edge.models.ts b/ui-ngx/src/app/shared/models/edge.models.ts index 569b2bec8a..6e1fe5dd70 100644 --- a/ui-ngx/src/app/shared/models/edge.models.ts +++ b/ui-ngx/src/app/shared/models/edge.models.ts @@ -22,9 +22,9 @@ import { EntitySearchQuery } from '@shared/models/relation.models'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { BaseEventBody } from '@shared/models/event.models'; import { EventId } from '@shared/models/id/event-id'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; -export interface Edge extends BaseData, HasTenantId { +export interface Edge extends BaseData, HasTenantId, HasVersion { tenantId?: TenantId; customerId?: CustomerId; name: string; diff --git a/ui-ngx/src/app/shared/models/entity-view.models.ts b/ui-ngx/src/app/shared/models/entity-view.models.ts index cb05dd3be5..f817456bbe 100644 --- a/ui-ngx/src/app/shared/models/entity-view.models.ts +++ b/ui-ngx/src/app/shared/models/entity-view.models.ts @@ -20,7 +20,7 @@ import { CustomerId } from '@shared/models/id/customer-id'; import { EntityViewId } from '@shared/models/id/entity-view-id'; import { EntityId } from '@shared/models/id/entity-id'; import { EntitySearchQuery } from '@shared/models/relation.models'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; export interface AttributesEntityView { cs: Array; @@ -33,7 +33,7 @@ export interface TelemetryEntityView { attributes: AttributesEntityView; } -export interface EntityView extends BaseData, HasTenantId, ExportableEntity { +export interface EntityView extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId: TenantId; customerId: CustomerId; entityId: EntityId; diff --git a/ui-ngx/src/app/shared/models/entity.models.ts b/ui-ngx/src/app/shared/models/entity.models.ts index cf8acd1c33..40ec570b13 100644 --- a/ui-ngx/src/app/shared/models/entity.models.ts +++ b/ui-ngx/src/app/shared/models/entity.models.ts @@ -187,3 +187,7 @@ export const entityFields: {[fieldName: string]: EntityField} = { export interface HasTenantId { tenantId?: TenantId; } + +export interface HasVersion { + version?: number; +} diff --git a/ui-ngx/src/app/shared/models/rule-chain.models.ts b/ui-ngx/src/app/shared/models/rule-chain.models.ts index aa7ec47301..0bfdfbc459 100644 --- a/ui-ngx/src/app/shared/models/rule-chain.models.ts +++ b/ui-ngx/src/app/shared/models/rule-chain.models.ts @@ -20,9 +20,9 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { RuleNodeId } from '@shared/models/id/rule-node-id'; import { RuleNode, RuleNodeComponentDescriptor, RuleNodeType } from '@shared/models/rule-node.models'; import { ComponentClusteringMode, ComponentType } from '@shared/models/component-descriptor.models'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; -export interface RuleChain extends BaseData, HasTenantId, ExportableEntity { +export interface RuleChain extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId: TenantId; name: string; firstRuleNodeId: RuleNodeId; diff --git a/ui-ngx/src/app/shared/models/widgets-bundle.model.ts b/ui-ngx/src/app/shared/models/widgets-bundle.model.ts index 9a6dbb5700..8bb34d9111 100644 --- a/ui-ngx/src/app/shared/models/widgets-bundle.model.ts +++ b/ui-ngx/src/app/shared/models/widgets-bundle.model.ts @@ -17,9 +17,9 @@ import { BaseData, ExportableEntity } from '@shared/models/base-data'; import { TenantId } from '@shared/models/id/tenant-id'; import { WidgetsBundleId } from '@shared/models/id/widgets-bundle-id'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; -export interface WidgetsBundle extends BaseData, HasTenantId, ExportableEntity { +export interface WidgetsBundle extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId: TenantId; alias: string; title: string; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 73888ddf11..9f92072680 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2284,6 +2284,13 @@ "type-edges": "Edges", "list-of-edges": "{ count, plural, =1 {One edge} other {List of # edges} }", "edge-name-starts-with": "Edges whose names start with '{{prefix}}'", + "version-conflict": { + "label": "Version conflict", + "message": "Do you want to cancel your changes or overwrite existing version?", + "link": "You can download your version of the {{entityType}} using this", + "overwrite": "Overwrite version", + "cancel": "Cancel changes" + }, "type-tb-resource": "Resource", "type-tb-resources": "Resources", "list-of-tb-resources": "{ count, plural, =1 {One resource} other {List of # resources} }", @@ -2302,7 +2309,8 @@ "type-notification-request": "Notification request", "type-notification-template": "Notification template", "type-notification-templates": "Notification templates", - "list-of-notification-templates": "{ count, plural, =1 {One notification template} other {List of # notification templates} }" + "list-of-notification-templates": "{ count, plural, =1 {One notification template} other {List of # notification templates} }", + "link": "link" }, "entity-field": { "created-time": "Created time",