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();
}
}