32 changed files with 524 additions and 221 deletions
@ -0,0 +1,51 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.service.sync.ie.exporting.impl; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.HasTenantId; |
||||
|
import org.thingsboard.server.common.data.cf.CalculatedField; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.CalculatedFieldExportData; |
||||
|
import org.thingsboard.server.dao.cf.CalculatedFieldService; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public abstract class BaseCalculatedFieldsExportService<ID extends EntityId, E extends ExportableEntity<ID> & HasTenantId, D extends CalculatedFieldExportData<E>> extends BaseEntityExportService<ID, E, D> { |
||||
|
|
||||
|
protected final CalculatedFieldService calculatedFieldService; |
||||
|
|
||||
|
protected BaseCalculatedFieldsExportService(CalculatedFieldService calculatedFieldService) { |
||||
|
this.calculatedFieldService = calculatedFieldService; |
||||
|
} |
||||
|
|
||||
|
protected void setCalculatedFields(EntitiesExportCtx<?> ctx, E entity, D exportData) { |
||||
|
if (ctx.getSettings().isExportCalculatedFields()) { |
||||
|
List<CalculatedField> calculatedFields = calculatedFieldService.findCalculatedFieldsByEntityId(ctx.getTenantId(), entity.getId()); |
||||
|
calculatedFields.forEach(calculatedField -> { |
||||
|
calculatedField.getConfiguration().getArguments().values().forEach(argument -> { |
||||
|
if (argument.getRefEntityId() != null) { |
||||
|
EntityId externalId = getExternalIdOrElseInternal(ctx, argument.getRefEntityId()); |
||||
|
argument.setRefEntityId(externalId); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
exportData.setCalculatedFields(calculatedFields); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,49 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2025 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.service.sync.ie.exporting.impl; |
|
||||
|
|
||||
import org.springframework.stereotype.Service; |
|
||||
import org.thingsboard.server.common.data.EntityType; |
|
||||
import org.thingsboard.server.common.data.cf.CalculatedField; |
|
||||
import org.thingsboard.server.common.data.id.CalculatedFieldId; |
|
||||
import org.thingsboard.server.common.data.id.EntityId; |
|
||||
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
|
||||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|
||||
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
|
||||
|
|
||||
import java.util.Set; |
|
||||
|
|
||||
@Service |
|
||||
@TbCoreComponent |
|
||||
public class CalculatedFieldExportService extends BaseEntityExportService<CalculatedFieldId, CalculatedField, EntityExportData<CalculatedField>> { |
|
||||
|
|
||||
@Override |
|
||||
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, CalculatedField calculatedField, EntityExportData<CalculatedField> exportData) { |
|
||||
calculatedField.setEntityId(getExternalIdOrElseInternal(ctx, calculatedField.getEntityId())); |
|
||||
calculatedField.getConfiguration().getArguments().values().forEach(argument -> { |
|
||||
if (argument.getRefEntityId() != null) { |
|
||||
EntityId internalEntityId = getExternalIdOrElseInternal(ctx, argument.getRefEntityId()); |
|
||||
argument.setRefEntityId(internalEntityId); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Set<EntityType> getSupportedEntityTypes() { |
|
||||
return Set.of(EntityType.CALCULATED_FIELD); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,123 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.service.sync.ie.importing.impl; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.HasTenantId; |
||||
|
import org.thingsboard.server.common.data.cf.CalculatedField; |
||||
|
import org.thingsboard.server.common.data.id.CalculatedFieldId; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.sync.ie.CalculatedFieldExportData; |
||||
|
import org.thingsboard.server.dao.cf.CalculatedFieldService; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
import java.util.LinkedHashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
import java.util.function.Function; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
public abstract class BaseCalculatedFieldsImportService<ID extends EntityId, E extends ExportableEntity<ID> & HasTenantId, D extends CalculatedFieldExportData<E>> extends BaseEntityImportService<ID, E, D> { |
||||
|
|
||||
|
private final CalculatedFieldService calculatedFieldService; |
||||
|
|
||||
|
protected BaseCalculatedFieldsImportService(CalculatedFieldService calculatedFieldService) { |
||||
|
this.calculatedFieldService = calculatedFieldService; |
||||
|
} |
||||
|
|
||||
|
protected E saveOrUpdateEntity(EntitiesImportCtx ctx, E entity, D exportData, IdProvider idProvider, Function<E, E> saveFunction) { |
||||
|
E savedEntity = saveFunction.apply(entity); |
||||
|
|
||||
|
if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) { |
||||
|
saveCalculatedFields(ctx, savedEntity, exportData, idProvider); |
||||
|
} |
||||
|
return savedEntity; |
||||
|
} |
||||
|
|
||||
|
protected void saveCalculatedFields(EntitiesImportCtx ctx, E savedEntity, D exportData, IdProvider idProvider) { |
||||
|
if (exportData.getCalculatedFields() == null || !ctx.isSaveCalculatedFields()) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
exportData.getCalculatedFields().forEach(calculatedField -> { |
||||
|
calculatedField.setTenantId(savedEntity.getTenantId()); |
||||
|
calculatedField.setExternalId(calculatedField.getId()); |
||||
|
calculatedField.setId(idProvider.getInternalId(calculatedField.getId(), false)); |
||||
|
calculatedField.setEntityId(savedEntity.getId()); |
||||
|
|
||||
|
calculatedField.getConfiguration().getArguments().values().forEach(argument -> { |
||||
|
if (argument.getRefEntityId() != null) { |
||||
|
argument.setRefEntityId(idProvider.getInternalId(argument.getRefEntityId(), false)); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
calculatedFieldService.save(calculatedField); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected boolean updateRelatedEntitiesIfUnmodified(EntitiesImportCtx ctx, E prepared, D exportData, IdProvider idProvider) { |
||||
|
boolean updated = super.updateRelatedEntitiesIfUnmodified(ctx, prepared, exportData, idProvider); |
||||
|
updated |= updateCalculatedFields(ctx, prepared, exportData, idProvider); |
||||
|
return updated; |
||||
|
} |
||||
|
|
||||
|
private boolean updateCalculatedFields(EntitiesImportCtx ctx, E prepared, D exportData, IdProvider idProvider) { |
||||
|
var calculatedFields = exportData.getCalculatedFields(); |
||||
|
if (calculatedFields == null || !ctx.isSaveCalculatedFields()) { |
||||
|
return false; |
||||
|
} |
||||
|
Map<CalculatedFieldId, CalculatedField> calculatedFieldMap = calculatedFields.stream() |
||||
|
.peek(newField -> { |
||||
|
newField.setTenantId(ctx.getTenantId()); |
||||
|
newField.setExternalId(newField.getId()); |
||||
|
newField.setId(idProvider.getInternalId(newField.getId(), false)); |
||||
|
newField.setEntityId(prepared.getId()); |
||||
|
newField.getConfiguration().getArguments().values().forEach(argument -> { |
||||
|
argument.setRefEntityId(idProvider.getInternalId(argument.getRefEntityId(), false)); |
||||
|
}); |
||||
|
}) |
||||
|
.collect(Collectors.toMap(CalculatedField::getId, field -> field)); |
||||
|
|
||||
|
List<CalculatedField> existingFields = calculatedFieldService.findCalculatedFieldsByEntityId(ctx.getTenantId(), prepared.getId()); |
||||
|
boolean updated = false; |
||||
|
|
||||
|
Map<CalculatedField, Boolean> result = new LinkedHashMap<>(); |
||||
|
for (CalculatedField existingField : existingFields) { |
||||
|
if (calculatedFieldMap.containsKey(existingField.getId())) { |
||||
|
CalculatedField newField = calculatedFieldMap.get(existingField.getId()); |
||||
|
if (!newField.equals(existingField)) { |
||||
|
result.put(newField, false); |
||||
|
} |
||||
|
calculatedFieldMap.remove(existingField.getId()); |
||||
|
} else { |
||||
|
updated = true; |
||||
|
calculatedFieldService.deleteCalculatedField(ctx.getTenantId(), existingField.getId()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (CalculatedField newField : calculatedFieldMap.values()) { |
||||
|
result.put(newField, true); |
||||
|
} |
||||
|
|
||||
|
if (!result.isEmpty()) { |
||||
|
updated = true; |
||||
|
ctx.addCalculatedFields(result); |
||||
|
} |
||||
|
return updated; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,85 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2025 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.service.sync.ie.importing.impl; |
|
||||
|
|
||||
import lombok.RequiredArgsConstructor; |
|
||||
import org.springframework.stereotype.Service; |
|
||||
import org.thingsboard.server.common.data.EntityType; |
|
||||
import org.thingsboard.server.common.data.User; |
|
||||
import org.thingsboard.server.common.data.audit.ActionType; |
|
||||
import org.thingsboard.server.common.data.cf.CalculatedField; |
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|
||||
import org.thingsboard.server.common.data.id.CalculatedFieldId; |
|
||||
import org.thingsboard.server.common.data.id.EntityId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
|
||||
import org.thingsboard.server.dao.cf.CalculatedFieldService; |
|
||||
import org.thingsboard.server.dao.service.ConstraintValidator; |
|
||||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|
||||
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
|
||||
|
|
||||
@Service |
|
||||
@TbCoreComponent |
|
||||
@RequiredArgsConstructor |
|
||||
public class CalculatedFieldImportService extends BaseEntityImportService<CalculatedFieldId, CalculatedField, EntityExportData<CalculatedField>> { |
|
||||
|
|
||||
private final CalculatedFieldService calculatedFieldService; |
|
||||
|
|
||||
@Override |
|
||||
protected void setOwner(TenantId tenantId, CalculatedField calculatedField, IdProvider idProvider) { |
|
||||
calculatedField.setTenantId(tenantId); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected CalculatedField prepare(EntitiesImportCtx ctx, CalculatedField calculatedField, CalculatedField oldEntity, EntityExportData<CalculatedField> exportData, IdProvider idProvider) { |
|
||||
calculatedField.setEntityId(idProvider.getInternalId(calculatedField.getEntityId())); |
|
||||
calculatedField.getConfiguration().getArguments().values().forEach(argument -> { |
|
||||
if (argument.getRefEntityId() != null) { |
|
||||
EntityId internalEntityId = idProvider.getInternalId(argument.getRefEntityId()); |
|
||||
argument.setRefEntityId(internalEntityId); |
|
||||
} |
|
||||
}); |
|
||||
return calculatedField; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected CalculatedField saveOrUpdate(EntitiesImportCtx ctx, CalculatedField calculatedField, EntityExportData<CalculatedField> exportData, IdProvider idProvider) { |
|
||||
ConstraintValidator.validateFields(calculatedField); |
|
||||
return calculatedFieldService.save(calculatedField); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected CalculatedField deepCopy(CalculatedField calculatedField) { |
|
||||
return new CalculatedField(calculatedField); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void onEntitySaved(User user, CalculatedField savedEntity, CalculatedField oldEntity) throws ThingsboardException { |
|
||||
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity, null, |
|
||||
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void cleanupForComparison(CalculatedField e) { |
|
||||
super.cleanupForComparison(e); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public EntityType getEntityType() { |
|
||||
return EntityType.CALCULATED_FIELD; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,28 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.sync.ie; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import lombok.ToString; |
||||
|
import org.thingsboard.server.common.data.asset.Asset; |
||||
|
|
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@ToString(callSuper = true) |
||||
|
@Data |
||||
|
public class AssetExportData extends CalculatedFieldExportData<Asset> { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.sync.ie; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import lombok.ToString; |
||||
|
import org.thingsboard.server.common.data.asset.AssetProfile; |
||||
|
|
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@ToString(callSuper = true) |
||||
|
@Data |
||||
|
public class AssetProfileExportData extends CalculatedFieldExportData<AssetProfile> { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.sync.ie; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import lombok.ToString; |
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.cf.CalculatedField; |
||||
|
|
||||
|
import java.util.Comparator; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@ToString(callSuper = true) |
||||
|
public class CalculatedFieldExportData<E extends ExportableEntity<?>> extends EntityExportData<E> { |
||||
|
|
||||
|
public static final Comparator<CalculatedField> calculatedFieldsComparator = Comparator.comparing(CalculatedField::getName); |
||||
|
|
||||
|
@JsonProperty(index = 102) |
||||
|
@JsonIgnoreProperties({"entityId", "createdTime", "version"}) |
||||
|
private List<CalculatedField> calculatedFields; |
||||
|
|
||||
|
@JsonIgnore |
||||
|
@Override |
||||
|
public boolean hasCalculatedFields() { |
||||
|
return calculatedFields != null; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public CalculatedFieldExportData<E> sort() { |
||||
|
super.sort(); |
||||
|
if (calculatedFields != null && !calculatedFields.isEmpty()) { |
||||
|
calculatedFields.sort(calculatedFieldsComparator); |
||||
|
} |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.sync.ie; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import lombok.ToString; |
||||
|
import org.thingsboard.server.common.data.DeviceProfile; |
||||
|
|
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@ToString(callSuper = true) |
||||
|
@Data |
||||
|
public class DeviceProfileExportData extends CalculatedFieldExportData<DeviceProfile> { |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue