diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index cd4a87d4de..c2bff23192 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.controller; -import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -33,12 +32,7 @@ import org.junit.Rule; import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; -import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootContextLoader; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; @@ -46,10 +40,6 @@ import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.mock.http.MockHttpInputMessage; import org.springframework.mock.http.MockHttpOutputMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; @@ -58,7 +48,6 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.context.WebApplicationContext; -import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; @@ -68,11 +57,13 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.id.HasId; -import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; @@ -330,7 +321,7 @@ public abstract class AbstractWebTest { } } - protected DeviceProfile createDeviceProfile(String name) { + protected DeviceProfile createDeviceProfile(String name, DeviceProfileTransportConfiguration deviceProfileTransportConfiguration) { DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setName(name); deviceProfile.setType(DeviceProfileType.DEFAULT); @@ -338,15 +329,34 @@ public abstract class AbstractWebTest { deviceProfile.setDescription(name + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); - DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration(); deviceProfileData.setConfiguration(configuration); - deviceProfileData.setTransportConfiguration(transportConfiguration); + if (deviceProfileTransportConfiguration != null) { + deviceProfileData.setTransportConfiguration(deviceProfileTransportConfiguration); + } else { + deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration()); + } deviceProfile.setProfileData(deviceProfileData); deviceProfile.setDefault(false); deviceProfile.setDefaultRuleChainId(null); return deviceProfile; } + protected MqttDeviceProfileTransportConfiguration createMqttDeviceProfileTransportConfiguration(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration) { + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_TELEMETRY_TOPIC); + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); + mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + return mqttDeviceProfileTransportConfiguration; + } + + protected ProtoTransportPayloadConfiguration createProtoTransportPayloadConfiguration(String deviceAttributesProtoSchema, String deviceTelemetryProtoSchema) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); + protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(deviceAttributesProtoSchema); + protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(deviceTelemetryProtoSchema); + return protoTransportPayloadConfiguration; + } + + protected ResultActions doGet(String urlTemplate, Object... urlVariables) throws Exception { MockHttpServletRequestBuilder getRequest = get(urlTemplate, urlVariables); setJwtToken(getRequest); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index 3376c16573..841e899473 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -16,6 +16,12 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -28,7 +34,10 @@ import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -36,9 +45,13 @@ import org.thingsboard.server.common.data.security.Authority; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public abstract class BaseDeviceProfileControllerTest extends AbstractControllerTest { @@ -78,7 +91,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Assert.assertNotNull(savedDeviceProfile); Assert.assertNotNull(savedDeviceProfile.getId()); @@ -96,7 +109,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testFindDeviceProfileById() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); Assert.assertNotNull(foundDeviceProfile); @@ -105,7 +118,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testFindDeviceProfileInfoById() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfileInfo foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/"+savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class); Assert.assertNotNull(foundDeviceProfileInfo); @@ -127,7 +140,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSetDefaultDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile defaultDeviceProfile = doPost("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString()+"/default", null, DeviceProfile.class); Assert.assertNotNull(defaultDeviceProfile); @@ -147,19 +160,19 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfileWithSameName() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); - DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile", null); doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Device profile with such name already exists"))); } @Test public void testSaveDeviceProfileWithSameProvisionDeviceKey() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); deviceProfile.setProvisionDeviceKey("testProvisionDeviceKey"); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); - DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2"); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2", null); deviceProfile2.setProvisionDeviceKey("testProvisionDeviceKey"); doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Device profile with such provision device key already exists"))); @@ -168,7 +181,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Ignore @Test public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); device.setName("Test device"); @@ -183,7 +196,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testChangeDeviceProfileTransportTypeWithExistingDevices() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); device.setName("Test device"); @@ -197,7 +210,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testDeleteDeviceProfileWithExistingDevice() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); @@ -214,7 +227,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testDeleteDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString()) @@ -235,7 +248,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController deviceProfiles.addAll(pageData.getData()); for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i, null); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); } @@ -280,7 +293,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController deviceProfiles.addAll(deviceProfilePageData.getData()); for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i, null); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); } @@ -318,4 +331,341 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController Assert.assertEquals(1, pageData.getTotalElements()); } + @Test + public void testSaveProtoDeviceProfileWithInvalidProtoFile() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " required int32 parameter = 1;\n" + + "}", "[Transport Configuration] failed to parse attributes proto schema due to: Syntax error in :6:4: 'required' label forbidden in proto3 field declarations"); + } + + @Test + public void testSaveProtoDeviceProfileWithInvalidProtoSyntax() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto2\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " required int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid schema syntax: proto2 for attributes proto schema provided! Only proto3 allowed!"); + } + + @Test + public void testSaveProtoDeviceProfileOptionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "option java_package = \"com.test.schemavalidation\";\n" + + "option java_multiple_files = true;\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema options don't support!"); + } + + @Test + public void testSaveProtoDeviceProfilePublicImportsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "import public \"oldschema.proto\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema public imports don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileImportsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "import \"oldschema.proto\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema imports don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileExtendDeclarationsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "extend google.protobuf.MethodOptions {\n" + + " MyMessage my_method_option = 50007;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema extend declarations don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileEnumOptionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "enum testEnum {\n" + + " option allow_alias = true;\n" + + " DEFAULT = 0;\n" + + " STARTED = 1;\n" + + " RUNNING = 2;\n" + + "}\n" + + "\n" + + "message testMessage {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Enum definitions options are not supported!"); + } + + @Test + public void testSaveProtoDeviceProfileNoOneMessageTypeExists() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "enum testEnum {\n" + + " DEFAULT = 0;\n" + + " STARTED = 1;\n" + + " RUNNING = 2;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! At least one Message definition should exists!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeOptionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message testMessage {\n" + + " option allow_alias = true;\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition options don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeExtensionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message TestMessage {\n" + + " extensions 100 to 199;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition extensions don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeReservedElementsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message Foo {\n" + + " reserved 2, 15, 9 to 11;\n" + + " reserved \"foo\", \"bar\";\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition reserved elements don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeGroupsElementsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message TestMessage {\n" + + " repeated group Result = 1 {\n" + + " string url = 2;\n" + + " string title = 3;\n" + + " repeated string snippets = 4;\n" + + " }\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition groups don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileOneOfsGroupsElementsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SampleMessage {\n" + + " oneof test_oneof {\n" + + " string name = 1;\n" + + " group Result = 2 {\n" + + " \tstring url = 3;\n" + + " \tstring title = 4;\n" + + " \trepeated string snippets = 5;\n" + + " }\n" + + " }" + + "}", "[Transport Configuration] invalid attributes proto schema provided! OneOf definition groups don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileWithMessageNestedTypes() throws Exception { + String schema = "syntax = \"proto3\";\n" + + "\n" + + "package testnested;\n" + + "\n" + + "message Outer {\n" + + " message MiddleAA {\n" + + " message Inner {\n" + + " int64 ival = 1;\n" + + " bool booly = 2;\n" + + " }\n" + + " Inner inner = 1;\n" + + " }\n" + + " message MiddleBB {\n" + + " message Inner {\n" + + " int32 ival = 1;\n" + + " bool booly = 2;\n" + + " }\n" + + " Inner inner = 1;\n" + + " }\n" + + " MiddleAA middleAA = 1;\n" + + " MiddleBB middleBB = 2;\n" + + "}"; + DynamicSchema dynamicSchema = getDynamicSchema(schema); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(5, messageTypes.size()); + assertTrue(messageTypes.contains("testnested.Outer")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA.Inner")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB.Inner")); + + DynamicMessage.Builder middleAAInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleAAInnerMsgDescriptor = middleAAInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAInnerMsg = middleAAInnerMsgBuilder + .setField(middleAAInnerMsgDescriptor.findFieldByName("ival"), 1L) + .setField(middleAAInnerMsgDescriptor.findFieldByName("booly"), true) + .build(); + + DynamicMessage.Builder middleAAMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA"); + Descriptors.Descriptor middleAAMsgDescriptor = middleAAMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAMsg = middleAAMsgBuilder + .setField(middleAAMsgDescriptor.findFieldByName("inner"), middleAAInnerMsg) + .build(); + + DynamicMessage.Builder middleBBInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleBBInnerMsgDescriptor = middleBBInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBInnerMsg = middleBBInnerMsgBuilder + .setField(middleBBInnerMsgDescriptor.findFieldByName("ival"), 0L) + .setField(middleBBInnerMsgDescriptor.findFieldByName("booly"), false) + .build(); + + DynamicMessage.Builder middleBBMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleBB"); + Descriptors.Descriptor middleBBMsgDescriptor = middleBBMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBMsg = middleBBMsgBuilder + .setField(middleBBMsgDescriptor.findFieldByName("inner"), middleBBInnerMsg) + .build(); + + + DynamicMessage.Builder outerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer"); + Descriptors.Descriptor outerMsgBuilderDescriptor = outerMsgBuilder.getDescriptorForType(); + DynamicMessage outerMsg = outerMsgBuilder + .setField(outerMsgBuilderDescriptor.findFieldByName("middleAA"), middleAAMsg) + .setField(outerMsgBuilderDescriptor.findFieldByName("middleBB"), middleBBMsg) + .build(); + + assertEquals("{\n" + + " \"middleAA\": {\n" + + " \"inner\": {\n" + + " \"ival\": \"1\",\n" + + " \"booly\": true\n" + + " }\n" + + " },\n" + + " \"middleBB\": {\n" + + " \"inner\": {\n" + + " \"ival\": 0,\n" + + " \"booly\": false\n" + + " }\n" + + " }\n" + + "}", dynamicMsgToJson(outerMsgBuilderDescriptor, outerMsg.toByteArray())); + } + + @Test + public void testSaveProtoDeviceProfileWithMessageOneOfs() throws Exception { + String schema = "syntax = \"proto3\";\n" + + "\n" + + "package testoneofs;\n" + + "\n" + + "message SubMessage {\n" + + " repeated string name = 1;\n" + + "}\n" + + "\n" + + "message SampleMessage {\n" + + " oneof testOneOf {\n" + + " string name = 4;\n" + + " SubMessage subMessage = 9;\n" + + " }\n" + + "}"; + DynamicSchema dynamicSchema = getDynamicSchema(schema); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(2, messageTypes.size()); + assertTrue(messageTypes.contains("testoneofs.SubMessage")); + assertTrue(messageTypes.contains("testoneofs.SampleMessage")); + + DynamicMessage.Builder sampleMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SampleMessage"); + Descriptors.Descriptor sampleMsgDescriptor = sampleMsgBuilder.getDescriptorForType(); + assertNotNull(sampleMsgDescriptor); + + List fields = sampleMsgDescriptor.getFields(); + assertEquals(2, fields.size()); + DynamicMessage sampleMsg = sampleMsgBuilder + .setField(sampleMsgDescriptor.findFieldByName("name"), "Bob") + .build(); + assertEquals("{\n" + " \"name\": \"Bob\"\n" + "}", dynamicMsgToJson(sampleMsgDescriptor, sampleMsg.toByteArray())); + + DynamicMessage.Builder subMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SubMessage"); + Descriptors.Descriptor subMsgDescriptor = subMsgBuilder.getDescriptorForType(); + DynamicMessage subMsg = subMsgBuilder + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "Alice") + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "John") + .build(); + + DynamicMessage sampleMsgWithOneOfSubMessage = sampleMsgBuilder.setField(sampleMsgDescriptor.findFieldByName("subMessage"), subMsg).build(); + assertEquals("{\n" + " \"subMessage\": {\n" + " \"name\": [\"Alice\", \"John\"]\n" + " }\n" + "}", + dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); + } + + private DeviceProfile testSaveDeviceProfileWithProtoPayloadType(String schema) throws Exception { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); + Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName()); + return savedDeviceProfile; + } + + private void testSaveDeviceProfileWithInvalidProtoSchema(String schema, String errorMsg) throws Exception { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(errorMsg))); + } + + private DynamicSchema getDynamicSchema(String schema) throws Exception { + DeviceProfile deviceProfile = testSaveDeviceProfileWithProtoPayloadType(schema); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement protoFile = protoTransportPayloadConfiguration.getTransportProtoSchema(schema); + return protoTransportPayloadConfiguration.getDynamicSchema(protoFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + } + + private String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); + return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + } + } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index de84698dc0..715ffeade3 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -38,9 +38,10 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileCon import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttJsonDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.controller.AbstractControllerTest; @@ -94,7 +95,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest protected Device savedGateway; protected String gatewayAccessToken; - protected MqttDeviceProfileTransportConfiguration transportConfiguration; + protected DeviceProfile deviceProfile; protected void processBeforeTest (String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, DeviceProfileProvisionType.DISABLED, null, null); @@ -139,11 +140,11 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest if (payloadType != null) { DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, telemetryProtoSchema, attributesProtoSchema, provisionType, provisionKey, provisionSecret); - DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); - device.setType(savedDeviceProfile.getName()); - device.setDeviceProfileId(savedDeviceProfile.getId()); - gateway.setType(savedDeviceProfile.getName()); - gateway.setDeviceProfileId(savedDeviceProfile.getId()); + deviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); + device.setType(deviceProfile.getName()); + device.setDeviceProfileId(deviceProfile.getId()); + gateway.setType(deviceProfile.getName()); + gateway.setDeviceProfileId(deviceProfile.getId()); } savedDevice = doPost("/api/device", device, Device.class); @@ -242,27 +243,30 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest deviceProfile.setDescription(transportPayloadType.name() + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); + if (!StringUtils.isEmpty(telemetryTopic)) { + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(telemetryTopic); + } + if (!StringUtils.isEmpty(attributesTopic)) { + mqttDeviceProfileTransportConfiguration.setDeviceAttributesTopic(attributesTopic); + } + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; if (TransportPayloadType.JSON.equals(transportPayloadType)) { - transportConfiguration = new MqttJsonDeviceProfileTransportConfiguration(); + transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration(); } else { - MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); if (StringUtils.isEmpty(telemetryProtoSchema)) { telemetryProtoSchema = DEVICE_TELEMETRY_PROTO_SCHEMA; } if (StringUtils.isEmpty(attributesProtoSchema)) { attributesProtoSchema = DEVICE_ATTRIBUTES_PROTO_SCHEMA; } - protoTransportConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema); - protoTransportConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); - transportConfiguration = protoTransportConfiguration; + protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema); + protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); + transportPayloadTypeConfiguration = protoTransportPayloadConfiguration; } - if (!StringUtils.isEmpty(telemetryTopic)) { - transportConfiguration.setDeviceTelemetryTopic(telemetryTopic); - } - if (!StringUtils.isEmpty(attributesTopic)) { - transportConfiguration.setDeviceAttributesTopic(attributesTopic); - } - deviceProfileData.setTransportConfiguration(transportConfiguration); + mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + deviceProfileData.setTransportConfiguration(mqttDeviceProfileTransportConfiguration); DeviceProfileProvisionConfiguration provisionConfiguration; switch (provisionType) { case ALLOW_CREATE_NEW_DEVICES: @@ -274,6 +278,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest case DISABLED: default: provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(provisionSecret); + break; } deviceProfileData.setProvisionConfiguration(provisionConfiguration); deviceProfileData.setConfiguration(configuration); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 8a8be4d1cc..0ea53d2801 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -30,8 +30,11 @@ import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -82,11 +85,15 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client) throws Exception { doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); - assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); - MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); - DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "attributesSchema"); - DynamicMessage.Builder postAttributesBuilder = telemetrySchema.newMessageBuilder("PostAttributes"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); + DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); assertNotNull(postAttributesMsgDescriptor); DynamicMessage postAttributesMsg = postAttributesBuilder diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java index f7f9bdef7d..9cc7c66f55 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java @@ -23,7 +23,10 @@ import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -47,11 +50,15 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac public void testPushMqttAttributes() throws Exception { super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); - MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); - DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "attributesSchema"); - DynamicMessage.Builder postAttributesBuilder = telemetrySchema.newMessageBuilder("PostAttributes"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); + DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); assertNotNull(postAttributesMsgDescriptor); DynamicMessage postAttributesMsg = postAttributesBuilder diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index d463dc1963..f8277b1934 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -26,8 +26,11 @@ import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -51,10 +54,14 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac public void testPushMqttTelemetry() throws Exception { super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); - MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); - DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); + DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); assertNotNull(postTelemetryMsgDescriptor); @@ -88,10 +95,14 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac "}"; super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); - MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(schemaStr); - DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); + DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("Values"); Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java index e685692895..fb337b24f4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M")}) public interface DeviceProfileTransportConfiguration { + @JsonIgnore DeviceTransportType getType(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java similarity index 62% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java index 3f9144dc8f..f4fd93459b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java @@ -15,24 +15,14 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; -@Slf4j -@EqualsAndHashCode(callSuper = true) @Data -@JsonDeserialize(as = MqttJsonDeviceProfileTransportConfiguration.class) -public class MqttJsonDeviceProfileTransportConfiguration extends MqttDeviceProfileTransportConfiguration{ +public class JsonTransportPayloadConfiguration implements TransportPayloadTypeConfiguration { @Override public TransportPayloadType getTransportPayloadType() { return TransportPayloadType.JSON; } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 66174e3d29..6a5c3c474c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -15,30 +15,15 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.DeviceTransportType; -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.PROPERTY, - property = "transportPayloadType") -@JsonSubTypes({ - @JsonSubTypes.Type(value = MqttJsonDeviceProfileTransportConfiguration.class, name = "JSON"), - @JsonSubTypes.Type(value = MqttProtoDeviceProfileTransportConfiguration.class, name = "PROTOBUF")}) -@JsonDeserialize(using = MqttTransportConfigurationDeserializer.class) @Data -public abstract class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { +public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { - public abstract TransportPayloadType getTransportPayloadType(); - - protected String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC; - protected String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; + private String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC; + private String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; + private TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; @Override public DeviceTransportType getType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java deleted file mode 100644 index 4754792fb2..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright © 2016-2020 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.device.profile; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.TransportPayloadType; - -import java.io.IOException; - -@Slf4j -public class MqttTransportConfigurationDeserializer extends StdDeserializer { - - public MqttTransportConfigurationDeserializer() { - super(MqttDeviceProfileTransportConfiguration.class); - } - - @Override - public MqttDeviceProfileTransportConfiguration deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { - try { - JsonNode jsonNode = jsonParser.readValueAsTree(); - if (jsonNode.hasNonNull("transportPayloadType") && jsonNode.get("transportPayloadType").asText().equals(TransportPayloadType.PROTOBUF.name())) { - return jsonParser.getCodec().treeToValue(jsonNode, MqttProtoDeviceProfileTransportConfiguration.class); - } else { - return jsonParser.getCodec().treeToValue(jsonNode, MqttJsonDeviceProfileTransportConfiguration.class); - } - } catch (IOException e) { - log.trace("Failed to deserialize JSON content into equivalent tree model during creating {}!", MqttDeviceProfileTransportConfiguration.class.getSimpleName(), e); - throw new RuntimeException("Failed to deserialize JSON content into equivalent tree model during creating " + MqttDeviceProfileTransportConfiguration.class.getSimpleName() + "!", e); - } - } - -} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java similarity index 56% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java index 158acfaf2a..c5bdf28833 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java @@ -15,16 +15,11 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.github.os72.protobuf.dynamic.DynamicSchema; import com.github.os72.protobuf.dynamic.EnumDefinition; import com.github.os72.protobuf.dynamic.MessageDefinition; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; -import com.squareup.wire.Syntax; -import com.squareup.wire.schema.Field; import com.squareup.wire.schema.Location; import com.squareup.wire.schema.internal.parser.EnumConstantElement; import com.squareup.wire.schema.internal.parser.EnumElement; @@ -35,30 +30,22 @@ import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoParser; import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.Data; -import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.TransportPayloadType; - import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @Slf4j -@EqualsAndHashCode(callSuper = true) @Data -@JsonDeserialize(as = MqttProtoDeviceProfileTransportConfiguration.class) -public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProfileTransportConfiguration { +public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeConfiguration { public static final Location LOCATION = new Location("", "", -1, -1); public static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; public static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; - public static String invalidSchemaProvidedMessage(String schemaName) { - return "[Transport Configuration] invalid " + schemaName + " schema provided!"; - } - private String deviceTelemetryProtoSchema; private String deviceAttributesProtoSchema; @@ -67,23 +54,15 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf return TransportPayloadType.PROTOBUF; } - public void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException { - ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); - ProtoFileElement protoFileElement; - try { - protoFileElement = schemaParser.readProtoFile(); - } catch (Exception e) { - throw new IllegalArgumentException("[Transport Configuration] failed to parse " + schemaName + " due to: " + e.getMessage()); - } - checkProtoFileSyntax(schemaName, protoFileElement); - checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!"); - checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!"); - checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!"); - checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!"); - checkTypeElements(schemaName, protoFileElement); + public Descriptors.Descriptor getTelemetryDynamicMessageDescriptor(String deviceTelemetryProtoSchema) { + return getDescriptor(deviceTelemetryProtoSchema, TELEMETRY_PROTO_SCHEMA); } - public Descriptors.Descriptor getDynamicMessageDescriptor(String protoSchema, String schemaName) { + public Descriptors.Descriptor getAttributesDynamicMessageDescriptor(String deviceAttributesProtoSchema) { + return getDescriptor(deviceAttributesProtoSchema, ATTRIBUTES_PROTO_SCHEMA); + } + + private Descriptors.Descriptor getDescriptor(String protoSchema, String schemaName) { try { ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName); @@ -125,91 +104,6 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf } } - private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { - if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { - throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + - " for: " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); - } - } - - private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { - if (!isEmptySettings) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); - } - } - - private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) { - List types = protoFileElement.getTypes(); - if (!types.isEmpty()) { - if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!"); - } else { - checkEnumElements(schemaName, getEnumElements(types)); - checkMessageElements(schemaName, getMessageTypes(types)); - } - } else { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!"); - } - } - - private void checkFieldElements(String schemaName, List fieldElements) { - if (!fieldElements.isEmpty()) { - boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { - Field.Label label = fieldElement.getLabel(); - return label != null && label.equals(Field.Label.REQUIRED); - }); - if (hasRequiredLabel) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!"); - } - boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); - if (hasDefaultValue) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!"); - } - } - } - - private void checkEnumElements(String schemaName, List enumTypes) { - if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); - } - if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!"); - } - } - - private void checkMessageElements(String schemaName, List messageElementsList) { - if (!messageElementsList.isEmpty()) { - messageElementsList.forEach(messageElement -> { - checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), - " Message definition groups don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), - " Message definition options don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), - " Message definition extensions don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), - " Message definition reserved elements don't support!"); - checkFieldElements(schemaName, messageElement.getFields()); - List oneOfs = messageElement.getOneOfs(); - if (!oneOfs.isEmpty()) { - oneOfs.forEach(oneOfElement -> { - checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), - " OneOf definition groups don't support!"); - checkFieldElements(schemaName, oneOfElement.getFields()); - }); - } - List nestedTypes = messageElement.getNestedTypes(); - if (!nestedTypes.isEmpty()) { - List nestedEnumTypes = getEnumElements(nestedTypes); - if (!nestedEnumTypes.isEmpty()) { - checkEnumElements(schemaName, nestedEnumTypes); - } - List nestedMessageTypes = getMessageTypes(nestedTypes); - checkMessageElements(schemaName, nestedMessageTypes); - } - }); - } - } - public ProtoFileElement getTransportProtoSchema(String protoSchema) { return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java new file mode 100644 index 0000000000..5a6bbd5b25 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2020 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.device.profile; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.TransportPayloadType; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "transportPayloadType") +@JsonSubTypes({ + @JsonSubTypes.Type(value = JsonTransportPayloadConfiguration.class, name = "JSON"), + @JsonSubTypes.Type(value = ProtoTransportPayloadConfiguration.class, name = "PROTOBUF")}) +public interface TransportPayloadTypeConfiguration { + + @JsonIgnore + TransportPayloadType getTransportPayloadType(); + +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 6ecea5a77c..fb4512a59b 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -25,7 +25,8 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.transport.mqtt.MqttTransportContext; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter; @@ -118,11 +119,12 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { if (transportConfiguration.getType().equals(DeviceTransportType.MQTT) && transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { MqttDeviceProfileTransportConfiguration mqttConfig = (MqttDeviceProfileTransportConfiguration) transportConfiguration; - payloadType = mqttConfig.getTransportPayloadType(); + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttConfig.getTransportPayloadTypeConfiguration(); + payloadType = transportPayloadTypeConfiguration.getTransportPayloadType(); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); - if (mqttConfig instanceof MqttProtoDeviceProfileTransportConfiguration) { - updateDynamicMessageDescriptors(mqttConfig); + if (transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration) { + updateDynamicMessageDescriptors(transportPayloadTypeConfiguration); } } else { telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); @@ -130,9 +132,9 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { } } - private void updateDynamicMessageDescriptors(MqttDeviceProfileTransportConfiguration mqttConfig) { - MqttProtoDeviceProfileTransportConfiguration protoMqttConfig = (MqttProtoDeviceProfileTransportConfiguration) mqttConfig; - telemetryDynamicMessageDescriptor = protoMqttConfig.getDynamicMessageDescriptor(protoMqttConfig.getDeviceTelemetryProtoSchema(), "telemetrySchema"); - attributesDynamicMessageDescriptor = protoMqttConfig.getDynamicMessageDescriptor(protoMqttConfig.getDeviceAttributesProtoSchema(), "attributesSchema"); + private void updateDynamicMessageDescriptors(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfig = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + telemetryDynamicMessageDescriptor = protoTransportPayloadConfig.getTelemetryDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceTelemetryProtoSchema()); + attributesDynamicMessageDescriptor = protoTransportPayloadConfig.getAttributesDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceAttributesProtoSchema()); } } diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java deleted file mode 100644 index 47726ff610..0000000000 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java +++ /dev/null @@ -1,406 +0,0 @@ -/** - * Copyright © 2016-2020 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.transport.mqtt.util; - -import com.github.os72.protobuf.dynamic.DynamicSchema; -import com.google.protobuf.Descriptors; -import com.google.protobuf.DynamicMessage; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.util.JsonFormat; -import com.squareup.wire.schema.internal.parser.ProtoFileElement; -import com.squareup.wire.schema.internal.parser.ProtoParser; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; - -import java.util.List; -import java.util.Set; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration.LOCATION; - -@RunWith(MockitoJUnitRunner.class) -public class MqttDynamicProtoSchemaTest { - - private static final String PROTO_SCHEMA_WITH_NESTED_MSG_TYPES = "syntax = \"proto3\";\n" + - "\n" + - "package testnested;\n" + - "\n" + - "message Outer {\n" + - " message MiddleAA {\n" + - " message Inner {\n" + - " int64 ival = 1;\n" + - " bool booly = 2;\n" + - " }\n" + - " Inner inner = 1;\n" + - " }\n" + - " message MiddleBB {\n" + - " message Inner {\n" + - " int32 ival = 1;\n" + - " bool booly = 2;\n" + - " }\n" + - " Inner inner = 1;\n" + - " }\n" + - " MiddleAA middleAA = 1;\n" + - " MiddleBB middleBB = 2;\n" + - "}\n"; - - private static final String PROTO_SCHEMA_WITH_ONE_OFS = "syntax = \"proto3\";\n" + - "\n" + - "package testoneofs;\n" + - "\n" + - "message SubMessage {\n" + - " repeated string name = 1;\n" + - "}\n" + - "\n" + - "message SampleMessage {\n" + - " oneof testOneOf {\n" + - " string name = 4;\n" + - " SubMessage subMessage = 9;\n" + - " }\n" + - "}"; - - private static final String IVALID_PROTO_SCHEMA_REQUIRED_FIELD_EXISTS = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " required int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_NOT_VALID_SYNTAX = "syntax = \"proto2\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " required int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "option java_package = \"com.test.schemavalidation\";\n" + - "option java_multiple_files = true;\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_PUBLIC_IMPORTS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "import public \"oldschema.proto\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "import \"oldschema.proto\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_EXTEND_DECLARATION_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "extend google.protobuf.MethodOptions {\n" + - " MyMessage my_method_option = 50007;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_ENUM_OPTIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "enum testEnum {\n" + - " option allow_alias = true;\n" + - " DEFAULT = 0;\n" + - " STARTED = 1;\n" + - " RUNNING = 2;\n" + - "}\n" + - "\n" + - "message testMessage {\n" + - " int32 parameter = 1;\n" + - "}\n"; - - private static final String INVALID_PROTO_SCHEMA_NO_MESSAGE_TYPES_EXISTS = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "enum testEnum {\n" + - " DEFAULT = 0;\n" + - " STARTED = 1;\n" + - " RUNNING = 2;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_MESSAGE_OPTIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message testMessage {\n" + - " option allow_alias = true;\n" + - " int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_MESSAGE_EXTENSIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message TestMessage {\n" + - " extensions 100 to 199;\n" + - "}\n"; - - private static final String INVALID_PROTO_SCHEMA_MESSAGE_GROUPS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message TestMessage {\n" + - " repeated group Result = 1 {\n" + - " string url = 2;\n" + - " string title = 3;\n" + - " repeated string snippets = 4;\n" + - " }\n" + - "}\n"; - - private static final String INVALID_PROTO_SCHEMA_MESSAGE_RESERVED_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message Foo {\n" + - " reserved 2, 15, 9 to 11;\n" + - " reserved \"foo\", \"bar\";\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_ONE_OFS_GROUPS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SampleMessage {\n" + - " oneof test_oneof {\n" + - " string name = 1;\n" + - " group Result = 2 {\n" + - " \tstring url = 3;\n" + - " \tstring title = 4;\n" + - " \trepeated string snippets = 5;\n" + - " }\n" + - " }\n" + - "}"; - - private static final MqttProtoDeviceProfileTransportConfiguration mqttProtoDeviceProfileTransportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); - - private static void validateTransportProtoSchema(String schema, String schemaName) { - mqttProtoDeviceProfileTransportConfiguration.validateTransportProtoSchema(schema, schemaName); - } - - private static DynamicSchema getDynamicSchema(String schema, String schemaName) { - ProtoFileElement protoFileElement = getTransportProtoSchema(schema); - return mqttProtoDeviceProfileTransportConfiguration.getDynamicSchema(protoFileElement, schemaName); - } - - @Rule - public ExpectedException exceptionRule = ExpectedException.none(); - - @Test - public void testDynamicSchemaProtoFileValidation() { - processValidation("[Transport Configuration] failed to parse testParseToProtoFile due to: Syntax error in :6:4: 'required' label forbidden in proto3 field declarations", IVALID_PROTO_SCHEMA_REQUIRED_FIELD_EXISTS, "testParseToProtoFile"); - } - - @Test - public void testDynamicSchemaSyntaxValidation() { - processValidation("[Transport Configuration] invalid schema syntax: proto2 for: testSyntaxValidation provided! Only proto3 allowed!", INVALID_PROTO_SCHEMA_NOT_VALID_SYNTAX, "testSyntaxValidation"); - } - - @Test - public void testDynamicSchemaOptionsValidation() { - processValidation("[Transport Configuration] invalid testOptionsValidation schema provided! Schema options don't support!", INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED, "testOptionsValidation"); - } - - @Test - public void testDynamicSchemaPublicImportsValidation() { - processValidation("[Transport Configuration] invalid testPublicImportsValidation schema provided! Schema public imports don't support!", INVALID_PROTO_SCHEMA_PUBLIC_IMPORTS_NOT_SUPPORTED, "testPublicImportsValidation"); - } - - @Test - public void testDynamicSchemaImportsValidation() { - processValidation("[Transport Configuration] invalid testImportsValidation schema provided! Schema imports don't support!", INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED, "testImportsValidation"); - } - - @Test - public void testDynamicSchemaExtendDeclarationsValidation() { - processValidation("[Transport Configuration] invalid testExtendDeclarationsValidation schema provided! Schema extend declarations don't support!", INVALID_PROTO_SCHEMA_EXTEND_DECLARATION_NOT_SUPPORTED, "testExtendDeclarationsValidation"); - } - - @Test - public void testDynamicSchemaEnumOptionsValidation() { - processValidation("[Transport Configuration] invalid testEnumOptionsValidation schema provided! Enum definitions options are not supported!", INVALID_PROTO_SCHEMA_ENUM_OPTIONS_NOT_SUPPORTED, "testEnumOptionsValidation"); - } - - @Test - public void testDynamicSchemaNoOneMessageTypeExistsValidation() { - processValidation("[Transport Configuration] invalid noOneMessageTypeExists schema provided! At least one Message definition should exists!", INVALID_PROTO_SCHEMA_NO_MESSAGE_TYPES_EXISTS, "noOneMessageTypeExists"); - } - - @Test - public void testDynamicSchemaMessageTypeOptionsValidation() { - processValidation("[Transport Configuration] invalid messageTypeOptions schema provided! Message definition options don't support!", INVALID_PROTO_SCHEMA_MESSAGE_OPTIONS_NOT_SUPPORTED, "messageTypeOptions"); - } - - @Test - public void testDynamicSchemaMessageTypeExtensionsValidation() { - processValidation("[Transport Configuration] invalid messageTypeExtensions schema provided! Message definition extensions don't support!", INVALID_PROTO_SCHEMA_MESSAGE_EXTENSIONS_NOT_SUPPORTED, "messageTypeExtensions"); - } - - @Test - public void testDynamicSchemaMessageTypeReservedElementsValidation() { - processValidation("[Transport Configuration] invalid messageTypeReservedElements schema provided! Message definition reserved elements don't support!", INVALID_PROTO_SCHEMA_MESSAGE_RESERVED_NOT_SUPPORTED, "messageTypeReservedElements"); - } - - @Test - public void testDynamicSchemaMessageTypeGroupsElementsValidation() { - processValidation("[Transport Configuration] invalid messageTypeGroupsElements schema provided! Message definition groups don't support!", INVALID_PROTO_SCHEMA_MESSAGE_GROUPS_NOT_SUPPORTED, "messageTypeGroupsElements"); - } - - @Test - public void testDynamicSchemaOneOfsTypeGroupsElementsValidation() { - processValidation("[Transport Configuration] invalid oneOfsTypeGroupsElements schema provided! OneOf definition groups don't support!", INVALID_PROTO_SCHEMA_ONE_OFS_GROUPS_NOT_SUPPORTED, "oneOfsTypeGroupsElements"); - } - - private void processValidation(String expectedMessage, String schema, String schemaName) { - exceptionRule.expect(IllegalArgumentException.class); - exceptionRule.expectMessage(expectedMessage); - validateTransportProtoSchema(schema, schemaName); - } - - @Test - public void testDynamicSchemaCreationWithMessageNestedTypes() throws Exception { - String testNestedTypesProtoSchema = "testNestedTypesProtoSchema"; - validateTransportProtoSchema(PROTO_SCHEMA_WITH_NESTED_MSG_TYPES, testNestedTypesProtoSchema); - DynamicSchema dynamicSchema = getDynamicSchema(PROTO_SCHEMA_WITH_NESTED_MSG_TYPES, testNestedTypesProtoSchema); - assertNotNull(dynamicSchema); - Set messageTypes = dynamicSchema.getMessageTypes(); - assertEquals(5, messageTypes.size()); - assertTrue(messageTypes.contains("testnested.Outer")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleAA")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleAA.Inner")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleBB")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleBB.Inner")); - - DynamicMessage.Builder middleAAInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); - Descriptors.Descriptor middleAAInnerMsgDescriptor = middleAAInnerMsgBuilder.getDescriptorForType(); - DynamicMessage middleAAInnerMsg = middleAAInnerMsgBuilder - .setField(middleAAInnerMsgDescriptor.findFieldByName("ival"), 1L) - .setField(middleAAInnerMsgDescriptor.findFieldByName("booly"), true) - .build(); - - DynamicMessage.Builder middleAAMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA"); - Descriptors.Descriptor middleAAMsgDescriptor = middleAAMsgBuilder.getDescriptorForType(); - DynamicMessage middleAAMsg = middleAAMsgBuilder - .setField(middleAAMsgDescriptor.findFieldByName("inner"), middleAAInnerMsg) - .build(); - - DynamicMessage.Builder middleBBInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); - Descriptors.Descriptor middleBBInnerMsgDescriptor = middleBBInnerMsgBuilder.getDescriptorForType(); - DynamicMessage middleBBInnerMsg = middleBBInnerMsgBuilder - .setField(middleBBInnerMsgDescriptor.findFieldByName("ival"), 0L) - .setField(middleBBInnerMsgDescriptor.findFieldByName("booly"), false) - .build(); - - DynamicMessage.Builder middleBBMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleBB"); - Descriptors.Descriptor middleBBMsgDescriptor = middleBBMsgBuilder.getDescriptorForType(); - DynamicMessage middleBBMsg = middleBBMsgBuilder - .setField(middleBBMsgDescriptor.findFieldByName("inner"), middleBBInnerMsg) - .build(); - - - DynamicMessage.Builder outerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer"); - Descriptors.Descriptor outerMsgBuilderDescriptor = outerMsgBuilder.getDescriptorForType(); - DynamicMessage outerMsg = outerMsgBuilder - .setField(outerMsgBuilderDescriptor.findFieldByName("middleAA"), middleAAMsg) - .setField(outerMsgBuilderDescriptor.findFieldByName("middleBB"), middleBBMsg) - .build(); - - assertEquals("{\n" + - " \"middleAA\": {\n" + - " \"inner\": {\n" + - " \"ival\": \"1\",\n" + - " \"booly\": true\n" + - " }\n" + - " },\n" + - " \"middleBB\": {\n" + - " \"inner\": {\n" + - " \"ival\": 0,\n" + - " \"booly\": false\n" + - " }\n" + - " }\n" + - "}", dynamicMsgToJson(outerMsgBuilderDescriptor, outerMsg.toByteArray())); - } - - @Test - public void testDynamicSchemaCreationWithMessageOneOfs() throws Exception { - String testOneOfsProtoSchema = "testOneOfsProtoSchema"; - validateTransportProtoSchema(PROTO_SCHEMA_WITH_ONE_OFS, testOneOfsProtoSchema); - DynamicSchema dynamicSchema = getDynamicSchema(PROTO_SCHEMA_WITH_ONE_OFS, testOneOfsProtoSchema); - assertNotNull(dynamicSchema); - Set messageTypes = dynamicSchema.getMessageTypes(); - assertEquals(2, messageTypes.size()); - assertTrue(messageTypes.contains("testoneofs.SubMessage")); - assertTrue(messageTypes.contains("testoneofs.SampleMessage")); - - DynamicMessage.Builder sampleMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SampleMessage"); - Descriptors.Descriptor sampleMsgDescriptor = sampleMsgBuilder.getDescriptorForType(); - assertNotNull(sampleMsgDescriptor); - - List fields = sampleMsgDescriptor.getFields(); - assertEquals(2, fields.size()); - DynamicMessage sampleMsg = sampleMsgBuilder - .setField(sampleMsgDescriptor.findFieldByName("name"), "Bob") - .build(); - assertEquals("{\n" + " \"name\": \"Bob\"\n" + "}", dynamicMsgToJson(sampleMsgDescriptor, sampleMsg.toByteArray())); - - DynamicMessage.Builder subMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SubMessage"); - Descriptors.Descriptor subMsgDescriptor = subMsgBuilder.getDescriptorForType(); - DynamicMessage subMsg = subMsgBuilder - .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "Alice") - .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "John") - .build(); - - DynamicMessage sampleMsgWithOneOfSubMessage = sampleMsgBuilder.setField(sampleMsgDescriptor.findFieldByName("subMessage"), subMsg).build(); - assertEquals("{\n" + " \"subMessage\": {\n" + " \"name\": [\"Alice\", \"John\"]\n" + " }\n" + "}", - dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); - - } - - private String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { - DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); - return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); - } - - private static ProtoFileElement getTransportProtoSchema(String protoSchema) { - return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index e5399fc8d4..d1a7820df4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -15,6 +15,16 @@ */ package org.thingsboard.server.dao.device; +import com.squareup.wire.Syntax; +import com.squareup.wire.schema.Field; +import com.squareup.wire.schema.Location; +import com.squareup.wire.schema.internal.parser.EnumElement; +import com.squareup.wire.schema.internal.parser.FieldElement; +import com.squareup.wire.schema.internal.parser.MessageElement; +import com.squareup.wire.schema.internal.parser.OneOfElement; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import com.squareup.wire.schema.internal.parser.ProtoParser; +import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; @@ -35,7 +45,8 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTra import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -49,6 +60,8 @@ import org.thingsboard.server.dao.tenant.TenantDao; import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -61,6 +74,14 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D private static final String INCORRECT_DEVICE_PROFILE_ID = "Incorrect deviceProfileId "; private static final String INCORRECT_DEVICE_PROFILE_NAME = "Incorrect deviceProfileName "; + private static final Location LOCATION = new Location("", "", -1, -1); + private static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; + private static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; + + private static String invalidSchemaProvidedMessage(String schemaName) { + return "[Transport Configuration] invalid " + schemaName + " provided!"; + } + @Autowired private DeviceProfileDao deviceProfileDao; @@ -312,13 +333,16 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } else { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { - MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - try { - protoTransportConfiguration.validateTransportProtoSchema(protoTransportConfiguration.getDeviceAttributesProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.ATTRIBUTES_PROTO_SCHEMA); - protoTransportConfiguration.validateTransportProtoSchema(protoTransportConfiguration.getDeviceTelemetryProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.TELEMETRY_PROTO_SCHEMA); - } catch (Exception exception) { - throw new DataValidationException(exception.getMessage()); + if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + if (mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration = (ProtoTransportPayloadConfiguration) mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration(); + try { + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA); + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA); + } catch (Exception exception) { + throw new DataValidationException(exception.getMessage()); + } } } } @@ -345,6 +369,121 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } } + + private void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException { + ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); + ProtoFileElement protoFileElement; + try { + protoFileElement = schemaParser.readProtoFile(); + } catch (Exception e) { + throw new IllegalArgumentException("[Transport Configuration] failed to parse " + schemaName + " due to: " + e.getMessage()); + } + checkProtoFileSyntax(schemaName, protoFileElement); + checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!"); + checkTypeElements(schemaName, protoFileElement); + } + + private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { + if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { + throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + + " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); + } + } + private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { + if (!isEmptySettings) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); + } + } + + private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) { + List types = protoFileElement.getTypes(); + if (!types.isEmpty()) { + if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!"); + } else { + checkEnumElements(schemaName, getEnumElements(types)); + checkMessageElements(schemaName, getMessageTypes(types)); + } + } else { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!"); + } + } + + private void checkFieldElements(String schemaName, List fieldElements) { + if (!fieldElements.isEmpty()) { + boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { + Field.Label label = fieldElement.getLabel(); + return label != null && label.equals(Field.Label.REQUIRED); + }); + if (hasRequiredLabel) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!"); + } + boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); + if (hasDefaultValue) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!"); + } + } + } + + private void checkEnumElements(String schemaName, List enumTypes) { + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); + } + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!"); + } + } + + private void checkMessageElements(String schemaName, List messageElementsList) { + if (!messageElementsList.isEmpty()) { + messageElementsList.forEach(messageElement -> { + checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), + " Message definition groups don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), + " Message definition options don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), + " Message definition extensions don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), + " Message definition reserved elements don't support!"); + checkFieldElements(schemaName, messageElement.getFields()); + List oneOfs = messageElement.getOneOfs(); + if (!oneOfs.isEmpty()) { + oneOfs.forEach(oneOfElement -> { + checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), + " OneOf definition groups don't support!"); + checkFieldElements(schemaName, oneOfElement.getFields()); + }); + } + List nestedTypes = messageElement.getNestedTypes(); + if (!nestedTypes.isEmpty()) { + List nestedEnumTypes = getEnumElements(nestedTypes); + if (!nestedEnumTypes.isEmpty()) { + checkEnumElements(schemaName, nestedEnumTypes); + } + List nestedMessageTypes = getMessageTypes(nestedTypes); + checkMessageElements(schemaName, nestedMessageTypes); + } + }); + } + } + + private List getMessageTypes(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof MessageElement) + .map(typeElement -> (MessageElement) typeElement) + .collect(Collectors.toList()); + } + + private List getEnumElements(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof EnumElement) + .map(typeElement -> (EnumElement) typeElement) + .collect(Collectors.toList()); + } + }; private PaginatedRemover tenantDeviceProfilesRemover = diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index c83066bbae..cedf147bc1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -57,40 +57,42 @@
-
- device-profile.mqtt-device-payload-type -
- - - - {{mqttTransportPayloadTypeTranslations.get(type) | translate}} - - - - {{ 'device-profile.mqtt-payload-type-required' | translate }} - - -
- - device-profile.telemetry-proto-schema - - - {{ 'device-profile.telemetry-proto-schema-required' | translate}} - - - - device-profile.attributes-proto-schema - - - {{ 'device-profile.attributes-proto-schema-required' | translate}} +
+
+ device-profile.mqtt-device-payload-type +
+ + + + {{mqttTransportPayloadTypeTranslations.get(type) | translate}} + + + + {{ 'device-profile.mqtt-payload-type-required' | translate }} +
+ + device-profile.telemetry-proto-schema + + + {{ 'device-profile.telemetry-proto-schema-required' | translate}} + + + + device-profile.attributes-proto-schema + + + {{ 'device-profile.attributes-proto-schema-required' | translate}} + + +
-
-
+ + diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 5569a0b530..d16ad840f1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -87,13 +87,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control configuration: this.fb.group({ deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], - transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] + transportPayloadTypeConfiguration: this.fb.group({ + transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] + }) }) }); - let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; - configurationFormGroup.get('transportPayloadType').valueChanges.subscribe(payloadType => { - this.updateTransportPayloadBasedControls(payloadType, configurationFormGroup); - this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').valueChanges.subscribe(payloadType => { + this.updateTransportPayloadBasedControls(payloadType); + this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); @@ -109,15 +110,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control } } - protoPayloadType(): boolean { - let configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; - return configuration.transportPayloadType === MqttTransportPayloadType.PROTOBUF; + get protoPayloadType(): boolean { + let transportPayloadType = this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').value; + return transportPayloadType === MqttTransportPayloadType.PROTOBUF; } writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { - let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; - this.updateTransportPayloadBasedControls(value.transportPayloadType, configurationFormGroup); + this.updateTransportPayloadBasedControls(value.transportPayloadTypeConfiguration.transportPayloadType); this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); } } @@ -131,13 +131,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control this.propagateChange(configuration); } - private updateTransportPayloadBasedControls(type: MqttTransportPayloadType, configurationFormGroup: FormGroup) { + private updateTransportPayloadBasedControls(type: MqttTransportPayloadType) { + const transportPayloadTypeConfigurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration') as FormGroup; if (type === MqttTransportPayloadType.PROTOBUF) { - configurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); - configurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); + transportPayloadTypeConfigurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); + transportPayloadTypeConfigurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); } else { - configurationFormGroup.removeControl('deviceTelemetryProtoSchema'); - configurationFormGroup.removeControl('deviceAttributesProtoSchema'); + transportPayloadTypeConfigurationFormGroup.removeControl('deviceTelemetryProtoSchema'); + transportPayloadTypeConfigurationFormGroup.removeControl('deviceAttributesProtoSchema'); } } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 4ffe4af922..ffeae2f56d 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -148,7 +148,9 @@ export interface DefaultDeviceProfileTransportConfiguration { export interface MqttDeviceProfileTransportConfiguration { deviceTelemetryTopic?: string; deviceAttributesTopic?: string; - transportPayloadType?: MqttTransportPayloadType; + transportPayloadTypeConfiguration?: { + transportPayloadType?: MqttTransportPayloadType; + }; [key: string]: any; } @@ -208,7 +210,7 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT const mqttTransportConfiguration: MqttDeviceProfileTransportConfiguration = { deviceTelemetryTopic: 'v1/devices/me/telemetry', deviceAttributesTopic: 'v1/devices/me/attributes', - transportPayloadType: MqttTransportPayloadType.JSON + transportPayloadTypeConfiguration: {transportPayloadType: MqttTransportPayloadType.JSON} }; transportConfiguration = {...mqttTransportConfiguration, type: DeviceTransportType.MQTT}; break;