Browse Source

added TransportPayloadTypeConfiguration, fix tests, ui

pull/3740/head
ShvaykaD 6 years ago
parent
commit
4a18781304
  1. 44
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  2. 380
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java
  3. 45
      application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java
  4. 19
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java
  5. 19
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java
  6. 29
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java
  7. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java
  8. 12
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java
  9. 23
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java
  10. 50
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java
  11. 122
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java
  12. 37
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java
  13. 18
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java
  14. 406
      common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java
  15. 155
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  16. 66
      ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html
  17. 31
      ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts
  18. 6
      ui-ngx/src/app/shared/models/device.models.ts

44
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);

380
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<String> 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<String> 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<Descriptors.FieldDescriptor> 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);
}
}

45
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);

19
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

19
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<String> 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

29
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<String> 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<String> 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();

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

12
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java → 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;
}
}

23
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() {

50
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java

@ -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<MqttDeviceProfileTransportConfiguration> {
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);
}
}
}

122
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java → 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<TypeElement> 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<FieldElement> 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<EnumElement> 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<MessageElement> 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<OneOfElement> 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<TypeElement> nestedTypes = messageElement.getNestedTypes();
if (!nestedTypes.isEmpty()) {
List<EnumElement> nestedEnumTypes = getEnumElements(nestedTypes);
if (!nestedEnumTypes.isEmpty()) {
checkEnumElements(schemaName, nestedEnumTypes);
}
List<MessageElement> nestedMessageTypes = getMessageTypes(nestedTypes);
checkMessageElements(schemaName, nestedMessageTypes);
}
});
}
}
public ProtoFileElement getTransportProtoSchema(String protoSchema) {
return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile();
}

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

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

406
common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java

@ -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<String> 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<String> 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<Descriptors.FieldDescriptor> 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();
}
}

155
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<TypeElement> 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<FieldElement> 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<EnumElement> 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<MessageElement> 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<OneOfElement> 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<TypeElement> nestedTypes = messageElement.getNestedTypes();
if (!nestedTypes.isEmpty()) {
List<EnumElement> nestedEnumTypes = getEnumElements(nestedTypes);
if (!nestedEnumTypes.isEmpty()) {
checkEnumElements(schemaName, nestedEnumTypes);
}
List<MessageElement> nestedMessageTypes = getMessageTypes(nestedTypes);
checkMessageElements(schemaName, nestedMessageTypes);
}
});
}
}
private List<MessageElement> getMessageTypes(List<TypeElement> types) {
return types.stream()
.filter(typeElement -> typeElement instanceof MessageElement)
.map(typeElement -> (MessageElement) typeElement)
.collect(Collectors.toList());
}
private List<EnumElement> getEnumElements(List<TypeElement> types) {
return types.stream()
.filter(typeElement -> typeElement instanceof EnumElement)
.map(typeElement -> (EnumElement) typeElement)
.collect(Collectors.toList());
}
};
private PaginatedRemover<TenantId, DeviceProfile> tenantDeviceProfilesRemover =

66
ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html

@ -57,40 +57,42 @@
<div class="tb-hint" innerHTML="{{ 'device-profile.multi-level-wildcards-hint' | translate }}"></div>
</div>
</fieldset>
<fieldset class="fields-group">
<legend class="group-title" translate>device-profile.mqtt-device-payload-type</legend>
<div fxLayoutGap="8px" fxLayout="column">
<mat-form-field class="mat-block">
<mat-select formControlName="transportPayloadType" required>
<mat-option *ngFor="let type of mqttTransportPayloadTypes" [value]="type">
{{mqttTransportPayloadTypeTranslations.get(type) | translate}}
</mat-option>
</mat-select>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadType').hasError('required')">
{{ 'device-profile.mqtt-payload-type-required' | translate }}
</mat-error>
</mat-form-field>
<div *ngIf="protoPayloadType()" fxLayout="column">
<mat-form-field fxFlex>
<mat-label translate>device-profile.telemetry-proto-schema</mat-label>
<textarea matInput required
formControlName="deviceTelemetryProtoSchema"
rows="5"></textarea>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceTelemetryProtoSchema').hasError('required')">
{{ 'device-profile.telemetry-proto-schema-required' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label translate>device-profile.attributes-proto-schema</mat-label>
<textarea matInput required
formControlName="deviceAttributesProtoSchema"
rows="5"></textarea>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceAttributesProtoSchema').hasError('required')">
{{ 'device-profile.attributes-proto-schema-required' | translate}}
<section formGroupName="transportPayloadTypeConfiguration">
<fieldset class="fields-group">
<legend class="group-title" translate>device-profile.mqtt-device-payload-type</legend>
<div fxLayoutGap="8px" fxLayout="column">
<mat-form-field class="mat-block">
<mat-select formControlName="transportPayloadType" required>
<mat-option *ngFor="let type of mqttTransportPayloadTypes" [value]="type">
{{mqttTransportPayloadTypeTranslations.get(type) | translate}}
</mat-option>
</mat-select>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').hasError('required')">
{{ 'device-profile.mqtt-payload-type-required' | translate }}
</mat-error>
</mat-form-field>
<div *ngIf="protoPayloadType" fxLayout="column">
<mat-form-field fxFlex>
<mat-label translate>device-profile.telemetry-proto-schema</mat-label>
<textarea matInput required
formControlName="deviceTelemetryProtoSchema"
rows="5"></textarea>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.deviceTelemetryProtoSchema').hasError('required')">
{{ 'device-profile.telemetry-proto-schema-required' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label translate>device-profile.attributes-proto-schema</mat-label>
<textarea matInput required
formControlName="deviceAttributesProtoSchema"
rows="5"></textarea>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.deviceAttributesProtoSchema').hasError('required')">
{{ 'device-profile.attributes-proto-schema-required' | translate}}
</mat-error>
</mat-form-field>
</div>
</div>
</div>
</fieldset>
</fieldset>
</section>
</section>
</form>

31
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');
}
}

6
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;

Loading…
Cancel
Save