From 9d97d0c0b5e109d2e653527fbc7102c4674a3c08 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 16 Nov 2023 15:47:25 +0200 Subject: [PATCH] save attributes node: fixed escaped boolean parameters && minor refactoring to notify device logic --- .../install/ThingsboardInstallService.java | 3 + .../install/SqlDatabaseUpgradeService.java | 24 ++++ .../engine/telemetry/TbMsgAttributesNode.java | 40 +++++- .../TbMsgAttributesNodeConfiguration.java | 4 +- .../telemetry/TbMsgAttributesNodeTest.java | 132 ++++++++++++++++-- 5 files changed, 179 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 0730bca8f8..7c8f9de0a8 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -266,6 +266,9 @@ public class ThingsboardInstallService { log.info("Upgrading ThingsBoard from version 3.6.0 to 3.6.1 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.6.0"); dataUpdateService.updateData("3.6.0"); + case "3.6.1": + log.info("Upgrading ThingsBoard from version 3.6.1 to 3.6.2 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.6.1"); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache break; default: diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index c02b7e9cc1..5b1904953b 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -792,6 +792,30 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.error("Failed updating schema!!!", e); } break; + case "3.6.1": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + if (isOldSchema(conn, 3006001)) { + log.info("Updating schema ..."); + try { + conn.createStatement().execute("UPDATE rule_node SET " + + "configuration = (configuration::jsonb || jsonb_build_object(" + + "'notifyDevice', CASE WHEN configuration::jsonb ->> 'notifyDevice' = 'false' THEN false ELSE true END, " + + "'sendAttributesUpdatedNotification', CASE WHEN configuration::jsonb ->> 'sendAttributesUpdatedNotification' = 'true' THEN true ELSE false END, " + + "'updateAttributesOnlyOnValueChange', CASE WHEN configuration::jsonb ->> 'updateAttributesOnlyOnValueChange' = 'true' THEN true ELSE false END" + + ")::jsonb)::varchar, " + + "configuration_version = 2 " + + "WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode' AND configuration_version = 1;"); + } catch (Exception e) { + } + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3006002;"); + log.info("Schema updated to version 3.6.2."); + } else { + log.info("Skip schema re-update to version 3.6.2. Use env flag 'SKIP_SCHEMA_VERSION_CHECK' to force the re-update."); + } + } catch (Exception e) { + log.error("Failed updating schema!!!", e); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 2ebb0193f9..59ff53557b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import java.util.ArrayList; @@ -53,7 +54,7 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R type = ComponentType.ACTION, name = "save attributes", configClazz = TbMsgAttributesNodeConfiguration.class, - version = 1, + version = 2, nodeDescription = "Saves attributes data", nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type. " + "If upsert(update/insert) operation is completed successfully rule node will send the incoming message via Success chain, otherwise, Failure chain is used. " + @@ -66,15 +67,15 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R ) public class TbMsgAttributesNode implements TbNode { + static final String NOTIFY_DEVICE_KEY = "notifyDevice"; + static final String SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY = "sendAttributesUpdatedNotification"; static final String UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY = "updateAttributesOnlyOnValueChange"; + private TbMsgAttributesNodeConfiguration config; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbMsgAttributesNodeConfiguration.class); - if (config.getNotifyDevice() == null) { - config.setNotifyDevice(true); - } } @Override @@ -119,7 +120,7 @@ public class TbMsgAttributesNode implements TbNode { msg.getOriginator(), scope, attributes, - checkNotifyDevice(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY)), + config.isNotifyDevice() || checkNotifyDeviceMdValue(msg.getMetaData()), sendAttributesUpdateNotification ? new AttributesUpdateNodeCallback(ctx, msg, scope, attributes) : new TelemetryNodeCallback(ctx, msg) @@ -148,8 +149,10 @@ public class TbMsgAttributesNode implements TbNode { return config.isSendAttributesUpdatedNotification() && !CLIENT_SCOPE.equals(scope); } - private boolean checkNotifyDevice(String notifyDeviceMdValue) { - return config.getNotifyDevice() || StringUtils.isEmpty(notifyDeviceMdValue) || Boolean.parseBoolean(notifyDeviceMdValue); + private boolean checkNotifyDeviceMdValue(TbMsgMetaData md) { + var notifyDeviceMdStr = md.getValue(NOTIFY_DEVICE_METADATA_KEY); + // Check for empty string for backward-compatibility. A while ago node always notified devices. + return StringUtils.isEmpty(notifyDeviceMdStr) || Boolean.parseBoolean(notifyDeviceMdStr); } private String getScope(String mdScopeValue) { @@ -168,6 +171,13 @@ public class TbMsgAttributesNode implements TbNode { hasChanges = true; ((ObjectNode) oldConfiguration).put(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY, false); } + case 1: + // update notifyDevice. set true if null or property doesn't exist for backward-compatibility. + hasChanges = fixEscapedBooleanConfigParameters(oldConfiguration, NOTIFY_DEVICE_KEY, hasChanges, true); + // update sendAttributesUpdatedNotification. + hasChanges = fixEscapedBooleanConfigParameters(oldConfiguration, SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY, hasChanges, false); + // update updateAttributesOnlyOnValueChange. + hasChanges = fixEscapedBooleanConfigParameters(oldConfiguration, UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY, hasChanges, false); break; default: break; @@ -175,4 +185,20 @@ public class TbMsgAttributesNode implements TbNode { return new TbPair<>(hasChanges, oldConfiguration); } + private static boolean fixEscapedBooleanConfigParameters(JsonNode oldConfiguration, String boolKey, boolean hasChanges, boolean defaultValue) { + if (oldConfiguration.hasNonNull(boolKey)) { + var value = oldConfiguration.get(boolKey); + if (value.isTextual()) { + hasChanges = true; + ((ObjectNode) oldConfiguration) + .put(boolKey, value.asBoolean(defaultValue)); + } + } else { + hasChanges = true; + ((ObjectNode) oldConfiguration) + .put(boolKey, defaultValue); + } + return hasChanges; + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java index 1dd98feb16..d8502e76d9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java @@ -24,7 +24,7 @@ public class TbMsgAttributesNodeConfiguration implements NodeConfiguration upgradeResult = node.upgrade(0, jsonNode); - ObjectNode resultNode = (ObjectNode) upgradeResult.getSecond(); assertThat(upgradeResult.getFirst()).as("upgrade result has changes").isTrue(); - assertThat(resultNode.has(updateAttributesOnlyOnValueChangeKey)).as("upgrade result has key " + updateAttributesOnlyOnValueChangeKey).isTrue(); - assertThat(resultNode.get(updateAttributesOnlyOnValueChangeKey).asBoolean()).as("upgrade result value [false] for key " + updateAttributesOnlyOnValueChangeKey).isFalse(); + + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + assertThat(upgradedConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("upgrade result has key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("upgrade result value [false] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isFalse(); } @Test - void testUpgrade_fromVersion0_alreadyHasupdateAttributesOnlyOnValueChange() throws TbNodeException { + void testUpgrade_fromVersion0_alreadyHasUpdateAttributesOnlyOnValueChange() throws TbNodeException { TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); willCallRealMethod().given(node).upgrade(anyInt(), any()); ObjectNode jsonNode = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); - jsonNode.remove(updateAttributesOnlyOnValueChangeKey); - jsonNode.put(updateAttributesOnlyOnValueChangeKey, true); - assertThat(jsonNode.has(updateAttributesOnlyOnValueChangeKey)).as("pre condition has no " + updateAttributesOnlyOnValueChangeKey).isTrue(); - assertThat(jsonNode.get(updateAttributesOnlyOnValueChangeKey).asBoolean()).as("pre condition has [true] for key " + updateAttributesOnlyOnValueChangeKey).isTrue(); + jsonNode.remove(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY); + jsonNode.put(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY, true); + assertThat(jsonNode.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has no " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + assertThat(jsonNode.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [true] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); TbPair upgradeResult = node.upgrade(0, jsonNode); - ObjectNode resultNode = (ObjectNode) upgradeResult.getSecond(); assertThat(upgradeResult.getFirst()).as("upgrade result has changes").isFalse(); - assertThat(resultNode.has(updateAttributesOnlyOnValueChangeKey)).as("upgrade result has key " + updateAttributesOnlyOnValueChangeKey).isTrue(); - assertThat(resultNode.get(updateAttributesOnlyOnValueChangeKey).asBoolean()).as("upgrade result value [true] for key " + updateAttributesOnlyOnValueChangeKey).isTrue(); + + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + assertThat(upgradedConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("upgrade result has key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("upgrade result value [true] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + } + + @Test + void testUpgrade_fromVersion1_AllFlagsAreBooleans() throws TbNodeException { + TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); + willCallRealMethod().given(node).upgrade(anyInt(), any()); + + ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); + + assertThat(defaultConfig.has(NOTIFY_DEVICE_KEY)).as("pre condition has no" + NOTIFY_DEVICE_KEY).isTrue(); + assertThat(defaultConfig.has(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY)).as("pre condition has no" + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isTrue(); + assertThat(defaultConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has no" + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + + assertThat(defaultConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [true] for key " + NOTIFY_DEVICE_KEY).isFalse(); + assertThat(defaultConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asBoolean()).as("pre condition has [true] for key " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); + assertThat(defaultConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [false] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + + TbPair upgradeResult = node.upgrade(1, defaultConfig); + + assertThat(upgradeResult.getFirst()).as("upgrade result has changes").isFalse(); + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + assertThat(upgradedConfig).as("upgraded config has changes").isEqualTo(defaultConfig); + } + + @Test + void testUpgrade_fromVersion1_NoFlagsSet() throws TbNodeException { + TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); + willCallRealMethod().given(node).upgrade(anyInt(), any()); + + ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); + defaultConfig.remove(NOTIFY_DEVICE_KEY); + defaultConfig.remove(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY); + defaultConfig.remove(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY); + + assertThat(defaultConfig.has(NOTIFY_DEVICE_KEY)).as("pre condition has " + NOTIFY_DEVICE_KEY).isFalse(); + assertThat(defaultConfig.has(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY)).as("pre condition has " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); + assertThat(defaultConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isFalse(); + + TbPair upgradeResult = node.upgrade(1, defaultConfig); + + assertThat(upgradeResult.getFirst()).as("upgrade result has no changes").isTrue(); + + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [false] for key " + NOTIFY_DEVICE_KEY).isTrue(); + assertThat(upgradedConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asBoolean()).as("pre condition has [true] for key " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); + assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [true] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isFalse(); + } + + @Test + void testUpgrade_fromVersion1_AllFlagsAreBooleanStrings() throws TbNodeException { + TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); + willCallRealMethod().given(node).upgrade(anyInt(), any()); + + ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); + defaultConfig.put(NOTIFY_DEVICE_KEY, defaultConfig.get(NOTIFY_DEVICE_KEY).asText()); + defaultConfig.put(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY, defaultConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asText()); + defaultConfig.put(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY, defaultConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asText()); + + assertThat(defaultConfig.has(NOTIFY_DEVICE_KEY)).as("pre condition has no " + NOTIFY_DEVICE_KEY).isTrue(); + assertThat(defaultConfig.has(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY)).as("pre condition has no " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isTrue(); + assertThat(defaultConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has no " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + + assertThat(defaultConfig.get(NOTIFY_DEVICE_KEY).isTextual()).as("pre condition " + NOTIFY_DEVICE_KEY + " is not textual").isTrue(); + assertThat(defaultConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isTextual()).as("pre condition " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY + " is not textual").isTrue(); + assertThat(defaultConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTextual()).as("pre condition " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY + " is not textual").isTrue(); + + + TbPair upgradeResult = node.upgrade(1, defaultConfig); + + assertThat(upgradeResult.getFirst()).as("upgrade result has no changes").isTrue(); + + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + + assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).isBoolean()).as("pre condition " + NOTIFY_DEVICE_KEY + " is not boolean").isTrue(); + assertThat(upgradedConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isBoolean()).as("pre condition " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY + " is not boolean").isTrue(); + assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isBoolean()).as("pre condition " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY + " is not boolean").isTrue(); + + assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [true] for key " + NOTIFY_DEVICE_KEY).isFalse(); + assertThat(upgradedConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asBoolean()).as("pre condition has [true] for key " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); + assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [false] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + } + + @Test + void testUpgrade_fromVersion1_NotifyDeviceFlagIsNull() throws TbNodeException { + TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); + willCallRealMethod().given(node).upgrade(anyInt(), any()); + + ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); + defaultConfig.set(NOTIFY_DEVICE_KEY, NullNode.instance); + + assertThat(defaultConfig.has(NOTIFY_DEVICE_KEY)).as("pre condition has no " + NOTIFY_DEVICE_KEY).isTrue(); + + TbPair upgradeResult = node.upgrade(1, defaultConfig); + + assertThat(upgradeResult.getFirst()).as("upgrade result has no changes").isTrue(); + + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [false] or [null] for key " + NOTIFY_DEVICE_KEY).isTrue(); } }