From 66563e345913630f98399b449912734694fcafd5 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 27 Jun 2024 14:48:18 +0300 Subject: [PATCH 01/26] Introduce optimistic locking and 'version' for entities --- .../main/data/upgrade/3.7.0/schema_update.sql | 17 +++++++++ .../csv/AbstractBulkImportService.java | 4 ++ .../controller/DeviceControllerTest.java | 5 ++- .../server/common/data/Device.java | 37 ++++--------------- .../server/common/data/DeviceInfo.java | 2 + .../server/common/data/HasVersion.java | 24 ++++++++++++ .../dao/model/BaseVersionedSqlEntity.java | 34 +++++++++++++++++ .../server/dao/model/ModelConstants.java | 1 + .../dao/model/sql/AbstractDeviceEntity.java | 7 +++- .../server/dao/sql/JpaAbstractDao.java | 29 ++++++++++++++- .../dao/sql/JpaPartitionedAbstractDao.java | 13 +------ .../server/dao/sql/device/JpaDeviceDao.java | 9 ----- .../main/resources/sql/schema-entities.sql | 1 + .../server/dao/service/DeviceServiceTest.java | 13 ++++--- 14 files changed, 133 insertions(+), 63 deletions(-) create mode 100644 application/src/main/data/upgrade/3.7.0/schema_update.sql create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/HasVersion.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedSqlEntity.java 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 new file mode 100644 index 0000000000..a51697bf96 --- /dev/null +++ b/application/src/main/data/upgrade/3.7.0/schema_update.sql @@ -0,0 +1,17 @@ +-- +-- 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. +-- + +ALTER TABLE device ADD COLUMN IF NOT EXISTS version INT DEFAULT 0; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java index 3890e5791d..3fc2a35026 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasAdditionalInfo; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.HasVersion; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.audit.ActionType; @@ -147,6 +148,9 @@ public abstract class AbstractBulkImportService 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 Integer 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/HasVersion.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasVersion.java new file mode 100644 index 0000000000..bbebbc6b2c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasVersion.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; + +public interface HasVersion { + + Integer getVersion(); + + void setVersion(Integer version); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedSqlEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedSqlEntity.java new file mode 100644 index 0000000000..04abd58b51 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedSqlEntity.java @@ -0,0 +1,34 @@ +/** + * 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.dao.model; + +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.Version; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.HasVersion; + +@Data +@EqualsAndHashCode(callSuper = true) +@MappedSuperclass +public abstract class BaseVersionedSqlEntity extends BaseSqlEntity implements HasVersion { + + @Version + @Column(name = ModelConstants.VERSION_PROPERTY) + protected Integer 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 fb77ad6987..aa37961fae 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/AbstractDeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java index 1c0f47d062..ac66204132 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.BaseVersionedSqlEntity; 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 BaseVersionedSqlEntity { @Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY, columnDefinition = "uuid") private UUID tenantId; @@ -111,6 +111,7 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti if (device.getExternalId() != null) { this.externalId = device.getExternalId().getId(); } + this.version = device.getVersion(); } public AbstractDeviceEntity(DeviceEntity deviceEntity) { @@ -127,6 +128,7 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti this.firmwareId = deviceEntity.getFirmwareId(); this.softwareId = deviceEntity.getSoftwareId(); this.externalId = deviceEntity.getExternalId(); + this.version = deviceEntity.getVersion(); } protected Device toDevice() { @@ -155,6 +157,7 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti if (externalId != null) { device.setExternalId(new DeviceId(externalId)); } + device.setVersion(version); return device; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java index 973cb4b5b2..73ce7e4056 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java @@ -18,11 +18,15 @@ package org.thingsboard.server.dao.sql; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; +import jakarta.persistence.EntityManager; +import jakarta.persistence.OptimisticLockException; +import jakarta.persistence.PersistenceContext; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.common.data.HasVersion; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.DaoUtil; @@ -47,6 +51,9 @@ public abstract class JpaAbstractDao, D> @Autowired protected JdbcTemplate jdbcTemplate; + @PersistenceContext + private EntityManager entityManager; + protected abstract Class getEntityClass(); protected abstract JpaRepository getRepository(); @@ -68,12 +75,30 @@ 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 IllegalStateException("The entity was already changed by someone else"); + } return DaoUtil.getData(entity); } protected E doSave(E entity, boolean isNew) { - return getRepository().save(entity); + if (isNew) { + entityManager.persist(entity); + } else { + if (entity instanceof HasVersion versionedEntity) { + if (versionedEntity.getVersion() == null) { + HasVersion existingEntity = entityManager.find(versionedEntity.getClass(), entity.getUuid()); + versionedEntity.setVersion(existingEntity.getVersion()); // manually resetting the version to latest to allow force overwrite of the entity + } + entity = entityManager.merge(entity); + entityManager.flush(); + } else { + entity = entityManager.merge(entity); + } + } + return entity; } @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 45438a9b51..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 @@ -18,24 +18,13 @@ package org.thingsboard.server.dao.sql; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.util.SqlDao; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; - @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/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index 64f37f3838..f6ff3c8915 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -22,7 +22,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.DeviceInfo; @@ -81,14 +80,6 @@ public class JpaDeviceDao 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/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index f2978b7f4a..a48b1a52fd 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -340,6 +340,7 @@ CREATE TABLE IF NOT EXISTS device ( firmware_id uuid, software_id uuid, external_id uuid, + version INT DEFAULT 0, CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_external_id_unq_key UNIQUE (tenant_id, external_id), CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id), diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java index 87463ebda6..a54399e85d 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java @@ -368,7 +368,7 @@ public class DeviceServiceTest extends AbstractServiceTest { Device device = new Device(); device.setType(deviceProfile.getName()); device.setTenantId(tenantId); - device.setName("My device"+ StringUtils.randomAlphabetic(5)); + device.setName("My device" + StringUtils.randomAlphabetic(5)); DefaultTransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = platformTransactionManager.getTransaction(def); @@ -945,8 +945,8 @@ public class DeviceServiceTest extends AbstractServiceTest { deviceInfosWithLabel.stream() .anyMatch( d -> 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 { ) ); } + } From 67b8ded9f42519e17995821d4e15dfd368e77740 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 1 Jul 2024 13:11:59 +0300 Subject: [PATCH 02/26] Add version to most entities --- .../main/data/upgrade/3.7.0/schema_update.sql | 18 +++++++++++- .../server/common/data/Customer.java | 5 +++- .../server/common/data/DashboardInfo.java | 8 ++++- .../server/common/data/DeviceProfile.java | 4 ++- .../server/common/data/EntityView.java | 8 +++-- .../thingsboard/server/common/data/User.java | 9 +++++- .../server/common/data/asset/Asset.java | 7 ++++- .../common/data/asset/AssetProfile.java | 5 +++- .../server/common/data/edge/Edge.java | 9 +++++- .../server/common/data/rule/RuleChain.java | 7 +++-- .../data/security/DeviceCredentials.java | 12 ++++++-- .../common/data/sync/ie/EntityExportData.java | 2 +- .../common/data/widget/BaseWidgetType.java | 11 +++++-- .../common/data/widget/WidgetTypeDetails.java | 9 +++--- .../common/data/widget/WidgetsBundle.java | 7 ++++- .../server/dao/model/BaseSqlEntity.java | 20 +++++++++++-- .../dao/model/BaseVersionedSqlEntity.java | 29 ++++++++++++++++++- .../model/sql/AbstractAlarmCommentEntity.java | 4 +-- .../dao/model/sql/AbstractAssetEntity.java | 15 ++++------ .../dao/model/sql/AbstractDeviceEntity.java | 18 ++++-------- .../dao/model/sql/AbstractEdgeEntity.java | 16 +++++----- .../model/sql/AbstractEntityViewEntity.java | 16 +++++----- .../model/sql/AbstractWidgetTypeEntity.java | 20 +++++-------- .../server/dao/model/sql/AssetInfoEntity.java | 1 + .../dao/model/sql/AssetProfileEntity.java | 16 +++++----- .../server/dao/model/sql/CustomerEntity.java | 9 +++--- .../server/dao/model/sql/DashboardEntity.java | 11 ++++--- .../dao/model/sql/DashboardInfoEntity.java | 10 +++---- .../model/sql/DeviceCredentialsEntity.java | 21 ++++++-------- .../dao/model/sql/DeviceProfileEntity.java | 11 ++++--- .../server/dao/model/sql/RuleChainEntity.java | 11 ++++--- .../server/dao/model/sql/UserEntity.java | 10 +++---- .../dao/model/sql/WidgetsBundleEntity.java | 17 +++++------ .../server/dao/sql/JpaAbstractDao.java | 12 ++++++-- .../main/resources/sql/schema-entities.sql | 16 ++++++++-- 35 files changed, 254 insertions(+), 150 deletions(-) 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 a51697bf96..b16a647128 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 @@ -14,4 +14,20 @@ -- limitations under the License. -- -ALTER TABLE device ADD COLUMN IF NOT EXISTS version INT DEFAULT 0; +-- Optimistic locking update START + +ALTER TABLE device ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +ALTER TABLE device_profile ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +ALTER TABLE device_credentials ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +ALTER TABLE asset ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +ALTER TABLE asset_profile ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +ALTER TABLE entity_view ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +ALTER TABLE customer ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +ALTER TABLE edge ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +ALTER TABLE rule_chain ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +ALTER TABLE dashboard ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +ALTER TABLE widget_type ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +ALTER TABLE widgets_bundle ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; + +-- Optimistic locking update END 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 398cd17aa1..0aa44515d2 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 @@ -30,7 +30,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; @@ -43,6 +43,8 @@ public class Customer extends ContactBased implements HasTenantId, E @Getter @Setter private CustomerId externalId; + @Getter @Setter + private Integer version; public Customer() { super(); @@ -57,6 +59,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 238ef8744c..8906fb00bf 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 @@ -17,6 +17,8 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; +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 Integer 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/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 6791a460ed..9896fb1d33 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 Integer 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..37c79dd55a 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 Integer 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/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java index 678b7cdac6..aa80110b77 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 Integer 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..c3d8ad6500 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 Integer 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..c55e21fe22 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 Integer 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..d0d963a852 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 Integer 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/rule/RuleChain.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java index 5448fcd259..ee199c140c 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 Integer 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..6bb758a9ba 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 Integer 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/ie/EntityExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java index 2336c283a6..e4c54d6545 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 @@ -56,7 +56,7 @@ public class EntityExportData> { .comparing(AttributeExportData::getKey).thenComparing(AttributeExportData::getLastUpdateTs); @JsonProperty(index = 2) - @JsonTbEntity + @JsonTbEntity // FIXME: version is serialized. also check single entity export/import from UI! private E entity; @JsonProperty(index = 1) private EntityType entityType; 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..4953595ddd 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 Integer 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..25a1ad5f1c 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 Integer 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/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java index fd349828e2..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 @@ -16,17 +16,18 @@ package org.thingsboard.server.dao.model; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Id; +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; import org.thingsboard.server.dao.DaoUtil; -import jakarta.persistence.Column; -import jakarta.persistence.Id; -import jakarta.persistence.MappedSuperclass; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -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/BaseVersionedSqlEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedSqlEntity.java index 04abd58b51..5c413183dc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedSqlEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedSqlEntity.java @@ -20,15 +20,42 @@ 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 BaseVersionedSqlEntity extends BaseSqlEntity implements HasVersion { +public abstract class BaseVersionedSqlEntity extends BaseSqlEntity implements HasVersion { + @Getter @Setter @Version @Column(name = ModelConstants.VERSION_PROPERTY) protected Integer version; + public BaseVersionedSqlEntity() { + super(); + } + + public BaseVersionedSqlEntity(D domain) { + super(domain); + this.version = domain.getVersion(); + } + + public BaseVersionedSqlEntity(BaseVersionedSqlEntity entity) { + super(entity); + this.version = entity.version; + } + + @Override + public String toString() { + return "BaseVersionedSqlEntity{" + + "id=" + id + + ", createdTime=" + createdTime + + ", version=" + version + + '}'; + } + } 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..16b29f66b0 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.BaseVersionedSqlEntity; 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 BaseVersionedSqlEntity { @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 ac66204132..b03d740ad4 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 @@ -41,7 +41,7 @@ import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @MappedSuperclass -public abstract class AbstractDeviceEntity extends BaseVersionedSqlEntity { +public abstract class AbstractDeviceEntity extends BaseVersionedSqlEntity { @Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY, columnDefinition = "uuid") private UUID tenantId; @@ -83,11 +83,8 @@ public abstract class AbstractDeviceEntity extends BaseVersion 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(); } @@ -111,12 +108,10 @@ public abstract class AbstractDeviceEntity extends BaseVersion if (device.getExternalId() != null) { this.externalId = device.getExternalId().getId(); } - this.version = device.getVersion(); } - 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(); @@ -128,12 +123,12 @@ public abstract class AbstractDeviceEntity extends BaseVersion this.firmwareId = deviceEntity.getFirmwareId(); this.softwareId = deviceEntity.getSoftwareId(); this.externalId = deviceEntity.getExternalId(); - this.version = deviceEntity.getVersion(); } protected Device toDevice() { Device device = new Device(new DeviceId(getUuid())); device.setCreatedTime(createdTime); + device.setVersion(version); if (tenantId != null) { device.setTenantId(TenantId.fromUUID(tenantId)); } @@ -157,7 +152,6 @@ public abstract class AbstractDeviceEntity extends BaseVersion if (externalId != null) { device.setExternalId(new DeviceId(externalId)); } - device.setVersion(version); return device; } 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..0680b6ba63 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.BaseVersionedSqlEntity; 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 BaseVersionedSqlEntity { @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 BaseVersionedSqlEntity { @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/AbstractWidgetTypeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java index ea26b60cab..ad1ab993e3 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 @@ -15,23 +15,22 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; import lombok.Data; 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.BaseEntity; -import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import jakarta.persistence.Column; -import jakarta.persistence.MappedSuperclass; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @MappedSuperclass -public abstract class AbstractWidgetTypeEntity extends BaseSqlEntity { +public abstract class AbstractWidgetTypeEntity extends BaseVersionedSqlEntity { @Column(name = ModelConstants.WIDGET_TYPE_TENANT_ID_PROPERTY) private UUID tenantId; @@ -49,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(); } @@ -63,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(); @@ -74,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 d035db07a7..870ed93206 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 @@ -15,6 +15,9 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.asset.AssetProfile; @@ -22,19 +25,16 @@ 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.BaseVersionedSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.ASSET_PROFILE_TABLE_NAME) -public final class AssetProfileEntity extends BaseSqlEntity { +public final class AssetProfileEntity extends BaseVersionedSqlEntity { @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..e61f04fcc4 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 @@ -26,6 +26,7 @@ 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.BaseVersionedSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -35,7 +36,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 BaseVersionedSqlEntity { @Column(name = ModelConstants.CUSTOMER_TENANT_ID_PROPERTY) private UUID tenantId; @@ -82,10 +83,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 +105,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..c01558c231 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.BaseVersionedSqlEntity; 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 BaseVersionedSqlEntity { 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..d9ced896e2 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.BaseVersionedSqlEntity; 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 BaseVersionedSqlEntity { 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 34a98f1c28..0d7340d56d 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 @@ -15,28 +15,27 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; 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.BaseVersionedSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.Table; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.DEVICE_CREDENTIALS_TABLE_NAME) -public final class DeviceCredentialsEntity extends BaseSqlEntity implements BaseEntity { +public final class DeviceCredentialsEntity extends BaseVersionedSqlEntity { @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 BaseVersionedSqlEntity { @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..a0a73a0a93 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.BaseVersionedSqlEntity; 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 BaseVersionedSqlEntity { @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..3029a8a70a 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.BaseVersionedSqlEntity; 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 BaseVersionedSqlEntity { @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 1edf3bb614..090e1d02e0 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 @@ -16,24 +16,24 @@ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; 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.BaseVersionedSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.WIDGETS_BUNDLE_TABLE_NAME) -public final class WidgetsBundleEntity extends BaseSqlEntity { +public final class WidgetsBundleEntity extends BaseVersionedSqlEntity { @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/sql/JpaAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java index 73ce7e4056..8c390be3cb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java @@ -85,12 +85,18 @@ public abstract class JpaAbstractDao, D> protected E doSave(E entity, boolean isNew) { if (isNew) { + if (entity instanceof HasVersion versionedEntity) { + versionedEntity.setVersion(1); + } entityManager.persist(entity); } else { if (entity instanceof HasVersion versionedEntity) { if (versionedEntity.getVersion() == null) { - HasVersion existingEntity = entityManager.find(versionedEntity.getClass(), entity.getUuid()); - versionedEntity.setVersion(existingEntity.getVersion()); // manually resetting the version to latest to allow force overwrite of the entity + // fixme tmp + throw new IllegalArgumentException("TEST - unexpected null version for " + versionedEntity); + +// HasVersion existingEntity = entityManager.find(versionedEntity.getClass(), entity.getUuid()); +// versionedEntity.setVersion(existingEntity.getVersion()); // manually resetting the version to latest to allow force overwrite of the entity } entity = entityManager.merge(entity); entityManager.flush(); @@ -137,6 +143,8 @@ public abstract class JpaAbstractDao, D> @Override @Transactional public boolean removeById(TenantId tenantId, UUID id) { +// jdbcTemplate.queryForObject("DELETE FROM " + getEntityType().getTableName() + " WHERE id = ? RETURNING version", Integer.class, id); + // TODO: increment version... getRepository().deleteById(id); log.debug("Remove request: {}", id); return !getRepository().existsById(id); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index a48b1a52fd..1ec66047f1 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -145,6 +145,7 @@ CREATE TABLE IF NOT EXISTS customer ( zip varchar(255), external_id uuid, is_public boolean, + version INT DEFAULT 1, CONSTRAINT customer_title_unq_key UNIQUE (tenant_id, title), CONSTRAINT customer_external_id_unq_key UNIQUE (tenant_id, external_id) ); @@ -160,6 +161,7 @@ CREATE TABLE IF NOT EXISTS dashboard ( mobile_order int, image varchar(1000000), external_id uuid, + version INT DEFAULT 1, CONSTRAINT dashboard_external_id_unq_key UNIQUE (tenant_id, external_id) ); @@ -175,6 +177,7 @@ CREATE TABLE IF NOT EXISTS rule_chain ( debug_mode boolean, tenant_id uuid, external_id uuid, + version INT DEFAULT 1, CONSTRAINT rule_chain_external_id_unq_key UNIQUE (tenant_id, external_id) ); @@ -252,6 +255,7 @@ CREATE TABLE IF NOT EXISTS asset_profile ( default_queue_name varchar(255), default_edge_rule_chain_id uuid, external_id uuid, + version INT DEFAULT 1, CONSTRAINT asset_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT asset_profile_external_id_unq_key UNIQUE (tenant_id, external_id), CONSTRAINT fk_default_rule_chain_asset_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id), @@ -270,6 +274,7 @@ CREATE TABLE IF NOT EXISTS asset ( tenant_id uuid, type varchar(255), external_id uuid, + version INT DEFAULT 1, CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT asset_external_id_unq_key UNIQUE (tenant_id, external_id), CONSTRAINT fk_asset_profile FOREIGN KEY (asset_profile_id) REFERENCES asset_profile(id) @@ -295,6 +300,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( provision_device_key varchar, default_edge_rule_chain_id uuid, external_id uuid, + version INT DEFAULT 1, CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), CONSTRAINT device_profile_external_id_unq_key UNIQUE (tenant_id, external_id), @@ -340,7 +346,7 @@ CREATE TABLE IF NOT EXISTS device ( firmware_id uuid, software_id uuid, external_id uuid, - version INT DEFAULT 0, + version INT DEFAULT 1, CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_external_id_unq_key UNIQUE (tenant_id, external_id), CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id), @@ -355,6 +361,7 @@ CREATE TABLE IF NOT EXISTS device_credentials ( credentials_type varchar(255), credentials_value varchar, device_id uuid, + version INT DEFAULT 1, CONSTRAINT device_credentials_id_unq_key UNIQUE (credentials_id), CONSTRAINT device_credentials_device_id_unq_key UNIQUE (device_id) ); @@ -439,7 +446,8 @@ CREATE TABLE IF NOT EXISTS tb_user ( first_name varchar(255), last_name varchar(255), phone varchar(255), - tenant_id uuid + tenant_id uuid, + version INT DEFAULT 1 ); CREATE TABLE IF NOT EXISTS tenant_profile ( @@ -495,6 +503,7 @@ CREATE TABLE IF NOT EXISTS widget_type ( description varchar(1024), tags text[], external_id uuid, + version INT DEFAULT 1, CONSTRAINT uq_widget_type_fqn UNIQUE (tenant_id, fqn), CONSTRAINT widget_type_external_id_unq_key UNIQUE (tenant_id, external_id) ); @@ -509,6 +518,7 @@ CREATE TABLE IF NOT EXISTS widgets_bundle ( description varchar(1024), widgets_bundle_order int, external_id uuid, + version INT DEFAULT 1, CONSTRAINT uq_widgets_bundle_alias UNIQUE (tenant_id, alias), CONSTRAINT widgets_bundle_external_id_unq_key UNIQUE (tenant_id, external_id) ); @@ -536,6 +546,7 @@ CREATE TABLE IF NOT EXISTS entity_view ( end_ts bigint, additional_info varchar, external_id uuid, + version INT DEFAULT 1, CONSTRAINT entity_view_external_id_unq_key UNIQUE (tenant_id, external_id) ); @@ -741,6 +752,7 @@ CREATE TABLE IF NOT EXISTS edge ( routing_key varchar(255), secret varchar(255), tenant_id uuid, + version INT DEFAULT 1, CONSTRAINT edge_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT edge_routing_key_unq_key UNIQUE (routing_key) ); From 4b7b69313f7a89a62f8cfeb5185ad545a90d9da8 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 1 Jul 2024 13:49:17 +0300 Subject: [PATCH 03/26] Fix version in proto; fix some tests --- .../entitiy/device/DefaultTbDeviceService.java | 2 +- .../importing/csv/AbstractBulkImportService.java | 6 +++--- .../server/controller/DeviceControllerTest.java | 3 +-- .../server/common/util/ProtoUtils.java | 16 ++++++++++++++++ common/proto/src/main/proto/queue.proto | 3 +++ .../dao/sql/device/JpaDeviceCredentialsDao.java | 9 --------- 6 files changed, 24 insertions(+), 15 deletions(-) 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/importing/csv/AbstractBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java index 3fc2a35026..dfec2ee206 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java @@ -148,9 +148,9 @@ public abstract class AbstractBulkImportService Date: Mon, 1 Jul 2024 14:11:57 +0300 Subject: [PATCH 04/26] HTTP status 409 (Conflict) on EntityVersionMismatchException --- .../server/controller/BaseController.java | 3 +++ .../ThingsboardErrorResponseHandler.java | 1 + .../controller/DeviceControllerTest.java | 16 +++++++++++++ .../EntityVersionMismatchException.java | 24 +++++++++++++++++++ .../data/exception/ThingsboardErrorCode.java | 1 + .../server/dao/sql/JpaAbstractDao.java | 3 ++- 6 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/exception/EntityVersionMismatchException.java 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 0942329f40..cbec2aabba 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -64,6 +64,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; @@ -381,6 +382,8 @@ public abstract class BaseController { log.warn("Database error: {} - {}", errorType, ExceptionUtils.getRootCauseMessage(exception)); } return new ThingsboardException("Database error", ThingsboardErrorCode.GENERAL); + } else if (exception instanceof EntityVersionMismatchException) { + return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.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 d097beb4c2..a1fee2e7b3 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java @@ -91,6 +91,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.CONFLICT, HttpStatus.CONFLICT); } private static ThingsboardErrorCode statusToErrorCode(HttpStatus status) { 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 20c854b482..9ad072422c 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -1585,6 +1585,22 @@ public class DeviceControllerTest extends AbstractControllerTest { Assert.assertEquals(newAttributeValue, actualAttribute.get("value")); } + @Test + public void testSaveDeviceWithOutdatedVersion() throws Exception { + Device device = createDevice("Device v1"); + assertThat(device.getVersion()).isOne(); + + device.setName("Device v2"); + device = doPost("/api/device", device, Device.class); + assertThat(device.getVersion()).isEqualTo(2); + + device.setVersion(1); + 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"); + } + private Device createDevice(String name) { Device device = new Device(); device.setName(name); 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..e781c2e346 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), + CONFLICT(35), SUBSCRIPTION_VIOLATION(40), PASSWORD_VIOLATION(45); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java index 8c390be3cb..06db0855fb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java @@ -27,6 +27,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.HasVersion; +import org.thingsboard.server.common.data.exception.EntityVersionMismatchException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.DaoUtil; @@ -78,7 +79,7 @@ public abstract class JpaAbstractDao, D> try { entity = doSave(entity, isNew); } catch (OptimisticLockException e) { - throw new IllegalStateException("The entity was already changed by someone else"); + throw new EntityVersionMismatchException("The entity was already changed by someone else", e); } return DaoUtil.getData(entity); } From 8b9021d5349d5d8db6b8428924c47101e2981c24 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 1 Jul 2024 14:23:38 +0300 Subject: [PATCH 05/26] Fix entity creation with preset id --- .../thingsboard/server/dao/sql/JpaAbstractDao.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java index 06db0855fb..21835484ca 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java @@ -93,11 +93,13 @@ public abstract class JpaAbstractDao, D> } else { if (entity instanceof HasVersion versionedEntity) { if (versionedEntity.getVersion() == null) { - // fixme tmp - throw new IllegalArgumentException("TEST - unexpected null version for " + versionedEntity); - -// HasVersion existingEntity = entityManager.find(versionedEntity.getClass(), entity.getUuid()); -// versionedEntity.setVersion(existingEntity.getVersion()); // manually resetting the version to latest to allow force overwrite of the entity + HasVersion existingEntity = entityManager.find(versionedEntity.getClass(), entity.getUuid()); + if (existingEntity != null) { + throw new IllegalArgumentException("TEST - unexpected null version for " + versionedEntity); +// fixme tmp 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(); From 8c8666c3d0e53678c62f77965b8e8d769fd32e16 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 1 Jul 2024 15:06:38 +0300 Subject: [PATCH 06/26] Ignore version for export/import --- .../sync/ie/exporting/impl/DefaultEntityExportService.java | 4 ++++ .../sync/ie/importing/csv/AbstractBulkImportService.java | 6 +++--- .../sync/ie/importing/impl/BaseEntityImportService.java | 4 ++++ .../thingsboard/server/common/data/sync/JsonTbEntity.java | 2 +- .../server/common/data/sync/ie/DeviceExportData.java | 2 +- .../server/common/data/sync/ie/EntityExportData.java | 3 ++- .../java/org/thingsboard/server/dao/sql/JpaAbstractDao.java | 3 +-- .../server/dao/service/DeviceCredentialsServiceTest.java | 2 +- 8 files changed, 17 insertions(+), 9 deletions(-) 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 { @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 e4c54d6545..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 @@ -56,7 +56,7 @@ public class EntityExportData> { .comparing(AttributeExportData::getKey).thenComparing(AttributeExportData::getLastUpdateTs); @JsonProperty(index = 2) - @JsonTbEntity // FIXME: version is serialized. also check single entity export/import from UI! + @JsonTbEntity private E entity; @JsonProperty(index = 1) private EntityType entityType; @@ -95,4 +95,5 @@ public class EntityExportData> { public boolean hasRelations() { return relations != null; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java index 21835484ca..9a58e68895 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java @@ -95,8 +95,7 @@ public abstract class JpaAbstractDao, D> if (versionedEntity.getVersion() == null) { HasVersion existingEntity = entityManager.find(versionedEntity.getClass(), entity.getUuid()); if (existingEntity != null) { - throw new IllegalArgumentException("TEST - unexpected null version for " + versionedEntity); -// fixme tmp versionedEntity.setVersion(existingEntity.getVersion()); // manually resetting the version to latest to allow force overwrite of the entity + versionedEntity.setVersion(existingEntity.getVersion()); // manually resetting the version to latest to allow force overwrite of the entity } else { return doSave(entity, true); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceTest.java index 188763dc71..2be8cd12c5 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceTest.java @@ -180,7 +180,7 @@ public class DeviceCredentialsServiceTest extends AbstractServiceTest { Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); deviceCredentials.setCredentialsId("access_token"); - deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); + deviceCredentials = deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); DeviceCredentials foundDeviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); Assert.assertEquals(deviceCredentials, foundDeviceCredentials); deviceService.deleteDevice(tenantId, savedDevice.getId()); From 1a2040d4cd1b7d82b09e5c9b41b407262e3c481d Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 5 Jul 2024 14:32:42 +0300 Subject: [PATCH 07/26] Remove redundant existsById from dao.removeById --- dao/src/main/java/org/thingsboard/server/dao/Dao.java | 2 +- .../java/org/thingsboard/server/dao/sql/JpaAbstractDao.java | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) 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/sql/JpaAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java index 9a58e68895..423dc8a41a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java @@ -144,12 +144,9 @@ public abstract class JpaAbstractDao, D> @Override @Transactional - public boolean removeById(TenantId tenantId, UUID id) { -// jdbcTemplate.queryForObject("DELETE FROM " + getEntityType().getTableName() + " WHERE id = ? RETURNING version", Integer.class, id); - // TODO: increment version... + public void removeById(TenantId tenantId, UUID id) { getRepository().deleteById(id); log.debug("Remove request: {}", id); - return !getRepository().existsById(id); } @Transactional From 8c0f2be9e49a6d56350c8df3d6d0b43e5afef407 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 5 Jul 2024 14:52:14 +0300 Subject: [PATCH 08/26] Fix tests --- .../org/thingsboard/server/controller/AbstractWebTest.java | 2 +- .../server/controller/AuditLogControllerTest.java | 2 +- .../server/controller/EntityViewControllerTest.java | 2 +- .../server/controller/RuleChainControllerTest.java | 2 +- .../java/org/thingsboard/server/edge/DeviceEdgeTest.java | 6 ++---- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 8304d0dd1e..9a74ad1b90 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -525,7 +525,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { JsonNode activateRequest = getActivateRequest(password); ResultActions resultActions = doPost("/api/noauth/activate", activateRequest); resultActions.andExpect(status().isOk()); - return savedUser; + return doGet("/api/user/" + savedUser.getId(), User.class); } private JsonNode getActivateRequest(String password) throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java index 5005e6ad34..4c80e12390 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java @@ -161,7 +161,7 @@ public class AuditLogControllerTest extends AbstractControllerTest { Device savedDevice = doPost("/api/device", device, Device.class); for (int i = 0; i < 11; i++) { savedDevice.setName("Device name" + i); - doPost("/api/device", savedDevice, Device.class); + savedDevice = doPost("/api/device", savedDevice, Device.class); } List loadedAuditLogs = new ArrayList<>(); 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/edge/DeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java index 9e09a16fd9..b1b1f59643 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); From 1dcb64d298a9ce1433c48c88899492352181932c Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 5 Jul 2024 16:20:19 +0300 Subject: [PATCH 09/26] Entities version refactoring --- .../controller/DeviceControllerTest.java | 12 +++- .../server/common/data/Customer.java | 2 +- .../server/common/data/DashboardInfo.java | 2 +- .../server/common/data/Device.java | 2 +- .../server/common/data/DeviceProfile.java | 2 +- .../server/common/data/EntityView.java | 2 +- .../server/common/data/HasVersion.java | 3 +- .../thingsboard/server/common/data/User.java | 2 +- .../server/common/data/asset/Asset.java | 2 +- .../common/data/asset/AssetProfile.java | 2 +- .../server/common/data/edge/Edge.java | 2 +- .../server/common/data/rule/RuleChain.java | 2 +- .../data/security/DeviceCredentials.java | 2 +- .../common/data/widget/BaseWidgetType.java | 2 +- .../common/data/widget/WidgetsBundle.java | 2 +- common/proto/src/main/proto/queue.proto | 6 +- .../server/dao/model/BaseVersionedEntity.java | 45 +++++++++++++- .../dao/model/BaseVersionedSqlEntity.java | 61 ------------------- .../dao/model/sql/AbstractAssetEntity.java | 4 +- .../dao/model/sql/AbstractDeviceEntity.java | 4 +- .../dao/model/sql/AbstractEdgeEntity.java | 4 +- .../model/sql/AbstractEntityViewEntity.java | 4 +- .../model/sql/AbstractWidgetTypeEntity.java | 4 +- .../dao/model/sql/AssetProfileEntity.java | 4 +- .../server/dao/model/sql/CustomerEntity.java | 5 +- .../server/dao/model/sql/DashboardEntity.java | 4 +- .../dao/model/sql/DashboardInfoEntity.java | 4 +- .../model/sql/DeviceCredentialsEntity.java | 4 +- .../dao/model/sql/DeviceProfileEntity.java | 4 +- .../server/dao/model/sql/RuleChainEntity.java | 4 +- .../server/dao/model/sql/UserEntity.java | 4 +- .../server/dao/model/sql/VersionedEntity.java | 1 + .../dao/model/sql/WidgetsBundleEntity.java | 4 +- .../server/dao/sql/JpaAbstractDao.java | 2 +- 34 files changed, 100 insertions(+), 113 deletions(-) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedSqlEntity.java 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 9ad072422c..cf46d0f7fd 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -1587,18 +1587,24 @@ public class DeviceControllerTest extends AbstractControllerTest { @Test public void testSaveDeviceWithOutdatedVersion() throws Exception { - Device device = createDevice("Device v1"); + Device device = createDevice("Device v1.0"); assertThat(device.getVersion()).isOne(); - device.setName("Device v2"); + device.setName("Device v2.0"); device = doPost("/api/device", device, Device.class); assertThat(device.getVersion()).isEqualTo(2); - device.setVersion(1); + 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) { 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 0aa44515d2..fa3561a263 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 @@ -44,7 +44,7 @@ public class Customer extends ContactBased implements HasTenantId, E @Getter @Setter private CustomerId externalId; @Getter @Setter - private Integer version; + private Long version; public Customer() { super(); 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 8906fb00bf..259a5f5fa6 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 @@ -46,7 +46,7 @@ public class DashboardInfo extends BaseData implements HasName, Has private Integer mobileOrder; @Getter @Setter - private Integer version; + private Long version; public DashboardInfo() { super(); 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 afecbd5020..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 @@ -68,7 +68,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL @Getter @Setter private DeviceId externalId; @Getter @Setter - private Integer version; + private Long version; public Device() { super(); 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 9896fb1d33..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 @@ -97,7 +97,7 @@ public class DeviceProfile extends BaseData implements HasName, private RuleChainId defaultEdgeRuleChainId; private DeviceProfileId externalId; - private Integer version; + private Long version; public DeviceProfile() { super(); 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 37c79dd55a..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 @@ -60,7 +60,7 @@ public class EntityView extends BaseDataWithAdditionalInfo private long endTimeMs; private EntityViewId externalId; - private Integer version; + private Long version; public EntityView() { super(); 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 a5d03caafb..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 @@ -19,6 +19,7 @@ public interface HasVersion { Long getVersion(); - void setVersion(Integer version); + default void setVersion(Long version) { + } } 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 aa80110b77..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 @@ -53,7 +53,7 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, private String phone; @Getter @Setter - private Integer version; + private Long version; public User() { super(); 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 c3d8ad6500..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 @@ -58,7 +58,7 @@ public class Asset extends BaseDataWithAdditionalInfo implements HasLab @Getter @Setter private AssetId externalId; @Getter @Setter - private Integer version; + private Long version; public Asset() { super(); 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 c55e21fe22..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 @@ -75,7 +75,7 @@ public class AssetProfile extends BaseData implements HasName, H private RuleChainId defaultEdgeRuleChainId; private AssetProfileId externalId; - private Integer version; + private Long version; public AssetProfile() { super(); 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 d0d963a852..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 @@ -60,7 +60,7 @@ public class Edge extends BaseDataWithAdditionalInfo implements HasLabel private String secret; @Getter - private Integer version; + private Long version; public Edge() { super(); 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 ee199c140c..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 @@ -59,7 +59,7 @@ public class RuleChain extends BaseDataWithAdditionalInfo implement private transient JsonNode configuration; private RuleChainId externalId; - private Integer version; + private Long version; @JsonIgnore private byte[] configurationBytes; 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 6bb758a9ba..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 @@ -35,7 +35,7 @@ public class DeviceCredentials extends BaseData implements private String credentialsValue; @Getter @Setter - private Integer version; + private Long version; public DeviceCredentials() { super(); 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 4953595ddd..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 @@ -47,7 +47,7 @@ public class BaseWidgetType extends BaseData implements HasName, H @Schema(description = "Whether widget type is deprecated.", example = "true") private boolean deprecated; - private Integer version; + private Long version; public BaseWidgetType() { super(); 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 25a1ad5f1c..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 @@ -79,7 +79,7 @@ public class WidgetsBundle extends BaseData implements HasName, private WidgetsBundleId externalId; @Getter @Setter - private Integer version; + private Long version; public WidgetsBundle() { super(); diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index b6e4999b90..7d18ed2d0b 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -209,7 +209,7 @@ message DeviceProto { optional int64 softwareIdLSB = 18; optional int64 externalIdMSB = 19; optional int64 externalIdLSB = 20; - optional int32 version = 21; + optional int64 version = 21; } message DeviceProfileProto { @@ -240,7 +240,7 @@ message DeviceProfileProto { optional int64 defaultEdgeRuleChainIdLSB = 25; optional int64 externalIdMSB = 26; optional int64 externalIdLSB = 27; - optional int32 version = 28; + optional int64 version = 28; } message TenantProto { @@ -670,7 +670,7 @@ message DeviceCredentialsProto { CredentialsType credentialsType = 6; string credentialsId = 7; optional string credentialsValue = 8; - optional int32 version = 9; + optional int64 version = 9; } message CredentialsDataProto { 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/BaseVersionedSqlEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedSqlEntity.java deleted file mode 100644 index 5c413183dc..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/BaseVersionedSqlEntity.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * 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.dao.model; - -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 BaseVersionedSqlEntity extends BaseSqlEntity implements HasVersion { - - @Getter @Setter - @Version - @Column(name = ModelConstants.VERSION_PROPERTY) - protected Integer version; - - public BaseVersionedSqlEntity() { - super(); - } - - public BaseVersionedSqlEntity(D domain) { - super(domain); - this.version = domain.getVersion(); - } - - public BaseVersionedSqlEntity(BaseVersionedSqlEntity entity) { - super(entity); - this.version = entity.version; - } - - @Override - public String toString() { - return "BaseVersionedSqlEntity{" + - "id=" + id + - ", createdTime=" + createdTime + - ", version=" + version + - '}'; - } - -} 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 16b29f66b0..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.BaseVersionedSqlEntity; +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 BaseVersionedSqlEntity { +public abstract class AbstractAssetEntity extends BaseVersionedEntity { @Column(name = ASSET_TENANT_ID_PROPERTY) private UUID 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 b03d740ad4..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.BaseVersionedSqlEntity; +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 BaseVersionedSqlEntity { +public abstract class AbstractDeviceEntity extends BaseVersionedEntity { @Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY, columnDefinition = "uuid") private UUID 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 0680b6ba63..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.BaseVersionedSqlEntity; +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 BaseVersionedSqlEntity { +public abstract class AbstractEdgeEntity extends BaseVersionedEntity { @Column(name = EDGE_TENANT_ID_PROPERTY, columnDefinition = "uuid") private UUID tenantId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEntityViewEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEntityViewEntity.java index b0d1e252c5..c6c76ac12d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEntityViewEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEntityViewEntity.java @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.objects.TelemetryEntityView; -import org.thingsboard.server.dao.model.BaseVersionedSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -48,7 +48,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPER @EqualsAndHashCode(callSuper = true) @MappedSuperclass @Slf4j -public abstract class AbstractEntityViewEntity extends BaseVersionedSqlEntity { +public abstract class AbstractEntityViewEntity extends BaseVersionedEntity { @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY) private UUID entityId; 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 ad1ab993e3..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.BaseVersionedSqlEntity; +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 BaseVersionedSqlEntity { +public abstract class AbstractWidgetTypeEntity extends BaseVersionedEntity { @Column(name = ModelConstants.WIDGET_TYPE_TENANT_ID_PROPERTY) private UUID tenantId; 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 870ed93206..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.BaseVersionedSqlEntity; +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 BaseVersionedSqlEntity { +public final class AssetProfileEntity extends BaseVersionedEntity { @Column(name = ModelConstants.ASSET_PROFILE_TENANT_ID_PROPERTY) private UUID 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 e61f04fcc4..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,8 +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.BaseVersionedSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -36,7 +35,7 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.CUSTOMER_TABLE_NAME) -public final class CustomerEntity extends BaseVersionedSqlEntity { +public final class CustomerEntity extends BaseVersionedEntity { @Column(name = ModelConstants.CUSTOMER_TENANT_ID_PROPERTY) private UUID tenantId; 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 c01558c231..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.BaseVersionedSqlEntity; +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 BaseVersionedSqlEntity { +public final class DashboardEntity extends BaseVersionedEntity { private static final JavaType assignedCustomersType = JacksonUtil.constructCollectionType(HashSet.class, ShortCustomerInfo.class); 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 d9ced896e2..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.BaseVersionedSqlEntity; +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 BaseVersionedSqlEntity { +public class DashboardInfoEntity extends BaseVersionedEntity { private static final JavaType assignedCustomersType = JacksonUtil.constructCollectionType(HashSet.class, ShortCustomerInfo.class); 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 0d7340d56d..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,7 +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.BaseVersionedSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import java.util.UUID; @@ -35,7 +35,7 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.DEVICE_CREDENTIALS_TABLE_NAME) -public final class DeviceCredentialsEntity extends BaseVersionedSqlEntity { +public final class DeviceCredentialsEntity extends BaseVersionedEntity { @Column(name = ModelConstants.DEVICE_CREDENTIALS_DEVICE_ID_PROPERTY) private UUID deviceId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index e235405714..c5e6bc4968 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -38,7 +38,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.BaseVersionedSqlEntity; +import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonConverter; @@ -48,7 +48,7 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.DEVICE_PROFILE_TABLE_NAME) -public final class DeviceProfileEntity extends BaseVersionedSqlEntity { +public final class DeviceProfileEntity extends BaseVersionedEntity { @Column(name = ModelConstants.DEVICE_PROFILE_TENANT_ID_PROPERTY) private UUID tenantId; 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 a0a73a0a93..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.BaseVersionedSqlEntity; +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 BaseVersionedSqlEntity { +public class RuleChainEntity extends BaseVersionedEntity { @Column(name = ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY) private UUID tenantId; 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 3029a8a70a..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.BaseVersionedSqlEntity; +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 BaseVersionedSqlEntity { +public class UserEntity extends BaseVersionedEntity { @Column(name = ModelConstants.USER_TENANT_ID_PROPERTY) private UUID tenantId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/VersionedEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/VersionedEntity.java index 2dfee6466a..b4d6577b82 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/VersionedEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/VersionedEntity.java @@ -27,4 +27,5 @@ public abstract class VersionedEntity { @Column(name = VERSION_COLUMN) protected Long version; + } 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 090e1d02e0..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.BaseVersionedSqlEntity; +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 BaseVersionedSqlEntity { +public final class WidgetsBundleEntity extends BaseVersionedEntity { @Column(name = ModelConstants.WIDGETS_BUNDLE_TENANT_ID_PROPERTY) private UUID tenantId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java index 423dc8a41a..dc7aec3fcc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java @@ -87,7 +87,7 @@ public abstract class JpaAbstractDao, D> protected E doSave(E entity, boolean isNew) { if (isNew) { if (entity instanceof HasVersion versionedEntity) { - versionedEntity.setVersion(1); + versionedEntity.setVersion(1L); } entityManager.persist(entity); } else { From d0546ae83cdaed34adad3ce76460614232bf0a68 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 16 Jul 2024 10:06:34 +0300 Subject: [PATCH 10/26] Put to cache after save for versioned entities --- .../cache/CaffeineTbTransactionalCache.java | 5 ++ .../server/cache/RedisTbCacheTransaction.java | 2 +- .../cache/RedisTbTransactionalCache.java | 13 ++-- .../server/cache/TbTransactionalCache.java | 6 +- .../server/cache/VersionedRedisTbCache.java | 11 ++- .../server/cache/VersionedTbCache.java | 8 ++- .../cache/device/DeviceCacheEvictEvent.java | 2 + .../cache/device/DeviceCaffeineCache.java | 4 +- .../server/cache/device/DeviceRedisCache.java | 4 +- .../dao/asset/AssetProfileCacheKey.java | 6 +- .../dao/asset/AssetProfileCaffeineCache.java | 4 +- .../dao/asset/AssetProfileEvictEvent.java | 6 ++ .../dao/asset/AssetProfileRedisCache.java | 5 +- .../dao/asset/AssetProfileServiceImpl.java | 31 ++++---- .../device/DeviceCredentialsServiceImpl.java | 12 ++-- .../dao/device/DeviceProfileCacheKey.java | 8 +-- .../device/DeviceProfileCaffeineCache.java | 4 +- .../dao/device/DeviceProfileEvictEvent.java | 6 ++ .../dao/device/DeviceProfileRedisCache.java | 5 +- .../dao/device/DeviceProfileServiceImpl.java | 51 +++++++------ .../server/dao/device/DeviceServiceImpl.java | 72 ++++++++----------- .../entity/CachedVersionedEntityService.java | 29 ++++++++ .../dao/entityview/EntityViewCacheValue.java | 8 ++- .../entityview/EntityViewCaffeineCache.java | 4 +- .../dao/entityview/EntityViewEvictEvent.java | 6 +- .../dao/entityview/EntityViewRedisCache.java | 4 +- .../dao/entityview/EntityViewServiceImpl.java | 42 ++++++----- .../DeviceCredentialsDataValidator.java | 3 +- .../server/dao/sql/JpaAbstractDao.java | 7 +- 29 files changed, 224 insertions(+), 144 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/entity/CachedVersionedEntityService.java diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/CaffeineTbTransactionalCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/CaffeineTbTransactionalCache.java index d2ea960e68..4ce6571f1c 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/CaffeineTbTransactionalCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/CaffeineTbTransactionalCache.java @@ -54,6 +54,11 @@ public abstract class CaffeineTbTransactionalCache get(K key, boolean transactionMode) { + return get(key); + } + @Override public void put(K key, V value) { lock.lock(); diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbCacheTransaction.java b/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbCacheTransaction.java index fb852493ce..3dcb6e878f 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbCacheTransaction.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbCacheTransaction.java @@ -31,7 +31,7 @@ public class RedisTbCacheTransaction get(K key) { + return get(key, false); + } + + @Override + public TbCacheValueWrapper get(K key, boolean transactionMode) { try (var connection = connectionFactory.getConnection()) { byte[] rawKey = getRawKey(key); - byte[] rawValue = doGet(connection, rawKey); + byte[] rawValue = doGet(connection, rawKey, transactionMode); if (rawValue == null || rawValue.length == 0) { return null; } else if (Arrays.equals(rawValue, BINARY_NULL_VALUE)) { @@ -96,18 +101,18 @@ public abstract class RedisTbTransactionalCache get(K key); + TbCacheValueWrapper get(K key, boolean transactionMode); + void put(K key, V value); void putIfAbsent(K key, V value); @@ -60,7 +62,7 @@ public interface TbTransactionalCache dbCall, boolean cacheNullValue) { - TbCacheValueWrapper cacheValueWrapper = get(key); + TbCacheValueWrapper cacheValueWrapper = get(key, true); if (cacheValueWrapper != null) { return cacheValueWrapper.get(); } @@ -95,7 +97,7 @@ public interface TbTransactionalCache R getAndPutInTransaction(K key, Supplier dbCall, Function cacheValueToResult, Function dbValueToCacheValue, boolean cacheNullValue) { - TbCacheValueWrapper cacheValueWrapper = get(key); + TbCacheValueWrapper cacheValueWrapper = get(key, true); if (cacheValueWrapper != null) { var cacheValue = cacheValueWrapper.get(); return cacheValue == null ? null : cacheValueToResult.apply(cacheValue); 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 bfb19ad01a..016b22883d 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 @@ -88,7 +88,10 @@ public abstract class VersionedRedisTbCache get(K key); default V get(K key, Supplier supplier) { + return get(key, supplier, true); + } + + default V get(K key, Supplier supplier, boolean putToCache) { return Optional.ofNullable(get(key)) .map(TbCacheValueWrapper::get) .orElseGet(() -> { V value = supplier.get(); - put(key, value); + if (putToCache) { + put(key, value); + } return value; }); } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceCacheEvictEvent.java b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceCacheEvictEvent.java index 63fa62f013..3fb78c53a9 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceCacheEvictEvent.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceCacheEvictEvent.java @@ -16,6 +16,7 @@ package org.thingsboard.server.cache.device; import lombok.Data; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -26,5 +27,6 @@ public class DeviceCacheEvictEvent { private final DeviceId deviceId; private final String newName; private final String oldName; + private Device savedDevice; } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceCaffeineCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceCaffeineCache.java index d6e2e3e6cc..aa44010f53 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceCaffeineCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceCaffeineCache.java @@ -18,13 +18,13 @@ package org.thingsboard.server.cache.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.Device; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) @Service("DeviceCache") -public class DeviceCaffeineCache extends CaffeineTbTransactionalCache { +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/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..6502c33d43 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,12 +236,13 @@ 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/entity/CachedVersionedEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/CachedVersionedEntityService.java new file mode 100644 index 0000000000..356fafbbff --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/CachedVersionedEntityService.java @@ -0,0 +1,29 @@ +/** + * 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.dao.entity; + +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.cache.VersionedTbCache; +import org.thingsboard.server.common.data.HasVersion; + +import java.io.Serializable; + +public abstract class CachedVersionedEntityService 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 2004184cda..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 @@ -19,6 +19,7 @@ import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import jakarta.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -43,16 +44,15 @@ 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 jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -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/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> try { entity = doSave(entity, isNew); } catch (OptimisticLockException e) { - throw new EntityVersionMismatchException("The entity was already changed by someone else", e); + throw new EntityVersionMismatchException((getEntityType() != null ? getEntityType().getNormalName() : "Entity") + " was already changed by someone else", e); } return DaoUtil.getData(entity); } @@ -145,7 +145,9 @@ public abstract class JpaAbstractDao, D> @Override @Transactional public void removeById(TenantId tenantId, UUID id) { - getRepository().deleteById(id); + JpaRepository repository = getRepository(); + repository.deleteById(id); + repository.flush(); log.debug("Remove request: {}", id); } @@ -153,6 +155,7 @@ public abstract class JpaAbstractDao, D> public void removeAllByIds(Collection ids) { JpaRepository repository = getRepository(); ids.forEach(repository::deleteById); + repository.flush(); } @Override From 701260c4e9936d6b19a3db392be4ab0fc0ad0dab Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 17 Jul 2024 16:51:07 +0300 Subject: [PATCH 11/26] Entities versioning refactoring --- .../main/data/upgrade/3.7.0/schema_update.sql | 26 +++++++++---------- .../cache/VersionedCaffeineTbCache.java | 10 +++---- .../server/cache/VersionedRedisTbCache.java | 10 ------- .../server/cache/VersionedTbCache.java | 10 +++++++ .../main/resources/sql/schema-entities.sql | 26 +++++++++---------- 5 files changed, 41 insertions(+), 41 deletions(-) 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 afab04e533..90d6eafdf6 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 @@ -27,18 +27,18 @@ ALTER TABLE ts_kv_latest ADD COLUMN version bigint default 0; -- ENTITIES VERSIONING UPDATE START -ALTER TABLE device ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; -ALTER TABLE device_profile ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; -ALTER TABLE device_credentials ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; -ALTER TABLE asset ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; -ALTER TABLE asset_profile ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; -ALTER TABLE entity_view ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; -ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; -ALTER TABLE customer ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; -ALTER TABLE edge ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; -ALTER TABLE rule_chain ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; -ALTER TABLE dashboard ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; -ALTER TABLE widget_type ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; -ALTER TABLE widgets_bundle ADD COLUMN IF NOT EXISTS version INT DEFAULT 1; +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; -- ENTITIES VERSIONING UPDATE END 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..dde84c259a 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 @@ -168,14 +168,4 @@ public abstract class VersionedRedisTbCache Date: Mon, 22 Jul 2024 14:27:26 +0300 Subject: [PATCH 12/26] Add version for tenant entity --- .../src/main/data/upgrade/3.7.0/schema_update.sql | 1 + .../server/controller/TenantControllerTest.java | 8 ++++---- .../thingsboard/server/common/data/Tenant.java | 12 +++++++++--- .../dao/model/sql/AbstractTenantEntity.java | 15 ++++++--------- dao/src/main/resources/sql/schema-entities.sql | 1 + 5 files changed, 21 insertions(+), 16 deletions(-) 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 90d6eafdf6..58256949a5 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 @@ -40,5 +40,6 @@ 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/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/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/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/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 4e5f89d427..1d2ef9f915 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -480,6 +480,7 @@ CREATE TABLE IF NOT EXISTS tenant ( state varchar(255), title varchar(255), zip varchar(255), + version BIGINT DEFAULT 1, CONSTRAINT fk_tenant_profile FOREIGN KEY (tenant_profile_id) REFERENCES tenant_profile(id) ); From dee62dfad22bbddd7b0b7cdc99e2357af8bcc736 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 22 Jul 2024 14:28:55 +0300 Subject: [PATCH 13/26] Fix edge user update handling --- .../edge/EdgeEventSourcingListener.java | 4 +- .../thingsboard/server/edge/UserEdgeTest.java | 8 ++-- .../server/dao/user/UserServiceImpl.java | 40 +++++-------------- 3 files changed, 17 insertions(+), 35 deletions(-) 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 11974d2ebc..301650a7dc 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 @@ -181,7 +181,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); @@ -225,6 +226,7 @@ public class EdgeEventSourcingListener { user.setAdditionalInfo(additionalInfo); } } + user.setVersion(null); } private EdgeEventType getEdgeEventTypeForEntityEvent(Object entity) { 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 6314cf53b9..76be149da0 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/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 Date: Mon, 22 Jul 2024 14:39:53 +0300 Subject: [PATCH 14/26] Fix tenant proto --- .../org/thingsboard/server/common/util/ProtoUtils.java | 8 ++++++-- common/proto/src/main/proto/queue.proto | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) 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 0353c9964e..5a61c9a4e0 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; @@ -752,6 +750,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(); } @@ -791,6 +792,9 @@ public class ProtoUtils { if (proto.hasAdditionalInfo()) { tenant.setAdditionalInfo(JacksonUtil.toJsonNode(proto.getAdditionalInfo())); } + if (proto.hasVersion()) { + tenant.setVersion(proto.getVersion()); + } return tenant; } diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index b8f393b9cd..2dfbc39c54 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -262,6 +262,7 @@ message TenantProto { optional string phone = 14; optional string email = 15; optional string additionalInfo = 16; + optional int64 version = 17; } message TenantProfileProto { From 3c526529b96b6e9f359fd0c1bf6ac45428e90692 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 5 Aug 2024 16:59:56 +0300 Subject: [PATCH 15/26] Fix 3.7.1 upgrade script to run multiple times --- application/src/main/data/upgrade/3.7.0/schema_update.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 88e2945a7e..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,15 +19,15 @@ 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 From 31259a4d56ab2d01b5a771f2749ae9a95a975fbd Mon Sep 17 00:00:00 2001 From: mpetrov Date: Tue, 6 Aug 2024 12:48:24 +0300 Subject: [PATCH 16/26] Version Conflict UI implementation --- ui-ngx/src/app/app.module.ts | 6 +- .../entity-conflict-dialog.component.html | 53 ++++++++++++++++ .../entity-conflict-dialog.component.scss | 26 ++++++++ .../entity-conflict-dialog.component.ts | 63 +++++++++++++++++++ .../import-export/import-export.models.ts | 10 +++ .../import-export/import-export.service.ts | 27 +++++++- .../entity-conflict.interceptor.ts | 63 +++++++++++++++++++ .../src/app/shared/interceptors/public-api.ts | 17 +++++ ui-ngx/src/app/shared/public-api.ts | 1 + .../assets/locale/locale.constant-en_US.json | 10 ++- 10 files changed, 273 insertions(+), 3 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html create mode 100644 ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.scss create mode 100644 ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts create mode 100644 ui-ngx/src/app/shared/interceptors/entity-conflict.interceptor.ts create mode 100644 ui-ngx/src/app/shared/interceptors/public-api.ts diff --git a/ui-ngx/src/app/app.module.ts b/ui-ngx/src/app/app.module.ts index e18c909c9a..a51fe8205a 100644 --- a/ui-ngx/src/app/app.module.ts +++ b/ui-ngx/src/app/app.module.ts @@ -26,6 +26,8 @@ import { HomeModule } from '@home/home.module'; import { AppComponent } from './app.component'; import { DashboardRoutingModule } from '@modules/dashboard/dashboard-routing.module'; import { RouterModule, Routes } from '@angular/router'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { EntityConflictInterceptor } from '@shared/interceptors/entity-conflict.interceptor'; const routes: Routes = [ { path: '**', @@ -55,7 +57,9 @@ export class PageNotFoundRoutingModule { } DashboardRoutingModule, PageNotFoundRoutingModule ], - providers: [], + providers: [ + { provide: HTTP_INTERCEPTORS, useClass: EntityConflictInterceptor, multi: true } + ], bootstrap: [AppComponent] }) export class AppModule { } 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..c500923723 --- /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.entityId.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..32c0a45bdc --- /dev/null +++ b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts @@ -0,0 +1,63 @@ +/// +/// 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 { ExportableEntityTypes } from '@shared/import-export/import-export.models'; +import { EntityId } from '@shared/models/id/entity-id'; +import { CommonModule } from '@angular/common'; +import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; + +interface EntityConflictDialogData { + message: string; + entityId: EntityId & {entityType: EntityType}; +} + +@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 ExportableEntityTypes = ExportableEntityTypes; + 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.entityId); + } +} diff --git a/ui-ngx/src/app/shared/import-export/import-export.models.ts b/ui-ngx/src/app/shared/import-export/import-export.models.ts index 4933377d80..09c1bb3226 100644 --- a/ui-ngx/src/app/shared/import-export/import-export.models.ts +++ b/ui-ngx/src/app/shared/import-export/import-export.models.ts @@ -17,6 +17,16 @@ import { Widget, WidgetTypeDetails } from '@app/shared/models/widget.models'; import { DashboardLayoutId } from '@shared/models/dashboard.models'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; +import { EntityType } from '@shared/models/entity-type.models'; + +export const ExportableEntityTypes = [ + EntityType.DEVICE_PROFILE, + EntityType.ASSET_PROFILE, + EntityType.RULE_CHAIN, + EntityType.DASHBOARD, + EntityType.WIDGET_TYPE, + EntityType.WIDGETS_BUNDLE +]; export interface ImportWidgetResult { widget: Widget; 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..c4fa2f4049 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 @@ -34,7 +34,7 @@ import { } from '@shared/models/alias.models'; import { MatDialog } from '@angular/material/dialog'; import { ImportDialogComponent, ImportDialogData } from '@shared/import-export/import-dialog.component'; -import { forkJoin, Observable, of, Subject } from 'rxjs'; +import { forkJoin, Observable, of, Subject, throwError } from 'rxjs'; import { catchError, map, mergeMap, switchMap, take, tap } from 'rxjs/operators'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { EntityService } from '@core/http/entity.service'; @@ -380,6 +380,31 @@ export class ImportExportService { }); } + public exportEntity(entityId: EntityId): void { + switch (entityId.entityType) { + case EntityType.DEVICE_PROFILE: + this.exportDeviceProfile(entityId.id); + break; + case EntityType.ASSET_PROFILE: + this.exportAssetProfile(entityId.id); + break; + case EntityType.RULE_CHAIN: + this.exportRuleChain(entityId.id); + break; + case EntityType.DASHBOARD: + this.exportDashboard(entityId.id); + break; + case EntityType.WIDGET_TYPE: + this.exportWidgetType(entityId.id); + break; + case EntityType.WIDGETS_BUNDLE: + this.exportWidgetsBundle(entityId.id); + break; + default: + throwError(() => 'Not supported Entity Type'); + } + } + private exportWidgetsBundleWithWidgetTypes(widgetsBundle: WidgetsBundle) { this.widgetService.exportBundleWidgetTypesDetails(widgetsBundle.id.id).subscribe({ next: (widgetTypesDetails) => { diff --git a/ui-ngx/src/app/shared/interceptors/entity-conflict.interceptor.ts b/ui-ngx/src/app/shared/interceptors/entity-conflict.interceptor.ts new file mode 100644 index 0000000000..a999aabb2a --- /dev/null +++ b/ui-ngx/src/app/shared/interceptors/entity-conflict.interceptor.ts @@ -0,0 +1,63 @@ +/// +/// 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 { + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, + HttpErrorResponse, + HttpStatusCode +} from '@angular/common/http'; +import { Observable, throwError, of } 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 { EntityId } from '@shared/models/id/entity-id'; + +interface ConflictedEntity { version: number; id: EntityId } + +@Injectable() +export class EntityConflictInterceptor implements HttpInterceptor { + constructor(private dialog: MatDialog) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle(request).pipe( + catchError((error: HttpErrorResponse) => { + if (error.status === HttpStatusCode.Conflict) { + return this.resolveConflictRequest(request, error.error.message) + .pipe(switchMap(httpRequest => next.handle(httpRequest))); + } else { + return throwError(() => error); + } + }) + ); + } + + private resolveConflictRequest(request: HttpRequest, message: string): Observable> { + const dialogRef = this.dialog.open(EntityConflictDialogComponent, {data: {message, entityId: request.body.id}}); + + return dialogRef.afterClosed().pipe( + switchMap(result => { + if (result) { + request.body.version = null; + } + return of(request); + }) + ); + } +} diff --git a/ui-ngx/src/app/shared/interceptors/public-api.ts b/ui-ngx/src/app/shared/interceptors/public-api.ts new file mode 100644 index 0000000000..17cbcd22cd --- /dev/null +++ b/ui-ngx/src/app/shared/interceptors/public-api.ts @@ -0,0 +1,17 @@ +/// +/// 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. +/// + +export * from './entity-conflict.interceptor'; diff --git a/ui-ngx/src/app/shared/public-api.ts b/ui-ngx/src/app/shared/public-api.ts index bd3d565da3..8207b2e955 100644 --- a/ui-ngx/src/app/shared/public-api.ts +++ b/ui-ngx/src/app/shared/public-api.ts @@ -19,3 +19,4 @@ export * from './decorators/public-api'; export * from './models/public-api'; export * from './pipe/public-api'; export * from './shared.module'; +export * from './interceptors/public-api'; 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 4eb0679b92..4ae1c3a45a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2282,6 +2282,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} }", @@ -2300,7 +2307,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", From 5813722a0efa267bb9ae67c10a7d865deb0c3684 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Tue, 6 Aug 2024 16:22:05 +0300 Subject: [PATCH 17/26] interceptor to core --- ui-ngx/src/app/app.module.ts | 5 ----- ui-ngx/src/app/core/core.module.ts | 6 ++++++ .../interceptors/entity-conflict.interceptor.ts | 0 .../src/app/shared/interceptors/public-api.ts | 17 ----------------- ui-ngx/src/app/shared/public-api.ts | 1 - 5 files changed, 6 insertions(+), 23 deletions(-) rename ui-ngx/src/app/{shared => core}/interceptors/entity-conflict.interceptor.ts (100%) delete mode 100644 ui-ngx/src/app/shared/interceptors/public-api.ts diff --git a/ui-ngx/src/app/app.module.ts b/ui-ngx/src/app/app.module.ts index a51fe8205a..0868a2f381 100644 --- a/ui-ngx/src/app/app.module.ts +++ b/ui-ngx/src/app/app.module.ts @@ -26,8 +26,6 @@ import { HomeModule } from '@home/home.module'; import { AppComponent } from './app.component'; import { DashboardRoutingModule } from '@modules/dashboard/dashboard-routing.module'; import { RouterModule, Routes } from '@angular/router'; -import { HTTP_INTERCEPTORS } from '@angular/common/http'; -import { EntityConflictInterceptor } from '@shared/interceptors/entity-conflict.interceptor'; const routes: Routes = [ { path: '**', @@ -57,9 +55,6 @@ export class PageNotFoundRoutingModule { } DashboardRoutingModule, PageNotFoundRoutingModule ], - providers: [ - { provide: HTTP_INTERCEPTORS, useClass: EntityConflictInterceptor, multi: true } - ], bootstrap: [AppComponent] }) export class AppModule { } 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/shared/interceptors/entity-conflict.interceptor.ts b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts similarity index 100% rename from ui-ngx/src/app/shared/interceptors/entity-conflict.interceptor.ts rename to ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts diff --git a/ui-ngx/src/app/shared/interceptors/public-api.ts b/ui-ngx/src/app/shared/interceptors/public-api.ts deleted file mode 100644 index 17cbcd22cd..0000000000 --- a/ui-ngx/src/app/shared/interceptors/public-api.ts +++ /dev/null @@ -1,17 +0,0 @@ -/// -/// 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. -/// - -export * from './entity-conflict.interceptor'; diff --git a/ui-ngx/src/app/shared/public-api.ts b/ui-ngx/src/app/shared/public-api.ts index 8207b2e955..bd3d565da3 100644 --- a/ui-ngx/src/app/shared/public-api.ts +++ b/ui-ngx/src/app/shared/public-api.ts @@ -19,4 +19,3 @@ export * from './decorators/public-api'; export * from './models/public-api'; export * from './pipe/public-api'; export * from './shared.module'; -export * from './interceptors/public-api'; From a291e1d2e3ea1225039958f4fd5d6c226c4dcd77 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Tue, 6 Aug 2024 16:23:21 +0300 Subject: [PATCH 18/26] refactoring --- ui-ngx/src/app/app.module.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/app.module.ts b/ui-ngx/src/app/app.module.ts index 0868a2f381..e18c909c9a 100644 --- a/ui-ngx/src/app/app.module.ts +++ b/ui-ngx/src/app/app.module.ts @@ -55,6 +55,7 @@ export class PageNotFoundRoutingModule { } DashboardRoutingModule, PageNotFoundRoutingModule ], + providers: [], bootstrap: [AppComponent] }) export class AppModule { } From 55e33d7f3df8f058ee0d3deaa3417acbd1e8b4ea Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 7 Aug 2024 16:01:08 +0300 Subject: [PATCH 19/26] Major adjustments --- .../entity-conflict.interceptor.ts | 62 ++++++++++++++----- .../interceptors/global-http-interceptor.ts | 47 ++++---------- .../core/interceptors/interceptor-config.ts | 1 + .../services/interceptor-config.service.ts | 53 ++++++++++++++++ .../entity-conflict-dialog.component.html | 4 +- .../entity-conflict-dialog.component.ts | 10 ++- .../import-export/import-export.models.ts | 10 --- .../import-export/import-export.service.ts | 41 +++++++----- ui-ngx/src/app/shared/models/asset.models.ts | 6 +- .../src/app/shared/models/customer.model.ts | 4 +- .../src/app/shared/models/dashboard.models.ts | 4 +- ui-ngx/src/app/shared/models/device.models.ts | 8 +-- ui-ngx/src/app/shared/models/edge.models.ts | 4 +- .../app/shared/models/entity-view.models.ts | 4 +- ui-ngx/src/app/shared/models/entity.models.ts | 4 ++ .../app/shared/models/rule-chain.models.ts | 4 +- .../app/shared/models/widgets-bundle.model.ts | 4 +- 17 files changed, 163 insertions(+), 107 deletions(-) create mode 100644 ui-ngx/src/app/core/services/interceptor-config.service.ts diff --git a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts index a999aabb2a..e97aa67fd7 100644 --- a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts @@ -16,48 +16,76 @@ import { Injectable } from '@angular/core'; import { + HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, - HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; -import { Observable, throwError, of } from 'rxjs'; +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 { EntityId } from '@shared/models/id/entity-id'; - -interface ConflictedEntity { version: number; id: EntityId } +import { + EntityConflictDialogComponent +} from '@shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component'; +import { InterceptorConfigService } from '@core/services/interceptor-config.service'; +import { HasId } from '@shared/models/base-data'; +import { HasVersion } from '@shared/models/entity.models'; @Injectable() export class EntityConflictInterceptor implements HttpInterceptor { - constructor(private dialog: MatDialog) {} - intercept(request: HttpRequest, next: HttpHandler): Observable> { + constructor( + private dialog: MatDialog, + private interceptorConfigService: InterceptorConfigService + ) {} + + 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 this.resolveConflictRequest(request, error.error.message) - .pipe(switchMap(httpRequest => next.handle(httpRequest))); - } else { + if (error.status !== HttpStatusCode.Conflict) { return throwError(() => error); } + + return this.handleConflictError(request, next, error); }) ); } - private resolveConflictRequest(request: HttpRequest, message: string): Observable> { - const dialogRef = this.dialog.open(EntityConflictDialogComponent, {data: {message, entityId: request.body.id}}); + private handleConflictError( + request: HttpRequest, + next: HttpHandler, + error: HttpErrorResponse + ): Observable> { + if (this.interceptorConfigService.getInterceptorConfig(request).ignoreVersionConflict) { + return next.handle(this.updateRequestVersion(request)); + } - return dialogRef.afterClosed().pipe( + return this.openConflictDialog(request, error.error.message).pipe( switchMap(result => { if (result) { - request.body.version = null; + return next.handle(this.updateRequestVersion(request)); } - return of(request); + return of(null); }) ); } + + private updateRequestVersion(request: HttpRequest): HttpRequest { + const body = { ...request.body, version: null }; + return request.clone({ body }); + } + + private openConflictDialog(request: HttpRequest, message: string): Observable { + const dialogRef = this.dialog.open(EntityConflictDialogComponent, { + data: { message, entity: request.body } + }); + + 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..6023267ff9 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 { InterceptorConfigService } from '@core/services/interceptor-config.service'; const tmpHeaders = {}; @@ -39,22 +39,19 @@ 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, + private interceptorConfigService: InterceptorConfigService + ) {} intercept(req: HttpRequest, next: HttpHandler): Observable> { if (req.url.startsWith('/api/')) { - const config = this.getInterceptorConfig(req); + const config = this.interceptorConfigService.getInterceptorConfig(req); this.updateLoadingState(config, true); let observable$: Observable>; if (this.isTokenBasedAuthEntryPoint(req.url)) { @@ -98,7 +95,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } private handleResponseError(req: HttpRequest, next: HttpHandler, errorResponse: HttpErrorResponse): Observable> { - const config = this.getInterceptorConfig(req); + const config = this.interceptorConfigService.getInterceptorConfig(req); let unhandled = false; const ignoreErrors = config.ignoreErrors; const resendRequest = config.resendRequest; @@ -171,15 +168,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 +190,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/services/interceptor-config.service.ts b/ui-ngx/src/app/core/services/interceptor-config.service.ts new file mode 100644 index 0000000000..a5e4d39cec --- /dev/null +++ b/ui-ngx/src/app/core/services/interceptor-config.service.ts @@ -0,0 +1,53 @@ +/// +/// 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 { HttpRequest } from '@angular/common/http'; +import { InterceptorConfig } from '@core/interceptors/interceptor-config'; +import { InterceptorHttpParams } from '@core/interceptors/interceptor-http-params'; + +@Injectable({ + providedIn: 'root' +}) +export class InterceptorConfigService { + + private readonly internalUrlPrefixes = [ + '/api/auth/token', + '/api/rpc' + ]; + + 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 (this.isInternalUrlPrefix(req.url)) { + config.ignoreLoading = true; + } + return config; + } + + private isInternalUrlPrefix(url: string): boolean { + for (const prefix of this.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 index c500923723..502bc3381c 100644 --- 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 @@ -27,9 +27,9 @@
{{ data.message }}. - + {{ 'entity.version-conflict.link' | translate: - { entityType: (entityTypeTranslations.get(data.entityId.entityType).type | translate) } + { entityType: (entityTypeTranslations.get(data.entity.id.entityType).type | translate) } }} {{ 'entity.link' | translate }}. 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 index 32c0a45bdc..ec8c38e5d4 100644 --- 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 @@ -18,14 +18,13 @@ 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 { ExportableEntityTypes } from '@shared/import-export/import-export.models'; -import { EntityId } from '@shared/models/id/entity-id'; import { CommonModule } from '@angular/common'; -import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { entityTypeTranslations } from '@shared/models/entity-type.models'; +import { EntityInfoData } from '@shared/models/entity.models'; interface EntityConflictDialogData { message: string; - entityId: EntityId & {entityType: EntityType}; + entity: EntityInfoData; } @Component({ @@ -39,7 +38,6 @@ interface EntityConflictDialogData { ], }) export class EntityConflictDialogComponent { - readonly ExportableEntityTypes = ExportableEntityTypes; readonly entityTypeTranslations = entityTypeTranslations; constructor( @@ -58,6 +56,6 @@ export class EntityConflictDialogComponent { onLinkClick(event: MouseEvent): void { event.preventDefault(); - this.importExportService.exportEntity(this.data.entityId); + this.importExportService.exportEntity(this.data.entity); } } diff --git a/ui-ngx/src/app/shared/import-export/import-export.models.ts b/ui-ngx/src/app/shared/import-export/import-export.models.ts index 09c1bb3226..4933377d80 100644 --- a/ui-ngx/src/app/shared/import-export/import-export.models.ts +++ b/ui-ngx/src/app/shared/import-export/import-export.models.ts @@ -17,16 +17,6 @@ import { Widget, WidgetTypeDetails } from '@app/shared/models/widget.models'; import { DashboardLayoutId } from '@shared/models/dashboard.models'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; -import { EntityType } from '@shared/models/entity-type.models'; - -export const ExportableEntityTypes = [ - EntityType.DEVICE_PROFILE, - EntityType.ASSET_PROFILE, - EntityType.RULE_CHAIN, - EntityType.DASHBOARD, - EntityType.WIDGET_TYPE, - EntityType.WIDGETS_BUNDLE -]; export interface ImportWidgetResult { widget: Widget; 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 c4fa2f4049..e3be8206a2 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'; @@ -79,7 +79,7 @@ import { ImageService } from '@core/http/image.service'; import { ImageExportData, ImageResourceInfo, ImageResourceType } from '@shared/models/resource.models'; import { selectUserSettingsProperty } from '@core/auth/auth.selectors'; import { ActionPreferencesPutUserSettings } from '@core/auth/auth.actions'; -import { ExportableEntity } from '@shared/models/base-data'; +import { ExportableEntity, HasId } from '@shared/models/base-data'; import { EntityId } from '@shared/models/id/entity-id'; export type editMissingAliasesFunction = (widgets: Array, isSingleWidget: boolean, @@ -380,29 +380,33 @@ export class ImportExportService { }); } - public exportEntity(entityId: EntityId): void { - switch (entityId.entityType) { + public exportEntity(entityData: EntityInfoData): void { + let preparedData; + switch (entityData.id.entityType) { case EntityType.DEVICE_PROFILE: - this.exportDeviceProfile(entityId.id); - break; case EntityType.ASSET_PROFILE: - this.exportAssetProfile(entityId.id); + preparedData = this.prepareProfileExport(entityData as DeviceProfile | AssetProfile); break; case EntityType.RULE_CHAIN: - this.exportRuleChain(entityId.id); - break; + 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: - this.exportDashboard(entityId.id); - break; - case EntityType.WIDGET_TYPE: - this.exportWidgetType(entityId.id); - break; - case EntityType.WIDGETS_BUNDLE: - this.exportWidgetsBundle(entityId.id); + preparedData = this.prepareDashboardExport(entityData as Dashboard); break; default: - throwError(() => 'Not supported Entity Type'); + preparedData = this.prepareExport(entityData); } + this.exportToPc(preparedData, entityData.name); } private exportWidgetsBundleWithWidgetTypes(widgetsBundle: WidgetsBundle) { @@ -1133,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 b38833bbaa..ba93568e17 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; From 9395390d7a83bb3dc98e5e7e71c5bfaade105cb2 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 7 Aug 2024 16:30:59 +0300 Subject: [PATCH 20/26] changed interceptor service to interceptor utils --- .../interceptors/entity-conflict.interceptor.ts | 11 +++++------ .../core/interceptors/global-http-interceptor.ts | 7 +++---- .../interceptor.util.ts} | 13 ++++--------- 3 files changed, 12 insertions(+), 19 deletions(-) rename ui-ngx/src/app/core/{services/interceptor-config.service.ts => interceptors/interceptor.util.ts} (82%) diff --git a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts index e97aa67fd7..18890874c0 100644 --- a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts @@ -29,16 +29,15 @@ import { MatDialog } from '@angular/material/dialog'; import { EntityConflictDialogComponent } from '@shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component'; -import { InterceptorConfigService } from '@core/services/interceptor-config.service'; import { HasId } from '@shared/models/base-data'; import { HasVersion } from '@shared/models/entity.models'; +import { InterceptorUtil } from '@core/utils/interceptor.util'; @Injectable() export class EntityConflictInterceptor implements HttpInterceptor { constructor( private dialog: MatDialog, - private interceptorConfigService: InterceptorConfigService ) {} intercept(request: HttpRequest, next: HttpHandler): Observable> { @@ -62,11 +61,11 @@ export class EntityConflictInterceptor implements HttpInterceptor { next: HttpHandler, error: HttpErrorResponse ): Observable> { - if (this.interceptorConfigService.getInterceptorConfig(request).ignoreVersionConflict) { + if (InterceptorUtil.getConfig(request).ignoreVersionConflict) { return next.handle(this.updateRequestVersion(request)); } - return this.openConflictDialog(request, error.error.message).pipe( + return this.openConflictDialog(request.body, error.error.message).pipe( switchMap(result => { if (result) { return next.handle(this.updateRequestVersion(request)); @@ -81,9 +80,9 @@ export class EntityConflictInterceptor implements HttpInterceptor { return request.clone({ body }); } - private openConflictDialog(request: HttpRequest, message: string): Observable { + private openConflictDialog(entity: unknown & HasId & HasVersion, message: string): Observable { const dialogRef = this.dialog.open(EntityConflictDialogComponent, { - data: { message, entity: request.body } + 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 6023267ff9..d74d159851 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -29,7 +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 { InterceptorConfigService } from '@core/services/interceptor-config.service'; +import { InterceptorUtil } from '@core/interceptors/interceptor.util'; const tmpHeaders = {}; @@ -46,12 +46,11 @@ export class GlobalHttpInterceptor implements HttpInterceptor { private dialogService: DialogService, private translate: TranslateService, private authService: AuthService, - private interceptorConfigService: InterceptorConfigService ) {} intercept(req: HttpRequest, next: HttpHandler): Observable> { if (req.url.startsWith('/api/')) { - const config = this.interceptorConfigService.getInterceptorConfig(req); + const config = InterceptorUtil.getConfig(req); this.updateLoadingState(config, true); let observable$: Observable>; if (this.isTokenBasedAuthEntryPoint(req.url)) { @@ -95,7 +94,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } private handleResponseError(req: HttpRequest, next: HttpHandler, errorResponse: HttpErrorResponse): Observable> { - const config = this.interceptorConfigService.getInterceptorConfig(req); + const config = InterceptorUtil.getConfig(req); let unhandled = false; const ignoreErrors = config.ignoreErrors; const resendRequest = config.resendRequest; diff --git a/ui-ngx/src/app/core/services/interceptor-config.service.ts b/ui-ngx/src/app/core/interceptors/interceptor.util.ts similarity index 82% rename from ui-ngx/src/app/core/services/interceptor-config.service.ts rename to ui-ngx/src/app/core/interceptors/interceptor.util.ts index a5e4d39cec..5f4a651219 100644 --- a/ui-ngx/src/app/core/services/interceptor-config.service.ts +++ b/ui-ngx/src/app/core/interceptors/interceptor.util.ts @@ -14,22 +14,17 @@ /// limitations under the License. /// -import { Injectable } from '@angular/core'; import { HttpRequest } from '@angular/common/http'; import { InterceptorConfig } from '@core/interceptors/interceptor-config'; import { InterceptorHttpParams } from '@core/interceptors/interceptor-http-params'; -@Injectable({ - providedIn: 'root' -}) -export class InterceptorConfigService { - - private readonly internalUrlPrefixes = [ +export class InterceptorUtil { + private static readonly internalUrlPrefixes = [ '/api/auth/token', '/api/rpc' ]; - getInterceptorConfig(req: HttpRequest): InterceptorConfig { + static getConfig(req: HttpRequest): InterceptorConfig { let config: InterceptorConfig; if (req.params && req.params instanceof InterceptorHttpParams) { config = (req.params as InterceptorHttpParams).interceptorConfig; @@ -42,7 +37,7 @@ export class InterceptorConfigService { return config; } - private isInternalUrlPrefix(url: string): boolean { + private static isInternalUrlPrefix(url: string): boolean { for (const prefix of this.internalUrlPrefixes) { if (url.startsWith(prefix)) { return true; From ec78dc5b596c5bfd4f70da1321a14ae268d64fd3 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 7 Aug 2024 16:32:43 +0300 Subject: [PATCH 21/26] refactoring --- ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts | 2 +- ui-ngx/src/app/core/interceptors/global-http-interceptor.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts index 18890874c0..c19f51f4ba 100644 --- a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts @@ -31,7 +31,7 @@ import { } 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 { InterceptorUtil } from '@core/utils/interceptor.util'; +import { InterceptorUtil } from './interceptor.util'; @Injectable() export class EntityConflictInterceptor implements HttpInterceptor { 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 d74d159851..ab3a41f86e 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -29,7 +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 { InterceptorUtil } from '@core/interceptors/interceptor.util'; +import { InterceptorUtil } from './interceptor.util'; const tmpHeaders = {}; From d9fbb2bf1cd677cb07818affc890c3ad00aeb62f Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 7 Aug 2024 16:35:47 +0300 Subject: [PATCH 22/26] refactoring --- ui-ngx/src/app/shared/import-export/import-export.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 e3be8206a2..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 @@ -34,7 +34,7 @@ import { } from '@shared/models/alias.models'; import { MatDialog } from '@angular/material/dialog'; import { ImportDialogComponent, ImportDialogData } from '@shared/import-export/import-dialog.component'; -import { forkJoin, Observable, of, Subject, throwError } from 'rxjs'; +import { forkJoin, Observable, of, Subject } from 'rxjs'; import { catchError, map, mergeMap, switchMap, take, tap } from 'rxjs/operators'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { EntityService } from '@core/http/entity.service'; @@ -79,7 +79,7 @@ import { ImageService } from '@core/http/image.service'; import { ImageExportData, ImageResourceInfo, ImageResourceType } from '@shared/models/resource.models'; import { selectUserSettingsProperty } from '@core/auth/auth.selectors'; import { ActionPreferencesPutUserSettings } from '@core/auth/auth.actions'; -import { ExportableEntity, HasId } from '@shared/models/base-data'; +import { ExportableEntity } from '@shared/models/base-data'; import { EntityId } from '@shared/models/id/entity-id'; export type editMissingAliasesFunction = (widgets: Array, isSingleWidget: boolean, From bfd9a94650bddc901824c7a44ca5d82f8a9f9f38 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 7 Aug 2024 16:38:34 +0300 Subject: [PATCH 23/26] refactoring --- ui-ngx/src/app/core/interceptors/interceptor.util.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/core/interceptors/interceptor.util.ts b/ui-ngx/src/app/core/interceptors/interceptor.util.ts index 5f4a651219..13352acf76 100644 --- a/ui-ngx/src/app/core/interceptors/interceptor.util.ts +++ b/ui-ngx/src/app/core/interceptors/interceptor.util.ts @@ -19,6 +19,7 @@ import { InterceptorConfig } from '@core/interceptors/interceptor-config'; import { InterceptorHttpParams } from '@core/interceptors/interceptor-http-params'; export class InterceptorUtil { + private static readonly internalUrlPrefixes = [ '/api/auth/token', '/api/rpc' From f1737cc206ec14b92ee4fd9a3f6b04f4aa6bf265 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 7 Aug 2024 17:22:14 +0300 Subject: [PATCH 24/26] changed interceptor util to non class --- .../entity-conflict.interceptor.ts | 6 +-- .../interceptors/global-http-interceptor.ts | 6 +-- .../app/core/interceptors/interceptor.util.ts | 45 +++++++++---------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts index c19f51f4ba..57ee28c779 100644 --- a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts @@ -31,7 +31,7 @@ import { } 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 { InterceptorUtil } from './interceptor.util'; +import { getInterceptorConfig } from './interceptor.util'; @Injectable() export class EntityConflictInterceptor implements HttpInterceptor { @@ -61,8 +61,8 @@ export class EntityConflictInterceptor implements HttpInterceptor { next: HttpHandler, error: HttpErrorResponse ): Observable> { - if (InterceptorUtil.getConfig(request).ignoreVersionConflict) { - return next.handle(this.updateRequestVersion(request)); + if (getInterceptorConfig(request).ignoreVersionConflict) { + return throwError(() => error); } return this.openConflictDialog(request.body, error.error.message).pipe( 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 ab3a41f86e..1cdd0c506e 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -29,7 +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 { InterceptorUtil } from './interceptor.util'; +import { getInterceptorConfig } from './interceptor.util'; const tmpHeaders = {}; @@ -50,7 +50,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { intercept(req: HttpRequest, next: HttpHandler): Observable> { if (req.url.startsWith('/api/')) { - const config = InterceptorUtil.getConfig(req); + const config = getInterceptorConfig(req); this.updateLoadingState(config, true); let observable$: Observable>; if (this.isTokenBasedAuthEntryPoint(req.url)) { @@ -94,7 +94,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } private handleResponseError(req: HttpRequest, next: HttpHandler, errorResponse: HttpErrorResponse): Observable> { - const config = InterceptorUtil.getConfig(req); + const config = getInterceptorConfig(req); let unhandled = false; const ignoreErrors = config.ignoreErrors; const resendRequest = config.resendRequest; diff --git a/ui-ngx/src/app/core/interceptors/interceptor.util.ts b/ui-ngx/src/app/core/interceptors/interceptor.util.ts index 13352acf76..ed9fc41868 100644 --- a/ui-ngx/src/app/core/interceptors/interceptor.util.ts +++ b/ui-ngx/src/app/core/interceptors/interceptor.util.ts @@ -18,32 +18,29 @@ import { HttpRequest } from '@angular/common/http'; import { InterceptorConfig } from '@core/interceptors/interceptor-config'; import { InterceptorHttpParams } from '@core/interceptors/interceptor-http-params'; -export class InterceptorUtil { +const internalUrlPrefixes = [ + '/api/auth/token', + '/api/rpc' +]; - private static readonly internalUrlPrefixes = [ - '/api/auth/token', - '/api/rpc' - ]; - - static getConfig(req: HttpRequest): InterceptorConfig { - let config: InterceptorConfig; - if (req.params && req.params instanceof InterceptorHttpParams) { - config = (req.params as InterceptorHttpParams).interceptorConfig; - } else { - config = new InterceptorConfig(); - } - if (this.isInternalUrlPrefix(req.url)) { - config.ignoreLoading = true; - } - return config; +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; +}; - private static isInternalUrlPrefix(url: string): boolean { - for (const prefix of this.internalUrlPrefixes) { - if (url.startsWith(prefix)) { - return true; - } +const isInternalUrlPrefix = (url: string): boolean => { + for (const prefix of internalUrlPrefixes) { + if (url.startsWith(prefix)) { + return true; } - return false; } -} + return false; +}; From dc1e2f94c64c31d936c9758ae6c43c5a312c44d8 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 9 Aug 2024 12:24:20 +0300 Subject: [PATCH 25/26] Improve Lua script for versioned cache --- .../server/controller/BaseController.java | 2 +- .../exception/ThingsboardErrorResponseHandler.java | 2 +- .../server/cache/VersionedRedisTbCache.java | 13 ++----------- .../common/data/exception/ThingsboardErrorCode.java | 2 +- 4 files changed, 5 insertions(+), 14 deletions(-) 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 a701e6cb1f..29b9db10ab 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -392,7 +392,7 @@ public abstract class BaseController { return new ThingsboardException("Database error", ThingsboardErrorCode.GENERAL); } } else if (exception instanceof EntityVersionMismatchException) { - return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.CONFLICT); + 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 f086e2d013..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,7 +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.CONFLICT, HttpStatus.CONFLICT); + errorCodeToStatusMap.put(ThingsboardErrorCode.VERSION_CONFLICT, HttpStatus.CONFLICT); } private static ThingsboardErrorCode statusToErrorCode(HttpStatus status) { 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 dde84c259a..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); 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 e781c2e346..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,7 +29,7 @@ public enum ThingsboardErrorCode { ITEM_NOT_FOUND(32), TOO_MANY_REQUESTS(33), TOO_MANY_UPDATES(34), - CONFLICT(35), + VERSION_CONFLICT(35), SUBSCRIPTION_VIOLATION(40), PASSWORD_VIOLATION(45); From ea3b5c80f3e38bff9d0602972b77696682fa336b Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 9 Aug 2024 13:57:57 +0300 Subject: [PATCH 26/26] Fix entity update events order --- .../lwm2m/ota/sql/OtaLwM2MIntegrationTest.java | 10 ++++++---- .../server/dao/customer/CustomerServiceImpl.java | 2 +- .../server/dao/device/DeviceServiceImpl.java | 4 ++-- .../thingsboard/server/dao/edge/EdgeServiceImpl.java | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) 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/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java index dc2fb4c4aa..2067ef4a5d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java @@ -201,9 +201,9 @@ public class CustomerServiceImpl extends AbstractCachedEntityService