Browse Source

Merge d5ced6ad22 into 9abbcdb40a

pull/14725/merge
Copilot 3 days ago
committed by GitHub
parent
commit
4fa84ef8a2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      application/src/main/resources/thingsboard.yml
  2. 11
      common/data/src/main/java/org/thingsboard/server/common/data/DynamicProtoUtils.java
  3. 112
      common/data/src/test/java/org/thingsboard/server/common/data/DynamicProtoUtilsTest.java
  4. 5
      transport/coap/src/main/resources/tb-coap-transport.yml
  5. 5
      transport/http/src/main/resources/tb-http-transport.yml
  6. 5
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
  7. 5
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  8. 5
      transport/snmp/src/main/resources/tb-snmp-transport.yml

5
application/src/main/resources/thingsboard.yml

@ -1182,6 +1182,11 @@ transport:
type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"
# Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
# Preserve proto field names (e.g., 'current_fw_title') instead of converting to camelCase (e.g., 'currentFwTitle') when processing Protobuf messages.
# When set to 'false' (default), field names are converted to camelCase for backward compatibility.
# When set to 'true', field names are preserved as defined in the .proto schema.
# This affects MQTT, CoAP, and LwM2M transports using Protobuf payload.
preserve_proto_field_names: "${TB_TRANSPORT_JSON_PRESERVE_PROTO_FIELD_NAMES:false}"
client_side_rpc: client_side_rpc:
# Processing timeout interval of the RPC command on the CLIENT SIDE. Time in milliseconds # Processing timeout interval of the RPC command on the CLIENT SIDE. Time in milliseconds
timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}" timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}"

11
common/data/src/main/java/org/thingsboard/server/common/data/DynamicProtoUtils.java

@ -45,6 +45,10 @@ public class DynamicProtoUtils {
public static final Location LOCATION = new Location("", "", -1, -1); public static final Location LOCATION = new Location("", "", -1, -1);
public static final String PROTO_3_SYNTAX = "proto3"; public static final String PROTO_3_SYNTAX = "proto3";
private static final JsonFormat.Printer JSON_PRINTER = JsonFormat.printer().includingDefaultValueFields();
private static final JsonFormat.Printer JSON_PRINTER_PRESERVING_PROTO_FIELD_NAMES = JsonFormat.printer().preservingProtoFieldNames().includingDefaultValueFields();
private static boolean preserveProtoFieldNames = Boolean.parseBoolean(System.getProperty("transport.json.preserve_proto_field_names", System.getenv("TB_TRANSPORT_JSON_PRESERVE_PROTO_FIELD_NAMES")));
public static Descriptors.Descriptor getDescriptor(String protoSchema, String schemaName) { public static Descriptors.Descriptor getDescriptor(String protoSchema, String schemaName) {
try { try {
@ -99,7 +103,8 @@ public class DynamicProtoUtils {
public static String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { public static String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException {
DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload);
return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); JsonFormat.Printer printer = preserveProtoFieldNames ? JSON_PRINTER_PRESERVING_PROTO_FIELD_NAMES : JSON_PRINTER;
return printer.print(dynamicMessage);
} }
public static DynamicMessage jsonToDynamicMessage(DynamicMessage.Builder builder, String payload) throws InvalidProtocolBufferException { public static DynamicMessage jsonToDynamicMessage(DynamicMessage.Builder builder, String payload) throws InvalidProtocolBufferException {
@ -107,6 +112,10 @@ public class DynamicProtoUtils {
return builder.build(); return builder.build();
} }
static void setPreserveProtoFieldNames(boolean preserve) {
preserveProtoFieldNames = preserve;
}
private static List<MessageElement> getMessageTypes(List<TypeElement> types) { private static List<MessageElement> getMessageTypes(List<TypeElement> types) {
return types.stream() return types.stream()
.filter(typeElement -> typeElement instanceof MessageElement) .filter(typeElement -> typeElement instanceof MessageElement)

112
common/data/src/test/java/org/thingsboard/server/common/data/DynamicProtoUtilsTest.java

@ -19,20 +19,34 @@ import com.github.os72.protobuf.dynamic.DynamicSchema;
import com.google.protobuf.Descriptors; import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage; import com.google.protobuf.DynamicMessage;
import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.parallel.Isolated;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(MockitoExtension.class) @Isolated("DynamicProtoUtils static settings being modified")
public class DynamicProtoUtilsTest { public class DynamicProtoUtilsTest {
@BeforeEach
public void before() {
// Restore default state before each test
DynamicProtoUtils.setPreserveProtoFieldNames(false);
}
@AfterEach
public void after() {
// Restore default state after each test
DynamicProtoUtils.setPreserveProtoFieldNames(false);
}
@Test @Test
public void testProtoSchemaWithMessageNestedTypes() throws Exception { public void testProtoSchemaWithMessageNestedTypes() throws Exception {
String schema = "syntax = \"proto3\";\n" + String schema = "syntax = \"proto3\";\n" +
@ -166,4 +180,96 @@ public class DynamicProtoUtilsTest {
DynamicProtoUtils.dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); DynamicProtoUtils.dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray()));
} }
@Test
public void testProtoSchemaDefaultBehaviorConvertsToCamelCase() throws Exception {
// Explicitly set to false to test default behavior (camelCase conversion)
DynamicProtoUtils.setPreserveProtoFieldNames(false);
String schema = "syntax = \"proto3\";\n" +
"\n" +
"package firmware;\n" +
"\n" +
"message FirmwareStatus {\n" +
" string current_fw_title = 1;\n" +
" string current_fw_version = 2;\n" +
" string fw_state = 3;\n" +
" string target_fw_title = 4;\n" +
" string target_fw_version = 5;\n" +
"}";
ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(schema);
DynamicSchema dynamicSchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, "test schema with snake_case fields");
assertNotNull(dynamicSchema);
DynamicMessage.Builder firmwareStatusBuilder = dynamicSchema.newMessageBuilder("firmware.FirmwareStatus");
Descriptors.Descriptor firmwareStatusDescriptor = firmwareStatusBuilder.getDescriptorForType();
assertNotNull(firmwareStatusDescriptor);
DynamicMessage firmwareStatus = firmwareStatusBuilder
.setField(firmwareStatusDescriptor.findFieldByName("current_fw_title"), "firmware_v1")
.setField(firmwareStatusDescriptor.findFieldByName("current_fw_version"), "1.0.0")
.setField(firmwareStatusDescriptor.findFieldByName("fw_state"), "DOWNLOADING")
.setField(firmwareStatusDescriptor.findFieldByName("target_fw_title"), "firmware_v2")
.setField(firmwareStatusDescriptor.findFieldByName("target_fw_version"), "2.0.0")
.build();
String json = DynamicProtoUtils.dynamicMsgToJson(firmwareStatusDescriptor, firmwareStatus.toByteArray());
// Default behavior: field names converted to camelCase
assertTrue(json.contains("\"currentFwTitle\""), "JSON should contain camelCase field 'currentFwTitle'");
assertTrue(json.contains("\"currentFwVersion\""), "JSON should contain camelCase field 'currentFwVersion'");
assertTrue(json.contains("\"fwState\""), "JSON should contain camelCase field 'fwState'");
assertTrue(json.contains("\"targetFwTitle\""), "JSON should contain camelCase field 'targetFwTitle'");
assertTrue(json.contains("\"targetFwVersion\""), "JSON should contain camelCase field 'targetFwVersion'");
// Verify snake_case versions are NOT present
assertFalse(json.contains("\"current_fw_title\""), "JSON should NOT contain snake_case field 'current_fw_title'");
assertFalse(json.contains("\"fw_state\""), "JSON should NOT contain snake_case field 'fw_state'");
}
@Test
public void testProtoSchemaPreservesSnakeCaseFieldNamesWhenEnabled() throws Exception {
// Explicitly set to true to test preserve behavior (snake_case preservation)
DynamicProtoUtils.setPreserveProtoFieldNames(true);
String schema = "syntax = \"proto3\";\n" +
"\n" +
"package firmware;\n" +
"\n" +
"message FirmwareStatus {\n" +
" string current_fw_title = 1;\n" +
" string current_fw_version = 2;\n" +
" string fw_state = 3;\n" +
" string target_fw_title = 4;\n" +
" string target_fw_version = 5;\n" +
"}";
ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(schema);
DynamicSchema dynamicSchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, "test schema with snake_case fields");
assertNotNull(dynamicSchema);
DynamicMessage.Builder firmwareStatusBuilder = dynamicSchema.newMessageBuilder("firmware.FirmwareStatus");
Descriptors.Descriptor firmwareStatusDescriptor = firmwareStatusBuilder.getDescriptorForType();
assertNotNull(firmwareStatusDescriptor);
DynamicMessage firmwareStatus = firmwareStatusBuilder
.setField(firmwareStatusDescriptor.findFieldByName("current_fw_title"), "firmware_v1")
.setField(firmwareStatusDescriptor.findFieldByName("current_fw_version"), "1.0.0")
.setField(firmwareStatusDescriptor.findFieldByName("fw_state"), "DOWNLOADING")
.setField(firmwareStatusDescriptor.findFieldByName("target_fw_title"), "firmware_v2")
.setField(firmwareStatusDescriptor.findFieldByName("target_fw_version"), "2.0.0")
.build();
String json = DynamicProtoUtils.dynamicMsgToJson(firmwareStatusDescriptor, firmwareStatus.toByteArray());
// When flag is enabled, verify snake_case is preserved
assertTrue(json.contains("\"current_fw_title\""), "JSON should contain snake_case field 'current_fw_title'");
assertTrue(json.contains("\"current_fw_version\""), "JSON should contain snake_case field 'current_fw_version'");
assertTrue(json.contains("\"fw_state\""), "JSON should contain snake_case field 'fw_state'");
assertTrue(json.contains("\"target_fw_title\""), "JSON should contain snake_case field 'target_fw_title'");
assertTrue(json.contains("\"target_fw_version\""), "JSON should contain snake_case field 'target_fw_version'");
// Verify camelCase versions are NOT present
assertFalse(json.contains("\"currentFwTitle\""), "JSON should NOT contain camelCase field 'currentFwTitle'");
assertFalse(json.contains("\"fwState\""), "JSON should NOT contain camelCase field 'fwState'");
}
} }

5
transport/coap/src/main/resources/tb-coap-transport.yml

@ -165,6 +165,11 @@ transport:
type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"
# Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
# Preserve proto field names (e.g., 'current_fw_title') instead of converting to camelCase (e.g., 'currentFwTitle') when processing Protobuf messages.
# When set to 'false' (default), field names are converted to camelCase for backward compatibility.
# When set to 'true', field names are preserved as defined in the .proto schema.
# This affects dynamic Protobuf messages used in device communication.
preserve_proto_field_names: "${TB_TRANSPORT_JSON_PRESERVE_PROTO_FIELD_NAMES:false}"
log: log:
# Enable/Disable log of transport messages to telemetry. For example, logging of LwM2M registration update # Enable/Disable log of transport messages to telemetry. For example, logging of LwM2M registration update
enabled: "${TB_TRANSPORT_LOG_ENABLED:true}" enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"

5
transport/http/src/main/resources/tb-http-transport.yml

@ -197,6 +197,11 @@ transport:
type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"
# Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
# Preserve proto field names (e.g., 'current_fw_title') instead of converting to camelCase (e.g., 'currentFwTitle') when processing Protobuf messages.
# When set to 'false' (default), field names are converted to camelCase for backward compatibility.
# When set to 'true', field names are preserved as defined in the .proto schema.
# This affects dynamic Protobuf messages used in device communication.
preserve_proto_field_names: "${TB_TRANSPORT_JSON_PRESERVE_PROTO_FIELD_NAMES:false}"
log: log:
# Enable/Disable log of transport messages to telemetry. For example, logging of LwM2M registration update # Enable/Disable log of transport messages to telemetry. For example, logging of LwM2M registration update
enabled: "${TB_TRANSPORT_LOG_ENABLED:true}" enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"

5
transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

@ -157,6 +157,11 @@ transport:
type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:false}" type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:false}"
# Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
# Preserve proto field names (e.g., 'current_fw_title') instead of converting to camelCase (e.g., 'currentFwTitle') when processing Protobuf messages.
# When set to 'false' (default), field names are converted to camelCase for backward compatibility.
# When set to 'true', field names are preserved as defined in the .proto schema.
# This affects dynamic Protobuf messages used in device communication.
preserve_proto_field_names: "${TB_TRANSPORT_JSON_PRESERVE_PROTO_FIELD_NAMES:false}"
client_side_rpc: client_side_rpc:
# Processing timeout interval of the RPC command on the CLIENT SIDE. Time in milliseconds # Processing timeout interval of the RPC command on the CLIENT SIDE. Time in milliseconds
timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}" timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}"

5
transport/mqtt/src/main/resources/tb-mqtt-transport.yml

@ -220,6 +220,11 @@ transport:
type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"
# Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
# Preserve proto field names (e.g., 'current_fw_title') instead of converting to camelCase (e.g., 'currentFwTitle') when processing Protobuf messages.
# When set to 'false' (default), field names are converted to camelCase for backward compatibility.
# When set to 'true', field names are preserved as defined in the .proto schema.
# This affects dynamic Protobuf messages used in device communication.
preserve_proto_field_names: "${TB_TRANSPORT_JSON_PRESERVE_PROTO_FIELD_NAMES:false}"
log: log:
# Enable/Disable log of transport messages to telemetry. For example, logging of LwM2M registration update # Enable/Disable log of transport messages to telemetry. For example, logging of LwM2M registration update
enabled: "${TB_TRANSPORT_LOG_ENABLED:true}" enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"

5
transport/snmp/src/main/resources/tb-snmp-transport.yml

@ -178,6 +178,11 @@ transport:
type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"
# Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
# Preserve proto field names (e.g., 'current_fw_title') instead of converting to camelCase (e.g., 'currentFwTitle') when processing Protobuf messages.
# When set to 'false' (default), field names are converted to camelCase for backward compatibility.
# When set to 'true', field names are preserved as defined in the .proto schema.
# This affects dynamic Protobuf messages used in device communication.
preserve_proto_field_names: "${TB_TRANSPORT_JSON_PRESERVE_PROTO_FIELD_NAMES:false}"
log: log:
# Enable/Disable log of transport messages to telemetry. For example, logging of LwM2M registration update # Enable/Disable log of transport messages to telemetry. For example, logging of LwM2M registration update
enabled: "${TB_TRANSPORT_LOG_ENABLED:true}" enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"

Loading…
Cancel
Save