Added dirty updates support
This commit is contained in:
parent
589e159b54
commit
71f092c4e8
@ -75,8 +75,8 @@ public class CalculatedFieldEntityActor extends AbstractCalculatedFieldActor {
|
|||||||
case CF_LINKED_TELEMETRY_MSG:
|
case CF_LINKED_TELEMETRY_MSG:
|
||||||
processor.process((EntityCalculatedFieldLinkedTelemetryMsg) msg);
|
processor.process((EntityCalculatedFieldLinkedTelemetryMsg) msg);
|
||||||
break;
|
break;
|
||||||
case CF_ENTITY_CHECK_FOR_UPDATES_MSG:
|
case CF_ENTITY_MARK_STATE_DIRTY_MSG:
|
||||||
processor.process((EntityCalculatedFieldCheckForUpdatesMsg) msg);
|
processor.process((EntityCalculatedFieldMarkStateDirtyMsg) msg);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -228,29 +228,16 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void process(EntityCalculatedFieldCheckForUpdatesMsg msg) throws CalculatedFieldException {
|
public void process(EntityCalculatedFieldMarkStateDirtyMsg msg) throws CalculatedFieldException {
|
||||||
CalculatedFieldCtx cfCtx = msg.getCfCtx();
|
log.debug("[{}][{}] Processing entity CF invalidation msg.", entityId, msg.getCfId());
|
||||||
CalculatedFieldId cfId = cfCtx.getCfId();
|
CalculatedFieldState currentState = states.get(msg.getCfId());
|
||||||
log.debug("[{}][{}] Processing CF check for updates msg.", entityId, cfId);
|
if (currentState == null) {
|
||||||
CalculatedFieldState currentState = states.get(cfId);
|
log.debug("[{}][{}] Failed to find CF state for entity.", entityId, msg.getCfId());
|
||||||
try {
|
} else {
|
||||||
var stateFromDb = getStateFromDb(cfCtx);
|
currentState.setDirty(true);
|
||||||
if (currentState.equals(stateFromDb)) {
|
log.debug("[{}][{}] CF state marked as dirty.", entityId, msg.getCfId());
|
||||||
log.debug("[{}][{}] CF state is up-to-date.", entityId, cfId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
states.put(cfId, stateFromDb);
|
|
||||||
if (stateFromDb.isSizeOk()) {
|
|
||||||
processStateIfReady(cfCtx, Collections.singletonList(cfId), stateFromDb, null, null, msg.getCallback());
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException(cfCtx.getSizeExceedsLimitMessage());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (e instanceof CalculatedFieldException cfe) {
|
|
||||||
throw cfe;
|
|
||||||
}
|
|
||||||
throw CalculatedFieldException.builder().ctx(cfCtx).eventEntity(entityId).cause(e).build();
|
|
||||||
}
|
}
|
||||||
|
msg.getCallback().onSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processTelemetry(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, List<CalculatedFieldId> cfIdList, MultipleTbCallback callback) throws CalculatedFieldException {
|
private void processTelemetry(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, List<CalculatedFieldId> cfIdList, MultipleTbCallback callback) throws CalculatedFieldException {
|
||||||
@ -280,6 +267,15 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
|
|||||||
if (state == null) {
|
if (state == null) {
|
||||||
state = getOrInitState(ctx);
|
state = getOrInitState(ctx);
|
||||||
justRestored = true;
|
justRestored = true;
|
||||||
|
} else if (state.isDirty()) {
|
||||||
|
log.debug("[{}][{}] Going to update dirty CF state.", entityId, ctx.getCfId());
|
||||||
|
try {
|
||||||
|
Map<String, ArgumentEntry> dynamicArgsFromDb = cfService.fetchDynamicArgsFromDb(ctx, entityId);
|
||||||
|
dynamicArgsFromDb.forEach(newArgValues::putIfAbsent);
|
||||||
|
state.setDirty(false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).cause(e).build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (state.isSizeOk()) {
|
if (state.isSizeOk()) {
|
||||||
if (state.updateState(ctx, newArgValues) || justRestored) {
|
if (state.updateState(ctx, newArgValues) || justRestored) {
|
||||||
|
|||||||
@ -91,8 +91,8 @@ public class CalculatedFieldManagerActor extends AbstractCalculatedFieldActor {
|
|||||||
case CF_LINKED_TELEMETRY_MSG:
|
case CF_LINKED_TELEMETRY_MSG:
|
||||||
processor.onLinkedTelemetryMsg((CalculatedFieldLinkedTelemetryMsg) msg);
|
processor.onLinkedTelemetryMsg((CalculatedFieldLinkedTelemetryMsg) msg);
|
||||||
break;
|
break;
|
||||||
case CF_SCHEDULED_CHECK_FOR_UPDATES_MSG:
|
case CF_SCHEDULED_INVALIDATION_MSG:
|
||||||
processor.onScheduledCheckForUpdatesMsg((CalculatedFieldScheduledCheckForUpdatesMsg) msg);
|
processor.onScheduledInvalidationMsg((CalculatedFieldScheduledInvalidationMsg) msg);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -76,7 +76,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
private final Map<CalculatedFieldId, CalculatedFieldCtx> calculatedFields = new HashMap<>();
|
private final Map<CalculatedFieldId, CalculatedFieldCtx> calculatedFields = new HashMap<>();
|
||||||
private final Map<EntityId, List<CalculatedFieldCtx>> entityIdCalculatedFields = new HashMap<>();
|
private final Map<EntityId, List<CalculatedFieldCtx>> entityIdCalculatedFields = new HashMap<>();
|
||||||
private final Map<EntityId, List<CalculatedFieldLink>> entityIdCalculatedFieldLinks = new HashMap<>();
|
private final Map<EntityId, List<CalculatedFieldLink>> entityIdCalculatedFieldLinks = new HashMap<>();
|
||||||
private final Map<CalculatedFieldId, ScheduledFuture<?>> checkForCalculatedFieldUpdateTasks = new ConcurrentHashMap<>();
|
private final Map<CalculatedFieldId, ScheduledFuture<?>> cfInvalidationScheduledTasks = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final CalculatedFieldProcessingService cfExecService;
|
private final CalculatedFieldProcessingService cfExecService;
|
||||||
private final CalculatedFieldStateService cfStateService;
|
private final CalculatedFieldStateService cfStateService;
|
||||||
@ -115,8 +115,8 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
calculatedFields.clear();
|
calculatedFields.clear();
|
||||||
entityIdCalculatedFields.clear();
|
entityIdCalculatedFields.clear();
|
||||||
entityIdCalculatedFieldLinks.clear();
|
entityIdCalculatedFieldLinks.clear();
|
||||||
checkForCalculatedFieldUpdateTasks.values().forEach(future -> future.cancel(true));
|
cfInvalidationScheduledTasks.values().forEach(future -> future.cancel(true));
|
||||||
checkForCalculatedFieldUpdateTasks.clear();
|
cfInvalidationScheduledTasks.clear();
|
||||||
ctx.stop(ctx.getSelf());
|
ctx.stop(ctx.getSelf());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +147,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
// We use copy on write lists to safely pass the reference to another actor for the iteration.
|
// We use copy on write lists to safely pass the reference to another actor for the iteration.
|
||||||
// Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
|
// Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
|
||||||
entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cfCtx);
|
entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cfCtx);
|
||||||
scheduleCalculatedFieldUpdateMsgIfNeeded(cfCtx);
|
scheduleCalculatedFieldInvalidationMsgIfNeeded(cfCtx);
|
||||||
msg.getCallback().onSuccess();
|
msg.getCallback().onSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +336,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
|
|
||||||
boolean hasSchedulingConfigChanges = newCfCtx.hasSchedulingConfigChanges(oldCfCtx);
|
boolean hasSchedulingConfigChanges = newCfCtx.hasSchedulingConfigChanges(oldCfCtx);
|
||||||
if (hasSchedulingConfigChanges) {
|
if (hasSchedulingConfigChanges) {
|
||||||
cancelCfUpdateTaskIfExists(cfId, false);
|
cancelCfScheduledInvalidationTaskIfExists(cfId, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<CalculatedFieldCtx> newCfList = new CopyOnWriteArrayList<>();
|
List<CalculatedFieldCtx> newCfList = new CopyOnWriteArrayList<>();
|
||||||
@ -379,7 +379,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
entityIdCalculatedFields.get(cfCtx.getEntityId()).remove(cfCtx);
|
entityIdCalculatedFields.get(cfCtx.getEntityId()).remove(cfCtx);
|
||||||
deleteLinks(cfCtx);
|
deleteLinks(cfCtx);
|
||||||
|
|
||||||
cancelCfUpdateTaskIfExists(cfId, true);
|
cancelCfScheduledInvalidationTaskIfExists(cfId, true);
|
||||||
|
|
||||||
EntityId entityId = cfCtx.getEntityId();
|
EntityId entityId = cfCtx.getEntityId();
|
||||||
EntityType entityType = cfCtx.getEntityId().getEntityType();
|
EntityType entityType = cfCtx.getEntityId().getEntityType();
|
||||||
@ -404,12 +404,12 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancelCfUpdateTaskIfExists(CalculatedFieldId cfId, boolean cfDeleted) {
|
private void cancelCfScheduledInvalidationTaskIfExists(CalculatedFieldId cfId, boolean cfDeleted) {
|
||||||
var existingTask = checkForCalculatedFieldUpdateTasks.remove(cfId);
|
var existingTask = cfInvalidationScheduledTasks.remove(cfId);
|
||||||
if (existingTask != null) {
|
if (existingTask != null) {
|
||||||
existingTask.cancel(false);
|
existingTask.cancel(false);
|
||||||
String reason = cfDeleted ? "removal" : "update";
|
String reason = cfDeleted ? "deletion" : "update";
|
||||||
log.debug("[{}][{}] Cancelled check for update task due to CF " + reason + "!", tenantId, cfId);
|
log.debug("[{}][{}] Cancelled scheduled invalidation task due to CF " + reason + "!", tenantId, cfId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,7 +515,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
private void initCf(CalculatedFieldCtx cfCtx, TbCallback callback, boolean forceStateReinit) {
|
private void initCf(CalculatedFieldCtx cfCtx, TbCallback callback, boolean forceStateReinit) {
|
||||||
EntityId entityId = cfCtx.getEntityId();
|
EntityId entityId = cfCtx.getEntityId();
|
||||||
EntityType entityType = cfCtx.getEntityId().getEntityType();
|
EntityType entityType = cfCtx.getEntityId().getEntityType();
|
||||||
scheduleCalculatedFieldUpdateMsgIfNeeded(cfCtx);
|
scheduleCalculatedFieldInvalidationMsgIfNeeded(cfCtx);
|
||||||
if (isProfileEntity(entityType)) {
|
if (isProfileEntity(entityType)) {
|
||||||
var entityIds = entityProfileCache.getEntityIdsByProfileId(entityId);
|
var entityIds = entityProfileCache.getEntityIdsByProfileId(entityId);
|
||||||
if (!entityIds.isEmpty()) {
|
if (!entityIds.isEmpty()) {
|
||||||
@ -533,31 +533,31 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleCalculatedFieldUpdateMsgIfNeeded(CalculatedFieldCtx cfCtx) {
|
private void scheduleCalculatedFieldInvalidationMsgIfNeeded(CalculatedFieldCtx cfCtx) {
|
||||||
CalculatedField cf = cfCtx.getCalculatedField();
|
CalculatedField cf = cfCtx.getCalculatedField();
|
||||||
CalculatedFieldConfiguration cfConfig = cf.getConfiguration();
|
CalculatedFieldConfiguration cfConfig = cf.getConfiguration();
|
||||||
if (!cfConfig.isScheduledUpdateEnabled()) {
|
if (!cfConfig.isScheduledUpdateEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (checkForCalculatedFieldUpdateTasks.containsKey(cf.getId())) {
|
if (cfInvalidationScheduledTasks.containsKey(cf.getId())) {
|
||||||
log.debug("[{}][{}] Check for update msg for CF is already scheduled!", tenantId, cf.getId());
|
log.debug("[{}][{}] Scheduled invalidation task for CF already exists!", tenantId, cf.getId());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long refreshDynamicSourceInterval = TimeUnit.SECONDS.toMillis(cfConfig.getScheduledUpdateIntervalSec());
|
long refreshDynamicSourceInterval = TimeUnit.SECONDS.toMillis(cfConfig.getScheduledUpdateIntervalSec());
|
||||||
var scheduledMsg = new CalculatedFieldScheduledCheckForUpdatesMsg(tenantId, cfCtx.getCfId());
|
var scheduledMsg = new CalculatedFieldScheduledInvalidationMsg(tenantId, cfCtx.getCfId());
|
||||||
|
|
||||||
ScheduledFuture<?> scheduledFuture = systemContext
|
ScheduledFuture<?> scheduledFuture = systemContext
|
||||||
.schedulePeriodicMsgWithDelay(ctx, scheduledMsg, refreshDynamicSourceInterval, refreshDynamicSourceInterval);
|
.schedulePeriodicMsgWithDelay(ctx, scheduledMsg, refreshDynamicSourceInterval, refreshDynamicSourceInterval);
|
||||||
checkForCalculatedFieldUpdateTasks.put(cf.getId(), scheduledFuture);
|
cfInvalidationScheduledTasks.put(cf.getId(), scheduledFuture);
|
||||||
log.debug("[{}][{}] Scheduled check for update msg for CF!", tenantId, cf.getId());
|
log.debug("[{}][{}] Scheduled invalidation task for CF!", tenantId, cf.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onScheduledCheckForUpdatesMsg(CalculatedFieldScheduledCheckForUpdatesMsg msg) {
|
public void onScheduledInvalidationMsg(CalculatedFieldScheduledInvalidationMsg msg) {
|
||||||
log.debug("[{}] [{}] Processing CF scheduled update msg.", tenantId, msg.getCfId());
|
log.debug("[{}] [{}] Processing CF scheduled invalidation msg.", tenantId, msg.getCfId());
|
||||||
CalculatedFieldCtx cfCtx = calculatedFields.get(msg.getCfId());
|
CalculatedFieldCtx cfCtx = calculatedFields.get(msg.getCfId());
|
||||||
if (cfCtx == null) {
|
if (cfCtx == null) {
|
||||||
log.debug("[{}][{}] Failed to find CF context, going to stop scheduler updates.", tenantId, msg.getCfId());
|
log.debug("[{}][{}] Failed to find CF context, going to stop scheduled invalidations for CF.", tenantId, msg.getCfId());
|
||||||
cancelCfUpdateTaskIfExists(msg.getCfId(), true);
|
cancelCfScheduledInvalidationTaskIfExists(msg.getCfId(), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
EntityId entityId = cfCtx.getEntityId();
|
EntityId entityId = cfCtx.getEntityId();
|
||||||
@ -568,7 +568,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
var multiCallback = new MultipleTbCallback(entityIds.size(), msg.getCallback());
|
var multiCallback = new MultipleTbCallback(entityIds.size(), msg.getCallback());
|
||||||
entityIds.forEach(id -> {
|
entityIds.forEach(id -> {
|
||||||
if (isMyPartition(id, multiCallback)) {
|
if (isMyPartition(id, multiCallback)) {
|
||||||
updateCfWithDynamicSourceForEntity(id, cfCtx, multiCallback);
|
InitCfInvalidationForEntity(id, msg.getCfId(), multiCallback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -576,14 +576,14 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isMyPartition(entityId, msg.getCallback())) {
|
if (isMyPartition(entityId, msg.getCallback())) {
|
||||||
updateCfWithDynamicSourceForEntity(entityId, cfCtx, msg.getCallback());
|
InitCfInvalidationForEntity(entityId, msg.getCfId(), msg.getCallback());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCfWithDynamicSourceForEntity(EntityId entityId, CalculatedFieldCtx cfCtx, TbCallback callback) {
|
private void InitCfInvalidationForEntity(EntityId entityId, CalculatedFieldId cfId, TbCallback callback) {
|
||||||
log.debug("Pushing entity dynamic source refresh CF msg to specific actor [{}]", entityId);
|
log.debug("Pushing entity CF invalidation msg to specific actor [{}]", entityId);
|
||||||
getOrCreateActor(entityId).tell(new EntityCalculatedFieldCheckForUpdatesMsg(tenantId, cfCtx, callback));
|
getOrCreateActor(entityId).tell(new EntityCalculatedFieldMarkStateDirtyMsg(tenantId, cfId, callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteCfForEntity(EntityId entityId, CalculatedFieldId cfId, TbCallback callback) {
|
private void deleteCfForEntity(EntityId entityId, CalculatedFieldId cfId, TbCallback callback) {
|
||||||
|
|||||||
@ -22,14 +22,14 @@ import org.thingsboard.server.common.msg.MsgType;
|
|||||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
|
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class CalculatedFieldScheduledCheckForUpdatesMsg implements ToCalculatedFieldSystemMsg {
|
public class CalculatedFieldScheduledInvalidationMsg implements ToCalculatedFieldSystemMsg {
|
||||||
|
|
||||||
private final TenantId tenantId;
|
private final TenantId tenantId;
|
||||||
private final CalculatedFieldId cfId;
|
private final CalculatedFieldId cfId;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MsgType getMsgType() {
|
public MsgType getMsgType() {
|
||||||
return MsgType.CF_SCHEDULED_CHECK_FOR_UPDATES_MSG;
|
return MsgType.CF_SCHEDULED_INVALIDATION_MSG;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -16,22 +16,22 @@
|
|||||||
package org.thingsboard.server.actors.calculatedField;
|
package org.thingsboard.server.actors.calculatedField;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.thingsboard.server.common.data.id.CalculatedFieldId;
|
||||||
import org.thingsboard.server.common.data.id.TenantId;
|
import org.thingsboard.server.common.data.id.TenantId;
|
||||||
import org.thingsboard.server.common.msg.MsgType;
|
import org.thingsboard.server.common.msg.MsgType;
|
||||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
|
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
|
||||||
import org.thingsboard.server.common.msg.queue.TbCallback;
|
import org.thingsboard.server.common.msg.queue.TbCallback;
|
||||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class EntityCalculatedFieldCheckForUpdatesMsg implements ToCalculatedFieldSystemMsg {
|
public class EntityCalculatedFieldMarkStateDirtyMsg implements ToCalculatedFieldSystemMsg {
|
||||||
|
|
||||||
private final TenantId tenantId;
|
private final TenantId tenantId;
|
||||||
private final CalculatedFieldCtx cfCtx;
|
private final CalculatedFieldId cfId;
|
||||||
private final TbCallback callback;
|
private final TbCallback callback;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MsgType getMsgType() {
|
public MsgType getMsgType() {
|
||||||
return MsgType.CF_ENTITY_CHECK_FOR_UPDATES_MSG;
|
return MsgType.CF_ENTITY_MARK_STATE_DIRTY_MSG;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -34,6 +34,8 @@ public interface CalculatedFieldProcessingService {
|
|||||||
|
|
||||||
ListenableFuture<CalculatedFieldState> fetchStateFromDb(CalculatedFieldCtx ctx, EntityId entityId);
|
ListenableFuture<CalculatedFieldState> fetchStateFromDb(CalculatedFieldCtx ctx, EntityId entityId);
|
||||||
|
|
||||||
|
Map<String, ArgumentEntry> fetchDynamicArgsFromDb(CalculatedFieldCtx ctx, EntityId entityId);
|
||||||
|
|
||||||
Map<String, ArgumentEntry> fetchArgsFromDb(TenantId tenantId, EntityId entityId, Map<String, Argument> arguments);
|
Map<String, ArgumentEntry> fetchArgsFromDb(TenantId tenantId, EntityId entityId, Map<String, Argument> arguments);
|
||||||
|
|
||||||
void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, CalculatedFieldResult calculationResult, List<CalculatedFieldId> cfIds, TbCallback callback);
|
void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, CalculatedFieldResult calculationResult, List<CalculatedFieldId> cfIds, TbCallback callback);
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.StringUtils;
|
|||||||
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
||||||
import org.thingsboard.server.common.data.cf.configuration.Argument;
|
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.ArgumentType;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.CFArgumentDynamicSourceType;
|
||||||
import org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration;
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration;
|
||||||
import org.thingsboard.server.common.data.cf.configuration.GeofencingZoneGroupConfiguration;
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingZoneGroupConfiguration;
|
||||||
import org.thingsboard.server.common.data.cf.configuration.OutputType;
|
import org.thingsboard.server.common.data.cf.configuration.OutputType;
|
||||||
@ -90,6 +91,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -132,20 +134,7 @@ public class DefaultCalculatedFieldProcessingService implements CalculatedFieldP
|
|||||||
Map<String, ListenableFuture<ArgumentEntry>> argFutures = new HashMap<>();
|
Map<String, ListenableFuture<ArgumentEntry>> argFutures = new HashMap<>();
|
||||||
|
|
||||||
if (ctx.getCalculatedField().getType().equals(CalculatedFieldType.GEOFENCING)) {
|
if (ctx.getCalculatedField().getType().equals(CalculatedFieldType.GEOFENCING)) {
|
||||||
var configuration = (GeofencingCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration();
|
fetchGeofencingCalculatedFieldArguments(ctx, entityId, argFutures, false);
|
||||||
var zoneGroupConfigs = configuration.getGeofencingZoneGroupConfigurations();
|
|
||||||
for (var entry : ctx.getArguments().entrySet()) {
|
|
||||||
switch (entry.getKey()) {
|
|
||||||
case ENTITY_ID_LATITUDE_ARGUMENT_KEY, ENTITY_ID_LONGITUDE_ARGUMENT_KEY ->
|
|
||||||
argFutures.put(entry.getKey(), fetchKvEntry(ctx.getTenantId(), resolveEntityId(entityId, entry), entry.getValue()));
|
|
||||||
default -> {
|
|
||||||
var zoneGroupConfiguration = zoneGroupConfigs.get(entry.getKey());
|
|
||||||
var resolvedEntityIdsFuture = resolveGeofencingEntityIds(ctx.getTenantId(), entityId, entry);
|
|
||||||
argFutures.put(entry.getKey(), Futures.transformAsync(resolvedEntityIdsFuture, resolvedEntityIds ->
|
|
||||||
fetchGeofencingKvEntry(ctx.getTenantId(), resolvedEntityIds, entry.getValue(), zoneGroupConfiguration), calculatedFieldCallbackExecutor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
for (var entry : ctx.getArguments().entrySet()) {
|
for (var entry : ctx.getArguments().entrySet()) {
|
||||||
var argEntityId = resolveEntityId(entityId, entry);
|
var argEntityId = resolveEntityId(entityId, entry);
|
||||||
@ -156,22 +145,45 @@ public class DefaultCalculatedFieldProcessingService implements CalculatedFieldP
|
|||||||
|
|
||||||
return Futures.whenAllComplete(argFutures.values()).call(() -> {
|
return Futures.whenAllComplete(argFutures.values()).call(() -> {
|
||||||
var result = createStateByType(ctx);
|
var result = createStateByType(ctx);
|
||||||
result.updateState(ctx, argFutures.entrySet().stream()
|
result.updateState(ctx, resolveArgumentFutures(argFutures));
|
||||||
.collect(Collectors.toMap(
|
|
||||||
Entry::getKey, // Keep the key as is
|
|
||||||
entry -> {
|
|
||||||
try {
|
|
||||||
// Resolve the future to get the value
|
|
||||||
return entry.getValue().get();
|
|
||||||
} catch (ExecutionException | InterruptedException e) {
|
|
||||||
throw new RuntimeException("Error getting future result for key: " + entry.getKey(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)));
|
|
||||||
return result;
|
return result;
|
||||||
}, calculatedFieldCallbackExecutor);
|
}, calculatedFieldCallbackExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, ArgumentEntry> fetchDynamicArgsFromDb(CalculatedFieldCtx ctx, EntityId entityId) {
|
||||||
|
// only geofencing calculated fields supports dynamic arguments scheduled updates
|
||||||
|
if (!ctx.getCalculatedField().getType().equals(CalculatedFieldType.GEOFENCING)) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
Map<String, ListenableFuture<ArgumentEntry>> argFutures = new HashMap<>();
|
||||||
|
fetchGeofencingCalculatedFieldArguments(ctx, entityId, argFutures, true);
|
||||||
|
return resolveArgumentFutures(argFutures);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchGeofencingCalculatedFieldArguments(CalculatedFieldCtx ctx, EntityId entityId, Map<String, ListenableFuture<ArgumentEntry>> argFutures, boolean dynamicArgumentsOnly) {
|
||||||
|
var configuration = (GeofencingCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration();
|
||||||
|
var zoneGroupConfigs = configuration.getGeofencingZoneGroupConfigurations();
|
||||||
|
Set<Entry<String, Argument>> entries = ctx.getArguments().entrySet();
|
||||||
|
if (dynamicArgumentsOnly) {
|
||||||
|
entries = entries.stream()
|
||||||
|
.filter(entry -> CFArgumentDynamicSourceType.RELATION_QUERY.equals(entry.getValue().getRefDynamicSource()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
for (var entry : entries) {
|
||||||
|
switch (entry.getKey()) {
|
||||||
|
case ENTITY_ID_LATITUDE_ARGUMENT_KEY, ENTITY_ID_LONGITUDE_ARGUMENT_KEY ->
|
||||||
|
argFutures.put(entry.getKey(), fetchKvEntry(ctx.getTenantId(), resolveEntityId(entityId, entry), entry.getValue()));
|
||||||
|
default -> {
|
||||||
|
var zoneGroupConfiguration = zoneGroupConfigs.get(entry.getKey());
|
||||||
|
var resolvedEntityIdsFuture = resolveGeofencingEntityIds(ctx.getTenantId(), entityId, entry);
|
||||||
|
argFutures.put(entry.getKey(), Futures.transformAsync(resolvedEntityIdsFuture, resolvedEntityIds ->
|
||||||
|
fetchGeofencingKvEntry(ctx.getTenantId(), resolvedEntityIds, entry.getValue(), zoneGroupConfiguration), calculatedFieldCallbackExecutor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, ArgumentEntry> fetchArgsFromDb(TenantId tenantId, EntityId entityId, Map<String, Argument> arguments) {
|
public Map<String, ArgumentEntry> fetchArgsFromDb(TenantId tenantId, EntityId entityId, Map<String, Argument> arguments) {
|
||||||
Map<String, ListenableFuture<ArgumentEntry>> argFutures = new HashMap<>();
|
Map<String, ListenableFuture<ArgumentEntry>> argFutures = new HashMap<>();
|
||||||
@ -180,6 +192,10 @@ public class DefaultCalculatedFieldProcessingService implements CalculatedFieldP
|
|||||||
var argValueFuture = fetchKvEntry(tenantId, argEntityId, entry.getValue());
|
var argValueFuture = fetchKvEntry(tenantId, argEntityId, entry.getValue());
|
||||||
argFutures.put(entry.getKey(), argValueFuture);
|
argFutures.put(entry.getKey(), argValueFuture);
|
||||||
}
|
}
|
||||||
|
return resolveArgumentFutures(argFutures);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, ArgumentEntry> resolveArgumentFutures(Map<String, ListenableFuture<ArgumentEntry>> argFutures) {
|
||||||
return argFutures.entrySet().stream()
|
return argFutures.entrySet().stream()
|
||||||
.collect(Collectors.toMap(
|
.collect(Collectors.toMap(
|
||||||
Entry::getKey, // Keep the key as is
|
Entry::getKey, // Keep the key as is
|
||||||
|
|||||||
@ -35,13 +35,15 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState {
|
|||||||
|
|
||||||
protected long latestTimestamp = -1;
|
protected long latestTimestamp = -1;
|
||||||
|
|
||||||
|
private boolean dirty;
|
||||||
|
|
||||||
public BaseCalculatedFieldState(List<String> requiredArguments) {
|
public BaseCalculatedFieldState(List<String> requiredArguments) {
|
||||||
this.requiredArguments = requiredArguments;
|
this.requiredArguments = requiredArguments;
|
||||||
this.arguments = new HashMap<>();
|
this.arguments = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseCalculatedFieldState() {
|
public BaseCalculatedFieldState() {
|
||||||
this(new ArrayList<>(), new HashMap<>(), false, -1);
|
this(new ArrayList<>(), new HashMap<>(), false, -1, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -47,6 +47,10 @@ public interface CalculatedFieldState {
|
|||||||
|
|
||||||
long getLatestTimestamp();
|
long getLatestTimestamp();
|
||||||
|
|
||||||
|
void setDirty(boolean dirty);
|
||||||
|
|
||||||
|
boolean isDirty();
|
||||||
|
|
||||||
void setRequiredArguments(List<String> requiredArguments);
|
void setRequiredArguments(List<String> requiredArguments);
|
||||||
|
|
||||||
boolean updateState(CalculatedFieldCtx ctx, Map<String, ArgumentEntry> argumentValues);
|
boolean updateState(CalculatedFieldCtx ctx, Map<String, ArgumentEntry> argumentValues);
|
||||||
|
|||||||
@ -46,13 +46,14 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
|||||||
|
|
||||||
private List<String> requiredArguments;
|
private List<String> requiredArguments;
|
||||||
private Map<String, ArgumentEntry> arguments;
|
private Map<String, ArgumentEntry> arguments;
|
||||||
|
private boolean sizeExceedsLimit;
|
||||||
protected boolean sizeExceedsLimit;
|
|
||||||
|
|
||||||
private long latestTimestamp = -1;
|
private long latestTimestamp = -1;
|
||||||
|
|
||||||
|
private boolean dirty;
|
||||||
|
|
||||||
public GeofencingCalculatedFieldState() {
|
public GeofencingCalculatedFieldState() {
|
||||||
this(new ArrayList<>(), new HashMap<>(), false, -1);
|
this(new ArrayList<>(), new HashMap<>(), false, -1, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GeofencingCalculatedFieldState(List<String> argNames) {
|
public GeofencingCalculatedFieldState(List<String> argNames) {
|
||||||
|
|||||||
@ -152,8 +152,8 @@ public enum MsgType {
|
|||||||
CF_ENTITY_INIT_CF_MSG,
|
CF_ENTITY_INIT_CF_MSG,
|
||||||
CF_ENTITY_DELETE_MSG,
|
CF_ENTITY_DELETE_MSG,
|
||||||
|
|
||||||
CF_SCHEDULED_CHECK_FOR_UPDATES_MSG,
|
CF_SCHEDULED_INVALIDATION_MSG,
|
||||||
CF_ENTITY_CHECK_FOR_UPDATES_MSG;
|
CF_ENTITY_MARK_STATE_DIRTY_MSG;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final boolean ignoreOnStart;
|
private final boolean ignoreOnStart;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user