|
|
|
@ -27,12 +27,15 @@ import jakarta.annotation.PostConstruct; |
|
|
|
import jakarta.annotation.PreDestroy; |
|
|
|
import lombok.Data; |
|
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
|
import org.thingsboard.common.util.DonAsynchron; |
|
|
|
import org.thingsboard.common.util.ThingsBoardExecutors; |
|
|
|
import org.thingsboard.rule.engine.api.AttributesSaveRequest; |
|
|
|
import org.thingsboard.rule.engine.api.AttributesSaveRequest.Strategy; |
|
|
|
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest; |
|
|
|
import org.thingsboard.server.common.adaptor.JsonConverter; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.Argument; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.ArgumentType; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.AttributeSkipRuleEngineOutputStrategy; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.OutputType; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.RelationPathQueryDynamicSourceConfiguration; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.TimeSeriesSkipRuleEngineOutputStrategy; |
|
|
|
@ -71,6 +74,7 @@ import java.util.Objects; |
|
|
|
import java.util.Optional; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.concurrent.ExecutionException; |
|
|
|
import java.util.function.Function; |
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
import static org.thingsboard.server.common.data.cf.CalculatedFieldType.PROPAGATION; |
|
|
|
@ -344,8 +348,8 @@ public abstract class AbstractCalculatedFieldProcessingService { |
|
|
|
|
|
|
|
SettableFuture<Void> future = SettableFuture.create(); |
|
|
|
switch (type) { |
|
|
|
case ATTRIBUTES -> saveAttributes(tenantId, entityId, jsonResult, cfIds, future); |
|
|
|
case TIME_SERIES -> saveTimeSeries(tenantId, entityId, jsonResult, ((TimeSeriesSkipRuleEngineOutputStrategy) cfResult.getOutputStrategy()).getTtl(), cfIds, System.currentTimeMillis(), TimeseriesSaveRequest.Strategy.PROCESS_ALL, future); |
|
|
|
case ATTRIBUTES -> saveAttributes(tenantId, entityId, cfResult, cfIds, future); |
|
|
|
case TIME_SERIES -> saveTimeSeries(tenantId, entityId, cfResult, cfIds, System.currentTimeMillis(), future); |
|
|
|
} |
|
|
|
|
|
|
|
if (log.isTraceEnabled()) { |
|
|
|
@ -365,20 +369,54 @@ public abstract class AbstractCalculatedFieldProcessingService { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void saveAttributes(TenantId tenantId, EntityId entityId, JsonElement jsonResult, List<CalculatedFieldId> cfIds, SettableFuture<Void> future) { |
|
|
|
private void saveAttributes(TenantId tenantId, EntityId entityId, TelemetryCalculatedFieldResult cfResult, List<CalculatedFieldId> cfIds, SettableFuture<Void> future) { |
|
|
|
if (!(cfResult.getOutputStrategy() instanceof AttributeSkipRuleEngineOutputStrategy outputStrategy)) { |
|
|
|
return; |
|
|
|
} |
|
|
|
JsonElement jsonResult = JsonParser.parseString(Objects.requireNonNull(cfResult.stringValue())); |
|
|
|
|
|
|
|
AttributesSaveRequest.Strategy strategy = new Strategy(outputStrategy.isSaveAttribute(), outputStrategy.isSendWsUpdate(), outputStrategy.isProcessCfs()); |
|
|
|
List<AttributeKvEntry> attributeKvEntries = JsonConverter.convertToAttributes(jsonResult); |
|
|
|
tsSubService.saveAttributesInternal(AttributesSaveRequest.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.entityId(entityId) |
|
|
|
.entries(attributeKvEntries) |
|
|
|
.strategy(AttributesSaveRequest.Strategy.PROCESS_ALL) |
|
|
|
.previousCalculatedFieldIds(cfIds) |
|
|
|
.future(future) |
|
|
|
.build() |
|
|
|
); |
|
|
|
|
|
|
|
if (!outputStrategy.isUpdateAttributesOnlyOnValueChange()) { |
|
|
|
tsSubService.saveAttributesInternal(AttributesSaveRequest.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.entityId(entityId) |
|
|
|
.entries(attributeKvEntries) |
|
|
|
.strategy(strategy) |
|
|
|
.previousCalculatedFieldIds(cfIds) |
|
|
|
.future(future) |
|
|
|
.build() |
|
|
|
); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
List<String> keys = attributeKvEntries.stream().map(KvEntry::getKey).collect(Collectors.toList()); |
|
|
|
|
|
|
|
ListenableFuture<List<AttributeKvEntry>> findFuture = attributesService.find(tenantId, entityId, cfResult.getScope(), keys); |
|
|
|
|
|
|
|
DonAsynchron.withCallback(findFuture, |
|
|
|
existingAttributes -> { |
|
|
|
List<AttributeKvEntry> attributesChanged = filterChangedAttr(existingAttributes, attributeKvEntries); |
|
|
|
tsSubService.saveAttributesInternal(AttributesSaveRequest.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.entityId(entityId) |
|
|
|
.entries(attributesChanged) |
|
|
|
.strategy(strategy) |
|
|
|
.previousCalculatedFieldIds(cfIds) |
|
|
|
.future(future) |
|
|
|
.build() |
|
|
|
); |
|
|
|
}, |
|
|
|
future::setException, |
|
|
|
MoreExecutors.directExecutor()); |
|
|
|
} |
|
|
|
|
|
|
|
private void saveTimeSeries(TenantId tenantId, EntityId entityId, JsonElement jsonResult, Long ttl, List<CalculatedFieldId> cfIds, long ts, TimeseriesSaveRequest.Strategy strategy, SettableFuture<Void> future) { |
|
|
|
private void saveTimeSeries(TenantId tenantId, EntityId entityId, TelemetryCalculatedFieldResult cfResult, List<CalculatedFieldId> cfIds, long ts, SettableFuture<Void> future) { |
|
|
|
if (!(cfResult.getOutputStrategy() instanceof TimeSeriesSkipRuleEngineOutputStrategy outputStrategy)) { |
|
|
|
return; |
|
|
|
} |
|
|
|
JsonElement jsonResult = JsonParser.parseString(Objects.requireNonNull(cfResult.stringValue())); |
|
|
|
Map<Long, List<KvEntry>> tsKvMap = JsonConverter.convertToTelemetry(jsonResult, ts); |
|
|
|
List<TsKvEntry> tsEntries = new ArrayList<>(); |
|
|
|
for (Map.Entry<Long, List<KvEntry>> tsKvEntry : tsKvMap.entrySet()) { |
|
|
|
@ -386,15 +424,34 @@ public abstract class AbstractCalculatedFieldProcessingService { |
|
|
|
tsEntries.add(new BasicTsKvEntry(tsKvEntry.getKey(), kvEntry)); |
|
|
|
} |
|
|
|
} |
|
|
|
TimeseriesSaveRequest.Strategy strategy = new TimeseriesSaveRequest.Strategy(outputStrategy.isSaveTimeSeries(), outputStrategy.isSaveLatest(), outputStrategy.isSendWsUpdate(), outputStrategy.isProcessCfs()); |
|
|
|
tsSubService.saveTimeseriesInternal(TimeseriesSaveRequest.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.entityId(entityId) |
|
|
|
.entries(tsEntries) |
|
|
|
.ttl(ttl) |
|
|
|
.ttl(outputStrategy.getTtl()) |
|
|
|
.strategy(strategy) |
|
|
|
.previousCalculatedFieldIds(cfIds) |
|
|
|
.future(future) |
|
|
|
.build()); |
|
|
|
} |
|
|
|
|
|
|
|
private List<AttributeKvEntry> filterChangedAttr(List<AttributeKvEntry> existingAttributes, List<AttributeKvEntry> newAttributes) { |
|
|
|
if (existingAttributes == null || existingAttributes.isEmpty()) { |
|
|
|
return newAttributes; |
|
|
|
} |
|
|
|
|
|
|
|
Map<String, AttributeKvEntry> currentAttrMap = existingAttributes.stream() |
|
|
|
.collect(Collectors.toMap(AttributeKvEntry::getKey, Function.identity(), (existing, replacement) -> existing)); |
|
|
|
|
|
|
|
return newAttributes.stream() |
|
|
|
.filter(item -> { |
|
|
|
AttributeKvEntry cacheAttr = currentAttrMap.get(item.getKey()); |
|
|
|
return cacheAttr == null |
|
|
|
|| !Objects.equals(item.getValue(), cacheAttr.getValue()) //JSON and String can be equals by value, but different by type
|
|
|
|
|| !Objects.equals(item.getDataType(), cacheAttr.getDataType()); |
|
|
|
}) |
|
|
|
.collect(Collectors.toList()); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|