From e37332a288b532c8f2cdfaae16d578122e9658a2 Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Fri, 5 Dec 2025 17:00:02 +0200 Subject: [PATCH 1/4] handle output update to avoid processing when minor strategy properties updated --- ...CalculatedFieldEntityMessageProcessor.java | 10 +- ...alculatedFieldManagerMessageProcessor.java | 2 + .../EntityInitCalculatedFieldMsg.java | 3 +- .../cf/ctx/state/CalculatedFieldCtx.java | 94 ++++++++++++++++--- 4 files changed, 91 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java index 2685e7f434..93fd7498e5 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java @@ -159,10 +159,14 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM } else { state.setCtx(ctx, actorCtx); } - if (state.isSizeOk()) { - processStateIfReady(state, Collections.emptyMap(), ctx, Collections.singletonList(ctx.getCfId()), null, null, msg.getCallback()); + if (msg.getStateAction() != StateAction.REFRESH_CTX) { + if (state.isSizeOk()) { + processStateIfReady(state, Collections.emptyMap(), ctx, Collections.singletonList(ctx.getCfId()), null, null, msg.getCallback()); + } else { + throw new RuntimeException(ctx.getSizeExceedsLimitMessage()); + } } else { - throw new RuntimeException(ctx.getSizeExceedsLimitMessage()); + msg.getCallback().onSuccess(); } } catch (Exception e) { log.debug("[{}][{}] Failed to initialize CF state", entityId, ctx.getCfId(), e); diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java index b19ad1a8b4..97d5d0dcea 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java @@ -455,6 +455,8 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware stateAction = StateAction.REINIT; // refetch arguments, call state.init, then calculate } else if (newCfCtx.hasContextOnlyChanges(oldCfCtx)) { stateAction = StateAction.REPROCESS; // call state.setCtx, then calculate + } else if (newCfCtx.hasRefreshContextOnlyChanges(oldCfCtx)) { + stateAction = StateAction.REFRESH_CTX; } else { callback.onSuccess(); return; diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityInitCalculatedFieldMsg.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityInitCalculatedFieldMsg.java index 1e0025988d..49f2c691d3 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityInitCalculatedFieldMsg.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityInitCalculatedFieldMsg.java @@ -39,6 +39,7 @@ public class EntityInitCalculatedFieldMsg implements ToCalculatedFieldSystemMsg INIT, REINIT, RECREATE, - REPROCESS + REPROCESS, + REFRESH_CTX } } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java index c2c65d3327..489632b9b9 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java @@ -37,12 +37,14 @@ import org.thingsboard.server.common.data.cf.configuration.AlarmCalculatedFieldC 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.ArgumentsBasedCalculatedFieldConfiguration; +import org.thingsboard.server.common.data.cf.configuration.AttributesImmediateOutputStrategy; import org.thingsboard.server.common.data.cf.configuration.ExpressionBasedCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.Output; import org.thingsboard.server.common.data.cf.configuration.PropagationCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey; import org.thingsboard.server.common.data.cf.configuration.ScheduledUpdateSupportedCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration; +import org.thingsboard.server.common.data.cf.configuration.TimeSeriesImmediateOutputStrategy; import org.thingsboard.server.common.data.cf.configuration.aggregation.AggFunctionInput; import org.thingsboard.server.common.data.cf.configuration.aggregation.RelatedEntitiesAggregationCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.aggregation.single.EntityAggregationCalculatedFieldConfiguration; @@ -623,16 +625,42 @@ public class CalculatedFieldCtx implements Closeable { return new CalculatedFieldEntityCtxId(tenantId, cfId, entityId); } + public boolean hasRefreshContextOnlyChanges(CalculatedFieldCtx other) { // has changes that do not require state recalculation + var thisConfig = calculatedField.getConfiguration(); + var otherConfig = other.getCalculatedField().getConfiguration(); + + var thisOutputStrategy = thisConfig.getOutput().getStrategy(); + var otherOutputStrategy = otherConfig.getOutput().getStrategy(); + + if (!thisOutputStrategy.getType().equals(otherOutputStrategy.getType())) { + return true; + } + + if (thisOutputStrategy instanceof TimeSeriesImmediateOutputStrategy thisTimeSeriesImmediateOutputStrategy + && otherOutputStrategy instanceof TimeSeriesImmediateOutputStrategy otherTimeSeriesImmediateOutputStrategy) { + return thisTimeSeriesImmediateOutputStrategy.getTtl() != otherTimeSeriesImmediateOutputStrategy.getTtl(); + } + + if (thisOutputStrategy instanceof AttributesImmediateOutputStrategy thisAttributesImmediateOutputStrategy + && otherOutputStrategy instanceof AttributesImmediateOutputStrategy otherAttributesImmediateOutputStrategy) { + boolean updateAttributesOnlyOnValueChangeChanged = thisAttributesImmediateOutputStrategy.isUpdateAttributesOnlyOnValueChange() != otherAttributesImmediateOutputStrategy.isUpdateAttributesOnlyOnValueChange(); + boolean sendAttributesUpdatedNotificationUpdated = thisAttributesImmediateOutputStrategy.isSendAttributesUpdatedNotification() != otherAttributesImmediateOutputStrategy.isSendAttributesUpdatedNotification(); + return updateAttributesOnlyOnValueChangeChanged || sendAttributesUpdatedNotificationUpdated; + } + + return false; + } + public boolean hasContextOnlyChanges(CalculatedFieldCtx other) { // has changes that do not require state reinit and will be picked up by the state on the fly if (calculatedField.getConfiguration() instanceof ExpressionBasedCalculatedFieldConfiguration && !Objects.equals(expression, other.expression)) { return true; } - if (!Objects.equals(output, other.output)) { + if (hasOutputChanges(other.output)) { return true; } if (calculatedField.getConfiguration() instanceof SimpleCalculatedFieldConfiguration thisConfig - && other.calculatedField.getConfiguration() instanceof SimpleCalculatedFieldConfiguration otherConfig - && thisConfig.isUseLatestTs() != otherConfig.isUseLatestTs()) { + && other.calculatedField.getConfiguration() instanceof SimpleCalculatedFieldConfiguration otherConfig + && thisConfig.isUseLatestTs() != otherConfig.isUseLatestTs()) { return true; } if (cfType == CalculatedFieldType.ALARM) { @@ -654,14 +682,14 @@ public class CalculatedFieldCtx implements Closeable { return true; } if (calculatedField.getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration thisConfig - && other.getCalculatedField().getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration otherConfig - && (thisConfig.getDeduplicationIntervalInSec() != otherConfig.getDeduplicationIntervalInSec() + && other.getCalculatedField().getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration otherConfig + && (thisConfig.getDeduplicationIntervalInSec() != otherConfig.getDeduplicationIntervalInSec() || !thisConfig.getMetrics().equals(otherConfig.getMetrics()) || thisConfig.isUseLatestTs() != otherConfig.isUseLatestTs())) { return true; } if (calculatedField.getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration thisConfig - && other.getCalculatedField().getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration otherConfig) { + && other.getCalculatedField().getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration otherConfig) { boolean metricsChanged = !Objects.equals(thisConfig.getMetrics(), otherConfig.getMetrics()); boolean watermarkChanged = !Objects.equals(thisConfig.getWatermark(), otherConfig.getWatermark()); return metricsChanged || watermarkChanged; @@ -695,9 +723,47 @@ public class CalculatedFieldCtx implements Closeable { return false; } + private boolean hasOutputChanges(Output otherOutput) { + if (!output.getType().equals(otherOutput.getType())) { + return true; + } + if (!output.getName().equals(otherOutput.getName())) { + return true; + } + if (output.getScope() != (otherOutput.getScope())) { + return true; + } + if (!Objects.equals(output.getDecimalsByDefault(), otherOutput.getDecimalsByDefault())) { + return true; + } + + var thisOutputStrategy = output.getStrategy(); + var otherOutputStrategy = otherOutput.getStrategy(); + + if (thisOutputStrategy instanceof TimeSeriesImmediateOutputStrategy thisTimeSeriesImmediateOutputStrategy + && otherOutputStrategy instanceof TimeSeriesImmediateOutputStrategy otherTimeSeriesImmediateOutputStrategy) { + boolean saveTimeSeriesUpdated = thisTimeSeriesImmediateOutputStrategy.isSaveTimeSeries() != otherTimeSeriesImmediateOutputStrategy.isSaveTimeSeries(); + boolean saveLatestUpdated = thisTimeSeriesImmediateOutputStrategy.isSaveLatest() != otherTimeSeriesImmediateOutputStrategy.isSaveLatest(); + boolean sendWsUpdateUpdated = thisTimeSeriesImmediateOutputStrategy.isSendWsUpdate() != otherTimeSeriesImmediateOutputStrategy.isSendWsUpdate(); + boolean processCfsUpdated = thisTimeSeriesImmediateOutputStrategy.isProcessCfs() != otherTimeSeriesImmediateOutputStrategy.isProcessCfs(); + return saveTimeSeriesUpdated || saveLatestUpdated || sendWsUpdateUpdated || processCfsUpdated; + } + + if (thisOutputStrategy instanceof AttributesImmediateOutputStrategy thisAttributesImmediateOutputStrategy + && otherOutputStrategy instanceof AttributesImmediateOutputStrategy otherAttributesImmediateOutputStrategy) { + + boolean saveTimeSeriesUpdated = thisAttributesImmediateOutputStrategy.isSaveAttribute() != otherAttributesImmediateOutputStrategy.isSaveAttribute(); + boolean sendWsUpdateUpdated = thisAttributesImmediateOutputStrategy.isSendWsUpdate() != otherAttributesImmediateOutputStrategy.isSendWsUpdate(); + boolean processCfsUpdated = thisAttributesImmediateOutputStrategy.isProcessCfs() != otherAttributesImmediateOutputStrategy.isProcessCfs(); + return saveTimeSeriesUpdated || sendWsUpdateUpdated || processCfsUpdated; + } + + return false; + } + private boolean hasGeofencingZoneGroupConfigurationChanges(CalculatedFieldCtx other) { if (calculatedField.getConfiguration() instanceof GeofencingCalculatedFieldConfiguration thisConfig - && other.calculatedField.getConfiguration() instanceof GeofencingCalculatedFieldConfiguration otherConfig) { + && other.calculatedField.getConfiguration() instanceof GeofencingCalculatedFieldConfiguration otherConfig) { return !thisConfig.getZoneGroups().equals(otherConfig.getZoneGroups()); } return false; @@ -705,7 +771,7 @@ public class CalculatedFieldCtx implements Closeable { private boolean hasRelatedEntitiesAggregationConfigurationChanges(CalculatedFieldCtx other) { if (calculatedField.getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration thisConfig - && other.calculatedField.getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration otherConfig) { + && other.calculatedField.getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration otherConfig) { return !thisConfig.getRelation().equals(otherConfig.getRelation()); } return false; @@ -713,7 +779,7 @@ public class CalculatedFieldCtx implements Closeable { private boolean hasEntityAggregationConfigurationChanges(CalculatedFieldCtx other) { if (calculatedField.getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration thisConfig - && other.calculatedField.getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration otherConfig) { + && other.calculatedField.getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration otherConfig) { return !thisConfig.getInterval().equals(otherConfig.getInterval()); } return false; @@ -738,7 +804,7 @@ public class CalculatedFieldCtx implements Closeable { yield true; } yield geofencingState.getLastDynamicArgumentsRefreshTs() < - System.currentTimeMillis() - scheduledUpdateIntervalMillis; + System.currentTimeMillis() - scheduledUpdateIntervalMillis; } default -> false; }; @@ -782,10 +848,10 @@ public class CalculatedFieldCtx implements Closeable { @Override public String toString() { return "CalculatedFieldCtx{" + - "cfId=" + cfId + - ", cfType=" + cfType + - ", entityId=" + entityId + - '}'; + "cfId=" + cfId + + ", cfType=" + cfType + + ", entityId=" + entityId + + '}'; } } From 607287b3ac815d472a5b24e5e824660139e6f365 Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Mon, 8 Dec 2025 16:29:31 +0200 Subject: [PATCH 2/4] moved check for intermediate result to hasRefreshContextOnlyChanges --- .../cf/ctx/state/CalculatedFieldCtx.java | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java index f6c577545e..d2bc8d30cf 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java @@ -639,26 +639,34 @@ public class CalculatedFieldCtx implements Closeable { } public boolean hasRefreshContextOnlyChanges(CalculatedFieldCtx other) { // has changes that do not require state recalculation - var thisConfig = calculatedField.getConfiguration(); - var otherConfig = other.getCalculatedField().getConfiguration(); - - var thisOutputStrategy = thisConfig.getOutput().getStrategy(); - var otherOutputStrategy = otherConfig.getOutput().getStrategy(); + var thisOutputStrategy = calculatedField.getConfiguration().getOutput().getStrategy(); + var otherOutputStrategy = other.getCalculatedField().getConfiguration().getOutput().getStrategy(); if (!thisOutputStrategy.getType().equals(otherOutputStrategy.getType())) { return true; } - if (thisOutputStrategy instanceof TimeSeriesImmediateOutputStrategy thisTimeSeriesImmediateOutputStrategy - && otherOutputStrategy instanceof TimeSeriesImmediateOutputStrategy otherTimeSeriesImmediateOutputStrategy) { - return thisTimeSeriesImmediateOutputStrategy.getTtl() != otherTimeSeriesImmediateOutputStrategy.getTtl(); + if (thisOutputStrategy instanceof TimeSeriesImmediateOutputStrategy thisTsOutputStrategy + && otherOutputStrategy instanceof TimeSeriesImmediateOutputStrategy otherTsOutputStrategy) { + if (thisTsOutputStrategy.getTtl() != otherTsOutputStrategy.getTtl()) { + return true; + } } - if (thisOutputStrategy instanceof AttributesImmediateOutputStrategy thisAttributesImmediateOutputStrategy - && otherOutputStrategy instanceof AttributesImmediateOutputStrategy otherAttributesImmediateOutputStrategy) { - boolean updateAttributesOnlyOnValueChangeChanged = thisAttributesImmediateOutputStrategy.isUpdateAttributesOnlyOnValueChange() != otherAttributesImmediateOutputStrategy.isUpdateAttributesOnlyOnValueChange(); - boolean sendAttributesUpdatedNotificationUpdated = thisAttributesImmediateOutputStrategy.isSendAttributesUpdatedNotification() != otherAttributesImmediateOutputStrategy.isSendAttributesUpdatedNotification(); - return updateAttributesOnlyOnValueChangeChanged || sendAttributesUpdatedNotificationUpdated; + if (thisOutputStrategy instanceof AttributesImmediateOutputStrategy thisAttrOutputStrategy + && otherOutputStrategy instanceof AttributesImmediateOutputStrategy otherAttrOutputStrategy) { + boolean updateAttrOnValueChangedChanged = thisAttrOutputStrategy.isUpdateAttributesOnlyOnValueChange() != otherAttrOutputStrategy.isUpdateAttributesOnlyOnValueChange(); + boolean sendAttrUpdatedNotificationChanged = thisAttrOutputStrategy.isSendAttributesUpdatedNotification() != otherAttrOutputStrategy.isSendAttributesUpdatedNotification(); + if (updateAttrOnValueChangedChanged || sendAttrUpdatedNotificationChanged) { + return true; + } + } + + if (calculatedField.getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration thisConfig + && other.getCalculatedField().getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration otherConfig) { + if (thisConfig.isProduceIntermediateResult() != otherConfig.isProduceIntermediateResult()) { + return true; + } } return false; @@ -705,8 +713,9 @@ public class CalculatedFieldCtx implements Closeable { && other.getCalculatedField().getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration otherConfig) { boolean metricsChanged = !Objects.equals(thisConfig.getMetrics(), otherConfig.getMetrics()); boolean watermarkChanged = !Objects.equals(thisConfig.getWatermark(), otherConfig.getWatermark()); - boolean produceIntermediateResultChanged = thisConfig.isProduceIntermediateResult() != otherConfig.isProduceIntermediateResult(); - return metricsChanged || watermarkChanged || produceIntermediateResultChanged; + if (metricsChanged || watermarkChanged) { + return true; + } } return false; } @@ -741,7 +750,7 @@ public class CalculatedFieldCtx implements Closeable { if (!output.getType().equals(otherOutput.getType())) { return true; } - if (!output.getName().equals(otherOutput.getName())) { + if (!Objects.equals(output.getName(), otherOutput.getName())) { return true; } if (output.getScope() != (otherOutput.getScope())) { From 530f9dcb90c2425232f69e134934d84b8f3e8056 Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Mon, 8 Dec 2025 17:04:16 +0200 Subject: [PATCH 3/4] refactoring --- .../cf/ctx/state/CalculatedFieldCtx.java | 44 ++----------------- .../AttributesImmediateOutputStrategy.java | 22 ++++++++++ .../AttributesRuleChainOutputStrategy.java | 11 +++++ .../data/cf/configuration/OutputStrategy.java | 6 +++ .../TimeSeriesImmediateOutputStrategy.java | 21 +++++++++ .../TimeSeriesRuleChainOutputStrategy.java | 11 +++++ 6 files changed, 74 insertions(+), 41 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java index d2bc8d30cf..7e5d666ca5 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java @@ -37,14 +37,12 @@ import org.thingsboard.server.common.data.cf.configuration.AlarmCalculatedFieldC 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.ArgumentsBasedCalculatedFieldConfiguration; -import org.thingsboard.server.common.data.cf.configuration.AttributesImmediateOutputStrategy; import org.thingsboard.server.common.data.cf.configuration.ExpressionBasedCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.Output; import org.thingsboard.server.common.data.cf.configuration.PropagationCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey; import org.thingsboard.server.common.data.cf.configuration.ScheduledUpdateSupportedCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration; -import org.thingsboard.server.common.data.cf.configuration.TimeSeriesImmediateOutputStrategy; import org.thingsboard.server.common.data.cf.configuration.aggregation.AggFunctionInput; import org.thingsboard.server.common.data.cf.configuration.aggregation.RelatedEntitiesAggregationCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.aggregation.single.EntityAggregationCalculatedFieldConfiguration; @@ -641,27 +639,10 @@ public class CalculatedFieldCtx implements Closeable { public boolean hasRefreshContextOnlyChanges(CalculatedFieldCtx other) { // has changes that do not require state recalculation var thisOutputStrategy = calculatedField.getConfiguration().getOutput().getStrategy(); var otherOutputStrategy = other.getCalculatedField().getConfiguration().getOutput().getStrategy(); - - if (!thisOutputStrategy.getType().equals(otherOutputStrategy.getType())) { + if (thisOutputStrategy.hasRefreshContextOnlyChanges(otherOutputStrategy)) { return true; } - if (thisOutputStrategy instanceof TimeSeriesImmediateOutputStrategy thisTsOutputStrategy - && otherOutputStrategy instanceof TimeSeriesImmediateOutputStrategy otherTsOutputStrategy) { - if (thisTsOutputStrategy.getTtl() != otherTsOutputStrategy.getTtl()) { - return true; - } - } - - if (thisOutputStrategy instanceof AttributesImmediateOutputStrategy thisAttrOutputStrategy - && otherOutputStrategy instanceof AttributesImmediateOutputStrategy otherAttrOutputStrategy) { - boolean updateAttrOnValueChangedChanged = thisAttrOutputStrategy.isUpdateAttributesOnlyOnValueChange() != otherAttrOutputStrategy.isUpdateAttributesOnlyOnValueChange(); - boolean sendAttrUpdatedNotificationChanged = thisAttrOutputStrategy.isSendAttributesUpdatedNotification() != otherAttrOutputStrategy.isSendAttributesUpdatedNotification(); - if (updateAttrOnValueChangedChanged || sendAttrUpdatedNotificationChanged) { - return true; - } - } - if (calculatedField.getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration thisConfig && other.getCalculatedField().getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration otherConfig) { if (thisConfig.isProduceIntermediateResult() != otherConfig.isProduceIntermediateResult()) { @@ -759,28 +740,9 @@ public class CalculatedFieldCtx implements Closeable { if (!Objects.equals(output.getDecimalsByDefault(), otherOutput.getDecimalsByDefault())) { return true; } - - var thisOutputStrategy = output.getStrategy(); - var otherOutputStrategy = otherOutput.getStrategy(); - - if (thisOutputStrategy instanceof TimeSeriesImmediateOutputStrategy thisTimeSeriesImmediateOutputStrategy - && otherOutputStrategy instanceof TimeSeriesImmediateOutputStrategy otherTimeSeriesImmediateOutputStrategy) { - boolean saveTimeSeriesUpdated = thisTimeSeriesImmediateOutputStrategy.isSaveTimeSeries() != otherTimeSeriesImmediateOutputStrategy.isSaveTimeSeries(); - boolean saveLatestUpdated = thisTimeSeriesImmediateOutputStrategy.isSaveLatest() != otherTimeSeriesImmediateOutputStrategy.isSaveLatest(); - boolean sendWsUpdateUpdated = thisTimeSeriesImmediateOutputStrategy.isSendWsUpdate() != otherTimeSeriesImmediateOutputStrategy.isSendWsUpdate(); - boolean processCfsUpdated = thisTimeSeriesImmediateOutputStrategy.isProcessCfs() != otherTimeSeriesImmediateOutputStrategy.isProcessCfs(); - return saveTimeSeriesUpdated || saveLatestUpdated || sendWsUpdateUpdated || processCfsUpdated; - } - - if (thisOutputStrategy instanceof AttributesImmediateOutputStrategy thisAttributesImmediateOutputStrategy - && otherOutputStrategy instanceof AttributesImmediateOutputStrategy otherAttributesImmediateOutputStrategy) { - - boolean saveTimeSeriesUpdated = thisAttributesImmediateOutputStrategy.isSaveAttribute() != otherAttributesImmediateOutputStrategy.isSaveAttribute(); - boolean sendWsUpdateUpdated = thisAttributesImmediateOutputStrategy.isSendWsUpdate() != otherAttributesImmediateOutputStrategy.isSendWsUpdate(); - boolean processCfsUpdated = thisAttributesImmediateOutputStrategy.isProcessCfs() != otherAttributesImmediateOutputStrategy.isProcessCfs(); - return saveTimeSeriesUpdated || sendWsUpdateUpdated || processCfsUpdated; + if (output.getStrategy().hasContextOnlyChanges(otherOutput.getStrategy())) { + return true; } - return false; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/AttributesImmediateOutputStrategy.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/AttributesImmediateOutputStrategy.java index 714180930d..81874002cb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/AttributesImmediateOutputStrategy.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/AttributesImmediateOutputStrategy.java @@ -35,4 +35,26 @@ public class AttributesImmediateOutputStrategy implements AttributesOutputStrate public OutputStrategyType getType() { return OutputStrategyType.IMMEDIATE; } + + @Override + public boolean hasContextOnlyChanges(OutputStrategy other) { + if (!(other instanceof AttributesImmediateOutputStrategy otherStrategy)) { + return true; + } + boolean saveTimeSeriesUpdated = saveAttribute != otherStrategy.isSaveAttribute(); + boolean sendWsUpdateUpdated = sendWsUpdate != otherStrategy.isSendWsUpdate(); + boolean processCfsUpdated = processCfs != otherStrategy.isProcessCfs(); + return saveTimeSeriesUpdated || sendWsUpdateUpdated || processCfsUpdated; + } + + @Override + public boolean hasRefreshContextOnlyChanges(OutputStrategy other) { + if (!(other instanceof AttributesImmediateOutputStrategy otherStrategy)) { + return true; + } + boolean updateAttrOnValueChangedChanged = updateAttributesOnlyOnValueChange != otherStrategy.isUpdateAttributesOnlyOnValueChange(); + boolean sendAttrUpdatedNotificationChanged = sendAttributesUpdatedNotification != otherStrategy.isSendAttributesUpdatedNotification(); + return updateAttrOnValueChangedChanged || sendAttrUpdatedNotificationChanged; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/AttributesRuleChainOutputStrategy.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/AttributesRuleChainOutputStrategy.java index 1a3348ce74..f39efadb86 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/AttributesRuleChainOutputStrategy.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/AttributesRuleChainOutputStrategy.java @@ -26,4 +26,15 @@ public class AttributesRuleChainOutputStrategy implements AttributesOutputStrate public OutputStrategyType getType() { return OutputStrategyType.RULE_CHAIN; } + + @Override + public boolean hasContextOnlyChanges(OutputStrategy other) { + return !(other instanceof AttributesRuleChainOutputStrategy); + } + + @Override + public boolean hasRefreshContextOnlyChanges(OutputStrategy other) { + return false; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/OutputStrategy.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/OutputStrategy.java index b635047ae3..252af82ef7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/OutputStrategy.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/OutputStrategy.java @@ -22,4 +22,10 @@ public interface OutputStrategy { @JsonIgnore OutputStrategyType getType(); + @JsonIgnore + boolean hasContextOnlyChanges(OutputStrategy other); + + @JsonIgnore + boolean hasRefreshContextOnlyChanges(OutputStrategy other); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/TimeSeriesImmediateOutputStrategy.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/TimeSeriesImmediateOutputStrategy.java index 31b15095d4..a24bfc8683 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/TimeSeriesImmediateOutputStrategy.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/TimeSeriesImmediateOutputStrategy.java @@ -35,4 +35,25 @@ public class TimeSeriesImmediateOutputStrategy implements TimeSeriesOutputStrate public OutputStrategyType getType() { return OutputStrategyType.IMMEDIATE; } + + @Override + public boolean hasContextOnlyChanges(OutputStrategy other) { + if (!(other instanceof TimeSeriesImmediateOutputStrategy otherStrategy)) { + return true; + } + boolean saveTimeSeriesUpdated = saveTimeSeries != otherStrategy.isSaveTimeSeries(); + boolean saveLatestUpdated = saveLatest != otherStrategy.isSaveLatest(); + boolean sendWsUpdateUpdated = sendWsUpdate != otherStrategy.isSendWsUpdate(); + boolean processCfsUpdated = processCfs != otherStrategy.isProcessCfs(); + return saveTimeSeriesUpdated || saveLatestUpdated || sendWsUpdateUpdated || processCfsUpdated; + } + + @Override + public boolean hasRefreshContextOnlyChanges(OutputStrategy other) { + if (!(other instanceof TimeSeriesImmediateOutputStrategy otherStrategy)) { + return true; + } + return ttl != otherStrategy.getTtl(); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/TimeSeriesRuleChainOutputStrategy.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/TimeSeriesRuleChainOutputStrategy.java index 7786efff1a..0b17594869 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/TimeSeriesRuleChainOutputStrategy.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/TimeSeriesRuleChainOutputStrategy.java @@ -26,4 +26,15 @@ public class TimeSeriesRuleChainOutputStrategy implements TimeSeriesOutputStrate public OutputStrategyType getType() { return OutputStrategyType.RULE_CHAIN; } + + @Override + public boolean hasContextOnlyChanges(OutputStrategy other) { + return !(other instanceof TimeSeriesRuleChainOutputStrategy); + } + + @Override + public boolean hasRefreshContextOnlyChanges(OutputStrategy other) { + return false; + } + } From c532cbaa5e0fe0bb94e07016a144dfdfc3e722e4 Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Tue, 9 Dec 2025 10:04:41 +0200 Subject: [PATCH 4/4] added tests --- .../cf/ctx/state/CalculatedFieldCtx.java | 21 +- .../common/data/cf/configuration/Output.java | 21 ++ .../data/cf/configuration/OutputStrategy.java | 2 - .../CalculatedFieldOutputTest.java | 202 ++++++++++++++++++ 4 files changed, 224 insertions(+), 22 deletions(-) create mode 100644 common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/CalculatedFieldOutputTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java index 7e5d666ca5..7f515a5c9a 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java @@ -657,7 +657,7 @@ public class CalculatedFieldCtx implements Closeable { if (calculatedField.getConfiguration() instanceof ExpressionBasedCalculatedFieldConfiguration && !Objects.equals(expression, other.expression)) { return true; } - if (hasOutputChanges(other.output)) { + if (output.hasContextOnlyChanges(other.output)) { return true; } if (calculatedField.getConfiguration() instanceof SimpleCalculatedFieldConfiguration thisConfig @@ -727,25 +727,6 @@ public class CalculatedFieldCtx implements Closeable { return false; } - private boolean hasOutputChanges(Output otherOutput) { - if (!output.getType().equals(otherOutput.getType())) { - return true; - } - if (!Objects.equals(output.getName(), otherOutput.getName())) { - return true; - } - if (output.getScope() != (otherOutput.getScope())) { - return true; - } - if (!Objects.equals(output.getDecimalsByDefault(), otherOutput.getDecimalsByDefault())) { - return true; - } - if (output.getStrategy().hasContextOnlyChanges(otherOutput.getStrategy())) { - return true; - } - return false; - } - private boolean hasGeofencingZoneGroupConfigurationChanges(CalculatedFieldCtx other) { if (calculatedField.getConfiguration() instanceof GeofencingCalculatedFieldConfiguration thisConfig && other.calculatedField.getConfiguration() instanceof GeofencingCalculatedFieldConfiguration otherConfig) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/Output.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/Output.java index 848ad759e8..fe85ff03e3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/Output.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/Output.java @@ -22,6 +22,8 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.thingsboard.server.common.data.AttributeScope; +import java.util.Objects; + @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, @@ -50,4 +52,23 @@ public interface Output { void setDecimalsByDefault(Integer decimalsByDefault); + default boolean hasContextOnlyChanges(Output other) { + if (!getType().equals(other.getType())) { + return true; + } + if (!Objects.equals(getName(), other.getName())) { + return true; + } + if (getScope() != (other.getScope())) { + return true; + } + if (!Objects.equals(getDecimalsByDefault(), other.getDecimalsByDefault())) { + return true; + } + if (getStrategy().hasContextOnlyChanges(other.getStrategy())) { + return true; + } + return false; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/OutputStrategy.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/OutputStrategy.java index 252af82ef7..66b156ca8a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/OutputStrategy.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/OutputStrategy.java @@ -22,10 +22,8 @@ public interface OutputStrategy { @JsonIgnore OutputStrategyType getType(); - @JsonIgnore boolean hasContextOnlyChanges(OutputStrategy other); - @JsonIgnore boolean hasRefreshContextOnlyChanges(OutputStrategy other); } diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/CalculatedFieldOutputTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/CalculatedFieldOutputTest.java new file mode 100644 index 0000000000..52afa47fbd --- /dev/null +++ b/common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/CalculatedFieldOutputTest.java @@ -0,0 +1,202 @@ +/** + * 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.cf.configuration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.server.common.data.AttributeScope; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +@ExtendWith(MockitoExtension.class) +public class CalculatedFieldOutputTest { + + @Test + public void testHasContextOnlyChanges_whenTypeChanged_shouldReturnTrue() { + TimeSeriesOutput output = new TimeSeriesOutput(); + AttributesOutput newOutput = new AttributesOutput(); + + assertThat(output.hasContextOnlyChanges(newOutput)).isTrue(); + } + + @Test + public void testHasContextOnlyChanges_whenNameChanged_shouldReturnTrue() { + TimeSeriesOutput output = new TimeSeriesOutput(); + TimeSeriesOutput newOutput = new TimeSeriesOutput(); + newOutput.setName("new"); + + assertThat(output.hasContextOnlyChanges(newOutput)).isTrue(); + } + + @Test + public void testHasContextOnlyChanges_whenScopeChanged_shouldReturnTrue() { + AttributesOutput output = new AttributesOutput(); + output.setScope(AttributeScope.SHARED_SCOPE); + AttributesOutput newOutput = new AttributesOutput(); + newOutput.setScope(AttributeScope.SERVER_SCOPE); + + assertThat(output.hasContextOnlyChanges(newOutput)).isTrue(); + } + + @Test + public void testHasContextOnlyChanges_whenDecimalsByDefaultChanged_shouldReturnTrue() { + AttributesOutput output = new AttributesOutput(); + AttributesOutput newOutput = new AttributesOutput(); + newOutput.setDecimalsByDefault(2); + + assertThat(output.hasContextOnlyChanges(newOutput)).isTrue(); + } + + @Test + public void testHasContextOnlyChanges_whenStrategyHasContextOnlyChanges_shouldReturnTrue() { + AttributesOutputStrategy outputStrategy = mock(AttributesRuleChainOutputStrategy.class); + given(outputStrategy.hasContextOnlyChanges(any())).willReturn(true); + + AttributesOutput output = new AttributesOutput(); + output.setStrategy(outputStrategy); + AttributesOutput newOutput = new AttributesOutput(); + + assertThat(output.hasContextOnlyChanges(newOutput)).isTrue(); + } + + @Test + public void testHasContextOnlyChanges_whenStrategyDoesNotHaveContextOnlyChanges_shouldReturnTrue() { + AttributesOutputStrategy outputStrategy = mock(AttributesRuleChainOutputStrategy.class); + given(outputStrategy.hasContextOnlyChanges(any())).willReturn(false); + + AttributesOutput output = new AttributesOutput(); + output.setStrategy(outputStrategy); + AttributesOutput newOutput = new AttributesOutput(); + + assertThat(output.hasContextOnlyChanges(newOutput)).isFalse(); + } + + /* .hasContextOnlyChanges() tests*/ + + @Test + public void testAttributesImmediateOutputStrategyHasContextOnlyChanges_whenTypeChanged_shouldReturnTrue() { + AttributesImmediateOutputStrategy strategy = new AttributesImmediateOutputStrategy(true, true, false, true, true); + AttributesRuleChainOutputStrategy newStrategy = new AttributesRuleChainOutputStrategy(); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isTrue(); + } + + @Test + public void testAttributesImmediateOutputStrategyHasContextOnlyChanges_whenSaveAttributesChanged_shouldReturnTrue() { + AttributesImmediateOutputStrategy strategy = new AttributesImmediateOutputStrategy(true, true, false, true, true); + AttributesImmediateOutputStrategy newStrategy = new AttributesImmediateOutputStrategy(true, true, true, true, true); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isTrue(); + } + + @Test + public void testAttributesImmediateOutputStrategyHasContextOnlyChanges_whenSendWsUpdateChanged_shouldReturnTrue() { + AttributesImmediateOutputStrategy strategy = new AttributesImmediateOutputStrategy(true, true, true, false, true); + AttributesImmediateOutputStrategy newStrategy = new AttributesImmediateOutputStrategy(true, true, true, true, true); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isTrue(); + } + + @Test + public void testAttributesImmediateOutputStrategyHasContextOnlyChanges_whenProcessCfsChanged_shouldReturnTrue() { + AttributesImmediateOutputStrategy strategy = new AttributesImmediateOutputStrategy(true, true, true, false, false); + AttributesImmediateOutputStrategy newStrategy = new AttributesImmediateOutputStrategy(true, true, true, false, true); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isTrue(); + } + + @Test + public void testAttributesRuleChainOutputStrategyHasContextOnlyChanges_whenTypeChanged_shouldReturnTrue() { + AttributesRuleChainOutputStrategy strategy = new AttributesRuleChainOutputStrategy(); + AttributesImmediateOutputStrategy newStrategy = new AttributesImmediateOutputStrategy(true, true, false, true, true); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isTrue(); + } + + @Test + public void testTimeSeriesImmediateOutputStrategyHasContextOnlyChanges_whenTypeChanged_shouldReturnTrue() { + TimeSeriesImmediateOutputStrategy strategy = new TimeSeriesImmediateOutputStrategy(0, false, true, true, true); + TimeSeriesRuleChainOutputStrategy newStrategy = new TimeSeriesRuleChainOutputStrategy(); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isTrue(); + } + + @Test + public void testTimeSeriesImmediateOutputStrategyHasContextOnlyChanges_whenSaveLatestChanged_shouldReturnTrue() { + TimeSeriesImmediateOutputStrategy strategy = new TimeSeriesImmediateOutputStrategy(0, false, false, true, true); + TimeSeriesImmediateOutputStrategy newStrategy = new TimeSeriesImmediateOutputStrategy(0, false, true, true, true); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isTrue(); + } + + @Test + public void testTimeSeriesImmediateOutputStrategyHasContextOnlyChanges_whenSendWsUpdateChanged_shouldReturnTrue() { + TimeSeriesImmediateOutputStrategy strategy = new TimeSeriesImmediateOutputStrategy(0, true, true, false, true); + TimeSeriesImmediateOutputStrategy newStrategy = new TimeSeriesImmediateOutputStrategy(0, true, true, true, true); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isTrue(); + } + + @Test + public void testTimeSeriesImmediateOutputStrategyHasContextOnlyChanges_whenProcessCfsChanged_shouldReturnTrue() { + TimeSeriesImmediateOutputStrategy strategy = new TimeSeriesImmediateOutputStrategy(0, true, true, true, false); + TimeSeriesImmediateOutputStrategy newStrategy = new TimeSeriesImmediateOutputStrategy(0, true, true, true, true); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isTrue(); + } + + @Test + public void testTimeSeriesRuleChainOutputStrategyHasContextOnlyChanges_whenProcessCfsChanged_shouldReturnTrue() { + TimeSeriesRuleChainOutputStrategy strategy = new TimeSeriesRuleChainOutputStrategy(); + TimeSeriesImmediateOutputStrategy newStrategy = new TimeSeriesImmediateOutputStrategy(0, true, true, true, false); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isTrue(); + } + + /* .hasRefreshContextOnlyChanges() tests*/ + + @Test + public void testAttributesImmediateOutputStrategyHasRefreshContextOnlyChanges_whenUpdateAttrOnValueChangedChanged_shouldReturnTrue() { + AttributesImmediateOutputStrategy strategy = new AttributesImmediateOutputStrategy(true, false, true, false, true); + AttributesImmediateOutputStrategy newStrategy = new AttributesImmediateOutputStrategy(true, true, true, false, true); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isFalse(); + assertThat(strategy.hasRefreshContextOnlyChanges(newStrategy)).isTrue(); + } + + @Test + public void testAttributesImmediateOutputStrategyHasRefreshContextOnlyChanges_whenSendAttrUpdatedNotificationChanged_shouldReturnTrue() { + AttributesImmediateOutputStrategy strategy = new AttributesImmediateOutputStrategy(false, true, true, false, true); + AttributesImmediateOutputStrategy newStrategy = new AttributesImmediateOutputStrategy(true, true, true, false, true); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isFalse(); + assertThat(strategy.hasRefreshContextOnlyChanges(newStrategy)).isTrue(); + } + + @Test + public void testTimeSeriesImmediateOutputStrategyHasRefreshContextOnlyChanges_whenTtlChanged_shouldReturnTrue() { + TimeSeriesImmediateOutputStrategy strategy = new TimeSeriesImmediateOutputStrategy(0, true, true, true, true); + TimeSeriesImmediateOutputStrategy newStrategy = new TimeSeriesImmediateOutputStrategy(300, true, true, true, true); + + assertThat(strategy.hasContextOnlyChanges(newStrategy)).isFalse(); + assertThat(strategy.hasRefreshContextOnlyChanges(newStrategy)).isTrue(); + } + +}