From 8d4718320b4b95ab1c0c03750b01a04e3cf78399 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 12 Oct 2020 16:21:05 +0300 Subject: [PATCH 01/84] init commit --- .../controller/DeviceProfileController.java | 25 +++ .../mqtt/AbstractMqttIntegrationTest.java | 10 +- ...AttributesRequestProtoIntegrationTest.java | 3 + ...AttributesUpdatesProtoIntegrationTest.java | 3 + ...MqttServerSideRpcProtoIntegrationTest.java | 4 + ...actMqttAttributesProtoIntegrationTest.java | 3 + ...actMqttTimeseriesProtoIntegrationTest.java | 5 + common/data/pom.xml | 12 ++ .../DeviceProfileTransportConfiguration.java | 2 +- ...ttDeviceProfileTransportConfiguration.java | 31 +++- ...onDeviceProfileTransportConfiguration.java | 42 +++++ ...toDeviceProfileTransportConfiguration.java | 152 ++++++++++++++++++ ...qttTransportConfigurationDeserializer.java | 50 ++++++ .../transport/mqtt/MqttTransportHandler.java | 14 +- .../mqtt/adaptors/ProtoMqttAdaptor.java | 20 ++- .../mqtt/session/DeviceSessionCtx.java | 22 ++- pom.xml | 12 ++ ...ile-transport-configuration.component.html | 46 ++++-- ...ofile-transport-configuration.component.ts | 24 ++- .../assets/locale/locale.constant-en_US.json | 4 + 20 files changed, 450 insertions(+), 34 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index b474a0c0f1..10a63d53d7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -29,7 +29,12 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.audit.ActionType; +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.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.page.PageData; @@ -92,6 +97,16 @@ public class DeviceProfileController extends BaseController { checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + + if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { + if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { + MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; + if (protoTransportConfiguration.getTransportPayloadType().equals(TransportPayloadType.PROTOBUF)) + checkProtoSchemas(protoTransportConfiguration); + } + } + DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); deviceProfileCache.put(savedDeviceProfile); @@ -200,4 +215,14 @@ public class DeviceProfileController extends BaseController { throw handleException(e); } } + + private void checkProtoSchemas(MqttProtoDeviceProfileTransportConfiguration mqttTransportConfiguration) throws ThingsboardException { + try { + mqttTransportConfiguration.validateTransportProtoSchema(mqttTransportConfiguration.getDeviceAttributesProtoSchema(), "attributes proto schema"); + mqttTransportConfiguration.validateTransportProtoSchema(mqttTransportConfiguration.getDeviceTelemetryProtoSchema(), "telemetry proto schema"); + } catch (Exception exception) { + throw new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.GENERAL); + } + } + } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index a25b6334e5..6c7572a425 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -34,6 +34,8 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; 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.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.controller.AbstractControllerTest; @@ -190,8 +192,12 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest deviceProfile.setDescription(transportPayloadType.name() + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); - MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration(); - transportConfiguration.setTransportPayloadType(transportPayloadType); + MqttDeviceProfileTransportConfiguration transportConfiguration; + if (TransportPayloadType.JSON.equals(transportPayloadType)) { + transportConfiguration = new MqttJsonDeviceProfileTransportConfiguration(); + } else { + transportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); + } if (!StringUtils.isEmpty(telemetryTopic)) { transportConfiguration.setDeviceTelemetryTopic(telemetryTopic); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 96cc88fa2f..80a469ed12 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -23,6 +23,7 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.TransportPayloadType; @@ -55,12 +56,14 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends } @Test + @Ignore public void testRequestAttributesValuesFromTheServer() throws Exception { processTestRequestAttributesValuesFromTheServer(); } @Test + @Ignore public void testRequestAttributesValuesFromTheServerGateway() throws Exception { processTestGatewayRequestAttributesValuesFromTheServer(); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java index faf8e1ce4d..e27fdda95c 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java @@ -19,6 +19,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; @@ -47,11 +48,13 @@ public abstract class AbstractMqttAttributesUpdatesProtoIntegrationTest extends } @Test + @Ignore public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { processTestSubscribeToAttributesUpdates(); } @Test + @Ignore public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { processGatewayTestSubscribeToAttributesUpdates(); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java index 759a5da912..41d5d380f4 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java @@ -46,21 +46,25 @@ public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends Abst } @Test + @Ignore public void testServerMqttOneWayRpc() throws Exception { processOneWayRpcTest(); } @Test + @Ignore public void testServerMqttTwoWayRpc() throws Exception { processTwoWayRpcTest(); } @Test + @Ignore public void testGatewayServerMqttOneWayRpc() throws Exception { processOneWayRpcTestGateway("Gateway Device OneWay RPC Proto"); } @Test + @Ignore public void testGatewayServerMqttTwoWayRpc() throws Exception { processTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto"); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java index e9adf19359..0e58ffeedb 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java @@ -18,6 +18,7 @@ package org.thingsboard.server.mqtt.telemetry.attributes; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.gen.transport.TransportApiProtos; @@ -46,6 +47,7 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac } @Test + @Ignore public void testPushMqttAttributes() throws Exception { List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); TransportProtos.PostAttributeMsg msg = getPostAttributeMsg(expectedKeys); @@ -53,6 +55,7 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac } @Test + @Ignore public void testPushMqttAttributesGateway() throws Exception { TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributesMsgProtoBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder(); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index de1c74109c..0a7cb223c3 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.TransportPayloadType; @@ -48,6 +49,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac } @Test + @Ignore public void testPushMqttTelemetry() throws Exception { List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); TransportProtos.TsKvListProto tsKvListProto = getTsKvListProto(expectedKeys, 0); @@ -55,6 +57,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac } @Test + @Ignore public void testPushMqttTelemetryWithTs() throws Exception { List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); TransportProtos.TsKvListProto tsKvListProto = getTsKvListProto(expectedKeys, 10000); @@ -62,6 +65,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac } @Test + @Ignore public void testPushMqttTelemetryGateway() throws Exception { TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder(); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); @@ -75,6 +79,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac } @Test + @Ignore public void testGatewayConnect() throws Exception { String deviceName = "Device A"; TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName); diff --git a/common/data/pom.xml b/common/data/pom.xml index eb7a3b226c..00e53ff2bb 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -71,6 +71,18 @@ java-driver-core test + + org.springframework + spring-web + + + com.squareup.wire + wire-schema + + + com.github.os72 + protobuf-dynamic + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java index 34854958d1..fb337b24f4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"), - @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), + @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M")}) public interface DeviceProfileTransportConfiguration { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index d88ac24cbb..8699a793ed 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -15,17 +15,40 @@ */ 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 com.squareup.wire.schema.Location; +import com.squareup.wire.schema.internal.parser.MessageElement; +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.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.DeviceTransportType; +import org.springframework.util.CollectionUtils; +import java.util.List; + +@Slf4j @Data -public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { +@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) +public abstract class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { - private TransportPayloadType transportPayloadType = TransportPayloadType.JSON; + 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; + public abstract TransportPayloadType getTransportPayloadType(); @Override public DeviceTransportType getType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java new file mode 100644 index 0000000000..83a17e93ef --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java @@ -0,0 +1,42 @@ +/** + * 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.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(using = JsonDeserializer.None.class) +public class MqttJsonDeviceProfileTransportConfiguration extends MqttDeviceProfileTransportConfiguration{ + + @Override + public DeviceTransportType getType() { + return super.getType(); + } + + @Override + public TransportPayloadType getTransportPayloadType() { + return TransportPayloadType.JSON; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java new file mode 100644 index 0000000000..f4185bd2e2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java @@ -0,0 +1,152 @@ +/** + * 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.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.schema.Location; +import com.squareup.wire.schema.internal.parser.EnumConstantElement; +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.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.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.TransportPayloadType; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +@EqualsAndHashCode(callSuper = true) +@Data +@JsonDeserialize(using = JsonDeserializer.None.class) +public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProfileTransportConfiguration { + + 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"; + + private String deviceTelemetryProtoSchema; + private String deviceAttributesProtoSchema; + + @JsonIgnore + private Descriptors.Descriptor telemetryMsgDescriptor; + + @JsonIgnore + private Descriptors.Descriptor attributesMsgDescriptor; + + @Override + public DeviceTransportType getType() { + return super.getType(); + } + + @Override + public TransportPayloadType getTransportPayloadType() { + 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("Failed to parse: " + schemaName + " due to: " + e.getMessage()); + } + List types = protoFileElement.getTypes(); + if (!CollectionUtils.isEmpty(types)) { + if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { + throw new IllegalArgumentException("Invalid " + schemaName + " provided! At least one Message definition should exists!"); + } + } else { + throw new IllegalArgumentException("Invalid " + schemaName + " provided!"); + } + } + + public Descriptors.Descriptor getDynamicMessageDescriptor(String protoSchema, String schemaName) { + ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); + DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder(); + schemaBuilder.setName(schemaName); + schemaBuilder.setPackage(!StringUtils.isEmpty(protoFileElement.getPackageName()) ? + protoFileElement.getPackageName() : schemaName.toLowerCase()); + + List types = protoFileElement.getTypes(); + + List enumTypes = types.stream() + .filter(typeElement -> typeElement instanceof EnumElement) + .map(typeElement -> (EnumElement) typeElement) + .collect(Collectors.toList()); + + List messageTypes = types.stream() + .filter(typeElement -> typeElement instanceof MessageElement) + .map(typeElement -> (MessageElement) typeElement) + .collect(Collectors.toList()); + + if (!CollectionUtils.isEmpty(enumTypes)) { + enumTypes.forEach(enumElement -> { + List enumElementTypeConstants = enumElement.getConstants(); + EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); + if (!CollectionUtils.isEmpty(enumElementTypeConstants)) { + enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); + } + EnumDefinition enumDefinition = enumDefinitionBuilder.build(); + schemaBuilder.addEnumDefinition(enumDefinition); + }); + } + + if (!CollectionUtils.isEmpty(messageTypes)) { + messageTypes.forEach(messageElement -> { + List messageElementFields = messageElement.getFields(); + MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); + if (!CollectionUtils.isEmpty(messageElementFields)) { + messageElementFields.forEach(fieldElement -> messageDefinitionBuilder.addField(fieldElement.getType(), fieldElement.getName(), fieldElement.getTag(), fieldElement.getDefaultValue())); + } + MessageDefinition messageDefinition = messageDefinitionBuilder.build(); + schemaBuilder.addMessageDefinition(messageDefinition); + }); + MessageElement lastMsg = messageTypes.stream().reduce((previous, last) -> last).get(); + try { + DynamicSchema dynamicSchema = schemaBuilder.build(); + DynamicMessage.Builder builder = dynamicSchema.newMessageBuilder(lastMsg.getName()); + return builder.getDescriptorForType(); + } catch (Descriptors.DescriptorValidationException e) { + throw new RuntimeException(e); + } + } else { + throw new RuntimeException("Failed to get Message Descriptor! Message types is empty for " + schemaName + " schema!"); + } + } + + + private ProtoFileElement getTransportProtoSchema(String protoSchema) { + return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java new file mode 100644 index 0000000000..0089420ed2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java @@ -0,0 +1,50 @@ +/** + * 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.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; + +@Slf4j +public class MqttTransportConfigurationDeserializer extends StdDeserializer { + + public MqttTransportConfigurationDeserializer() { + super(MqttDeviceProfileTransportConfiguration.class); + } + + @Override + public MqttDeviceProfileTransportConfiguration deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + try { + JsonNode jsonNode = jsonParser.readValueAsTree(); + if (jsonNode.has("deviceTelemetryProtoSchema") || jsonNode.has("deviceAttributesProtoSchema")) { + return jsonParser.getCodec().treeToValue(jsonNode, MqttProtoDeviceProfileTransportConfiguration.class); + } else { + return jsonParser.getCodec().treeToValue(jsonNode, MqttJsonDeviceProfileTransportConfiguration.class); + } + } catch (IOException e) { + log.trace("Failed to deserialize JSON content into equivalent tree model during creating {}!", MqttDeviceProfileTransportConfiguration.class.getSimpleName(), e); + throw new RuntimeException("Failed to deserialize JSON content into equivalent tree model during creating " + MqttDeviceProfileTransportConfiguration.class.getSimpleName() + "!", e); + } + } + +} \ No newline at end of file diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index cf353bdd72..16522eaa26 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -208,24 +208,24 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { try { - MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); + MqttTransportAdaptor adaptor = deviceSessionCtx.getPayloadAdaptor(); if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) { - TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); + TransportProtos.PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); } else if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { - TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); + TransportProtos.PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { - TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); + TransportProtos.GetAttributeRequestMsg getAttributeMsg = adaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC)) { - TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = payloadAdaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg); + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = adaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC)) { - TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = payloadAdaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg); + TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = adaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg)); } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) { - TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); + TransportProtos.ClaimDeviceMsg claimDeviceMsg = adaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); } else { transportService.reportActivity(deviceSessionCtx.getSessionInfo()); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index f66daf2289..15dff19532 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -15,7 +15,11 @@ */ package org.thingsboard.server.transport.mqtt.adaptors; +import com.google.gson.JsonParser; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.UnpooledByteBufAllocator; @@ -29,9 +33,11 @@ import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.ProtoConverter; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; import java.util.Optional; @@ -44,19 +50,27 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @Override public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx; byte[] bytes = toBytes(inbound.payload()); try { - return ProtoConverter.convertToTelemetryProto(bytes); - } catch (InvalidProtocolBufferException | IllegalArgumentException e) { + Descriptors.Descriptor telemetryDynamicMsgDescriptor = deviceSessionCtx.getTelemetryDynamicMsgDescriptor(); + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(telemetryDynamicMsgDescriptor, bytes); + String stringMsg = JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + return JsonConverter.convertToTelemetryProto(new JsonParser().parse(stringMsg)); + } catch (Exception e) { throw new AdaptorException(e); } } @Override public TransportProtos.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx; byte[] bytes = toBytes(inbound.payload()); try { - return ProtoConverter.validatePostAttributeMsg(bytes); + Descriptors.Descriptor attributesDynamicMessage = deviceSessionCtx.getAttributesDynamicMessageDescriptor(); + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(attributesDynamicMessage, bytes); + String stringMsg = JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + return JsonConverter.convertToAttributesProto(new JsonParser().parse(stringMsg)); } catch (InvalidProtocolBufferException | IllegalArgumentException e) { throw new AdaptorException(e); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index ba701ba56c..81fefde514 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.mqtt.session; +import com.google.protobuf.Descriptors; import io.netty.channel.ChannelHandlerContext; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -23,6 +24,7 @@ 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.transport.mqtt.MqttTransportContext; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter; @@ -49,6 +51,8 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); private volatile TransportPayloadType payloadType = TransportPayloadType.JSON; + private volatile Descriptors.Descriptor attributesDynamicMessageDescriptor; + private volatile Descriptors.Descriptor telemetryDynamicMessageDescriptor; public DeviceSessionCtx(UUID sessionId, ConcurrentMap mqttQoSMap, MqttTransportContext context) { super(sessionId, mqttQoSMap); @@ -63,7 +67,9 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { return msgIdSeq.incrementAndGet(); } - public boolean isDeviceTelemetryTopic(String topicName) { return telemetryTopicFilter.filter(topicName); } + public boolean isDeviceTelemetryTopic(String topicName) { + return telemetryTopicFilter.filter(topicName); + } public boolean isDeviceAttributesTopic(String topicName) { return attributesTopicFilter.filter(topicName); @@ -77,6 +83,14 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { return payloadType.equals(TransportPayloadType.JSON); } + public Descriptors.Descriptor getTelemetryDynamicMsgDescriptor() { + return telemetryDynamicMessageDescriptor; + } + + public Descriptors.Descriptor getAttributesDynamicMessageDescriptor() { + return attributesDynamicMessageDescriptor; + } + @Override public void setDeviceProfile(DeviceProfile deviceProfile) { super.setDeviceProfile(deviceProfile); @@ -98,10 +112,14 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { payloadType = mqttConfig.getTransportPayloadType(); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); + if (payloadType.equals(TransportPayloadType.PROTOBUF) && mqttConfig instanceof MqttProtoDeviceProfileTransportConfiguration) { + MqttProtoDeviceProfileTransportConfiguration protoMqttConfig = (MqttProtoDeviceProfileTransportConfiguration) mqttConfig; + telemetryDynamicMessageDescriptor = protoMqttConfig.getDynamicMessageDescriptor(protoMqttConfig.getDeviceTelemetryProtoSchema(), "telemetrySchema"); + attributesDynamicMessageDescriptor = protoMqttConfig.getDynamicMessageDescriptor(protoMqttConfig.getDeviceAttributesProtoSchema(), "attributesSchema"); + } } else { telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); } } - } diff --git a/pom.xml b/pom.xml index cc5bbce51b..ef98bd3b79 100755 --- a/pom.xml +++ b/pom.xml @@ -106,6 +106,8 @@ 3.2.2 1.5.0 1.5.2 + 1.0.1 + 3.4.0 @@ -1369,6 +1371,16 @@ micrometer-registry-prometheus ${micrometer.version} + + com.github.os72 + protobuf-dynamic + ${protobuf-dynamic.version} + + + com.squareup.wire + wire-schema + ${wire-schema.version} + diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index 00ff4760ab..c83066bbae 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -20,17 +20,6 @@
device-profile.mqtt-device-topic-filters
- - device-profile.mqtt-device-payload-type - - - {{mqttTransportPayloadTypeTranslations.get(type) | translate}} - - - - {{ 'device-profile.mqtt-payload-type-required' | translate }} - -
device-profile.telemetry-topic-filter @@ -68,5 +57,40 @@
+
+ device-profile.mqtt-device-payload-type +
+ + + + {{mqttTransportPayloadTypeTranslations.get(type) | translate}} + + + + {{ 'device-profile.mqtt-payload-type-required' | translate }} + + +
+ + device-profile.telemetry-proto-schema + + + {{ 'device-profile.telemetry-proto-schema-required' | translate}} + + + + device-profile.attributes-proto-schema + + + {{ 'device-profile.attributes-proto-schema-required' | translate}} + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 18dc1b2bf4..c654c24f75 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -28,12 +28,13 @@ import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { - MqttTransportPayloadType, DeviceProfileTransportConfiguration, DeviceTransportType, - MqttDeviceProfileTransportConfiguration, mqttTransportPayloadTypeTranslationMap + MqttDeviceProfileTransportConfiguration, + MqttTransportPayloadType, + mqttTransportPayloadTypeTranslationMap } from '@shared/models/device.models'; -import { isDefinedAndNotNull } from '@core/utils'; +import {isDefinedAndNotNull} from '@core/utils'; @Component({ selector: 'tb-mqtt-device-profile-transport-configuration', @@ -86,7 +87,9 @@ 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] + transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required], + // deviceTelemetryProtoSchema: [null, Validators.required], + // deviceAttributesProtoSchema: [null, Validators.required] }) }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { @@ -103,6 +106,11 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control } } + protoPayloadType(): boolean { + let configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; + return configuration.transportPayloadType === MqttTransportPayloadType.PROTOBUF; + } + writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); @@ -114,6 +122,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control if (this.mqttDeviceProfileTransportConfigurationFormGroup.valid) { configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; configuration.type = DeviceTransportType.MQTT; + let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; + if (configuration.transportPayloadType === MqttTransportPayloadType.PROTOBUF) { + configurationFormGroup.addControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); + configurationFormGroup.addControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); + } else { + configurationFormGroup.removeControl('deviceTelemetryProtoSchema'); + configurationFormGroup.removeControl('deviceAttributesProtoSchema'); + } } this.propagateChange(configuration); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 0fd0e1f7a2..3e3550f0d0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -807,6 +807,10 @@ "telemetry-topic-filter-required": "Telemetry topic filter is required.", "attributes-topic-filter": "Attributes topic filter", "attributes-topic-filter-required": "Attributes topic filter is required.", + "telemetry-proto-schema": "Telemetry proto schema", + "telemetry-proto-schema-required": "Telemetry proto schema is required.", + "attributes-proto-schema": "Attributes proto schema", + "attributes-proto-schema-required": "Attributes proto schema is required.", "rpc-response-topic-filter": "RPC response topic filter", "rpc-response-topic-filter-required": "RPC response topic filter is required.", "not-valid-pattern-topic-filter": "Not valid pattern topic filter", From 64cc74c26cc34f832ba33dff03a91f82eca47eb2 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 13 Oct 2020 13:57:38 +0300 Subject: [PATCH 02/84] cleanup code --- ...ttDeviceProfileTransportConfiguration.java | 7 ------ ...toDeviceProfileTransportConfiguration.java | 8 ------- ...qttTransportConfigurationDeserializer.java | 5 ++-- .../mqtt/adaptors/ProtoMqttAdaptor.java | 24 ++++++++++++------- .../mqtt/session/DeviceSessionCtx.java | 10 +++++--- ...ofile-transport-configuration.component.ts | 2 -- 6 files changed, 26 insertions(+), 30 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 8699a793ed..3395e398e8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -19,18 +19,11 @@ 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 com.squareup.wire.schema.Location; -import com.squareup.wire.schema.internal.parser.MessageElement; -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.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.DeviceTransportType; -import org.springframework.util.CollectionUtils; -import java.util.List; @Slf4j @Data diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java index f4185bd2e2..c3c9fe263a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.github.os72.protobuf.dynamic.DynamicSchema; @@ -40,7 +39,6 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; @Slf4j @@ -56,12 +54,6 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf private String deviceTelemetryProtoSchema; private String deviceAttributesProtoSchema; - @JsonIgnore - private Descriptors.Descriptor telemetryMsgDescriptor; - - @JsonIgnore - private Descriptors.Descriptor attributesMsgDescriptor; - @Override public DeviceTransportType getType() { return super.getType(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java index 0089420ed2..15f52f603e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java @@ -18,10 +18,10 @@ 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.JsonDeserializer; 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; @@ -36,7 +36,8 @@ public class MqttTransportConfigurationDeserializer extends StdDeserializer { From d11a1ba7e63e8a03df0f9e98cc69c89387f80769 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 13 Oct 2020 16:06:18 +0300 Subject: [PATCH 03/84] cleanup code --- .../controller/DeviceProfileController.java | 8 ++-- common/data/pom.xml | 4 -- ...ttDeviceProfileTransportConfiguration.java | 4 +- ...toDeviceProfileTransportConfiguration.java | 37 +++++++++++++------ .../transport/mqtt/MqttTransportHandler.java | 14 +++---- .../mqtt/adaptors/ProtoMqttAdaptor.java | 2 - .../mqtt/session/DeviceSessionCtx.java | 2 +- 7 files changed, 38 insertions(+), 33 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index 48c5ab6978..fef67ccc72 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -99,12 +99,10 @@ public class DeviceProfileController extends BaseController { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { - if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { - MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - if (protoTransportConfiguration.getTransportPayloadType().equals(TransportPayloadType.PROTOBUF)) + if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { + MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; + if (protoTransportConfiguration.getTransportPayloadType().equals(TransportPayloadType.PROTOBUF)) checkProtoSchemas(protoTransportConfiguration); - } } DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); diff --git a/common/data/pom.xml b/common/data/pom.xml index 00e53ff2bb..6d32a3df53 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -71,10 +71,6 @@ java-driver-core test - - org.springframework - spring-web - com.squareup.wire wire-schema diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 3395e398e8..720e81b8fd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -38,11 +38,11 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonDeserialize(using = MqttTransportConfigurationDeserializer.class) public abstract class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { + public abstract TransportPayloadType getTransportPayloadType(); + protected String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC; protected String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; - public abstract TransportPayloadType getTransportPayloadType(); - @Override public DeviceTransportType getType() { return DeviceTransportType.MQTT; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java index c3c9fe263a..06e8d57c8e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java @@ -22,6 +22,7 @@ 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.schema.Field; import com.squareup.wire.schema.Location; import com.squareup.wire.schema.internal.parser.EnumConstantElement; import com.squareup.wire.schema.internal.parser.EnumElement; @@ -33,8 +34,6 @@ import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -73,7 +72,7 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf throw new IllegalArgumentException("Failed to parse: " + schemaName + " due to: " + e.getMessage()); } List types = protoFileElement.getTypes(); - if (!CollectionUtils.isEmpty(types)) { + if (!types.isEmpty()) { if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { throw new IllegalArgumentException("Invalid " + schemaName + " provided! At least one Message definition should exists!"); } @@ -86,7 +85,7 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder(); schemaBuilder.setName(schemaName); - schemaBuilder.setPackage(!StringUtils.isEmpty(protoFileElement.getPackageName()) ? + schemaBuilder.setPackage(!isEmptyStr(protoFileElement.getPackageName()) ? protoFileElement.getPackageName() : schemaName.toLowerCase()); List types = protoFileElement.getTypes(); @@ -101,11 +100,11 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf .map(typeElement -> (MessageElement) typeElement) .collect(Collectors.toList()); - if (!CollectionUtils.isEmpty(enumTypes)) { + if (!enumTypes.isEmpty()) { enumTypes.forEach(enumElement -> { List enumElementTypeConstants = enumElement.getConstants(); EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); - if (!CollectionUtils.isEmpty(enumElementTypeConstants)) { + if (!enumElementTypeConstants.isEmpty()) { enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); } EnumDefinition enumDefinition = enumDefinitionBuilder.build(); @@ -113,12 +112,21 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf }); } - if (!CollectionUtils.isEmpty(messageTypes)) { + if (!messageTypes.isEmpty()) { messageTypes.forEach(messageElement -> { List messageElementFields = messageElement.getFields(); MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); - if (!CollectionUtils.isEmpty(messageElementFields)) { - messageElementFields.forEach(fieldElement -> messageDefinitionBuilder.addField(fieldElement.getType(), fieldElement.getName(), fieldElement.getTag(), fieldElement.getDefaultValue())); + if (!messageElementFields.isEmpty()) { + messageElementFields.forEach(fieldElement -> { + Field.Label label = fieldElement.getLabel(); + String labelStr = label != null ? label.name() : null; + messageDefinitionBuilder.addField( + labelStr, + fieldElement.getType(), + fieldElement.getName(), + fieldElement.getTag(), + fieldElement.getDefaultValue()); + }); } MessageDefinition messageDefinition = messageDefinitionBuilder.build(); schemaBuilder.addMessageDefinition(messageDefinition); @@ -129,16 +137,21 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf DynamicMessage.Builder builder = dynamicSchema.newMessageBuilder(lastMsg.getName()); return builder.getDescriptorForType(); } catch (Descriptors.DescriptorValidationException e) { - throw new RuntimeException(e); + log.error("Failed to create dynamic schema due to: ", e); + return null; } } else { - throw new RuntimeException("Failed to get Message Descriptor! Message types is empty for " + schemaName + " schema!"); + log.error("Failed to get Message Descriptor! Message types is empty for {} schema!", schemaName); + return null; } } - private ProtoFileElement getTransportProtoSchema(String protoSchema) { return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); } + private boolean isEmptyStr(String str) { + return str == null || "".equals(str); + } + } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 3b3f80e89f..fab0579bb2 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -262,24 +262,24 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { try { - MqttTransportAdaptor adaptor = deviceSessionCtx.getPayloadAdaptor(); + MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) { - TransportProtos.PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); + TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); } else if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { - TransportProtos.PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); + TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { - TransportProtos.GetAttributeRequestMsg getAttributeMsg = adaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); + TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC)) { - TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = adaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg); + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = payloadAdaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC)) { - TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = adaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg); + TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = payloadAdaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg)); } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) { - TransportProtos.ClaimDeviceMsg claimDeviceMsg = adaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); + TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); } else { transportService.reportActivity(deviceSessionCtx.getSessionInfo()); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index 532115a765..aaa8983a0c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -38,7 +38,6 @@ import org.thingsboard.server.common.transport.adaptor.ProtoConverter; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; -import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; import java.util.Optional; @@ -122,7 +121,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @Override public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException { byte[] bytes = toBytes(mqttMsg.payload()); - String topicName = mqttMsg.variableHeader().topicName(); try { return ProtoConverter.convertToProvisionRequestMsg(bytes); } catch (InvalidProtocolBufferException ex) { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index f595df7721..6ecea5a77c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -121,7 +121,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { payloadType = mqttConfig.getTransportPayloadType(); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); - if (payloadType.equals(TransportPayloadType.PROTOBUF) && mqttConfig instanceof MqttProtoDeviceProfileTransportConfiguration) { + if (mqttConfig instanceof MqttProtoDeviceProfileTransportConfiguration) { updateDynamicMessageDescriptors(mqttConfig); } } else { From 1f2719e321b0fb1771a1444272214231bbc9936a Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 13 Oct 2020 16:17:39 +0300 Subject: [PATCH 04/84] fix typos --- .../server/controller/DeviceProfileController.java | 3 +-- .../server/transport/mqtt/adaptors/ProtoMqttAdaptor.java | 2 +- .../server/transport/mqtt/session/DeviceSessionCtx.java | 4 +--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index fef67ccc72..f55ac89ab5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -101,8 +101,7 @@ public class DeviceProfileController extends BaseController { if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - if (protoTransportConfiguration.getTransportPayloadType().equals(TransportPayloadType.PROTOBUF)) - checkProtoSchemas(protoTransportConfiguration); + checkProtoSchemas(protoTransportConfiguration); } DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index aaa8983a0c..e4a35a435f 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -67,7 +67,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { Descriptors.Descriptor attributesDynamicMessage = getDescriptor(deviceSessionCtx.getAttributesDynamicMessageDescriptor()); try { return JsonConverter.convertToAttributesProto(new JsonParser().parse(dynamicMsgToJson(bytes, attributesDynamicMessage))); - } catch (InvalidProtocolBufferException | IllegalArgumentException e) { + } catch (Exception e) { throw new AdaptorException(e); } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 6ecea5a77c..79147c0935 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -80,9 +80,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { return telemetryTopicFilter.filter(topicName); } - public boolean isDeviceAttributesTopic(String topicName) { - return attributesTopicFilter.filter(topicName); - } + public boolean isDeviceAttributesTopic(String topicName) { return attributesTopicFilter.filter(topicName); } public MqttTransportAdaptor getPayloadAdaptor() { return payloadType.equals(TransportPayloadType.JSON) ? context.getJsonMqttAdaptor() : context.getProtoMqttAdaptor(); From f89fd60e7c99825ae34b7844ac30cb4312aaf8af Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 13 Oct 2020 16:21:57 +0300 Subject: [PATCH 05/84] fix typo --- .../device/profile/MqttDeviceProfileTransportConfiguration.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 720e81b8fd..d76cd55f8c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -20,12 +20,10 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; -import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.DeviceTransportType; -@Slf4j @Data @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( From 8bdf990ed2ac9c30d1c8c447b94c963ef8cd27dc Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 13 Oct 2020 16:23:23 +0300 Subject: [PATCH 06/84] revert changes --- .../profile/MqttDeviceProfileTransportConfiguration.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index d76cd55f8c..66174e3d29 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -23,8 +23,6 @@ import lombok.Data; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.DeviceTransportType; - -@Data @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, @@ -34,6 +32,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; @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 abstract TransportPayloadType getTransportPayloadType(); From 4eafa92cb73fb72f265a925ebd5ebd2f27484f08 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 23 Oct 2020 15:40:37 +0300 Subject: [PATCH 07/84] added parser tests --- .../mqtt/AbstractMqttIntegrationTest.java | 40 +- .../server/mqtt/MqttSqlTestSuite.java | 3 +- ...tMqttAttributesRequestIntegrationTest.java | 1 + ...AttributesRequestProtoIntegrationTest.java | 3 +- ...tMqttAttributesUpdatesIntegrationTest.java | 18 +- ...tAttributesUpdatesJsonIntegrationTest.java | 18 +- ...AttributesUpdatesProtoIntegrationTest.java | 2 - .../server/mqtt/proto/DynamicProtoTest.java | 408 ++++++++++++++++++ ...MqttServerSideRpcProtoIntegrationTest.java | 4 - ...toDeviceProfileTransportConfiguration.java | 244 ++++++++--- pom.xml | 2 +- 11 files changed, 659 insertions(+), 84 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index e7a3a3deb9..0ac5f679e0 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -62,6 +62,41 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest private static final AtomicInteger atomicInteger = new AtomicInteger(2); + protected static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax = \"proto2\";\n" + + "package test;\n" + + "\n" + + "option java_package = \"org.thingsboard.server.gen.test\";\n" + + "option java_outer_classname = \"TestProtos\";\n" + + "\n" + + "message nestedPostTelemetry {\n" + + " required bool bool_v = 1;\n" + + " required int64 long_v = 2;\n" + + " required double double_v = 3;\n" + + " required string string_v = 4;\n" + + " required string json_v = 5;\n" + + "}\n" + + "\n" + + "message PostTelemetry {\n" + + " repeated nestedPostTelemetry postTelemetry = 1;\n" + + "}"; + protected static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax = \"proto2\";\n" + + "package test;\n" + + "\n" + + "option java_package = \"org.thingsboard.server.gen.test\";\n" + + "option java_outer_classname = \"TestProtos\";\n" + + "\n" + + "message nestedPostAttributes {\n" + + " required bool bool_v = 1;\n" + + " required int64 long_v = 2;\n" + + " required double double_v = 3;\n" + + " required string string_v = 4;\n" + + " required string json_v = 5;\n" + + "}\n" + + "\n" + + "message PostAttributes {\n" + + " repeated nestedPostAttributes postAttributes = 1;\n" + + "}"; + protected Tenant savedTenant; protected User tenantAdmin; @@ -219,7 +254,10 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest if (TransportPayloadType.JSON.equals(transportPayloadType)) { transportConfiguration = new MqttJsonDeviceProfileTransportConfiguration(); } else { - transportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); + MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); + protoTransportConfiguration.setDeviceAttributesProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); + protoTransportConfiguration.setDeviceTelemetryProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); + transportConfiguration = protoTransportConfiguration; } if (!StringUtils.isEmpty(telemetryTopic)) { transportConfiguration.setDeviceTelemetryTopic(telemetryTopic); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index f71e8f37a2..440c587856 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -32,7 +32,8 @@ import java.util.Arrays; "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", "org.thingsboard.server.mqtt.attributes.request.sql.*Test", "org.thingsboard.server.mqtt.claim.sql.*Test", - "org.thingsboard.server.mqtt.provision.sql.*Test" + "org.thingsboard.server.mqtt.provision.sql.*Test", + "org.thingsboard.server.mqtt.proto.sql.*Test" }) public class MqttSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java index 9e4529caf2..438757bfff 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java @@ -25,6 +25,7 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.profile.MqttTopics; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 80a469ed12..d2981de353 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -55,15 +55,14 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends processAfterTest(); } - @Test @Ignore + @Test public void testRequestAttributesValuesFromTheServer() throws Exception { processTestRequestAttributesValuesFromTheServer(); } @Test - @Ignore public void testRequestAttributesValuesFromTheServerGateway() throws Exception { processTestGatewayRequestAttributesValuesFromTheServer(); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java index d2febdf357..c40c2f2914 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java @@ -65,15 +65,15 @@ public abstract class AbstractMqttAttributesUpdatesIntegrationTest extends Abstr processAfterTest(); } - @Test - public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { - processTestSubscribeToAttributesUpdates(); - } - - @Test - public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { - processGatewayTestSubscribeToAttributesUpdates(); - } +// @Test +// public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { +// processTestSubscribeToAttributesUpdates(); +// } +// +// @Test +// public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { +// processGatewayTestSubscribeToAttributesUpdates(); +// } protected void processTestSubscribeToAttributesUpdates() throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java index 58379e4016..4f5a5f9369 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java @@ -39,13 +39,13 @@ public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends A processAfterTest(); } - @Test - public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { - processTestSubscribeToAttributesUpdates(); - } - - @Test - public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { - processGatewayTestSubscribeToAttributesUpdates(); - } +// @Test +// public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { +// processTestSubscribeToAttributesUpdates(); +// } +// +// @Test +// public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { +// processGatewayTestSubscribeToAttributesUpdates(); +// } } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java index e27fdda95c..16ccc789ee 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java @@ -48,13 +48,11 @@ public abstract class AbstractMqttAttributesUpdatesProtoIntegrationTest extends } @Test - @Ignore public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { processTestSubscribeToAttributesUpdates(); } @Test - @Ignore public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { processGatewayTestSubscribeToAttributesUpdates(); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java b/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java new file mode 100644 index 0000000000..eb6912f257 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java @@ -0,0 +1,408 @@ +/** + * 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.mqtt.proto; + +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 lombok.extern.slf4j.Slf4j; +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; + +@Slf4j +@RunWith(MockitoJUnitRunner.class) +public class DynamicProtoTest { + + 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("Failed to parse: testParseToProtoFile schema 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("Invalid schema syntax: proto2 for: testSyntaxValidation provided! Only proto3 allowed!", INVALID_PROTO_SCHEMA_NOT_VALID_SYNTAX, "testSyntaxValidation"); + } + + @Test + public void testDynamicSchemaOptionsValidation() { + processValidation("Invalid testOptionsValidation schema provided! Schema options don't support!", INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED, "testOptionsValidation"); + } + + @Test + public void testDynamicSchemaPublicImportsValidation() { + processValidation("Invalid testPublicImportsValidation schema provided! Schema public imports don't support!", INVALID_PROTO_SCHEMA_PUBLIC_IMPORTS_NOT_SUPPORTED, "testPublicImportsValidation"); + } + + @Test + public void testDynamicSchemaImportsValidation() { + processValidation("Invalid testImportsValidation schema provided! Schema imports don't support!", INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED, "testImportsValidation"); + } + + @Test + public void testDynamicSchemaExtendDeclarationsValidation() { + processValidation("Invalid testExtendDeclarationsValidation schema provided! Schema extend declarations don't support!", INVALID_PROTO_SCHEMA_EXTEND_DECLARATION_NOT_SUPPORTED, "testExtendDeclarationsValidation"); + } + + @Test + public void testDynamicSchemaEnumOptionsValidation() { + processValidation("Invalid testEnumOptionsValidation schema provided! Enum definitions options are not supported!", INVALID_PROTO_SCHEMA_ENUM_OPTIONS_NOT_SUPPORTED, "testEnumOptionsValidation"); + } + + @Test + public void testDynamicSchemaNoOneMessageTypeExistsValidation() { + processValidation("Invalid noOneMessageTypeExists schema provided! At least one Message definition should exists!", INVALID_PROTO_SCHEMA_NO_MESSAGE_TYPES_EXISTS, "noOneMessageTypeExists"); + } + + @Test + public void testDynamicSchemaMessageTypeOptionsValidation() { + processValidation("Invalid messageTypeOptions schema provided! Message definition options don't support!", INVALID_PROTO_SCHEMA_MESSAGE_OPTIONS_NOT_SUPPORTED, "messageTypeOptions"); + } + + @Test + public void testDynamicSchemaMessageTypeExtensionsValidation() { + processValidation("Invalid messageTypeExtensions schema provided! Message definition extensions don't support!", INVALID_PROTO_SCHEMA_MESSAGE_EXTENSIONS_NOT_SUPPORTED, "messageTypeExtensions"); + } + + @Test + public void testDynamicSchemaMessageTypeReservedElementsValidation() { + processValidation("Invalid messageTypeReservedElements schema provided! Message definition reserved elements don't support!", INVALID_PROTO_SCHEMA_MESSAGE_RESERVED_NOT_SUPPORTED, "messageTypeReservedElements"); + } + + @Test + public void testDynamicSchemaMessageTypeGroupsElementsValidation() { + processValidation("Invalid messageTypeGroupsElements schema provided! Message definition groups don't support!", INVALID_PROTO_SCHEMA_MESSAGE_GROUPS_NOT_SUPPORTED, "messageTypeGroupsElements"); + } + + @Test + public void testDynamicSchemaOneOfsTypeGroupsElementsValidation() { + processValidation("Invalid oneOfsTypeGroupsElements schema provided! OneOf definition groups don't support!", INVALID_PROTO_SCHEMA_ONE_OFS_GROUPS_NOT_SUPPORTED, "oneOfsTypeGroupsElements"); + } + + private void processValidation(String expectedMessage, String schema, String schemaName) { + exceptionRule.expect(IllegalArgumentException.class); + exceptionRule.expectMessage(expectedMessage); + validateTransportProtoSchema(schema, schemaName); + } + + @Test + public void testDynamicSchemaCreationWithMessageNestedTypes() throws Exception { + String testNestedTypesProtoSchema = "testNestedTypesProtoSchema"; + validateTransportProtoSchema(PROTO_SCHEMA_WITH_NESTED_MSG_TYPES, testNestedTypesProtoSchema); + DynamicSchema dynamicSchema = getDynamicSchema(PROTO_SCHEMA_WITH_NESTED_MSG_TYPES, testNestedTypesProtoSchema); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(5, messageTypes.size()); + assertTrue(messageTypes.contains("testnested.Outer")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA.Inner")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB.Inner")); + + DynamicMessage.Builder middleAAInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleAAInnerMsgDescriptor = middleAAInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAInnerMsg = middleAAInnerMsgBuilder + .setField(middleAAInnerMsgDescriptor.findFieldByName("ival"), 1L) + .setField(middleAAInnerMsgDescriptor.findFieldByName("booly"), true) + .build(); + + DynamicMessage.Builder middleAAMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA"); + Descriptors.Descriptor middleAAMsgDescriptor = middleAAMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAMsg = middleAAMsgBuilder + .setField(middleAAMsgDescriptor.findFieldByName("inner"), middleAAInnerMsg) + .build(); + + DynamicMessage.Builder middleBBInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleBBInnerMsgDescriptor = middleBBInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBInnerMsg = middleBBInnerMsgBuilder + .setField(middleBBInnerMsgDescriptor.findFieldByName("ival"), 0L) + .setField(middleBBInnerMsgDescriptor.findFieldByName("booly"), false) + .build(); + + DynamicMessage.Builder middleBBMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleBB"); + Descriptors.Descriptor middleBBMsgDescriptor = middleBBMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBMsg = middleBBMsgBuilder + .setField(middleBBMsgDescriptor.findFieldByName("inner"), middleBBInnerMsg) + .build(); + + + DynamicMessage.Builder outerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer"); + Descriptors.Descriptor outerMsgBuilderDescriptor = outerMsgBuilder.getDescriptorForType(); + DynamicMessage outerMsg = outerMsgBuilder + .setField(outerMsgBuilderDescriptor.findFieldByName("middleAA"), middleAAMsg) + .setField(outerMsgBuilderDescriptor.findFieldByName("middleBB"), middleBBMsg) + .build(); + + assertEquals("{\n" + + " \"middleAA\": {\n" + + " \"inner\": {\n" + + " \"ival\": \"1\",\n" + + " \"booly\": true\n" + + " }\n" + + " },\n" + + " \"middleBB\": {\n" + + " \"inner\": {\n" + + " \"ival\": 0,\n" + + " \"booly\": false\n" + + " }\n" + + " }\n" + + "}", dynamicMsgToJson(outerMsgBuilderDescriptor, outerMsg.toByteArray())); + } + + @Test + public void testDynamicSchemaCreationWithMessageOneOfs() throws Exception { + String testOneOfsProtoSchema = "testOneOfsProtoSchema"; + validateTransportProtoSchema(PROTO_SCHEMA_WITH_ONE_OFS, testOneOfsProtoSchema); + DynamicSchema dynamicSchema = getDynamicSchema(PROTO_SCHEMA_WITH_ONE_OFS, testOneOfsProtoSchema); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(2, messageTypes.size()); + assertTrue(messageTypes.contains("testoneofs.SubMessage")); + assertTrue(messageTypes.contains("testoneofs.SampleMessage")); + + DynamicMessage.Builder sampleMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SampleMessage"); + Descriptors.Descriptor sampleMsgDescriptor = sampleMsgBuilder.getDescriptorForType(); + assertNotNull(sampleMsgDescriptor); + + List fields = sampleMsgDescriptor.getFields(); + assertEquals(2, fields.size()); + DynamicMessage sampleMsg = sampleMsgBuilder + .setField(sampleMsgDescriptor.findFieldByName("name"), "Bob") + .build(); + assertEquals("{\n" + " \"name\": \"Bob\"\n" + "}", dynamicMsgToJson(sampleMsgDescriptor, sampleMsg.toByteArray())); + + DynamicMessage.Builder subMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SubMessage"); + Descriptors.Descriptor subMsgDescriptor = subMsgBuilder.getDescriptorForType(); + DynamicMessage subMsg = subMsgBuilder + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "Alice") + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "John") + .build(); + + DynamicMessage sampleMsgWithOneOfSubMessage = sampleMsgBuilder.setField(sampleMsgDescriptor.findFieldByName("subMessage"), subMsg).build(); + assertEquals("{\n" + " \"subMessage\": {\n" + " \"name\": [\"Alice\", \"John\"]\n" + " }\n" + "}", + dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); + + } + + private String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); + return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + } + + private static ProtoFileElement getTransportProtoSchema(String protoSchema) { + return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); + } +} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java index 41d5d380f4..759a5da912 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java @@ -46,25 +46,21 @@ public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends Abst } @Test - @Ignore public void testServerMqttOneWayRpc() throws Exception { processOneWayRpcTest(); } @Test - @Ignore public void testServerMqttTwoWayRpc() throws Exception { processTwoWayRpcTest(); } @Test - @Ignore public void testGatewayServerMqttOneWayRpc() throws Exception { processOneWayRpcTestGateway("Gateway Device OneWay RPC Proto"); } @Test - @Ignore public void testGatewayServerMqttTwoWayRpc() throws Exception { processTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java index 06e8d57c8e..c96a1a565f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java @@ -22,12 +22,13 @@ 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.schema.Field; +import com.squareup.wire.Syntax; import com.squareup.wire.schema.Location; import com.squareup.wire.schema.internal.parser.EnumConstantElement; 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; @@ -37,6 +38,8 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -50,6 +53,10 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf 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 "Invalid " + schemaName + " schema provided!"; + } + private String deviceTelemetryProtoSchema; private String deviceAttributesProtoSchema; @@ -69,85 +76,212 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf try { protoFileElement = schemaParser.readProtoFile(); } catch (Exception e) { - throw new IllegalArgumentException("Failed to parse: " + schemaName + " due to: " + e.getMessage()); - } - List types = protoFileElement.getTypes(); - if (!types.isEmpty()) { - if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { - throw new IllegalArgumentException("Invalid " + schemaName + " provided! At least one Message definition should exists!"); - } - } else { - throw new IllegalArgumentException("Invalid " + schemaName + " provided!"); + throw new IllegalArgumentException("Failed to parse: " + schemaName + " schema 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 getDynamicMessageDescriptor(String protoSchema, String schemaName) { - ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); + try { + ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); + DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName); + String lastMsgName = getMessageTypes(protoFileElement.getTypes()).stream() + .map(MessageElement::getName).reduce((previous, last) -> last).get(); + DynamicMessage.Builder builder = dynamicSchema.newMessageBuilder(lastMsgName); + return builder.getDescriptorForType(); + } catch (Exception e) { + log.warn("Failed to get Message Descriptor due to {}", e.getMessage()); + return null; + } + } + + public DynamicSchema getDynamicSchema(ProtoFileElement protoFileElement, String schemaName) { DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder(); schemaBuilder.setName(schemaName); schemaBuilder.setPackage(!isEmptyStr(protoFileElement.getPackageName()) ? protoFileElement.getPackageName() : schemaName.toLowerCase()); + List types = protoFileElement.getTypes(); + List messageTypes = getMessageTypes(types); + + if (!messageTypes.isEmpty()) { + List enumTypes = getEnumElements(types); + if (!enumTypes.isEmpty()) { + enumTypes.forEach(enumElement -> { + EnumDefinition enumDefinition = getEnumDefinition(enumElement); + schemaBuilder.addEnumDefinition(enumDefinition); + }); + } + List messageDefinitions = getMessageDefinitions(messageTypes); + messageDefinitions.forEach(schemaBuilder::addMessageDefinition); + try { + return schemaBuilder.build(); + } catch (Descriptors.DescriptorValidationException e) { + throw new RuntimeException("Failed to create dynamic schema due to: " + e.getMessage()); + } + } else { + throw new RuntimeException("Failed to get Dynamic Schema! Message types is empty for schema:" + schemaName); + } + } + + private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { + if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { + throw new IllegalArgumentException("Invalid schema syntax: " + protoFileElement.getSyntax() + + " for: " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); + } + } + + private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { + if (!isEmptySettings) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); + } + } + private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) { List types = protoFileElement.getTypes(); + if (!types.isEmpty()) { + if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!"); + } else { + checkEnumElements(schemaName, getEnumElements(types)); + checkMessageElements(schemaName, getMessageTypes(types)); + } + } else { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!"); + } + } - List enumTypes = types.stream() - .filter(typeElement -> typeElement instanceof EnumElement) - .map(typeElement -> (EnumElement) typeElement) - .collect(Collectors.toList()); + private void checkEnumElements(String schemaName, List enumTypes) { + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); + } + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!"); + } + } + + private void checkMessageElements(String schemaName, List messageElementsList) { + if (!messageElementsList.isEmpty()) { + messageElementsList.forEach(messageElement -> { + checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), + " Message definition groups don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), + " Message definition options don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), + " Message definition extensions don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), + " Message definition reserved elements don't support!"); + List oneOfs = messageElement.getOneOfs(); + if (!oneOfs.isEmpty()) { + oneOfs.forEach(oneOfElement -> + checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), + " OneOf definition groups don't support!")); + } + List nestedTypes = messageElement.getNestedTypes(); + if (!nestedTypes.isEmpty()) { + List nestedEnumTypes = getEnumElements(nestedTypes); + if (!nestedEnumTypes.isEmpty()) { + checkEnumElements(schemaName, nestedEnumTypes); + } + List nestedMessageTypes = getMessageTypes(nestedTypes); + checkMessageElements(schemaName, nestedMessageTypes); + } + }); + } + } + + private ProtoFileElement getTransportProtoSchema(String protoSchema) { + return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); + } - List messageTypes = types.stream() + private List getMessageTypes(List types) { + return types.stream() .filter(typeElement -> typeElement instanceof MessageElement) .map(typeElement -> (MessageElement) typeElement) .collect(Collectors.toList()); + } - if (!enumTypes.isEmpty()) { - enumTypes.forEach(enumElement -> { - List enumElementTypeConstants = enumElement.getConstants(); - EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); - if (!enumElementTypeConstants.isEmpty()) { - enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); - } - EnumDefinition enumDefinition = enumDefinitionBuilder.build(); - schemaBuilder.addEnumDefinition(enumDefinition); - }); - } + private List getEnumElements(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof EnumElement) + .map(typeElement -> (EnumElement) typeElement) + .collect(Collectors.toList()); + } - if (!messageTypes.isEmpty()) { - messageTypes.forEach(messageElement -> { - List messageElementFields = messageElement.getFields(); + private List getMessageDefinitions(List messageElementsList) { + if (!messageElementsList.isEmpty()) { + List messageDefinitions = new ArrayList<>(); + messageElementsList.forEach(messageElement -> { MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); + List messageElementFields = messageElement.getFields(); + List oneOfs = messageElement.getOneOfs(); + + List nestedTypes = messageElement.getNestedTypes(); if (!messageElementFields.isEmpty()) { - messageElementFields.forEach(fieldElement -> { - Field.Label label = fieldElement.getLabel(); - String labelStr = label != null ? label.name() : null; - messageDefinitionBuilder.addField( - labelStr, - fieldElement.getType(), - fieldElement.getName(), - fieldElement.getTag(), - fieldElement.getDefaultValue()); - }); + addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); + } + if (!oneOfs.isEmpty()) { + for (OneOfElement oneOfelement: oneOfs) { + MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); + addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); + } } - MessageDefinition messageDefinition = messageDefinitionBuilder.build(); - schemaBuilder.addMessageDefinition(messageDefinition); + if (!nestedTypes.isEmpty()) { + List nestedEnumTypes = getEnumElements(nestedTypes); + if (!nestedEnumTypes.isEmpty()) { + nestedEnumTypes.forEach(enumElement -> { + EnumDefinition nestedEnumDefinition = getEnumDefinition(enumElement); + messageDefinitionBuilder.addEnumDefinition(nestedEnumDefinition); + }); + } + List nestedMessageTypes = getMessageTypes(nestedTypes); + List nestedMessageDefinitions = getMessageDefinitions(nestedMessageTypes); + nestedMessageDefinitions.forEach(messageDefinitionBuilder::addMessageDefinition); + } + messageDefinitions.add(messageDefinitionBuilder.build()); }); - MessageElement lastMsg = messageTypes.stream().reduce((previous, last) -> last).get(); - try { - DynamicSchema dynamicSchema = schemaBuilder.build(); - DynamicMessage.Builder builder = dynamicSchema.newMessageBuilder(lastMsg.getName()); - return builder.getDescriptorForType(); - } catch (Descriptors.DescriptorValidationException e) { - log.error("Failed to create dynamic schema due to: ", e); - return null; - } + return messageDefinitions; } else { - log.error("Failed to get Message Descriptor! Message types is empty for {} schema!", schemaName); - return null; + return Collections.emptyList(); } } - private ProtoFileElement getTransportProtoSchema(String protoSchema) { - return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); + private EnumDefinition getEnumDefinition(EnumElement enumElement) { + List enumElementTypeConstants = enumElement.getConstants(); + EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); + if (!enumElementTypeConstants.isEmpty()) { + enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); + } + return enumDefinitionBuilder.build(); + } + + + private void addMessageFieldsToTheMessageDefinition(List messageElementFields, MessageDefinition.Builder messageDefinitionBuilder) { + messageElementFields.forEach(fieldElement -> { + String labelStr = null; + if (fieldElement.getLabel() != null) { + labelStr = fieldElement.getLabel().name().toLowerCase(); + } + messageDefinitionBuilder.addField( + labelStr, + fieldElement.getType(), + fieldElement.getName(), + fieldElement.getTag(), + fieldElement.getDefaultValue()); + }); + } + + private void addMessageFieldsToTheOneOfDefinition(List oneOfsElementFields, MessageDefinition.OneofBuilder oneofBuilder) { + oneOfsElementFields.forEach(fieldElement -> oneofBuilder.addField( + fieldElement.getType(), + fieldElement.getName(), + fieldElement.getTag(), + fieldElement.getDefaultValue())); + oneofBuilder.msgDefBuilder(); } private boolean isEmptyStr(String str) { diff --git a/pom.xml b/pom.xml index ef98bd3b79..c6fb306f25 100755 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,7 @@ 3.2.2 1.5.0 1.5.2 - 1.0.1 + 1.0.2 3.4.0 From e334def9358a9af9f2daacdd238e415b596e0050 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 29 Oct 2020 13:34:27 +0200 Subject: [PATCH 08/84] fix telemetry & attributes tests --- .../controller/DeviceProfileController.java | 4 +- .../mqtt/AbstractMqttIntegrationTest.java | 66 +++++++------- ...AttributesRequestProtoIntegrationTest.java | 46 +++++++--- .../AbstractMqttProvisionJsonDeviceTest.java | 14 +-- .../AbstractMqttProvisionProtoDeviceTest.java | 14 +-- ...AbstractMqttAttributesIntegrationTest.java | 17 ++-- ...actMqttAttributesProtoIntegrationTest.java | 31 +++++-- ...actMqttTimeseriesProtoIntegrationTest.java | 85 +++++++++++++++---- .../DeviceProfileTransportConfiguration.java | 2 +- ...onDeviceProfileTransportConfiguration.java | 8 +- ...toDeviceProfileTransportConfiguration.java | 44 ++++++---- .../transport/adaptor/JsonConverter.java | 57 +++++++++---- 12 files changed, 259 insertions(+), 129 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index d435f3e6ce..dba9088264 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -214,8 +214,8 @@ public class DeviceProfileController extends BaseController { private void checkProtoSchemas(MqttProtoDeviceProfileTransportConfiguration mqttTransportConfiguration) throws ThingsboardException { try { - mqttTransportConfiguration.validateTransportProtoSchema(mqttTransportConfiguration.getDeviceAttributesProtoSchema(), "attributes proto schema"); - mqttTransportConfiguration.validateTransportProtoSchema(mqttTransportConfiguration.getDeviceTelemetryProtoSchema(), "telemetry proto schema"); + mqttTransportConfiguration.validateTransportProtoSchema(mqttTransportConfiguration.getDeviceAttributesProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.ATTRIBUTES_PROTO_SCHEMA); + mqttTransportConfiguration.validateTransportProtoSchema(mqttTransportConfiguration.getDeviceTelemetryProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.TELEMETRY_PROTO_SCHEMA); } catch (Exception exception) { throw new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.GENERAL); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index 0ac5f679e0..4e9186f6f0 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -62,39 +62,28 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest private static final AtomicInteger atomicInteger = new AtomicInteger(2); - protected static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax = \"proto2\";\n" + - "package test;\n" + - "\n" + - "option java_package = \"org.thingsboard.server.gen.test\";\n" + - "option java_outer_classname = \"TestProtos\";\n" + - "\n" + - "message nestedPostTelemetry {\n" + - " required bool bool_v = 1;\n" + - " required int64 long_v = 2;\n" + - " required double double_v = 3;\n" + - " required string string_v = 4;\n" + - " required string json_v = 5;\n" + - "}\n" + + protected static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax =\"proto3\";\n" + "\n" + + "package test;\n" + + " \n" + "message PostTelemetry {\n" + - " repeated nestedPostTelemetry postTelemetry = 1;\n" + + " string key1 = 1;\n" + + " bool key2 = 2;\n" + + " double key3 = 3;\n" + + " int32 key4 = 4;\n" + + " string key5 = 5;\n" + "}"; - protected static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax = \"proto2\";\n" + - "package test;\n" + - "\n" + - "option java_package = \"org.thingsboard.server.gen.test\";\n" + - "option java_outer_classname = \"TestProtos\";\n" + + + protected static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax =\"proto3\";\n" + "\n" + - "message nestedPostAttributes {\n" + - " required bool bool_v = 1;\n" + - " required int64 long_v = 2;\n" + - " required double double_v = 3;\n" + - " required string string_v = 4;\n" + - " required string json_v = 5;\n" + - "}\n" + + "package test;\n" + "\n" + "message PostAttributes {\n" + - " repeated nestedPostAttributes postAttributes = 1;\n" + + " string key1 = 1;\n" + + " bool key2 = 2;\n" + + " double key3 = 3;\n" + + " int32 key4 = 4;\n" + + " string key5 = 5;\n" + "}"; protected Tenant savedTenant; @@ -106,8 +95,10 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest protected Device savedGateway; protected String gatewayAccessToken; + protected MqttDeviceProfileTransportConfiguration transportConfiguration; + protected void processBeforeTest (String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { - this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, DeviceProfileProvisionType.DISABLED, null, null); + this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, DeviceProfileProvisionType.DISABLED, null, null); } protected void processBeforeTest(String deviceName, @@ -115,6 +106,8 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest TransportPayloadType payloadType, String telemetryTopic, String attributesTopic, + String telemetryProtoSchema, + String attributesProtoSchema, DeviceProfileProvisionType provisionType, String provisionKey, String provisionSecret ) throws Exception { @@ -146,7 +139,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest gateway.setAdditionalInfo(additionalInfo); if (payloadType != null) { - DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, provisionType, provisionKey, provisionSecret); + 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()); @@ -238,9 +231,9 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic, + String telemetryProtoSchema, String attributesProtoSchema, DeviceProfileProvisionType provisionType, - String provisionKey, String provisionSecret - ) { + String provisionKey, String provisionSecret) { DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setName(transportPayloadType.name()); deviceProfile.setType(DeviceProfileType.DEFAULT); @@ -250,13 +243,18 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest deviceProfile.setDescription(transportPayloadType.name() + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); - MqttDeviceProfileTransportConfiguration transportConfiguration; if (TransportPayloadType.JSON.equals(transportPayloadType)) { transportConfiguration = new MqttJsonDeviceProfileTransportConfiguration(); } else { MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); - protoTransportConfiguration.setDeviceAttributesProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); - protoTransportConfiguration.setDeviceTelemetryProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); + 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; } if (!StringUtils.isEmpty(telemetryTopic)) { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index d2981de353..e28102e9de 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -15,18 +15,24 @@ */ package org.thingsboard.server.mqtt.attributes.request; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.gson.Gson; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; -import org.junit.Before; import org.junit.Ignore; 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.MqttTopics; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -39,40 +45,60 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; 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; @Slf4j public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest { - @Before - public void beforeTest() throws Exception { - processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null); - } + public static final String ATTRIBUTES_SCHEMA_STR = "syntax =\"proto3\";\n" + + "\n" + + "package test;\n" + + "\n" + + "message PostAttributes {\n" + + " string attribute1 = 1;\n" + + " bool attribute2 = 2;\n" + + " double attribute3 = 3;\n" + + " int32 attribute4 = 4;\n" + + " string attribute5 = 5;\n" + + "}"; @After public void afterTest() throws Exception { processAfterTest(); } - @Ignore @Test public void testRequestAttributesValuesFromTheServer() throws Exception { + super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, DeviceProfileProvisionType.DISABLED, null, null); processTestRequestAttributesValuesFromTheServer(); } @Test public void testRequestAttributesValuesFromTheServerGateway() throws Exception { + super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null); processTestGatewayRequestAttributesValuesFromTheServer(); } 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()); - String keys = "attribute1,attribute2,attribute3,attribute4,attribute5"; - List expectedKeys = Arrays.asList(keys.split(",")); - TransportProtos.PostAttributeMsg postAttributeMsg = getPostAttributeMsg(expectedKeys); - byte[] payload = postAttributeMsg.toByteArray(); + 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"); + Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); + assertNotNull(postAttributesMsgDescriptor); + DynamicMessage postAttributesMsg = postAttributesBuilder + .setField(postAttributesMsgDescriptor.findFieldByName("attribute1"), "value1") + .setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true) + .setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0) + .setField(postAttributesMsgDescriptor.findFieldByName("attribute4"), 73) + .setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") + .build(); + byte[] payload = postAttributesMsg.toByteArray(); client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(payload)); client.subscribe(MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttQoS.AT_MOST_ONCE.value()); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java index 7c341029b6..9f69df0307 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java @@ -94,7 +94,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningDisabledDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.DISABLED, null, null); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.DISABLED, null, null); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); @@ -103,7 +103,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -121,7 +121,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\""; byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -142,7 +142,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -169,7 +169,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); String requestCredentials = ",\"credentialsType\": \"MQTT_BASIC\",\"clientId\": \"test_clientId\",\"username\": \"test_username\",\"password\": \"test_password\""; byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -197,7 +197,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn } protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals(savedDevice.getId().toString(), response.get("deviceId").getAsString()); @@ -210,7 +210,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn } protected void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index 12d8f91eb6..76b27ffd23 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -102,14 +102,14 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI protected void processTestProvisioningDisabledDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.DISABLED, null, null); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.DISABLED, null, null); ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertNotNull(result); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getProvisionResponseStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); @@ -125,7 +125,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null,null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes()); @@ -145,7 +145,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes()); @@ -171,7 +171,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg( ValidateBasicMqttCredRequestMsg.newBuilder() .setClientId("test_clientId") @@ -205,7 +205,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertEquals(savedDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); @@ -217,7 +217,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getProvisionResponseStatus().toString()); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java index 5ac0746a43..308983f070 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.mqtt.telemetry.attributes; +import com.fasterxml.jackson.core.JsonProcessingException; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; @@ -142,7 +143,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt } - protected void assertAttributesValues(List> deviceValues, Set expectedKeySet) { + protected void assertAttributesValues(List> deviceValues, Set expectedKeySet) throws JsonProcessingException { for (Map map : deviceValues) { String key = (String) map.get("key"); Object value = map.get("value"); @@ -162,10 +163,16 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt break; case "key5": assertNotNull(value); - assertEquals(3, ((LinkedHashMap) value).size()); - assertEquals(42, ((LinkedHashMap) value).get("someNumber")); - assertEquals(Arrays.asList(1, 2, 3), ((LinkedHashMap) value).get("someArray")); - LinkedHashMap someNestedObject = (LinkedHashMap) ((LinkedHashMap) value).get("someNestedObject"); + LinkedHashMap valueMap; + if (value instanceof String) { + valueMap = mapper.readValue((String) value, LinkedHashMap.class); + } else { + valueMap = (LinkedHashMap) value; + } + assertEquals(3, valueMap.size()); + assertEquals(42, valueMap.get("someNumber")); + assertEquals(Arrays.asList(1, 2, 3), valueMap.get("someArray")); + LinkedHashMap someNestedObject = (LinkedHashMap) valueMap.get("someNestedObject"); assertEquals("value", someNestedObject.get("key")); break; } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java index 0e58ffeedb..bbceb277f5 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java @@ -15,12 +15,17 @@ */ package org.thingsboard.server.mqtt.telemetry.attributes; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -36,27 +41,35 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac private static final String POST_DATA_ATTRIBUTES_TOPIC = "proto/attributes"; - @Before - public void beforeTest() throws Exception { - processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); - } - @After public void afterTest() throws Exception { processAfterTest(); } @Test - @Ignore public void testPushMqttAttributes() throws Exception { + super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - TransportProtos.PostAttributeMsg msg = getPostAttributeMsg(expectedKeys); - processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, msg.toByteArray()); + 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"); + Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); + assertNotNull(postAttributesMsgDescriptor); + DynamicMessage postAttributesMsg = postAttributesBuilder + .setField(postAttributesMsgDescriptor.findFieldByName("key1"), "value1") + .setField(postAttributesMsgDescriptor.findFieldByName("key2"), true) + .setField(postAttributesMsgDescriptor.findFieldByName("key3"), 3.0) + .setField(postAttributesMsgDescriptor.findFieldByName("key4"), 4) + .setField(postAttributesMsgDescriptor.findFieldByName("key5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") + .build(); + processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, postAttributesMsg.toByteArray()); } @Test - @Ignore public void testPushMqttAttributesGateway() throws Exception { + super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, null); TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributesMsgProtoBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder(); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); String deviceName1 = "Device A"; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index 0db487b4f7..384063f55a 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -15,14 +15,19 @@ */ package org.thingsboard.server.mqtt.telemetry.timeseries; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; + 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.MqttTopics; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -30,43 +35,91 @@ import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; @Slf4j public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends AbstractMqttTimeseriesIntegrationTest { private static final String POST_DATA_TELEMETRY_TOPIC = "proto/telemetry"; - @Before - public void beforeTest() throws Exception { - processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null); - } - @After public void afterTest() throws Exception { processAfterTest(); } @Test - @Ignore public void testPushMqttTelemetry() throws Exception { + super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - TransportProtos.TsKvListProto tsKvListProto = getTsKvListProto(expectedKeys, 0); - processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, tsKvListProto.toByteArray(), false); + assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); + MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; + ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); + DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); + Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); + assertNotNull(postTelemetryMsgDescriptor); + DynamicMessage postTelemetryMsg = postTelemetryBuilder + .setField(postTelemetryMsgDescriptor.findFieldByName("key1"), "value1") + .setField(postTelemetryMsgDescriptor.findFieldByName("key2"), true) + .setField(postTelemetryMsgDescriptor.findFieldByName("key3"), 3.0) + .setField(postTelemetryMsgDescriptor.findFieldByName("key4"), 4) + .setField(postTelemetryMsgDescriptor.findFieldByName("key5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") + .build(); + processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, postTelemetryMsg.toByteArray(), false); } @Test - @Ignore public void testPushMqttTelemetryWithTs() throws Exception { + String schemaStr = "syntax =\"proto3\";\n" + + "\n" + + "package test;\n" + + "\n" + + "message Values {\n" + + " string key1 = 1;\n" + + " bool key2 = 2;\n" + + " double key3 = 3;\n" + + " int32 key4 = 4;\n" + + " string key5 = 5;\n" + + "}\n" + + " \n" + + "message PostTelemetry {\n" + + " int64 ts = 1;\n" + + " Values values = 2;\n" + + "}"; + super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - TransportProtos.TsKvListProto tsKvListProto = getTsKvListProto(expectedKeys, 10000); - processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, tsKvListProto.toByteArray(), true); + assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); + MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; + ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(schemaStr); + DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + + DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("Values"); + Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType(); + assertNotNull(valuesDescriptor); + + DynamicMessage valuesMsg = valuesBuilder + .setField(valuesDescriptor.findFieldByName("key1"), "value1") + .setField(valuesDescriptor.findFieldByName("key2"), true) + .setField(valuesDescriptor.findFieldByName("key3"), 3.0) + .setField(valuesDescriptor.findFieldByName("key4"), 4) + .setField(valuesDescriptor.findFieldByName("key5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") + .build(); + + DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); + Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); + assertNotNull(postTelemetryMsgDescriptor); + DynamicMessage postTelemetryMsg = postTelemetryBuilder + .setField(postTelemetryMsgDescriptor.findFieldByName("ts"), 10000L) + .setField(postTelemetryMsgDescriptor.findFieldByName("values"), valuesMsg) + .build(); + + processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, postTelemetryMsg.toByteArray(), true); } @Test - @Ignore public void testPushMqttTelemetryGateway() throws Exception { + super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.DISABLED, null, null); TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder(); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); String deviceName1 = "Device A"; @@ -79,8 +132,8 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac } @Test - @Ignore public void testGatewayConnect() throws Exception { + super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, null, null, DeviceProfileProvisionType.DISABLED, null, null); String deviceName = "Device A"; TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName); MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java index fb337b24f4..5447866b4d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java @@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M")}) public interface DeviceProfileTransportConfiguration { - @JsonIgnore +// @JsonIgnore DeviceTransportType getType(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java index 83a17e93ef..3f9144dc8f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java @@ -15,6 +15,7 @@ */ 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; @@ -26,14 +27,9 @@ import org.thingsboard.server.common.data.TransportPayloadType; @Slf4j @EqualsAndHashCode(callSuper = true) @Data -@JsonDeserialize(using = JsonDeserializer.None.class) +@JsonDeserialize(as = MqttJsonDeviceProfileTransportConfiguration.class) public class MqttJsonDeviceProfileTransportConfiguration extends MqttDeviceProfileTransportConfiguration{ - @Override - public DeviceTransportType getType() { - return super.getType(); - } - @Override public TransportPayloadType getTransportPayloadType() { return TransportPayloadType.JSON; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java index c96a1a565f..f0833ba292 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java @@ -15,6 +15,7 @@ */ 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; @@ -23,6 +24,7 @@ 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,9 +37,9 @@ 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.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -46,7 +48,7 @@ import java.util.stream.Collectors; @Slf4j @EqualsAndHashCode(callSuper = true) @Data -@JsonDeserialize(using = JsonDeserializer.None.class) +@JsonDeserialize(as = MqttProtoDeviceProfileTransportConfiguration.class) public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProfileTransportConfiguration { public static final Location LOCATION = new Location("", "", -1, -1); @@ -60,11 +62,6 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf private String deviceTelemetryProtoSchema; private String deviceAttributesProtoSchema; - @Override - public DeviceTransportType getType() { - return super.getType(); - } - @Override public TransportPayloadType getTransportPayloadType() { return TransportPayloadType.PROTOBUF; @@ -155,6 +152,22 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf } } + private void checkFieldElements(String schemaName, List fieldElements) { + if (!fieldElements.isEmpty()) { + boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { + Field.Label label = fieldElement.getLabel(); + return label != null && label.equals(Field.Label.REQUIRED); + }); + if (hasRequiredLabel) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!"); + } + boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); + if (hasDefaultValue) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!"); + } + } + } + private void checkEnumElements(String schemaName, List enumTypes) { if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); @@ -175,11 +188,14 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf " Message definition extensions don't support!"); checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), " Message definition reserved elements don't support!"); + checkFieldElements(schemaName, messageElement.getFields()); List oneOfs = messageElement.getOneOfs(); if (!oneOfs.isEmpty()) { - oneOfs.forEach(oneOfElement -> - checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), - " OneOf definition groups don't support!")); + oneOfs.forEach(oneOfElement -> { + checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), + " OneOf definition groups don't support!"); + checkFieldElements(schemaName, oneOfElement.getFields()); + }); } List nestedTypes = messageElement.getNestedTypes(); if (!nestedTypes.isEmpty()) { @@ -194,7 +210,7 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf } } - private ProtoFileElement getTransportProtoSchema(String protoSchema) { + public ProtoFileElement getTransportProtoSchema(String protoSchema) { return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); } @@ -270,8 +286,7 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf labelStr, fieldElement.getType(), fieldElement.getName(), - fieldElement.getTag(), - fieldElement.getDefaultValue()); + fieldElement.getTag()); }); } @@ -279,8 +294,7 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf oneOfsElementFields.forEach(fieldElement -> oneofBuilder.addField( fieldElement.getType(), fieldElement.getName(), - fieldElement.getTag(), - fieldElement.getDefaultValue())); + fieldElement.getTag())); oneofBuilder.msgDefBuilder(); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 321ed22baa..65467c69a5 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -19,6 +19,7 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; @@ -193,40 +194,62 @@ public class JsonConverter { String message = String.format("String value length [%d] for key [%s] is greater than maximum allowed [%d]", value.getAsString().length(), valueEntry.getKey(), maxStringValueLength); throw new JsonSyntaxException(message); } - if (isTypeCastEnabled && NumberUtils.isParsable(value.getAsString())) { - try { - result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); - } catch (RuntimeException th) { - result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) - .setStringV(value.getAsString()).build()); + if (isTypeCastEnabled) { + if (NumberUtils.isParsable(value.getAsString())) { + try { + result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); + } catch (RuntimeException th) { + result.add(buildStringKVProto(valueEntry, value)); + } + } else { + try { + JsonElement jsonElement = JSON_PARSER.parse(value.getAsString()); + if (jsonElement.isJsonObject() || jsonElement.isJsonArray()) { + result.add(buildJsonKVProto(valueEntry, jsonElement)); + } else { + result.add(buildStringKVProto(valueEntry, value)); + } + } catch (JsonParseException e) { + result.add(buildStringKVProto(valueEntry, value)); + } } } else { - result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) - .setStringV(value.getAsString()).build()); + result.add(buildStringKVProto(valueEntry, value)); } } else if (value.isBoolean()) { result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.BOOLEAN_V) .setBoolV(value.getAsBoolean()).build()); } else if (value.isNumber()) { result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); - } else if (!value.isJsonNull()) { + } else { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); } } else if (element.isJsonObject() || element.isJsonArray()) { - result.add(KeyValueProto - .newBuilder() - .setKey(valueEntry - .getKey()) - .setType(KeyValueType.JSON_V) - .setJsonV(element.toString()) - .build()); - } else if (!element.isJsonNull()) { + result.add(buildJsonKVProto(valueEntry, element)); + } else { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); } } return result; } + private static KeyValueProto buildStringKVProto(Entry valueEntry, JsonPrimitive value) { + return KeyValueProto.newBuilder() + .setKey(valueEntry.getKey()) + .setType(KeyValueType.STRING_V) + .setStringV(value.getAsString()) + .build(); + } + + private static KeyValueProto buildJsonKVProto(Entry valueEntry, JsonElement jsonElement) { + return KeyValueProto + .newBuilder() + .setKey(valueEntry.getKey()) + .setType(KeyValueType.JSON_V) + .setJsonV(jsonElement.toString()) + .build(); + } + private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) { if (value.getAsString().contains(".")) { return KeyValueProto.newBuilder() From 37c381996e59c8b82f25a4327092ac0fb6b8e947 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 29 Oct 2020 14:58:28 +0200 Subject: [PATCH 09/84] ui updates --- .../MqttTransportConfigurationDeserializer.java | 1 - ...ice-profile-transport-configuration.component.ts | 13 ++++++++++--- ui-ngx/src/app/shared/models/device.models.ts | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java index 15f52f603e..4754792fb2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java @@ -36,7 +36,6 @@ public class MqttTransportConfigurationDeserializer extends StdDeserializer Date: Tue, 3 Nov 2020 19:36:11 +0200 Subject: [PATCH 10/84] fix mqtt-device-profile-transport-configuration.component.ts --- ...ofile-transport-configuration.component.ts | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index c1dc68f0cb..f44becd2f1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -90,6 +90,11 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required], }) }); + let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; + configurationFormGroup.get('transportPayloadType').valueChanges.subscribe(payloadType => { + this.onTransportPayloadTypeChanged(payloadType, configurationFormGroup); + this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); + }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); }); @@ -112,11 +117,8 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; - let payloadType = value.transportPayloadType; - if (payloadType === MqttTransportPayloadType.PROTOBUF) { - configurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); - configurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); - } + let type = value.transportPayloadType; + this.updateTransportPayloadBasedControls(type, configurationFormGroup); this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); } } @@ -126,19 +128,24 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control if (this.mqttDeviceProfileTransportConfigurationFormGroup.valid) { configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; configuration.type = DeviceTransportType.MQTT; - let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; - let transportPayloadType = configuration.transportPayloadType; - if (transportPayloadType === MqttTransportPayloadType.PROTOBUF) { - configurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); - configurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); - } else { - configurationFormGroup.removeControl('deviceTelemetryProtoSchema'); - configurationFormGroup.removeControl('deviceAttributesProtoSchema'); - } } this.propagateChange(configuration); } + private onTransportPayloadTypeChanged(type: MqttTransportPayloadType, configurationFormGroup: FormGroup) { + this.updateTransportPayloadBasedControls(type, configurationFormGroup) + } + + private updateTransportPayloadBasedControls(type: MqttTransportPayloadType, configurationFormGroup: FormGroup) { + if (type === MqttTransportPayloadType.PROTOBUF) { + configurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); + configurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); + } else { + configurationFormGroup.removeControl('deviceTelemetryProtoSchema'); + configurationFormGroup.removeControl('deviceAttributesProtoSchema'); + } + } + private validationMQTTTopic(): ValidatorFn { return (c: FormControl) => { const newTopic = c.value; From bec3790164cbb647b52362d5ec45b8758f73b7b1 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 3 Nov 2020 19:44:12 +0200 Subject: [PATCH 11/84] clean up ui code --- ...mqtt-device-profile-transport-configuration.component.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index f44becd2f1..972c0412cd 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -92,7 +92,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control }); let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; configurationFormGroup.get('transportPayloadType').valueChanges.subscribe(payloadType => { - this.onTransportPayloadTypeChanged(payloadType, configurationFormGroup); + this.updateTransportPayloadBasedControls(type, configurationFormGroup) this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { @@ -132,10 +132,6 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control this.propagateChange(configuration); } - private onTransportPayloadTypeChanged(type: MqttTransportPayloadType, configurationFormGroup: FormGroup) { - this.updateTransportPayloadBasedControls(type, configurationFormGroup) - } - private updateTransportPayloadBasedControls(type: MqttTransportPayloadType, configurationFormGroup: FormGroup) { if (type === MqttTransportPayloadType.PROTOBUF) { configurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); From 5ef30fb2efe43db9eb9f52fd66c101d9a6b7b73c Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 3 Nov 2020 19:45:05 +0200 Subject: [PATCH 12/84] fix typo --- .../mqtt-device-profile-transport-configuration.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 972c0412cd..f9f815a439 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -92,7 +92,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control }); let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; configurationFormGroup.get('transportPayloadType').valueChanges.subscribe(payloadType => { - this.updateTransportPayloadBasedControls(type, configurationFormGroup) + this.updateTransportPayloadBasedControls(payloadType, configurationFormGroup) this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { From 0ff8d4e7a436e6a3b3597f4ad5dade3740b1735d Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 3 Nov 2020 19:46:30 +0200 Subject: [PATCH 13/84] fix more typos --- ...qtt-device-profile-transport-configuration.component.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index f9f815a439..2b301420fa 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -87,12 +87,12 @@ 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], + transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] }) }); let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; configurationFormGroup.get('transportPayloadType').valueChanges.subscribe(payloadType => { - this.updateTransportPayloadBasedControls(payloadType, configurationFormGroup) + this.updateTransportPayloadBasedControls(payloadType, configurationFormGroup); this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { @@ -117,8 +117,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; - let type = value.transportPayloadType; - this.updateTransportPayloadBasedControls(type, configurationFormGroup); + this.updateTransportPayloadBasedControls(value.transportPayloadType, configurationFormGroup); this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); } } From 73489beaf66e76172413ffc8d0eb92b9d2127e79 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 5 Nov 2020 15:43:04 +0200 Subject: [PATCH 14/84] fix exceptions message regex, tests, ui code clean up --- .../src/main/resources/thingsboard.yml | 2 +- .../server/mqtt/proto/DynamicProtoTest.java | 26 +++++++++---------- .../DeviceProfileTransportConfiguration.java | 1 - ...toDeviceProfileTransportConfiguration.java | 6 ++--- ...ofile-transport-configuration.component.ts | 2 +- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index deb28ffd99..c935154b6e 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -429,7 +429,7 @@ spring: database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" datasource: driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}" - url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" + url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard_ce_3_2}" username: "${SPRING_DATASOURCE_USERNAME:postgres}" password: "${SPRING_DATASOURCE_PASSWORD:postgres}" hikari: diff --git a/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java b/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java index eb6912f257..a5b79310ec 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java @@ -229,67 +229,67 @@ public class DynamicProtoTest { @Test public void testDynamicSchemaProtoFileValidation() { - processValidation("Failed to parse: testParseToProtoFile schema due to: Syntax error in :6:4: 'required' label forbidden in proto3 field declarations", IVALID_PROTO_SCHEMA_REQUIRED_FIELD_EXISTS, "testParseToProtoFile"); + 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("Invalid schema syntax: proto2 for: testSyntaxValidation provided! Only proto3 allowed!", INVALID_PROTO_SCHEMA_NOT_VALID_SYNTAX, "testSyntaxValidation"); + 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("Invalid testOptionsValidation schema provided! Schema options don't support!", INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED, "testOptionsValidation"); + processValidation("[Transport Configuration] invalid testOptionsValidation schema provided! Schema options don't support!", INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED, "testOptionsValidation"); } @Test public void testDynamicSchemaPublicImportsValidation() { - processValidation("Invalid testPublicImportsValidation schema provided! Schema public imports don't support!", INVALID_PROTO_SCHEMA_PUBLIC_IMPORTS_NOT_SUPPORTED, "testPublicImportsValidation"); + 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("Invalid testImportsValidation schema provided! Schema imports don't support!", INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED, "testImportsValidation"); + processValidation("[Transport Configuration] invalid testImportsValidation schema provided! Schema imports don't support!", INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED, "testImportsValidation"); } @Test public void testDynamicSchemaExtendDeclarationsValidation() { - processValidation("Invalid testExtendDeclarationsValidation schema provided! Schema extend declarations don't support!", INVALID_PROTO_SCHEMA_EXTEND_DECLARATION_NOT_SUPPORTED, "testExtendDeclarationsValidation"); + 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("Invalid testEnumOptionsValidation schema provided! Enum definitions options are not supported!", INVALID_PROTO_SCHEMA_ENUM_OPTIONS_NOT_SUPPORTED, "testEnumOptionsValidation"); + 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("Invalid noOneMessageTypeExists schema provided! At least one Message definition should exists!", INVALID_PROTO_SCHEMA_NO_MESSAGE_TYPES_EXISTS, "noOneMessageTypeExists"); + 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("Invalid messageTypeOptions schema provided! Message definition options don't support!", INVALID_PROTO_SCHEMA_MESSAGE_OPTIONS_NOT_SUPPORTED, "messageTypeOptions"); + 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("Invalid messageTypeExtensions schema provided! Message definition extensions don't support!", INVALID_PROTO_SCHEMA_MESSAGE_EXTENSIONS_NOT_SUPPORTED, "messageTypeExtensions"); + 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("Invalid messageTypeReservedElements schema provided! Message definition reserved elements don't support!", INVALID_PROTO_SCHEMA_MESSAGE_RESERVED_NOT_SUPPORTED, "messageTypeReservedElements"); + 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("Invalid messageTypeGroupsElements schema provided! Message definition groups don't support!", INVALID_PROTO_SCHEMA_MESSAGE_GROUPS_NOT_SUPPORTED, "messageTypeGroupsElements"); + 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("Invalid oneOfsTypeGroupsElements schema provided! OneOf definition groups don't support!", INVALID_PROTO_SCHEMA_ONE_OFS_GROUPS_NOT_SUPPORTED, "oneOfsTypeGroupsElements"); + 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) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java index 5447866b4d..e685692895 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java @@ -33,7 +33,6 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M")}) public interface DeviceProfileTransportConfiguration { -// @JsonIgnore DeviceTransportType getType(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java index f0833ba292..158acfaf2a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java @@ -56,7 +56,7 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf public static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; public static String invalidSchemaProvidedMessage(String schemaName) { - return "Invalid " + schemaName + " schema provided!"; + return "[Transport Configuration] invalid " + schemaName + " schema provided!"; } private String deviceTelemetryProtoSchema; @@ -73,7 +73,7 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf try { protoFileElement = schemaParser.readProtoFile(); } catch (Exception e) { - throw new IllegalArgumentException("Failed to parse: " + schemaName + " schema due to: " + e.getMessage()); + 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!"); @@ -127,7 +127,7 @@ 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("Invalid schema syntax: " + protoFileElement.getSyntax() + + throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + " for: " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 2b301420fa..5569a0b530 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -34,7 +34,7 @@ import { MqttTransportPayloadType, mqttTransportPayloadTypeTranslationMap } from '@shared/models/device.models'; -import {isDefinedAndNotNull} from '@core/utils'; +import { isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-mqtt-device-profile-transport-configuration', From 78c06be350aebd90bfbd1087a22ee726b03c403b Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 16:00:04 +0200 Subject: [PATCH 15/84] change protobuf-dynamic dependency --- common/data/pom.xml | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/data/pom.xml b/common/data/pom.xml index 6d32a3df53..fc1311ab02 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -76,7 +76,7 @@ wire-schema - com.github.os72 + org.thingsboard protobuf-dynamic diff --git a/pom.xml b/pom.xml index c6fb306f25..66000b3316 100755 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,7 @@ 3.2.2 1.5.0 1.5.2 - 1.0.2 + 1.0.2TB 3.4.0 @@ -1372,7 +1372,7 @@ ${micrometer.version} - com.github.os72 + org.thingsboard protobuf-dynamic ${protobuf-dynamic.version} From 53de94410c0d021aa642ba665ac731b79fba0560 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 16:48:28 +0200 Subject: [PATCH 16/84] clean up code --- application/src/main/resources/thingsboard.yml | 2 +- ...ctMqttAttributesUpdatesIntegrationTest.java | 18 +++++++++--------- ...ttAttributesUpdatesJsonIntegrationTest.java | 18 +++++++++--------- ...tAttributesUpdatesProtoIntegrationTest.java | 1 - .../mqtt/session/DeviceSessionCtx.java | 4 +++- .../mqtt/util/MqttDynamicProtoSchemaTest.java | 6 ++---- 6 files changed, 24 insertions(+), 25 deletions(-) rename application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java => common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java (99%) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index c935154b6e..deb28ffd99 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -429,7 +429,7 @@ spring: database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" datasource: driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}" - url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard_ce_3_2}" + url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" username: "${SPRING_DATASOURCE_USERNAME:postgres}" password: "${SPRING_DATASOURCE_PASSWORD:postgres}" hikari: diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java index c40c2f2914..d2febdf357 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java @@ -65,15 +65,15 @@ public abstract class AbstractMqttAttributesUpdatesIntegrationTest extends Abstr processAfterTest(); } -// @Test -// public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { -// processTestSubscribeToAttributesUpdates(); -// } -// -// @Test -// public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { -// processGatewayTestSubscribeToAttributesUpdates(); -// } + @Test + public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { + processTestSubscribeToAttributesUpdates(); + } + + @Test + public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { + processGatewayTestSubscribeToAttributesUpdates(); + } protected void processTestSubscribeToAttributesUpdates() throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java index 4f5a5f9369..58379e4016 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java @@ -39,13 +39,13 @@ public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends A processAfterTest(); } -// @Test -// public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { -// processTestSubscribeToAttributesUpdates(); -// } -// -// @Test -// public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { -// processGatewayTestSubscribeToAttributesUpdates(); -// } + @Test + public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { + processTestSubscribeToAttributesUpdates(); + } + + @Test + public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { + processGatewayTestSubscribeToAttributesUpdates(); + } } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java index 16ccc789ee..faf8e1ce4d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java @@ -19,7 +19,6 @@ import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 79147c0935..6ecea5a77c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -80,7 +80,9 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { return telemetryTopicFilter.filter(topicName); } - public boolean isDeviceAttributesTopic(String topicName) { return attributesTopicFilter.filter(topicName); } + public boolean isDeviceAttributesTopic(String topicName) { + return attributesTopicFilter.filter(topicName); + } public MqttTransportAdaptor getPayloadAdaptor() { return payloadType.equals(TransportPayloadType.JSON) ? context.getJsonMqttAdaptor() : context.getProtoMqttAdaptor(); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java similarity index 99% rename from application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java rename to common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java index a5b79310ec..47726ff610 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.proto; +package org.thingsboard.server.transport.mqtt.util; import com.github.os72.protobuf.dynamic.DynamicSchema; import com.google.protobuf.Descriptors; @@ -22,7 +22,6 @@ 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 lombok.extern.slf4j.Slf4j; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -38,9 +37,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration.LOCATION; -@Slf4j @RunWith(MockitoJUnitRunner.class) -public class DynamicProtoTest { +public class MqttDynamicProtoSchemaTest { private static final String PROTO_SCHEMA_WITH_NESTED_MSG_TYPES = "syntax = \"proto3\";\n" + "\n" + From a52fee3aa1baa5c0945b7f61f05875fbb7fd5d0f Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 16:59:24 +0200 Subject: [PATCH 17/84] fix typos, remove unused imports --- .../mqtt/AbstractMqttIntegrationTest.java | 1 - .../server/mqtt/MqttSqlTestSuite.java | 1 - ...tMqttAttributesRequestIntegrationTest.java | 5 --- ...tAttributesRequestJsonIntegrationTest.java | 5 --- ...AttributesRequestProtoIntegrationTest.java | 2 -- ...tributesRequestJsonSqlIntegrationTest.java | 1 - ...ributesRequestProtoSqlIntegrationTest.java | 1 - ...tMqttAttributesUpdatesIntegrationTest.java | 6 ---- ...tAttributesUpdatesJsonIntegrationTest.java | 5 --- ...AttributesUpdatesProtoIntegrationTest.java | 2 -- ...tributesUpdatesSqlJsonIntegrationTest.java | 1 - ...ributesUpdatesSqlProtoIntegrationTest.java | 1 - .../claim/AbstractMqttClaimDeviceTest.java | 1 - .../AbstractMqttClaimJsonDeviceTest.java | 1 - .../AbstractMqttClaimProtoDeviceTest.java | 1 - .../claim/sql/MqttClaimDeviceJsonSqlTest.java | 1 - .../sql/MqttClaimDeviceProtoSqlTest.java | 1 - ...ttServerSideRpcDefaultIntegrationTest.java | 35 ------------------- ...tractMqttServerSideRpcIntegrationTest.java | 19 ---------- ...tMqttServerSideRpcJsonIntegrationTest.java | 1 - ...MqttServerSideRpcProtoIntegrationTest.java | 4 --- ...actMqttAttributesProtoIntegrationTest.java | 3 -- ...qttAttributesNoSqlJsonIntegrationTest.java | 1 - ...ttAttributesNoSqlProtoIntegrationTest.java | 1 - ...MqttAttributesSqlProtoIntegrationTest.java | 1 - ...AbstractMqttTimeseriesIntegrationTest.java | 1 - ...ractMqttTimeseriesJsonIntegrationTest.java | 1 - ...actMqttTimeseriesProtoIntegrationTest.java | 1 - .../MqttTimeseriesSqlJsonIntegrationTest.java | 1 - ...MqttTimeseriesSqlProtoIntegrationTest.java | 1 - 30 files changed, 106 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index 4e9186f6f0..de84698dc0 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -49,7 +49,6 @@ import org.thingsboard.server.gen.transport.TransportProtos; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index 440c587856..d25a2ea1ab 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -33,7 +33,6 @@ import java.util.Arrays; "org.thingsboard.server.mqtt.attributes.request.sql.*Test", "org.thingsboard.server.mqtt.claim.sql.*Test", "org.thingsboard.server.mqtt.provision.sql.*Test", - "org.thingsboard.server.mqtt.proto.sql.*Test" }) public class MqttSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java index 438757bfff..51d38e6312 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java @@ -18,14 +18,11 @@ package org.thingsboard.server.mqtt.attributes.request; import com.google.protobuf.InvalidProtocolBufferException; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.profile.MqttTopics; @@ -37,9 +34,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java index 4b824ea0b5..83ef04f65e 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java @@ -18,14 +18,9 @@ package org.thingsboard.server.mqtt.attributes.request; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - @Slf4j public abstract class AbstractMqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index e28102e9de..8a8be4d1cc 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.request; import com.github.os72.protobuf.dynamic.DynamicSchema; -import com.google.gson.Gson; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; @@ -27,7 +26,6 @@ import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java index d3750d49a3..146972bade 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.request.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest; import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java index f52e79b2ab..52ec27f6f8 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.request.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest; import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestProtoIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java index d2febdf357..b91739790c 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java @@ -18,11 +18,7 @@ package org.thingsboard.server.mqtt.attributes.updates; import com.google.protobuf.InvalidProtocolBufferException; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttCallback; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -33,12 +29,10 @@ import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import java.nio.charset.StandardCharsets; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; 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; @Slf4j diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java index 58379e4016..e41ff83291 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java @@ -21,11 +21,6 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - @Slf4j public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java index faf8e1ce4d..213540699b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java @@ -21,11 +21,9 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; -import java.nio.charset.StandardCharsets; import java.util.List; import java.util.stream.Collectors; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java index a8fd4687f7..1841340835 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.updates.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest; import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java index 723e5e3dcd..7ff542a4ec 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.updates.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest; import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesProtoIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java index dd16c17be2..5a1cbd6356 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java @@ -20,7 +20,6 @@ import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.Customer; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java index f55cfa57c8..49aa6c995b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java @@ -18,7 +18,6 @@ package org.thingsboard.server.mqtt.claim; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java index d371c09f37..be0cfc7c81 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java @@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.gen.transport.TransportApiProtos; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java index da794288f4..543d805721 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.claim.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest; import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java index a63978e4de..e5d866d6e2 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.claim.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest; import org.thingsboard.server.mqtt.claim.AbstractMqttClaimProtoDeviceTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java index 23b93f427e..e3de80e813 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java @@ -15,49 +15,14 @@ */ package org.thingsboard.server.mqtt.rpc; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.protobuf.InvalidProtocolBufferException; -import com.nimbusds.jose.util.StandardCharset; import com.datastax.oss.driver.api.core.uuid.Uuids; -import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; -import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttCallback; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; -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.TransportPayloadType; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.controller.AbstractControllerTest; -import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.service.security.AccessValidator; -import java.util.Arrays; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java index e08f1665a4..208aa03a48 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java @@ -15,9 +15,7 @@ */ package org.thingsboard.server.mqtt.rpc; -import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.protobuf.InvalidProtocolBufferException; import com.nimbusds.jose.util.StandardCharset; import io.netty.handler.codec.mqtt.MqttQoS; @@ -26,35 +24,18 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttCallback; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.junit.After; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; -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.TransportPayloadType; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; -import org.thingsboard.server.service.security.AccessValidator; import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java index d9ff14e1d2..2bfb0c1f2d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java @@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java index 759a5da912..062a074c0d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java @@ -22,16 +22,12 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - @Slf4j public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java index bbceb277f5..f7f9bdef7d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java @@ -21,8 +21,6 @@ import com.google.protobuf.DynamicMessage; import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; @@ -32,7 +30,6 @@ import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java index 68ab8cff62..53a6f4adde 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.attributes.nosql; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest; @DaoNoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java index d508fd76c5..23b6052272 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.attributes.nosql; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest; @DaoNoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java index 5f486916ae..c5eeb06546 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.attributes.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest; import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java index f6294cd990..9076edc546 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java @@ -27,7 +27,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java index edab8405cd..181a35fa93 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java @@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics; import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @Slf4j diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index 384063f55a..d463dc1963 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -22,7 +22,6 @@ import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; - import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java index f313cc5d29..0b494b6bea 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.timeseries.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest; import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest; /** diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java index 3c7d5e2e2a..2a80483879 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.timeseries.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest; import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesProtoIntegrationTest; /** From 46e2ebe3a49905bf6112870e2c909aa060041b75 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 17:58:45 +0200 Subject: [PATCH 18/84] moved DeviceController proto schema check to DeviceProfileServiceImpl --- .../controller/DeviceProfileController.java | 16 ---------------- .../controller/ControllerSqlTestSuite.java | 2 +- .../dao/device/DeviceProfileServiceImpl.java | 15 +++++++++++++-- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index dba9088264..7f0905b178 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -97,13 +97,6 @@ public class DeviceProfileController extends BaseController { checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE); - DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - - if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { - MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - checkProtoSchemas(protoTransportConfiguration); - } - DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); @@ -212,13 +205,4 @@ public class DeviceProfileController extends BaseController { } } - private void checkProtoSchemas(MqttProtoDeviceProfileTransportConfiguration mqttTransportConfiguration) throws ThingsboardException { - try { - mqttTransportConfiguration.validateTransportProtoSchema(mqttTransportConfiguration.getDeviceAttributesProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.ATTRIBUTES_PROTO_SCHEMA); - mqttTransportConfiguration.validateTransportProtoSchema(mqttTransportConfiguration.getDeviceTelemetryProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.TELEMETRY_PROTO_SCHEMA); - } catch (Exception exception) { - throw new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.GENERAL); - } - } - } diff --git a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java index 64f9b8fddf..dbacf4af64 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java @@ -28,7 +28,7 @@ import java.util.Arrays; @ClasspathSuite.ClassnameFilters({ // "org.thingsboard.server.controller.sql.WebsocketApiSqlTest", // "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest", - "org.thingsboard.server.controller.sql.*Test", + "org.thingsboard.server.controller.sql.DeviceProfileControllerSqlTest", }) public class ControllerSqlTestSuite { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index f3857cacd5..e5399fc8d4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -29,12 +29,13 @@ import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Tenant; 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.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.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -48,7 +49,6 @@ import org.thingsboard.server.dao.tenant.TenantDao; import java.util.Arrays; import java.util.Collections; -import java.util.List; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -310,6 +310,17 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D if (defaultDeviceProfile != null && !defaultDeviceProfile.getId().equals(deviceProfile.getId())) { throw new DataValidationException("Another default device profile is present in scope of current tenant!"); } + } 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()); + } + } } } From 4725ad14514036f773b23670fbce1880a22d8192 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 18:29:16 +0200 Subject: [PATCH 19/84] fix typo --- .../server/controller/DeviceProfileController.java | 6 ------ .../java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index 7f0905b178..fcb34530b5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -29,12 +29,7 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.audit.ActionType; -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.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.page.PageData; @@ -204,5 +199,4 @@ public class DeviceProfileController extends BaseController { throw handleException(e); } } - } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index d25a2ea1ab..f71e8f37a2 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -32,7 +32,7 @@ import java.util.Arrays; "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", "org.thingsboard.server.mqtt.attributes.request.sql.*Test", "org.thingsboard.server.mqtt.claim.sql.*Test", - "org.thingsboard.server.mqtt.provision.sql.*Test", + "org.thingsboard.server.mqtt.provision.sql.*Test" }) public class MqttSqlTestSuite { From 4a1878130493753063101c8d03ec1b13ca015dbe Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 13 Nov 2020 16:19:40 +0200 Subject: [PATCH 20/84] added TransportPayloadTypeConfiguration, fix tests, ui --- .../server/controller/AbstractWebTest.java | 44 +- .../BaseDeviceProfileControllerTest.java | 380 +++++++++++++++- .../mqtt/AbstractMqttIntegrationTest.java | 45 +- ...AttributesRequestProtoIntegrationTest.java | 19 +- ...actMqttAttributesProtoIntegrationTest.java | 19 +- ...actMqttTimeseriesProtoIntegrationTest.java | 29 +- .../DeviceProfileTransportConfiguration.java | 1 + ...=> JsonTransportPayloadConfiguration.java} | 12 +- ...ttDeviceProfileTransportConfiguration.java | 23 +- ...qttTransportConfigurationDeserializer.java | 50 --- ...> ProtoTransportPayloadConfiguration.java} | 122 +----- .../TransportPayloadTypeConfiguration.java | 37 ++ .../mqtt/session/DeviceSessionCtx.java | 18 +- .../mqtt/util/MqttDynamicProtoSchemaTest.java | 406 ------------------ .../dao/device/DeviceProfileServiceImpl.java | 155 ++++++- ...ile-transport-configuration.component.html | 66 +-- ...ofile-transport-configuration.component.ts | 31 +- ui-ngx/src/app/shared/models/device.models.ts | 6 +- 18 files changed, 725 insertions(+), 738 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/device/profile/{MqttJsonDeviceProfileTransportConfiguration.java => JsonTransportPayloadConfiguration.java} (62%) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java rename common/data/src/main/java/org/thingsboard/server/common/data/device/profile/{MqttProtoDeviceProfileTransportConfiguration.java => ProtoTransportPayloadConfiguration.java} (56%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java delete mode 100644 common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index cd4a87d4de..c2bff23192 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.controller; -import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -33,12 +32,7 @@ import org.junit.Rule; import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; -import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootContextLoader; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; @@ -46,10 +40,6 @@ import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.mock.http.MockHttpInputMessage; import org.springframework.mock.http.MockHttpOutputMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; @@ -58,7 +48,6 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.context.WebApplicationContext; -import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; @@ -68,11 +57,13 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.id.HasId; -import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; @@ -330,7 +321,7 @@ public abstract class AbstractWebTest { } } - protected DeviceProfile createDeviceProfile(String name) { + protected DeviceProfile createDeviceProfile(String name, DeviceProfileTransportConfiguration deviceProfileTransportConfiguration) { DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setName(name); deviceProfile.setType(DeviceProfileType.DEFAULT); @@ -338,15 +329,34 @@ public abstract class AbstractWebTest { deviceProfile.setDescription(name + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); - DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration(); deviceProfileData.setConfiguration(configuration); - deviceProfileData.setTransportConfiguration(transportConfiguration); + if (deviceProfileTransportConfiguration != null) { + deviceProfileData.setTransportConfiguration(deviceProfileTransportConfiguration); + } else { + deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration()); + } deviceProfile.setProfileData(deviceProfileData); deviceProfile.setDefault(false); deviceProfile.setDefaultRuleChainId(null); return deviceProfile; } + protected MqttDeviceProfileTransportConfiguration createMqttDeviceProfileTransportConfiguration(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration) { + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_TELEMETRY_TOPIC); + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); + mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + return mqttDeviceProfileTransportConfiguration; + } + + protected ProtoTransportPayloadConfiguration createProtoTransportPayloadConfiguration(String deviceAttributesProtoSchema, String deviceTelemetryProtoSchema) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); + protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(deviceAttributesProtoSchema); + protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(deviceTelemetryProtoSchema); + return protoTransportPayloadConfiguration; + } + + protected ResultActions doGet(String urlTemplate, Object... urlVariables) throws Exception { MockHttpServletRequestBuilder getRequest = get(urlTemplate, urlVariables); setJwtToken(getRequest); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index 3376c16573..841e899473 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -16,6 +16,12 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -28,7 +34,10 @@ import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -36,9 +45,13 @@ import org.thingsboard.server.common.data.security.Authority; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public abstract class BaseDeviceProfileControllerTest extends AbstractControllerTest { @@ -78,7 +91,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Assert.assertNotNull(savedDeviceProfile); Assert.assertNotNull(savedDeviceProfile.getId()); @@ -96,7 +109,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testFindDeviceProfileById() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); Assert.assertNotNull(foundDeviceProfile); @@ -105,7 +118,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testFindDeviceProfileInfoById() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfileInfo foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/"+savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class); Assert.assertNotNull(foundDeviceProfileInfo); @@ -127,7 +140,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSetDefaultDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile defaultDeviceProfile = doPost("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString()+"/default", null, DeviceProfile.class); Assert.assertNotNull(defaultDeviceProfile); @@ -147,19 +160,19 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfileWithSameName() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); - DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile", null); doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Device profile with such name already exists"))); } @Test public void testSaveDeviceProfileWithSameProvisionDeviceKey() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); deviceProfile.setProvisionDeviceKey("testProvisionDeviceKey"); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); - DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2"); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2", null); deviceProfile2.setProvisionDeviceKey("testProvisionDeviceKey"); doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Device profile with such provision device key already exists"))); @@ -168,7 +181,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Ignore @Test public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); device.setName("Test device"); @@ -183,7 +196,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testChangeDeviceProfileTransportTypeWithExistingDevices() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); device.setName("Test device"); @@ -197,7 +210,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testDeleteDeviceProfileWithExistingDevice() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); @@ -214,7 +227,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testDeleteDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString()) @@ -235,7 +248,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController deviceProfiles.addAll(pageData.getData()); for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i, null); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); } @@ -280,7 +293,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController deviceProfiles.addAll(deviceProfilePageData.getData()); for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i, null); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); } @@ -318,4 +331,341 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController Assert.assertEquals(1, pageData.getTotalElements()); } + @Test + public void testSaveProtoDeviceProfileWithInvalidProtoFile() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " required int32 parameter = 1;\n" + + "}", "[Transport Configuration] failed to parse attributes proto schema due to: Syntax error in :6:4: 'required' label forbidden in proto3 field declarations"); + } + + @Test + public void testSaveProtoDeviceProfileWithInvalidProtoSyntax() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto2\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " required int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid schema syntax: proto2 for attributes proto schema provided! Only proto3 allowed!"); + } + + @Test + public void testSaveProtoDeviceProfileOptionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "option java_package = \"com.test.schemavalidation\";\n" + + "option java_multiple_files = true;\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema options don't support!"); + } + + @Test + public void testSaveProtoDeviceProfilePublicImportsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "import public \"oldschema.proto\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema public imports don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileImportsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "import \"oldschema.proto\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema imports don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileExtendDeclarationsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "extend google.protobuf.MethodOptions {\n" + + " MyMessage my_method_option = 50007;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema extend declarations don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileEnumOptionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "enum testEnum {\n" + + " option allow_alias = true;\n" + + " DEFAULT = 0;\n" + + " STARTED = 1;\n" + + " RUNNING = 2;\n" + + "}\n" + + "\n" + + "message testMessage {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Enum definitions options are not supported!"); + } + + @Test + public void testSaveProtoDeviceProfileNoOneMessageTypeExists() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "enum testEnum {\n" + + " DEFAULT = 0;\n" + + " STARTED = 1;\n" + + " RUNNING = 2;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! At least one Message definition should exists!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeOptionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message testMessage {\n" + + " option allow_alias = true;\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition options don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeExtensionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message TestMessage {\n" + + " extensions 100 to 199;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition extensions don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeReservedElementsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message Foo {\n" + + " reserved 2, 15, 9 to 11;\n" + + " reserved \"foo\", \"bar\";\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition reserved elements don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeGroupsElementsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message TestMessage {\n" + + " repeated group Result = 1 {\n" + + " string url = 2;\n" + + " string title = 3;\n" + + " repeated string snippets = 4;\n" + + " }\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition groups don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileOneOfsGroupsElementsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SampleMessage {\n" + + " oneof test_oneof {\n" + + " string name = 1;\n" + + " group Result = 2 {\n" + + " \tstring url = 3;\n" + + " \tstring title = 4;\n" + + " \trepeated string snippets = 5;\n" + + " }\n" + + " }" + + "}", "[Transport Configuration] invalid attributes proto schema provided! OneOf definition groups don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileWithMessageNestedTypes() throws Exception { + String schema = "syntax = \"proto3\";\n" + + "\n" + + "package testnested;\n" + + "\n" + + "message Outer {\n" + + " message MiddleAA {\n" + + " message Inner {\n" + + " int64 ival = 1;\n" + + " bool booly = 2;\n" + + " }\n" + + " Inner inner = 1;\n" + + " }\n" + + " message MiddleBB {\n" + + " message Inner {\n" + + " int32 ival = 1;\n" + + " bool booly = 2;\n" + + " }\n" + + " Inner inner = 1;\n" + + " }\n" + + " MiddleAA middleAA = 1;\n" + + " MiddleBB middleBB = 2;\n" + + "}"; + DynamicSchema dynamicSchema = getDynamicSchema(schema); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(5, messageTypes.size()); + assertTrue(messageTypes.contains("testnested.Outer")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA.Inner")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB.Inner")); + + DynamicMessage.Builder middleAAInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleAAInnerMsgDescriptor = middleAAInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAInnerMsg = middleAAInnerMsgBuilder + .setField(middleAAInnerMsgDescriptor.findFieldByName("ival"), 1L) + .setField(middleAAInnerMsgDescriptor.findFieldByName("booly"), true) + .build(); + + DynamicMessage.Builder middleAAMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA"); + Descriptors.Descriptor middleAAMsgDescriptor = middleAAMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAMsg = middleAAMsgBuilder + .setField(middleAAMsgDescriptor.findFieldByName("inner"), middleAAInnerMsg) + .build(); + + DynamicMessage.Builder middleBBInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleBBInnerMsgDescriptor = middleBBInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBInnerMsg = middleBBInnerMsgBuilder + .setField(middleBBInnerMsgDescriptor.findFieldByName("ival"), 0L) + .setField(middleBBInnerMsgDescriptor.findFieldByName("booly"), false) + .build(); + + DynamicMessage.Builder middleBBMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleBB"); + Descriptors.Descriptor middleBBMsgDescriptor = middleBBMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBMsg = middleBBMsgBuilder + .setField(middleBBMsgDescriptor.findFieldByName("inner"), middleBBInnerMsg) + .build(); + + + DynamicMessage.Builder outerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer"); + Descriptors.Descriptor outerMsgBuilderDescriptor = outerMsgBuilder.getDescriptorForType(); + DynamicMessage outerMsg = outerMsgBuilder + .setField(outerMsgBuilderDescriptor.findFieldByName("middleAA"), middleAAMsg) + .setField(outerMsgBuilderDescriptor.findFieldByName("middleBB"), middleBBMsg) + .build(); + + assertEquals("{\n" + + " \"middleAA\": {\n" + + " \"inner\": {\n" + + " \"ival\": \"1\",\n" + + " \"booly\": true\n" + + " }\n" + + " },\n" + + " \"middleBB\": {\n" + + " \"inner\": {\n" + + " \"ival\": 0,\n" + + " \"booly\": false\n" + + " }\n" + + " }\n" + + "}", dynamicMsgToJson(outerMsgBuilderDescriptor, outerMsg.toByteArray())); + } + + @Test + public void testSaveProtoDeviceProfileWithMessageOneOfs() throws Exception { + String schema = "syntax = \"proto3\";\n" + + "\n" + + "package testoneofs;\n" + + "\n" + + "message SubMessage {\n" + + " repeated string name = 1;\n" + + "}\n" + + "\n" + + "message SampleMessage {\n" + + " oneof testOneOf {\n" + + " string name = 4;\n" + + " SubMessage subMessage = 9;\n" + + " }\n" + + "}"; + DynamicSchema dynamicSchema = getDynamicSchema(schema); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(2, messageTypes.size()); + assertTrue(messageTypes.contains("testoneofs.SubMessage")); + assertTrue(messageTypes.contains("testoneofs.SampleMessage")); + + DynamicMessage.Builder sampleMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SampleMessage"); + Descriptors.Descriptor sampleMsgDescriptor = sampleMsgBuilder.getDescriptorForType(); + assertNotNull(sampleMsgDescriptor); + + List fields = sampleMsgDescriptor.getFields(); + assertEquals(2, fields.size()); + DynamicMessage sampleMsg = sampleMsgBuilder + .setField(sampleMsgDescriptor.findFieldByName("name"), "Bob") + .build(); + assertEquals("{\n" + " \"name\": \"Bob\"\n" + "}", dynamicMsgToJson(sampleMsgDescriptor, sampleMsg.toByteArray())); + + DynamicMessage.Builder subMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SubMessage"); + Descriptors.Descriptor subMsgDescriptor = subMsgBuilder.getDescriptorForType(); + DynamicMessage subMsg = subMsgBuilder + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "Alice") + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "John") + .build(); + + DynamicMessage sampleMsgWithOneOfSubMessage = sampleMsgBuilder.setField(sampleMsgDescriptor.findFieldByName("subMessage"), subMsg).build(); + assertEquals("{\n" + " \"subMessage\": {\n" + " \"name\": [\"Alice\", \"John\"]\n" + " }\n" + "}", + dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); + } + + private DeviceProfile testSaveDeviceProfileWithProtoPayloadType(String schema) throws Exception { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); + Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName()); + return savedDeviceProfile; + } + + private void testSaveDeviceProfileWithInvalidProtoSchema(String schema, String errorMsg) throws Exception { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(errorMsg))); + } + + private DynamicSchema getDynamicSchema(String schema) throws Exception { + DeviceProfile deviceProfile = testSaveDeviceProfileWithProtoPayloadType(schema); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement protoFile = protoTransportPayloadConfiguration.getTransportProtoSchema(schema); + return protoTransportPayloadConfiguration.getDynamicSchema(protoFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + } + + private String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); + return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + } + } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index de84698dc0..715ffeade3 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -38,9 +38,10 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileCon import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttJsonDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.controller.AbstractControllerTest; @@ -94,7 +95,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest protected Device savedGateway; protected String gatewayAccessToken; - protected MqttDeviceProfileTransportConfiguration transportConfiguration; + protected DeviceProfile deviceProfile; protected void processBeforeTest (String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, DeviceProfileProvisionType.DISABLED, null, null); @@ -139,11 +140,11 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest if (payloadType != null) { DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, telemetryProtoSchema, attributesProtoSchema, provisionType, provisionKey, provisionSecret); - DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); - device.setType(savedDeviceProfile.getName()); - device.setDeviceProfileId(savedDeviceProfile.getId()); - gateway.setType(savedDeviceProfile.getName()); - gateway.setDeviceProfileId(savedDeviceProfile.getId()); + deviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); + device.setType(deviceProfile.getName()); + device.setDeviceProfileId(deviceProfile.getId()); + gateway.setType(deviceProfile.getName()); + gateway.setDeviceProfileId(deviceProfile.getId()); } savedDevice = doPost("/api/device", device, Device.class); @@ -242,27 +243,30 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest deviceProfile.setDescription(transportPayloadType.name() + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); + if (!StringUtils.isEmpty(telemetryTopic)) { + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(telemetryTopic); + } + if (!StringUtils.isEmpty(attributesTopic)) { + mqttDeviceProfileTransportConfiguration.setDeviceAttributesTopic(attributesTopic); + } + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; if (TransportPayloadType.JSON.equals(transportPayloadType)) { - transportConfiguration = new MqttJsonDeviceProfileTransportConfiguration(); + transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration(); } else { - MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); if (StringUtils.isEmpty(telemetryProtoSchema)) { telemetryProtoSchema = DEVICE_TELEMETRY_PROTO_SCHEMA; } if (StringUtils.isEmpty(attributesProtoSchema)) { attributesProtoSchema = DEVICE_ATTRIBUTES_PROTO_SCHEMA; } - protoTransportConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema); - protoTransportConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); - transportConfiguration = protoTransportConfiguration; + protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema); + protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); + transportPayloadTypeConfiguration = protoTransportPayloadConfiguration; } - if (!StringUtils.isEmpty(telemetryTopic)) { - transportConfiguration.setDeviceTelemetryTopic(telemetryTopic); - } - if (!StringUtils.isEmpty(attributesTopic)) { - transportConfiguration.setDeviceAttributesTopic(attributesTopic); - } - deviceProfileData.setTransportConfiguration(transportConfiguration); + mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + deviceProfileData.setTransportConfiguration(mqttDeviceProfileTransportConfiguration); DeviceProfileProvisionConfiguration provisionConfiguration; switch (provisionType) { case ALLOW_CREATE_NEW_DEVICES: @@ -274,6 +278,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest case DISABLED: default: provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(provisionSecret); + break; } deviceProfileData.setProvisionConfiguration(provisionConfiguration); deviceProfileData.setConfiguration(configuration); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 8a8be4d1cc..0ea53d2801 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -30,8 +30,11 @@ import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -82,11 +85,15 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client) throws Exception { doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); - assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); - MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); - DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "attributesSchema"); - DynamicMessage.Builder postAttributesBuilder = telemetrySchema.newMessageBuilder("PostAttributes"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); + DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); assertNotNull(postAttributesMsgDescriptor); DynamicMessage postAttributesMsg = postAttributesBuilder diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java index f7f9bdef7d..9cc7c66f55 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java @@ -23,7 +23,10 @@ import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -47,11 +50,15 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac public void testPushMqttAttributes() throws Exception { super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); - MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); - DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "attributesSchema"); - DynamicMessage.Builder postAttributesBuilder = telemetrySchema.newMessageBuilder("PostAttributes"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); + DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); assertNotNull(postAttributesMsgDescriptor); DynamicMessage postAttributesMsg = postAttributesBuilder diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index d463dc1963..f8277b1934 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -26,8 +26,11 @@ import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -51,10 +54,14 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac public void testPushMqttTelemetry() throws Exception { super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); - MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); - DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); + DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); assertNotNull(postTelemetryMsgDescriptor); @@ -88,10 +95,14 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac "}"; super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); - MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(schemaStr); - DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); + DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("Values"); Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java index e685692895..fb337b24f4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M")}) public interface DeviceProfileTransportConfiguration { + @JsonIgnore DeviceTransportType getType(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java similarity index 62% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java index 3f9144dc8f..f4fd93459b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java @@ -15,24 +15,14 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; -@Slf4j -@EqualsAndHashCode(callSuper = true) @Data -@JsonDeserialize(as = MqttJsonDeviceProfileTransportConfiguration.class) -public class MqttJsonDeviceProfileTransportConfiguration extends MqttDeviceProfileTransportConfiguration{ +public class JsonTransportPayloadConfiguration implements TransportPayloadTypeConfiguration { @Override public TransportPayloadType getTransportPayloadType() { return TransportPayloadType.JSON; } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 66174e3d29..6a5c3c474c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -15,30 +15,15 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.DeviceTransportType; -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.PROPERTY, - property = "transportPayloadType") -@JsonSubTypes({ - @JsonSubTypes.Type(value = MqttJsonDeviceProfileTransportConfiguration.class, name = "JSON"), - @JsonSubTypes.Type(value = MqttProtoDeviceProfileTransportConfiguration.class, name = "PROTOBUF")}) -@JsonDeserialize(using = MqttTransportConfigurationDeserializer.class) @Data -public abstract class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { +public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { - public abstract TransportPayloadType getTransportPayloadType(); - - protected String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC; - protected String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; + private String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC; + private String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; + private TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; @Override public DeviceTransportType getType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java deleted file mode 100644 index 4754792fb2..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.device.profile; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.TransportPayloadType; - -import java.io.IOException; - -@Slf4j -public class MqttTransportConfigurationDeserializer extends StdDeserializer { - - public MqttTransportConfigurationDeserializer() { - super(MqttDeviceProfileTransportConfiguration.class); - } - - @Override - public MqttDeviceProfileTransportConfiguration deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { - try { - JsonNode jsonNode = jsonParser.readValueAsTree(); - if (jsonNode.hasNonNull("transportPayloadType") && jsonNode.get("transportPayloadType").asText().equals(TransportPayloadType.PROTOBUF.name())) { - return jsonParser.getCodec().treeToValue(jsonNode, MqttProtoDeviceProfileTransportConfiguration.class); - } else { - return jsonParser.getCodec().treeToValue(jsonNode, MqttJsonDeviceProfileTransportConfiguration.class); - } - } catch (IOException e) { - log.trace("Failed to deserialize JSON content into equivalent tree model during creating {}!", MqttDeviceProfileTransportConfiguration.class.getSimpleName(), e); - throw new RuntimeException("Failed to deserialize JSON content into equivalent tree model during creating " + MqttDeviceProfileTransportConfiguration.class.getSimpleName() + "!", e); - } - } - -} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java similarity index 56% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java index 158acfaf2a..c5bdf28833 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java @@ -15,16 +15,11 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.github.os72.protobuf.dynamic.DynamicSchema; import com.github.os72.protobuf.dynamic.EnumDefinition; import com.github.os72.protobuf.dynamic.MessageDefinition; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; -import com.squareup.wire.Syntax; -import com.squareup.wire.schema.Field; import com.squareup.wire.schema.Location; import com.squareup.wire.schema.internal.parser.EnumConstantElement; import com.squareup.wire.schema.internal.parser.EnumElement; @@ -35,30 +30,22 @@ import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoParser; import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.Data; -import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.TransportPayloadType; - import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @Slf4j -@EqualsAndHashCode(callSuper = true) @Data -@JsonDeserialize(as = MqttProtoDeviceProfileTransportConfiguration.class) -public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProfileTransportConfiguration { +public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeConfiguration { public static final Location LOCATION = new Location("", "", -1, -1); public static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; public static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; - public static String invalidSchemaProvidedMessage(String schemaName) { - return "[Transport Configuration] invalid " + schemaName + " schema provided!"; - } - private String deviceTelemetryProtoSchema; private String deviceAttributesProtoSchema; @@ -67,23 +54,15 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf return TransportPayloadType.PROTOBUF; } - public void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException { - ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); - ProtoFileElement protoFileElement; - try { - protoFileElement = schemaParser.readProtoFile(); - } catch (Exception e) { - throw new IllegalArgumentException("[Transport Configuration] failed to parse " + schemaName + " due to: " + e.getMessage()); - } - checkProtoFileSyntax(schemaName, protoFileElement); - checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!"); - checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!"); - checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!"); - checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!"); - checkTypeElements(schemaName, protoFileElement); + public Descriptors.Descriptor getTelemetryDynamicMessageDescriptor(String deviceTelemetryProtoSchema) { + return getDescriptor(deviceTelemetryProtoSchema, TELEMETRY_PROTO_SCHEMA); } - public Descriptors.Descriptor getDynamicMessageDescriptor(String protoSchema, String schemaName) { + public Descriptors.Descriptor getAttributesDynamicMessageDescriptor(String deviceAttributesProtoSchema) { + return getDescriptor(deviceAttributesProtoSchema, ATTRIBUTES_PROTO_SCHEMA); + } + + private Descriptors.Descriptor getDescriptor(String protoSchema, String schemaName) { try { ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName); @@ -125,91 +104,6 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf } } - private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { - if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { - throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + - " for: " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); - } - } - - private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { - if (!isEmptySettings) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); - } - } - - private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) { - List types = protoFileElement.getTypes(); - if (!types.isEmpty()) { - if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!"); - } else { - checkEnumElements(schemaName, getEnumElements(types)); - checkMessageElements(schemaName, getMessageTypes(types)); - } - } else { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!"); - } - } - - private void checkFieldElements(String schemaName, List fieldElements) { - if (!fieldElements.isEmpty()) { - boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { - Field.Label label = fieldElement.getLabel(); - return label != null && label.equals(Field.Label.REQUIRED); - }); - if (hasRequiredLabel) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!"); - } - boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); - if (hasDefaultValue) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!"); - } - } - } - - private void checkEnumElements(String schemaName, List enumTypes) { - if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); - } - if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!"); - } - } - - private void checkMessageElements(String schemaName, List messageElementsList) { - if (!messageElementsList.isEmpty()) { - messageElementsList.forEach(messageElement -> { - checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), - " Message definition groups don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), - " Message definition options don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), - " Message definition extensions don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), - " Message definition reserved elements don't support!"); - checkFieldElements(schemaName, messageElement.getFields()); - List oneOfs = messageElement.getOneOfs(); - if (!oneOfs.isEmpty()) { - oneOfs.forEach(oneOfElement -> { - checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), - " OneOf definition groups don't support!"); - checkFieldElements(schemaName, oneOfElement.getFields()); - }); - } - List nestedTypes = messageElement.getNestedTypes(); - if (!nestedTypes.isEmpty()) { - List nestedEnumTypes = getEnumElements(nestedTypes); - if (!nestedEnumTypes.isEmpty()) { - checkEnumElements(schemaName, nestedEnumTypes); - } - List nestedMessageTypes = getMessageTypes(nestedTypes); - checkMessageElements(schemaName, nestedMessageTypes); - } - }); - } - } - public ProtoFileElement getTransportProtoSchema(String protoSchema) { return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java new file mode 100644 index 0000000000..5a6bbd5b25 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.TransportPayloadType; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "transportPayloadType") +@JsonSubTypes({ + @JsonSubTypes.Type(value = JsonTransportPayloadConfiguration.class, name = "JSON"), + @JsonSubTypes.Type(value = ProtoTransportPayloadConfiguration.class, name = "PROTOBUF")}) +public interface TransportPayloadTypeConfiguration { + + @JsonIgnore + TransportPayloadType getTransportPayloadType(); + +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 6ecea5a77c..fb4512a59b 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -25,7 +25,8 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.transport.mqtt.MqttTransportContext; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter; @@ -118,11 +119,12 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { if (transportConfiguration.getType().equals(DeviceTransportType.MQTT) && transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { MqttDeviceProfileTransportConfiguration mqttConfig = (MqttDeviceProfileTransportConfiguration) transportConfiguration; - payloadType = mqttConfig.getTransportPayloadType(); + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttConfig.getTransportPayloadTypeConfiguration(); + payloadType = transportPayloadTypeConfiguration.getTransportPayloadType(); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); - if (mqttConfig instanceof MqttProtoDeviceProfileTransportConfiguration) { - updateDynamicMessageDescriptors(mqttConfig); + if (transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration) { + updateDynamicMessageDescriptors(transportPayloadTypeConfiguration); } } else { telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); @@ -130,9 +132,9 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { } } - private void updateDynamicMessageDescriptors(MqttDeviceProfileTransportConfiguration mqttConfig) { - MqttProtoDeviceProfileTransportConfiguration protoMqttConfig = (MqttProtoDeviceProfileTransportConfiguration) mqttConfig; - telemetryDynamicMessageDescriptor = protoMqttConfig.getDynamicMessageDescriptor(protoMqttConfig.getDeviceTelemetryProtoSchema(), "telemetrySchema"); - attributesDynamicMessageDescriptor = protoMqttConfig.getDynamicMessageDescriptor(protoMqttConfig.getDeviceAttributesProtoSchema(), "attributesSchema"); + private void updateDynamicMessageDescriptors(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfig = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + telemetryDynamicMessageDescriptor = protoTransportPayloadConfig.getTelemetryDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceTelemetryProtoSchema()); + attributesDynamicMessageDescriptor = protoTransportPayloadConfig.getAttributesDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceAttributesProtoSchema()); } } diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java deleted file mode 100644 index 47726ff610..0000000000 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java +++ /dev/null @@ -1,406 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.mqtt.util; - -import com.github.os72.protobuf.dynamic.DynamicSchema; -import com.google.protobuf.Descriptors; -import com.google.protobuf.DynamicMessage; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.util.JsonFormat; -import com.squareup.wire.schema.internal.parser.ProtoFileElement; -import com.squareup.wire.schema.internal.parser.ProtoParser; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; - -import java.util.List; -import java.util.Set; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration.LOCATION; - -@RunWith(MockitoJUnitRunner.class) -public class MqttDynamicProtoSchemaTest { - - private static final String PROTO_SCHEMA_WITH_NESTED_MSG_TYPES = "syntax = \"proto3\";\n" + - "\n" + - "package testnested;\n" + - "\n" + - "message Outer {\n" + - " message MiddleAA {\n" + - " message Inner {\n" + - " int64 ival = 1;\n" + - " bool booly = 2;\n" + - " }\n" + - " Inner inner = 1;\n" + - " }\n" + - " message MiddleBB {\n" + - " message Inner {\n" + - " int32 ival = 1;\n" + - " bool booly = 2;\n" + - " }\n" + - " Inner inner = 1;\n" + - " }\n" + - " MiddleAA middleAA = 1;\n" + - " MiddleBB middleBB = 2;\n" + - "}\n"; - - private static final String PROTO_SCHEMA_WITH_ONE_OFS = "syntax = \"proto3\";\n" + - "\n" + - "package testoneofs;\n" + - "\n" + - "message SubMessage {\n" + - " repeated string name = 1;\n" + - "}\n" + - "\n" + - "message SampleMessage {\n" + - " oneof testOneOf {\n" + - " string name = 4;\n" + - " SubMessage subMessage = 9;\n" + - " }\n" + - "}"; - - private static final String IVALID_PROTO_SCHEMA_REQUIRED_FIELD_EXISTS = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " required int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_NOT_VALID_SYNTAX = "syntax = \"proto2\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " required int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "option java_package = \"com.test.schemavalidation\";\n" + - "option java_multiple_files = true;\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_PUBLIC_IMPORTS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "import public \"oldschema.proto\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "import \"oldschema.proto\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_EXTEND_DECLARATION_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "extend google.protobuf.MethodOptions {\n" + - " MyMessage my_method_option = 50007;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_ENUM_OPTIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "enum testEnum {\n" + - " option allow_alias = true;\n" + - " DEFAULT = 0;\n" + - " STARTED = 1;\n" + - " RUNNING = 2;\n" + - "}\n" + - "\n" + - "message testMessage {\n" + - " int32 parameter = 1;\n" + - "}\n"; - - private static final String INVALID_PROTO_SCHEMA_NO_MESSAGE_TYPES_EXISTS = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "enum testEnum {\n" + - " DEFAULT = 0;\n" + - " STARTED = 1;\n" + - " RUNNING = 2;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_MESSAGE_OPTIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message testMessage {\n" + - " option allow_alias = true;\n" + - " int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_MESSAGE_EXTENSIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message TestMessage {\n" + - " extensions 100 to 199;\n" + - "}\n"; - - private static final String INVALID_PROTO_SCHEMA_MESSAGE_GROUPS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message TestMessage {\n" + - " repeated group Result = 1 {\n" + - " string url = 2;\n" + - " string title = 3;\n" + - " repeated string snippets = 4;\n" + - " }\n" + - "}\n"; - - private static final String INVALID_PROTO_SCHEMA_MESSAGE_RESERVED_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message Foo {\n" + - " reserved 2, 15, 9 to 11;\n" + - " reserved \"foo\", \"bar\";\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_ONE_OFS_GROUPS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SampleMessage {\n" + - " oneof test_oneof {\n" + - " string name = 1;\n" + - " group Result = 2 {\n" + - " \tstring url = 3;\n" + - " \tstring title = 4;\n" + - " \trepeated string snippets = 5;\n" + - " }\n" + - " }\n" + - "}"; - - private static final MqttProtoDeviceProfileTransportConfiguration mqttProtoDeviceProfileTransportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); - - private static void validateTransportProtoSchema(String schema, String schemaName) { - mqttProtoDeviceProfileTransportConfiguration.validateTransportProtoSchema(schema, schemaName); - } - - private static DynamicSchema getDynamicSchema(String schema, String schemaName) { - ProtoFileElement protoFileElement = getTransportProtoSchema(schema); - return mqttProtoDeviceProfileTransportConfiguration.getDynamicSchema(protoFileElement, schemaName); - } - - @Rule - public ExpectedException exceptionRule = ExpectedException.none(); - - @Test - public void testDynamicSchemaProtoFileValidation() { - processValidation("[Transport Configuration] failed to parse testParseToProtoFile due to: Syntax error in :6:4: 'required' label forbidden in proto3 field declarations", IVALID_PROTO_SCHEMA_REQUIRED_FIELD_EXISTS, "testParseToProtoFile"); - } - - @Test - public void testDynamicSchemaSyntaxValidation() { - processValidation("[Transport Configuration] invalid schema syntax: proto2 for: testSyntaxValidation provided! Only proto3 allowed!", INVALID_PROTO_SCHEMA_NOT_VALID_SYNTAX, "testSyntaxValidation"); - } - - @Test - public void testDynamicSchemaOptionsValidation() { - processValidation("[Transport Configuration] invalid testOptionsValidation schema provided! Schema options don't support!", INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED, "testOptionsValidation"); - } - - @Test - public void testDynamicSchemaPublicImportsValidation() { - processValidation("[Transport Configuration] invalid testPublicImportsValidation schema provided! Schema public imports don't support!", INVALID_PROTO_SCHEMA_PUBLIC_IMPORTS_NOT_SUPPORTED, "testPublicImportsValidation"); - } - - @Test - public void testDynamicSchemaImportsValidation() { - processValidation("[Transport Configuration] invalid testImportsValidation schema provided! Schema imports don't support!", INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED, "testImportsValidation"); - } - - @Test - public void testDynamicSchemaExtendDeclarationsValidation() { - processValidation("[Transport Configuration] invalid testExtendDeclarationsValidation schema provided! Schema extend declarations don't support!", INVALID_PROTO_SCHEMA_EXTEND_DECLARATION_NOT_SUPPORTED, "testExtendDeclarationsValidation"); - } - - @Test - public void testDynamicSchemaEnumOptionsValidation() { - processValidation("[Transport Configuration] invalid testEnumOptionsValidation schema provided! Enum definitions options are not supported!", INVALID_PROTO_SCHEMA_ENUM_OPTIONS_NOT_SUPPORTED, "testEnumOptionsValidation"); - } - - @Test - public void testDynamicSchemaNoOneMessageTypeExistsValidation() { - processValidation("[Transport Configuration] invalid noOneMessageTypeExists schema provided! At least one Message definition should exists!", INVALID_PROTO_SCHEMA_NO_MESSAGE_TYPES_EXISTS, "noOneMessageTypeExists"); - } - - @Test - public void testDynamicSchemaMessageTypeOptionsValidation() { - processValidation("[Transport Configuration] invalid messageTypeOptions schema provided! Message definition options don't support!", INVALID_PROTO_SCHEMA_MESSAGE_OPTIONS_NOT_SUPPORTED, "messageTypeOptions"); - } - - @Test - public void testDynamicSchemaMessageTypeExtensionsValidation() { - processValidation("[Transport Configuration] invalid messageTypeExtensions schema provided! Message definition extensions don't support!", INVALID_PROTO_SCHEMA_MESSAGE_EXTENSIONS_NOT_SUPPORTED, "messageTypeExtensions"); - } - - @Test - public void testDynamicSchemaMessageTypeReservedElementsValidation() { - processValidation("[Transport Configuration] invalid messageTypeReservedElements schema provided! Message definition reserved elements don't support!", INVALID_PROTO_SCHEMA_MESSAGE_RESERVED_NOT_SUPPORTED, "messageTypeReservedElements"); - } - - @Test - public void testDynamicSchemaMessageTypeGroupsElementsValidation() { - processValidation("[Transport Configuration] invalid messageTypeGroupsElements schema provided! Message definition groups don't support!", INVALID_PROTO_SCHEMA_MESSAGE_GROUPS_NOT_SUPPORTED, "messageTypeGroupsElements"); - } - - @Test - public void testDynamicSchemaOneOfsTypeGroupsElementsValidation() { - processValidation("[Transport Configuration] invalid oneOfsTypeGroupsElements schema provided! OneOf definition groups don't support!", INVALID_PROTO_SCHEMA_ONE_OFS_GROUPS_NOT_SUPPORTED, "oneOfsTypeGroupsElements"); - } - - private void processValidation(String expectedMessage, String schema, String schemaName) { - exceptionRule.expect(IllegalArgumentException.class); - exceptionRule.expectMessage(expectedMessage); - validateTransportProtoSchema(schema, schemaName); - } - - @Test - public void testDynamicSchemaCreationWithMessageNestedTypes() throws Exception { - String testNestedTypesProtoSchema = "testNestedTypesProtoSchema"; - validateTransportProtoSchema(PROTO_SCHEMA_WITH_NESTED_MSG_TYPES, testNestedTypesProtoSchema); - DynamicSchema dynamicSchema = getDynamicSchema(PROTO_SCHEMA_WITH_NESTED_MSG_TYPES, testNestedTypesProtoSchema); - assertNotNull(dynamicSchema); - Set messageTypes = dynamicSchema.getMessageTypes(); - assertEquals(5, messageTypes.size()); - assertTrue(messageTypes.contains("testnested.Outer")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleAA")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleAA.Inner")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleBB")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleBB.Inner")); - - DynamicMessage.Builder middleAAInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); - Descriptors.Descriptor middleAAInnerMsgDescriptor = middleAAInnerMsgBuilder.getDescriptorForType(); - DynamicMessage middleAAInnerMsg = middleAAInnerMsgBuilder - .setField(middleAAInnerMsgDescriptor.findFieldByName("ival"), 1L) - .setField(middleAAInnerMsgDescriptor.findFieldByName("booly"), true) - .build(); - - DynamicMessage.Builder middleAAMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA"); - Descriptors.Descriptor middleAAMsgDescriptor = middleAAMsgBuilder.getDescriptorForType(); - DynamicMessage middleAAMsg = middleAAMsgBuilder - .setField(middleAAMsgDescriptor.findFieldByName("inner"), middleAAInnerMsg) - .build(); - - DynamicMessage.Builder middleBBInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); - Descriptors.Descriptor middleBBInnerMsgDescriptor = middleBBInnerMsgBuilder.getDescriptorForType(); - DynamicMessage middleBBInnerMsg = middleBBInnerMsgBuilder - .setField(middleBBInnerMsgDescriptor.findFieldByName("ival"), 0L) - .setField(middleBBInnerMsgDescriptor.findFieldByName("booly"), false) - .build(); - - DynamicMessage.Builder middleBBMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleBB"); - Descriptors.Descriptor middleBBMsgDescriptor = middleBBMsgBuilder.getDescriptorForType(); - DynamicMessage middleBBMsg = middleBBMsgBuilder - .setField(middleBBMsgDescriptor.findFieldByName("inner"), middleBBInnerMsg) - .build(); - - - DynamicMessage.Builder outerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer"); - Descriptors.Descriptor outerMsgBuilderDescriptor = outerMsgBuilder.getDescriptorForType(); - DynamicMessage outerMsg = outerMsgBuilder - .setField(outerMsgBuilderDescriptor.findFieldByName("middleAA"), middleAAMsg) - .setField(outerMsgBuilderDescriptor.findFieldByName("middleBB"), middleBBMsg) - .build(); - - assertEquals("{\n" + - " \"middleAA\": {\n" + - " \"inner\": {\n" + - " \"ival\": \"1\",\n" + - " \"booly\": true\n" + - " }\n" + - " },\n" + - " \"middleBB\": {\n" + - " \"inner\": {\n" + - " \"ival\": 0,\n" + - " \"booly\": false\n" + - " }\n" + - " }\n" + - "}", dynamicMsgToJson(outerMsgBuilderDescriptor, outerMsg.toByteArray())); - } - - @Test - public void testDynamicSchemaCreationWithMessageOneOfs() throws Exception { - String testOneOfsProtoSchema = "testOneOfsProtoSchema"; - validateTransportProtoSchema(PROTO_SCHEMA_WITH_ONE_OFS, testOneOfsProtoSchema); - DynamicSchema dynamicSchema = getDynamicSchema(PROTO_SCHEMA_WITH_ONE_OFS, testOneOfsProtoSchema); - assertNotNull(dynamicSchema); - Set messageTypes = dynamicSchema.getMessageTypes(); - assertEquals(2, messageTypes.size()); - assertTrue(messageTypes.contains("testoneofs.SubMessage")); - assertTrue(messageTypes.contains("testoneofs.SampleMessage")); - - DynamicMessage.Builder sampleMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SampleMessage"); - Descriptors.Descriptor sampleMsgDescriptor = sampleMsgBuilder.getDescriptorForType(); - assertNotNull(sampleMsgDescriptor); - - List fields = sampleMsgDescriptor.getFields(); - assertEquals(2, fields.size()); - DynamicMessage sampleMsg = sampleMsgBuilder - .setField(sampleMsgDescriptor.findFieldByName("name"), "Bob") - .build(); - assertEquals("{\n" + " \"name\": \"Bob\"\n" + "}", dynamicMsgToJson(sampleMsgDescriptor, sampleMsg.toByteArray())); - - DynamicMessage.Builder subMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SubMessage"); - Descriptors.Descriptor subMsgDescriptor = subMsgBuilder.getDescriptorForType(); - DynamicMessage subMsg = subMsgBuilder - .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "Alice") - .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "John") - .build(); - - DynamicMessage sampleMsgWithOneOfSubMessage = sampleMsgBuilder.setField(sampleMsgDescriptor.findFieldByName("subMessage"), subMsg).build(); - assertEquals("{\n" + " \"subMessage\": {\n" + " \"name\": [\"Alice\", \"John\"]\n" + " }\n" + "}", - dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); - - } - - private String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { - DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); - return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); - } - - private static ProtoFileElement getTransportProtoSchema(String protoSchema) { - return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index e5399fc8d4..d1a7820df4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -15,6 +15,16 @@ */ package org.thingsboard.server.dao.device; +import com.squareup.wire.Syntax; +import com.squareup.wire.schema.Field; +import com.squareup.wire.schema.Location; +import com.squareup.wire.schema.internal.parser.EnumElement; +import com.squareup.wire.schema.internal.parser.FieldElement; +import com.squareup.wire.schema.internal.parser.MessageElement; +import com.squareup.wire.schema.internal.parser.OneOfElement; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import com.squareup.wire.schema.internal.parser.ProtoParser; +import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; @@ -35,7 +45,8 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTra import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -49,6 +60,8 @@ import org.thingsboard.server.dao.tenant.TenantDao; import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -61,6 +74,14 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D private static final String INCORRECT_DEVICE_PROFILE_ID = "Incorrect deviceProfileId "; private static final String INCORRECT_DEVICE_PROFILE_NAME = "Incorrect deviceProfileName "; + private static final Location LOCATION = new Location("", "", -1, -1); + private static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; + private static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; + + private static String invalidSchemaProvidedMessage(String schemaName) { + return "[Transport Configuration] invalid " + schemaName + " provided!"; + } + @Autowired private DeviceProfileDao deviceProfileDao; @@ -312,13 +333,16 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } else { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { - MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - try { - protoTransportConfiguration.validateTransportProtoSchema(protoTransportConfiguration.getDeviceAttributesProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.ATTRIBUTES_PROTO_SCHEMA); - protoTransportConfiguration.validateTransportProtoSchema(protoTransportConfiguration.getDeviceTelemetryProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.TELEMETRY_PROTO_SCHEMA); - } catch (Exception exception) { - throw new DataValidationException(exception.getMessage()); + if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + if (mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration = (ProtoTransportPayloadConfiguration) mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration(); + try { + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA); + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA); + } catch (Exception exception) { + throw new DataValidationException(exception.getMessage()); + } } } } @@ -345,6 +369,121 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } } + + private void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException { + ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); + ProtoFileElement protoFileElement; + try { + protoFileElement = schemaParser.readProtoFile(); + } catch (Exception e) { + throw new IllegalArgumentException("[Transport Configuration] failed to parse " + schemaName + " due to: " + e.getMessage()); + } + checkProtoFileSyntax(schemaName, protoFileElement); + checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!"); + checkTypeElements(schemaName, protoFileElement); + } + + private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { + if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { + throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + + " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); + } + } + private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { + if (!isEmptySettings) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); + } + } + + private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) { + List types = protoFileElement.getTypes(); + if (!types.isEmpty()) { + if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!"); + } else { + checkEnumElements(schemaName, getEnumElements(types)); + checkMessageElements(schemaName, getMessageTypes(types)); + } + } else { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!"); + } + } + + private void checkFieldElements(String schemaName, List fieldElements) { + if (!fieldElements.isEmpty()) { + boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { + Field.Label label = fieldElement.getLabel(); + return label != null && label.equals(Field.Label.REQUIRED); + }); + if (hasRequiredLabel) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!"); + } + boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); + if (hasDefaultValue) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!"); + } + } + } + + private void checkEnumElements(String schemaName, List enumTypes) { + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); + } + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!"); + } + } + + private void checkMessageElements(String schemaName, List messageElementsList) { + if (!messageElementsList.isEmpty()) { + messageElementsList.forEach(messageElement -> { + checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), + " Message definition groups don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), + " Message definition options don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), + " Message definition extensions don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), + " Message definition reserved elements don't support!"); + checkFieldElements(schemaName, messageElement.getFields()); + List oneOfs = messageElement.getOneOfs(); + if (!oneOfs.isEmpty()) { + oneOfs.forEach(oneOfElement -> { + checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), + " OneOf definition groups don't support!"); + checkFieldElements(schemaName, oneOfElement.getFields()); + }); + } + List nestedTypes = messageElement.getNestedTypes(); + if (!nestedTypes.isEmpty()) { + List nestedEnumTypes = getEnumElements(nestedTypes); + if (!nestedEnumTypes.isEmpty()) { + checkEnumElements(schemaName, nestedEnumTypes); + } + List nestedMessageTypes = getMessageTypes(nestedTypes); + checkMessageElements(schemaName, nestedMessageTypes); + } + }); + } + } + + private List getMessageTypes(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof MessageElement) + .map(typeElement -> (MessageElement) typeElement) + .collect(Collectors.toList()); + } + + private List getEnumElements(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof EnumElement) + .map(typeElement -> (EnumElement) typeElement) + .collect(Collectors.toList()); + } + }; private PaginatedRemover tenantDeviceProfilesRemover = diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index c83066bbae..cedf147bc1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -57,40 +57,42 @@
-
- device-profile.mqtt-device-payload-type -
- - - - {{mqttTransportPayloadTypeTranslations.get(type) | translate}} - - - - {{ 'device-profile.mqtt-payload-type-required' | translate }} - - -
- - device-profile.telemetry-proto-schema - - - {{ 'device-profile.telemetry-proto-schema-required' | translate}} - - - - device-profile.attributes-proto-schema - - - {{ 'device-profile.attributes-proto-schema-required' | translate}} +
+
+ device-profile.mqtt-device-payload-type +
+ + + + {{mqttTransportPayloadTypeTranslations.get(type) | translate}} + + + + {{ 'device-profile.mqtt-payload-type-required' | translate }} +
+ + device-profile.telemetry-proto-schema + + + {{ 'device-profile.telemetry-proto-schema-required' | translate}} + + + + device-profile.attributes-proto-schema + + + {{ 'device-profile.attributes-proto-schema-required' | translate}} + + +
-
-
+ + diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 5569a0b530..d16ad840f1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -87,13 +87,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control configuration: this.fb.group({ deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], - transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] + transportPayloadTypeConfiguration: this.fb.group({ + transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] + }) }) }); - let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; - configurationFormGroup.get('transportPayloadType').valueChanges.subscribe(payloadType => { - this.updateTransportPayloadBasedControls(payloadType, configurationFormGroup); - this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').valueChanges.subscribe(payloadType => { + this.updateTransportPayloadBasedControls(payloadType); + this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); @@ -109,15 +110,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control } } - protoPayloadType(): boolean { - let configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; - return configuration.transportPayloadType === MqttTransportPayloadType.PROTOBUF; + get protoPayloadType(): boolean { + let transportPayloadType = this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').value; + return transportPayloadType === MqttTransportPayloadType.PROTOBUF; } writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { - let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; - this.updateTransportPayloadBasedControls(value.transportPayloadType, configurationFormGroup); + this.updateTransportPayloadBasedControls(value.transportPayloadTypeConfiguration.transportPayloadType); this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); } } @@ -131,13 +131,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control this.propagateChange(configuration); } - private updateTransportPayloadBasedControls(type: MqttTransportPayloadType, configurationFormGroup: FormGroup) { + private updateTransportPayloadBasedControls(type: MqttTransportPayloadType) { + const transportPayloadTypeConfigurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration') as FormGroup; if (type === MqttTransportPayloadType.PROTOBUF) { - configurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); - configurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); + transportPayloadTypeConfigurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); + transportPayloadTypeConfigurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); } else { - configurationFormGroup.removeControl('deviceTelemetryProtoSchema'); - configurationFormGroup.removeControl('deviceAttributesProtoSchema'); + transportPayloadTypeConfigurationFormGroup.removeControl('deviceTelemetryProtoSchema'); + transportPayloadTypeConfigurationFormGroup.removeControl('deviceAttributesProtoSchema'); } } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 4ffe4af922..ffeae2f56d 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -148,7 +148,9 @@ export interface DefaultDeviceProfileTransportConfiguration { export interface MqttDeviceProfileTransportConfiguration { deviceTelemetryTopic?: string; deviceAttributesTopic?: string; - transportPayloadType?: MqttTransportPayloadType; + transportPayloadTypeConfiguration?: { + transportPayloadType?: MqttTransportPayloadType; + }; [key: string]: any; } @@ -208,7 +210,7 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT const mqttTransportConfiguration: MqttDeviceProfileTransportConfiguration = { deviceTelemetryTopic: 'v1/devices/me/telemetry', deviceAttributesTopic: 'v1/devices/me/attributes', - transportPayloadType: MqttTransportPayloadType.JSON + transportPayloadTypeConfiguration: {transportPayloadType: MqttTransportPayloadType.JSON} }; transportConfiguration = {...mqttTransportConfiguration, type: DeviceTransportType.MQTT}; break; From 34c8294e9d11c2d26f9b92a788deb3a25e8715fa Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 13 Nov 2020 20:28:55 +0200 Subject: [PATCH 21/84] Fixed fit bounds marker is disabled covered all markers --- .../modules/home/components/widget/lib/maps/leaflet-map.ts | 3 ++- .../app/modules/home/components/widget/lib/maps/map-models.ts | 2 +- .../home/components/widget/lib/maps/providers/google-map.ts | 4 ++-- .../home/components/widget/lib/maps/providers/here-map.ts | 4 ++-- .../components/widget/lib/maps/providers/openstreet-map.ts | 4 ++-- .../home/components/widget/lib/maps/providers/tencent-map.ts | 4 ++-- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 0f1f08799c..6b903101ad 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -487,7 +487,8 @@ export default abstract class LeafletMap { } const mapBounds = this.map.getBounds(); - if (bounds.isValid() && (!this.bounds || !this.bounds.isValid() || !this.bounds.equals(bounds) && !mapBounds.contains(bounds))) { + if (bounds.isValid() && (!this.bounds || !this.bounds.isValid() || !this.bounds.equals(bounds) + && this.options.fitMapBounds ? !mapBounds.contains(bounds) : false)) { this.bounds = bounds; this.fitBounds(bounds); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts index 63fe097b98..ed238dcfe2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts @@ -20,6 +20,7 @@ import { EntityType } from '@shared/models/entity-type.models'; import tinycolor from 'tinycolor2'; export const DEFAULT_MAP_PAGE_SIZE = 16384; +export const DEFAULT_ZOOM_LEVEL = 8; export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; @@ -229,7 +230,6 @@ export const defaultSettings: any = { strokeWeight: 2, strokeOpacity: 1.0, initCallback: () => { }, - defaultZoomLevel: 8, disableScrollZooming: false, minZoomLevel: 16, credentials: '', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts index 94fa1a09ac..20d28afbd1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts @@ -17,7 +17,7 @@ import L from 'leaflet'; import LeafletMap from '../leaflet-map'; -import { UnitedMapSettings } from '../map-models'; +import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models'; import 'leaflet.gridlayer.googlemutant'; import { ResourcesService } from '@core/services/resources.service'; import { WidgetContext } from '@home/models/widget-component.models'; @@ -39,7 +39,7 @@ export class GoogleMap extends LeafletMap { const map = L.map($container, { attributionControl: false, editable: !!options.editablePolygon - }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); + }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL); (L.gridLayer as any).googleMutant({ type: options?.gmDefaultMapType || 'roadmap' }).addTo(map); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts index a6418ee557..7a0d12a03e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts @@ -16,7 +16,7 @@ import L from 'leaflet'; import LeafletMap from '../leaflet-map'; -import { UnitedMapSettings } from '../map-models'; +import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models'; import { WidgetContext } from '@home/models/widget-component.models'; export class HEREMap extends LeafletMap { @@ -24,7 +24,7 @@ export class HEREMap extends LeafletMap { super(ctx, $container, options); const map = L.map($container, { editable: !!options.editablePolygon - }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); + }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL); const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials); tileLayer.addTo(map); super.initSettings(options); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts index f4f10a0dac..7cfb77fd42 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts @@ -16,7 +16,7 @@ import L from 'leaflet'; import LeafletMap from '../leaflet-map'; -import { UnitedMapSettings } from '../map-models'; +import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models'; import { WidgetContext } from '@home/models/widget-component.models'; export class OpenStreetMap extends LeafletMap { @@ -24,7 +24,7 @@ export class OpenStreetMap extends LeafletMap { super(ctx, $container, options); const map = L.map($container, { editable: !!options.editablePolygon - }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); + }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL); let tileLayer; if (options.useCustomProvider) { tileLayer = L.tileLayer(options.customProviderTileUrl); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts index 498a6053a1..29074501ab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts @@ -17,7 +17,7 @@ import L from 'leaflet'; import LeafletMap from '../leaflet-map'; -import { UnitedMapSettings } from '../map-models'; +import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models'; import { WidgetContext } from '@home/models/widget-component.models'; export class TencentMap extends LeafletMap { @@ -26,7 +26,7 @@ export class TencentMap extends LeafletMap { const txUrl = 'http://rt{s}.map.gtimg.com/realtimerender?z={z}&x={x}&y={y}&type=vector&style=0'; const map = L.map($container, { editable: !!options.editablePolygon - }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); + }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL); const txLayer = L.tileLayer(txUrl, { subdomains: '0123', tms: true, From a87a3d684e4082b14c17595792a25e558f922dd7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 16 Nov 2020 10:52:18 +0200 Subject: [PATCH 22/84] Improvement to handling of subscription updates --- .../DefaultTbLocalSubscriptionService.java | 23 +++++-------------- .../assets/locale/locale.constant-en_US.json | 4 ++-- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index 4e62628851..cd85c44cbe 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -21,12 +21,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; @@ -36,7 +30,6 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.queue.TbClusterService; -import org.thingsboard.server.service.telemetry.DefaultTelemetryWebSocketService; import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; @@ -49,7 +42,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.stream.Collectors; @Slf4j @TbCoreComponent @@ -59,9 +51,6 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer private final Set currentPartitions = ConcurrentHashMap.newKeySet(); private final Map> subscriptionsBySessionId = new ConcurrentHashMap<>(); - @Autowired - private EntityViewService entityViewService; - @Autowired private PartitionService partitionService; @@ -72,17 +61,17 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer @Lazy private SubscriptionManagerService subscriptionManagerService; - private ExecutorService wsCallBackExecutor; + private ExecutorService subscriptionUpdateExecutor; @PostConstruct public void initExecutor() { - wsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ws-sub-callback")); + subscriptionUpdateExecutor = Executors.newWorkStealingPool(20); } @PreDestroy public void shutdownExecutor() { - if (wsCallBackExecutor != null) { - wsCallBackExecutor.shutdownNow(); + if (subscriptionUpdateExecutor != null) { + subscriptionUpdateExecutor.shutdownNow(); } } @@ -148,7 +137,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer update.getLatestValues().forEach((key, value) -> attrSub.getKeyStates().put(key, value)); break; } - subscription.getUpdateConsumer().accept(sessionId, update); + subscriptionUpdateExecutor.submit(() -> subscription.getUpdateConsumer().accept(sessionId, update)); } callback.onSuccess(); } @@ -158,7 +147,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer TbSubscription subscription = subscriptionsBySessionId .getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId()); if (subscription != null && subscription.getType() == TbSubscriptionType.ALARMS) { - subscription.getUpdateConsumer().accept(sessionId, update); + subscriptionUpdateExecutor.submit(() -> subscription.getUpdateConsumer().accept(sessionId, update)); } callback.onSuccess(); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 76799b73fd..9acd10ef95 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -941,8 +941,8 @@ "provision-strategy": "Provision strategy", "provision-strategy-required": "Provision strategy is required.", "provision-strategy-disabled": "Disabled", - "provision-strategy-created-new": "Allow create new devices", - "provision-strategy-check-pre-provisioned": "Check pre provisioned devices", + "provision-strategy-created-new": "Allow to create new devices", + "provision-strategy-check-pre-provisioned": "Check for pre-provisioned devices", "provision-device-key": "Provision device key", "provision-device-key-required": "Provision device key is required.", "provision-device-secret": "Provision device secret", From 699b2ef9bef480e512355d4efe03dac65320ab81 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 12:22:13 +0200 Subject: [PATCH 23/84] changed schema for testPushMqttTelemetryWithTs: set Values as inner object --- ...actMqttTimeseriesProtoIntegrationTest.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index f8277b1934..b69b53856f 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -80,16 +80,17 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac String schemaStr = "syntax =\"proto3\";\n" + "\n" + "package test;\n" + - "\n" + - "message Values {\n" + - " string key1 = 1;\n" + - " bool key2 = 2;\n" + - " double key3 = 3;\n" + - " int32 key4 = 4;\n" + - " string key5 = 5;\n" + - "}\n" + " \n" + "message PostTelemetry {\n" + + "\n" + + " message Values {\n" + + " string key1 = 1;\n" + + " bool key2 = 2;\n" + + " double key3 = 3;\n" + + " int32 key4 = 4;\n" + + " string key5 = 5;\n" + + " }\n" + + "\n" + " int64 ts = 1;\n" + " Values values = 2;\n" + "}"; @@ -104,7 +105,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); - DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("Values"); + DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.Values"); Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType(); assertNotNull(valuesDescriptor); From 150b1bb809b222fb3fbcad1db5e3736362071fba Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 16 Nov 2020 11:46:17 +0200 Subject: [PATCH 24/84] AlarmState improvements (used createDetails for update or clear alarm). --- .../rule/engine/profile/AlarmState.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index ce43ca260f..30a861134a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -111,7 +111,7 @@ class AlarmState { for (AlarmRuleState state : createRulesSortedBySeverityDesc) { stateUpdate = clearAlarmState(stateUpdate, state); } - ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis()); + ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), createDetails(clearState), System.currentTimeMillis()); pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm)); currentAlarm = null; } else if (AlarmEvalResult.FALSE.equals(evalResult)) { @@ -203,6 +203,7 @@ class AlarmState { AlarmSeverity oldSeverity = currentAlarm.getSeverity(); // Skip update if severity is decreased. if (severity.ordinal() <= oldSeverity.ordinal()) { + currentAlarm.setDetails(createDetails(ruleState)); if (!oldSeverity.equals(severity)) { currentAlarm.setSeverity(severity); currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); @@ -235,19 +236,24 @@ class AlarmState { } private JsonNode createDetails(AlarmRuleState ruleState) { - ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); - String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); + JsonNode alarmDetails; + String alarmDetailsStr = ruleState.getAlarmRule().getAlarmDetails(); - if (alarmDetails != null) { + if (alarmDetailsStr != null) { for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); - alarmDetails = alarmDetails.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); + alarmDetailsStr = alarmDetailsStr.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); } - - details.put("data", alarmDetails); + ObjectNode newDetails = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + newDetails.put("data", alarmDetailsStr); + alarmDetails = newDetails; + } else if (currentAlarm != null) { + alarmDetails = currentAlarm.getDetails(); + } else { + alarmDetails = JacksonUtil.OBJECT_MAPPER.createObjectNode(); } - return details; + return alarmDetails; } private static String getValueAsString(EntityKeyValue entityKeyValue) { From 076f5483587d5ea1a7e0cd1d79f16a70e0fd6c84 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 16 Nov 2020 12:40:12 +0200 Subject: [PATCH 25/84] refactored AlarmState --- .../org/thingsboard/rule/engine/profile/AlarmState.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 30a861134a..81622b6b93 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.thingsboard.rule.engine.action.TbAlarmResult; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState; @@ -239,18 +240,18 @@ class AlarmState { JsonNode alarmDetails; String alarmDetailsStr = ruleState.getAlarmRule().getAlarmDetails(); - if (alarmDetailsStr != null) { + if (StringUtils.isNotEmpty(alarmDetailsStr)) { for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); alarmDetailsStr = alarmDetailsStr.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); } - ObjectNode newDetails = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + ObjectNode newDetails = JacksonUtil.newObjectNode(); newDetails.put("data", alarmDetailsStr); alarmDetails = newDetails; } else if (currentAlarm != null) { alarmDetails = currentAlarm.getDetails(); } else { - alarmDetails = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + alarmDetails = JacksonUtil.newObjectNode(); } return alarmDetails; From 4fc7668a727045614672b1aa12b4bcf73971c709 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 14:12:49 +0200 Subject: [PATCH 26/84] added default mqtt proto schemas --- ...ofile-transport-configuration.component.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index be54cff29a..35feb53545 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -133,8 +133,32 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control private updateTransportPayloadBasedControls(type: MqttTransportPayloadType) { const transportPayloadTypeConfigurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration') as FormGroup; if (type === MqttTransportPayloadType.PROTOBUF) { - transportPayloadTypeConfigurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); - transportPayloadTypeConfigurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); + const defaultTelemetrySchema = "syntax =\"proto3\";\n" + + "package telemetry;\n" + + "\n" + + "message SensorDataReading {\n" + + "\n" + + " double temperature = 1;\n" + + " double humidity = 2;\n" + + " InnerObject innerObject = 3;\n" + + "\n" + + " message InnerObject {\n" + + " string key1 = 1;\n" + + " bool key2 = 2;\n" + + " double key3 = 3;\n" + + " int32 key4 = 4;\n" + + " string key5 = 5;\n" + + " }\n" + + "}\n"; + const defaultAttributesSchema = "syntax =\"proto3\";\n" + + "package attributes;\n" + + "\n" + + "message SensorDataReading {\n" + + " string firmwareVersion = 1;\n" + + " string serialNumber = 2;\n" + + "}"; + transportPayloadTypeConfigurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(defaultTelemetrySchema, Validators.required)); + transportPayloadTypeConfigurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(defaultAttributesSchema, Validators.required)); } else { transportPayloadTypeConfigurationFormGroup.removeControl('deviceTelemetryProtoSchema'); transportPayloadTypeConfigurationFormGroup.removeControl('deviceAttributesProtoSchema'); From abfb6893ba7dd67abba3e42f2c3f4f47b3b4a6d2 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 16 Nov 2020 16:48:10 +0200 Subject: [PATCH 27/84] UI: Added unique alarm type validation in alarm rule --- .../alarm/device-profile-alarm.component.html | 3 +++ .../profile/alarm/device-profile-alarm.component.ts | 13 +++++++++++++ .../components/profile/device-profile.component.ts | 10 +++++----- ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html index cb1f7afb06..3468442b50 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html @@ -41,6 +41,9 @@ {{ 'device-profile.alarm-type-required' | translate }} + + {{ 'device-profile.alarm-type-unique' | translate }} + diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts index cf4a55e6d8..fc0cee9639 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts @@ -133,6 +133,19 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit } public validate(c: FormControl) { + if (c.parent) { + const alarmType = c.value.alarmType; + const profileAlarmsType = []; + c.parent.getRawValue().forEach((alarm: DeviceProfileAlarm) => { + profileAlarmsType.push(alarm.alarmType); + } + ); + if (profileAlarmsType.filter(profileAlarmType => profileAlarmType === alarmType).length > 1) { + this.alarmFormGroup.get('alarmType').setErrors({ + unique: true + }); + } + } return (this.alarmFormGroup.valid) ? null : { alarm: { valid: false, diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index d30c8b47b9..8c521dcd77 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -38,7 +38,7 @@ import { } from '@shared/models/device.models'; import { EntityType } from '@shared/models/entity-type.models'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; -import {ServiceType} from "@shared/models/queue.models"; +import { ServiceType } from '@shared/models/queue.models'; @Component({ selector: 'tb-device-profile', @@ -176,10 +176,10 @@ export class DeviceProfileComponent extends EntityComponent { transportConfiguration: entity.profileData?.transportConfiguration, alarms: entity.profileData?.alarms, provisionConfiguration: deviceProvisionConfiguration - }}); - this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}); - this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}); - this.entityForm.patchValue({description: entity.description}); + }}, {emitEvent: false}); + this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}, {emitEvent: false}); + this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}, {emitEvent: false}); + this.entityForm.patchValue({description: entity.description}, {emitEvent: false}); } prepareFormValue(formValue: any): any { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 0b6ee1deeb..9275adcb6a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -909,6 +909,7 @@ "edit-alarm-rule": "Edit alarm rule", "alarm-type": "Alarm type", "alarm-type-required": "Alarm type is required.", + "alarm-type-unique": "Alarm type needs to be unique.", "alarm-type-pattern-hint": "Alarm type pattern, use ${metaKeyName} to substitute variables from metadata", "create-alarm-pattern": "Create {{alarmType}} alarm", "create-alarm-rules": "Create alarm rules", From 9c14088cade430bcea819253d3093f0db2cbe159 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 16:22:57 +0200 Subject: [PATCH 28/84] added # filter topic handling --- .../transport/mqtt/util/MqttTopicFilterFactory.java | 13 +++++++++---- .../mqtt/util/MqttTopicFilterFactoryTest.java | 11 ++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index 4d5a9a7c2b..9893f8cbef 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -34,10 +34,15 @@ public class MqttTopicFilterFactory { } return filters.computeIfAbsent(topicFilter, filter -> { if (filter.contains("+") || filter.contains("#")) { - String regex = filter - .replace("\\", "\\\\") - .replace("+", "[^/]+") - .replace("/#", "($|/.*)"); + String regex; + if (filter.equals("#")) { + regex = filter.replace("#", "^(?!/).+"); + } else { + regex = filter + .replace("\\", "\\\\") + .replace("+", "[^/]+") + .replace("/#", "($|/.*)"); + } log.debug("Converting [{}] to [{}]", filter, regex); return new RegexTopicFilter(regex); } else { diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java index 0b854d51ef..f3a65bda14 100644 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java @@ -20,7 +20,6 @@ import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; import javax.script.ScriptException; -import java.util.regex.Pattern; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -32,6 +31,9 @@ public class MqttTopicFilterFactoryTest { private static String TEST_STR_2 = "Sensor/Temperature"; private static String TEST_STR_3 = "Sensor/Temperature2/House/48"; + private static String TEST_STR_4 = String.format("%s%n%s", "/Sensor/Temperature", "/House/48"); + private static String TEST_STR_5 = "/" + TEST_STR_1; + @Test public void metadataCanBeUpdated() throws ScriptException { MqttTopicFilter filter = MqttTopicFilterFactory.toFilter("Sensor/Temperature/House/+"); @@ -51,6 +53,13 @@ public class MqttTopicFilterFactoryTest { assertTrue(filter.filter(TEST_STR_1)); assertTrue(filter.filter(TEST_STR_2)); assertFalse(filter.filter(TEST_STR_3)); + + filter = MqttTopicFilterFactory.toFilter("#"); + assertTrue(filter.filter(TEST_STR_1)); + assertTrue(filter.filter(TEST_STR_2)); + assertTrue(filter.filter(TEST_STR_3)); + assertFalse(filter.filter(TEST_STR_4)); + assertFalse(filter.filter(TEST_STR_5)); } } From fde0e7824de10157f38f40e2d10bc535978e49c2 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 19:31:21 +0200 Subject: [PATCH 29/84] change regex for # filter --- .../transport/mqtt/util/MqttTopicFilterFactory.java | 2 +- .../mqtt/util/MqttTopicFilterFactoryTest.java | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index 9893f8cbef..98e472ba5c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -36,7 +36,7 @@ public class MqttTopicFilterFactory { if (filter.contains("+") || filter.contains("#")) { String regex; if (filter.equals("#")) { - regex = filter.replace("#", "^(?!/).+"); + regex = filter.replace("#", "\\S+"); } else { regex = filter .replace("\\", "\\\\") diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java index f3a65bda14..2ec05fac78 100644 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java @@ -30,9 +30,10 @@ public class MqttTopicFilterFactoryTest { private static String TEST_STR_1 = "Sensor/Temperature/House/48"; private static String TEST_STR_2 = "Sensor/Temperature"; private static String TEST_STR_3 = "Sensor/Temperature2/House/48"; - - private static String TEST_STR_4 = String.format("%s%n%s", "/Sensor/Temperature", "/House/48"); - private static String TEST_STR_5 = "/" + TEST_STR_1; + private static String TEST_STR_4 = "/Sensor/Temperature2/House/48"; + private static String TEST_STR_5 = String.format("%s%n%s", "/Sensor/Temperature", "/House/48"); + private static String TEST_STR_6 = ""; + private static String TEST_STR_7 = " "; @Test public void metadataCanBeUpdated() throws ScriptException { @@ -58,8 +59,10 @@ public class MqttTopicFilterFactoryTest { assertTrue(filter.filter(TEST_STR_1)); assertTrue(filter.filter(TEST_STR_2)); assertTrue(filter.filter(TEST_STR_3)); - assertFalse(filter.filter(TEST_STR_4)); + assertTrue(filter.filter(TEST_STR_4)); assertFalse(filter.filter(TEST_STR_5)); + assertFalse(filter.filter(TEST_STR_6)); + assertFalse(filter.filter(TEST_STR_7)); } } From 7482d73a9ba47954860c02ae206294e01afc9a27 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 16:52:24 +0200 Subject: [PATCH 30/84] added AlwaysTrueTopicFilter --- .../mqtt/util/AlwaysTrueTopicFilter.java | 27 +++++++++++++++++++ .../mqtt/util/MqttTopicFilterFactory.java | 2 +- .../mqtt/util/MqttTopicFilterFactoryTest.java | 13 ++++----- 3 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java new file mode 100644 index 0000000000..9952c4b507 --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java @@ -0,0 +1,27 @@ +/** + * 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 lombok.Data; + +@Data +public class AlwaysTrueTopicFilter implements MqttTopicFilter { + + @Override + public boolean filter(String topic) { + return true; + } +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index 98e472ba5c..f2b4ef27e2 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -36,7 +36,7 @@ public class MqttTopicFilterFactory { if (filter.contains("+") || filter.contains("#")) { String regex; if (filter.equals("#")) { - regex = filter.replace("#", "\\S+"); + return new AlwaysTrueTopicFilter(); } else { regex = filter .replace("\\", "\\\\") diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java index 2ec05fac78..fac2e5c01d 100644 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java @@ -31,9 +31,8 @@ public class MqttTopicFilterFactoryTest { private static String TEST_STR_2 = "Sensor/Temperature"; private static String TEST_STR_3 = "Sensor/Temperature2/House/48"; private static String TEST_STR_4 = "/Sensor/Temperature2/House/48"; - private static String TEST_STR_5 = String.format("%s%n%s", "/Sensor/Temperature", "/House/48"); - private static String TEST_STR_6 = ""; - private static String TEST_STR_7 = " "; + private static String TEST_STR_5 = "Sensor/ Temperature"; + private static String TEST_STR_6 = "/"; @Test public void metadataCanBeUpdated() throws ScriptException { @@ -60,9 +59,11 @@ public class MqttTopicFilterFactoryTest { assertTrue(filter.filter(TEST_STR_2)); assertTrue(filter.filter(TEST_STR_3)); assertTrue(filter.filter(TEST_STR_4)); - assertFalse(filter.filter(TEST_STR_5)); - assertFalse(filter.filter(TEST_STR_6)); - assertFalse(filter.filter(TEST_STR_7)); + assertTrue(filter.filter(TEST_STR_5)); + assertTrue(filter.filter(TEST_STR_6)); + + filter = MqttTopicFilterFactory.toFilter("Sensor/Temperature#"); + assertFalse(filter.filter(TEST_STR_2)); } } From 13a731ecb38203d4b17b187519dd98fb93c755cb Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 16:55:24 +0200 Subject: [PATCH 31/84] fix MqttTopicFilterFactory toFilter --- .../mqtt/util/MqttTopicFilterFactory.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index f2b4ef27e2..0c3b497591 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -33,16 +33,13 @@ public class MqttTopicFilterFactory { throw new IllegalArgumentException("Topic filter can't be empty!"); } return filters.computeIfAbsent(topicFilter, filter -> { - if (filter.contains("+") || filter.contains("#")) { - String regex; - if (filter.equals("#")) { - return new AlwaysTrueTopicFilter(); - } else { - regex = filter - .replace("\\", "\\\\") - .replace("+", "[^/]+") - .replace("/#", "($|/.*)"); - } + if (filter.equals("#")) { + return new AlwaysTrueTopicFilter(); + } else if (filter.contains("+") || filter.contains("#")) { + String regex = filter + .replace("\\", "\\\\") + .replace("+", "[^/]+") + .replace("/#", "($|/.*)"); log.debug("Converting [{}] to [{}]", filter, regex); return new RegexTopicFilter(regex); } else { From 7dc793e777a623c682f68bde03637de898cc22ed Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 16 Nov 2020 17:12:27 +0200 Subject: [PATCH 32/84] UI: Change translate --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 9275adcb6a..27e504d6ee 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -909,7 +909,7 @@ "edit-alarm-rule": "Edit alarm rule", "alarm-type": "Alarm type", "alarm-type-required": "Alarm type is required.", - "alarm-type-unique": "Alarm type needs to be unique.", + "alarm-type-unique": "Alarm type must be unique within the device profile alarm rules.", "alarm-type-pattern-hint": "Alarm type pattern, use ${metaKeyName} to substitute variables from metadata", "create-alarm-pattern": "Create {{alarmType}} alarm", "create-alarm-rules": "Create alarm rules", From 706fd01d7619dc5c3d4802e57cf2f6af348e8293 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 16 Nov 2020 17:18:24 +0200 Subject: [PATCH 33/84] Device Profile Proto Improvement --- .../server/mqtt/MqttSqlTestSuite.java | 12 ++--- ...AbstractMqttAttributesIntegrationTest.java | 1 - ...tMqttAttributesRequestIntegrationTest.java | 2 +- ...AttributesRequestProtoIntegrationTest.java | 4 +- ...ttDeviceProfileTransportConfiguration.java | 9 ++++ .../ProtoTransportPayloadConfiguration.java | 3 +- .../mqtt/session/DeviceSessionCtx.java | 2 +- .../transport/adaptor/JsonConverter.java | 51 ++++--------------- .../dao/device/DeviceProfileServiceImpl.java | 25 ++++----- ...ofile-transport-configuration.component.ts | 2 +- 10 files changed, 46 insertions(+), 65 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index f71e8f37a2..ec47db14f1 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -26,13 +26,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.mqtt.rpc.sql.*Test", - "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test", - "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test", - "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", +// "org.thingsboard.server.mqtt.rpc.sql.*Test", +// "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test", +// "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test", +// "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", "org.thingsboard.server.mqtt.attributes.request.sql.*Test", - "org.thingsboard.server.mqtt.claim.sql.*Test", - "org.thingsboard.server.mqtt.provision.sql.*Test" +// "org.thingsboard.server.mqtt.claim.sql.*Test", +// "org.thingsboard.server.mqtt.provision.sql.*Test" }) public class MqttSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java index 32488c8eb0..d1b0c482f8 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java @@ -56,7 +56,6 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt return tsKvProtoList; } - protected TransportProtos.TsKvProto getTsKvProto(String key, String value, TransportProtos.KeyValueType keyValueType) { TransportProtos.TsKvProto.Builder tsKvProtoBuilder = TransportProtos.TsKvProto.newBuilder(); TransportProtos.KeyValueProto keyValueProto = getKeyValueProto(key, value, keyValueType); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java index 51d38e6312..2f33b8dcf8 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java @@ -122,7 +122,7 @@ public abstract class AbstractMqttAttributesRequestIntegrationTest extends Abstr client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage); latch.await(3, TimeUnit.SECONDS); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); - String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}"; + String expectedRequestPayload = "{\"client\":{\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}},\"attribute4\":73,\"attribute1\":\"value1\",\"attribute3\":42.0,\"attribute2\":true},\"shared\":{\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}},\"attribute4\":73,\"attribute1\":\"value1\",\"attribute3\":42.0,\"attribute2\":true}}"; assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8))); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 0ea53d2801..2df69c5fc4 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -72,11 +72,11 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends @Test public void testRequestAttributesValuesFromTheServer() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, DeviceProfileProvisionType.DISABLED, null, null); + super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", + TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, DeviceProfileProvisionType.DISABLED, null, null); processTestRequestAttributesValuesFromTheServer(); } - @Test public void testRequestAttributesValuesFromTheServerGateway() throws Exception { super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 6a5c3c474c..c49b7bd356 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -30,4 +30,13 @@ public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTra return DeviceTransportType.MQTT; } + public TransportPayloadTypeConfiguration getTransportPayloadTypeConfiguration() { + if (transportPayloadTypeConfiguration != null) { + return transportPayloadTypeConfiguration; + } else { + return new JsonTransportPayloadConfiguration(); + } + } + + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java index c5bdf28833..30bc7b9237 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.device.profile; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.github.os72.protobuf.dynamic.DynamicSchema; import com.github.os72.protobuf.dynamic.EnumDefinition; import com.github.os72.protobuf.dynamic.MessageDefinition; @@ -135,7 +136,7 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); } if (!oneOfs.isEmpty()) { - for (OneOfElement oneOfelement: oneOfs) { + for (OneOfElement oneOfelement : oneOfs) { MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index fb4512a59b..b45dc5add5 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -123,7 +123,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { payloadType = transportPayloadTypeConfiguration.getTransportPayloadType(); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); - if (transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration) { + if (TransportPayloadType.PROTOBUF.equals(payloadType)) { updateDynamicMessageDescriptors(transportPayloadTypeConfiguration); } } else { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 65467c69a5..63c7277ac2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -19,7 +19,6 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; @@ -194,62 +193,34 @@ public class JsonConverter { String message = String.format("String value length [%d] for key [%s] is greater than maximum allowed [%d]", value.getAsString().length(), valueEntry.getKey(), maxStringValueLength); throw new JsonSyntaxException(message); } - if (isTypeCastEnabled) { - if (NumberUtils.isParsable(value.getAsString())) { - try { - result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); - } catch (RuntimeException th) { - result.add(buildStringKVProto(valueEntry, value)); - } - } else { - try { - JsonElement jsonElement = JSON_PARSER.parse(value.getAsString()); - if (jsonElement.isJsonObject() || jsonElement.isJsonArray()) { - result.add(buildJsonKVProto(valueEntry, jsonElement)); - } else { - result.add(buildStringKVProto(valueEntry, value)); - } - } catch (JsonParseException e) { - result.add(buildStringKVProto(valueEntry, value)); - } + if (isTypeCastEnabled && NumberUtils.isParsable(value.getAsString())) { + try { + result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); + } catch (RuntimeException th) { + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) + .setStringV(value.getAsString()).build()); } } else { - result.add(buildStringKVProto(valueEntry, value)); + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) + .setStringV(value.getAsString()).build()); } } else if (value.isBoolean()) { result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.BOOLEAN_V) .setBoolV(value.getAsBoolean()).build()); } else if (value.isNumber()) { result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); - } else { + } else if (!value.isJsonNull()) { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); } } else if (element.isJsonObject() || element.isJsonArray()) { - result.add(buildJsonKVProto(valueEntry, element)); - } else { + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.JSON_V).setJsonV(element.toString()).build()); + } else if (!element.isJsonNull()) { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); } } return result; } - private static KeyValueProto buildStringKVProto(Entry valueEntry, JsonPrimitive value) { - return KeyValueProto.newBuilder() - .setKey(valueEntry.getKey()) - .setType(KeyValueType.STRING_V) - .setStringV(value.getAsString()) - .build(); - } - - private static KeyValueProto buildJsonKVProto(Entry valueEntry, JsonElement jsonElement) { - return KeyValueProto - .newBuilder() - .setKey(valueEntry.getKey()) - .setType(KeyValueType.JSON_V) - .setJsonV(jsonElement.toString()) - .build(); - } - private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) { if (value.getAsString().contains(".")) { return KeyValueProto.newBuilder() diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index d1a7820df4..09da415a84 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -331,18 +331,19 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D if (defaultDeviceProfile != null && !defaultDeviceProfile.getId().equals(deviceProfile.getId())) { throw new DataValidationException("Another default device profile is present in scope of current tenant!"); } - } else { - DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - 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()); - } + } + + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + if (mqttTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration = + (ProtoTransportPayloadConfiguration) mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + try { + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA); + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA); + } catch (Exception exception) { + throw new DataValidationException(exception.getMessage()); } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 35feb53545..a5d4ced861 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -153,7 +153,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control const defaultAttributesSchema = "syntax =\"proto3\";\n" + "package attributes;\n" + "\n" + - "message SensorDataReading {\n" + + "message SensorConfiguration {\n" + " string firmwareVersion = 1;\n" + " string serialNumber = 2;\n" + "}"; From aa61db22f2ec63efe3c167b0a012978bc95690ba Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 16 Nov 2020 18:20:23 +0200 Subject: [PATCH 34/84] added validations for DeviceProfileAlarm type --- .../dao/device/DeviceProfileServiceImpl.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index f3857cacd5..60be6137ce 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -23,16 +23,17 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Tenant; 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.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -48,7 +49,9 @@ import org.thingsboard.server.dao.tenant.TenantDao; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -115,7 +118,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_profile_name_unq_key")) { throw new DataValidationException("Device profile with such name already exists!"); } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_provision_key_unq_key")) { - throw new DataValidationException("Device profile with such provision device key already exists!"); + throw new DataValidationException("Device profile with such provision device key already exists!"); } else { throw t; } @@ -311,6 +314,21 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D throw new DataValidationException("Another default device profile is present in scope of current tenant!"); } } + List profileAlarms = deviceProfile.getProfileData().getAlarms(); + + if (!CollectionUtils.isEmpty(profileAlarms)) { + Set alarmTypes = new HashSet<>(); + for (DeviceProfileAlarm alarm : profileAlarms) { + String alarmType = alarm.getAlarmType(); + if (StringUtils.isEmpty(alarmType)) { + throw new DataValidationException("Alarm rule type should be specified!"); + } + if (!alarmTypes.add(alarmType)) { + throw new DataValidationException(String.format("Can't create device profile with the same alarm rule types: \"%s\"!", alarmType)); + } + } + + } } @Override From 3c5bce7d20509113efd5ae304eb298ea85016d71 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 16 Nov 2020 17:44:11 +0200 Subject: [PATCH 35/84] Updated thermostat demo --- .../json/demo/dashboards/thermostats.json | 88 +-------- .../demo/rule_chains/thermostat_alarms.json | 137 +++++++------ .../DefaultSystemDataLoaderService.java | 182 +++++++++++++++--- 3 files changed, 234 insertions(+), 173 deletions(-) diff --git a/application/src/main/data/json/demo/dashboards/thermostats.json b/application/src/main/data/json/demo/dashboards/thermostats.json index 06edbcbef0..07b542c3aa 100644 --- a/application/src/main/data/json/demo/dashboards/thermostats.json +++ b/application/src/main/data/json/demo/dashboards/thermostats.json @@ -219,8 +219,8 @@ "defaultPageSize": 10, "defaultSortOrder": "-createdTime", "enableSelectColumnDisplay": false, - "enableStatusFilter": true, - "alarmsTitle": "Alarms" + "alarmsTitle": "Alarms", + "enableFilter": true }, "title": "New Alarms table", "dropShadow": true, @@ -234,6 +234,9 @@ "showLegend": false, "alarmSource": { "type": "entity", + "name": "alarms", + "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e", + "filterId": null, "dataKeys": [ { "name": "createdTime", @@ -275,9 +278,7 @@ "settings": {}, "_hash": 0.7977920750136249 } - ], - "entityAliasId": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813", - "name": "alarms" + ] }, "alarmSearchStatus": "ANY", "alarmsPollingInterval": 5, @@ -1031,7 +1032,8 @@ "markerImageFunction": "var res;\nif(dsData[dsIndex].active !== \"true\"){\n\tvar res = {\n\t url: images[0],\n\t size: 48\n\t}\n} else {\n var res = {\n\t url: images[1],\n\t size: 48\n\t}\n}\nreturn res;", "useLabelFunction": true, "provider": "openstreet-map", - "draggableMarker": true + "draggableMarker": true, + "editablePolygon": true }, "title": "New Markers Placement - OpenStreetMap", "dropShadow": true, @@ -1062,61 +1064,6 @@ "displayTimewindow": true }, "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf" - }, - "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": { - "isSystemType": true, - "bundleAlias": "input_widgets", - "typeAlias": "update_double_timeseries", - "type": "latest", - "title": "New widget", - "sizeX": 7.5, - "sizeY": 3, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547", - "dataKeys": [ - { - "name": "temperature", - "type": "timeseries", - "label": "temperature", - "color": "#2196f3", - "settings": {}, - "_hash": 0.4164505192982848 - } - ] - } - ], - "timewindow": { - "realtime": { - "timewindowMs": 60000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "showResultMessage": true, - "showLabel": true - }, - "title": "New Update double timeseries", - "dropShadow": true, - "enableFullscreen": false, - "widgetStyle": {}, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "useDashboardTimewindow": true, - "showLegend": false, - "actions": {} - }, - "row": 0, - "col": 0, - "id": "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3" } }, "states": { @@ -1215,12 +1162,6 @@ "sizeY": 6, "row": 6, "col": 0 - }, - "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": { - "sizeX": 7.5, - "sizeY": 3, - "row": 12, - "col": 0 } }, "gridSettings": { @@ -1257,16 +1198,6 @@ "stateEntityParamName": null, "defaultStateEntity": null } - }, - "ce27a9d0-93bf-b7a4-054d-d0369a8cf813": { - "id": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813", - "alias": "Thermostat-alarm", - "filter": { - "type": "entityName", - "resolveMultiple": false, - "entityType": "ASSET", - "entityNameFilter": "Thermostat Alarms" - } } }, "timewindow": { @@ -1301,7 +1232,8 @@ "showDashboardTimewindow": true, "showDashboardExport": true, "toolbarAlwaysOpen": true - } + }, + "filters": {} }, "name": "Thermostats" } \ No newline at end of file diff --git a/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json b/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json index d67052cbc5..30812d6003 100644 --- a/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json +++ b/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json @@ -1,6 +1,8 @@ { "ruleChain": { - "additionalInfo": null, + "additionalInfo": { + "description": "" + }, "name": "Thermostat Alarms", "firstRuleNodeId": null, "root": false, @@ -8,131 +10,126 @@ "configuration": null }, "metadata": { - "firstNodeIndex": 5, + "firstNodeIndex": 6, "nodes": [ { "additionalInfo": { - "layoutX": 929, - "layoutY": 67 + "layoutX": 822, + "layoutY": 294 }, - "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode", - "name": "Create Temp Alarm", + "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", + "name": "Save Timeseries", "debugMode": false, "configuration": { - "alarmType": "High Temperature", - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.temperature;\nreturn details;", - "severity": "MAJOR", - "propagate": true, - "useMessageAlarmData": false, - "relationTypes": [ - "ToAlarmPropagationAsset" - ] + "defaultTTL": 0 } }, { "additionalInfo": { - "layoutX": 930, - "layoutY": 201 + "description": null, + "layoutX": 824, + "layoutY": 221 }, - "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode", - "name": "Clear Temp Alarm", + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", + "name": "Save Client Attributes", "debugMode": false, "configuration": { - "alarmType": "High Temperature", - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;" + "scope": "SERVER_SCOPE", + "notifyDevice": null } }, { "additionalInfo": { - "layoutX": 930, - "layoutY": 131 + "layoutX": 494, + "layoutY": 309 }, - "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode", - "name": "Create Humidity Alarm", + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", + "name": "Message Type Switch", "debugMode": false, "configuration": { - "alarmType": "Low Humidity", - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.humidity;\nreturn details;", - "severity": "MINOR", - "propagate": true, - "useMessageAlarmData": false, - "relationTypes": [ - "ToAlarmPropagationAsset" - ] + "version": 0 } }, { "additionalInfo": { - "layoutX": 929, - "layoutY": 275 + "layoutX": 824, + "layoutY": 383 }, - "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode", - "name": "Clear Humidity Alarm", + "type": "org.thingsboard.rule.engine.action.TbLogNode", + "name": "Log RPC from Device", "debugMode": false, "configuration": { - "alarmType": "Low Humidity", - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;" + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { "additionalInfo": { - "layoutX": 586, - "layoutY": 148 + "layoutX": 823, + "layoutY": 444 }, - "type": "org.thingsboard.rule.engine.filter.TbJsSwitchNode", - "name": "Check Alarms", + "type": "org.thingsboard.rule.engine.action.TbLogNode", + "name": "Log Other", "debugMode": false, "configuration": { - "jsScript": "var relations = [];\nif(metadata[\"ss_alarmTemperature\"] === \"true\"){\n if(msg.temperature > metadata[\"ss_thresholdTemperature\"]){\n relations.push(\"NewTempAlarm\");\n } else {\n relations.push(\"ClearTempAlarm\");\n }\n}\nif(metadata[\"ss_alarmHumidity\"] === \"true\"){\n if(msg.humidity < metadata[\"ss_thresholdHumidity\"]){\n relations.push(\"NewHumidityAlarm\");\n } else {\n relations.push(\"ClearHumidityAlarm\");\n }\n}\n\nreturn relations;" + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { "additionalInfo": { - "layoutX": 321, - "layoutY": 149 + "layoutX": 822, + "layoutY": 507 }, - "type": "org.thingsboard.rule.engine.metadata.TbGetAttributesNode", - "name": "Fetch Configuration", + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", + "name": "RPC Call Request", "debugMode": false, "configuration": { - "clientAttributeNames": [], - "sharedAttributeNames": [], - "serverAttributeNames": [ - "alarmTemperature", - "thresholdTemperature", - "alarmHumidity", - "thresholdHumidity" - ], - "latestTsKeyNames": [], - "tellFailureIfAbsent": false, - "getLatestValueWithTs": false + "timeoutInSeconds": 60 + } + }, + { + "additionalInfo": { + "description": "", + "layoutX": 209, + "layoutY": 307 + }, + "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", + "name": "Device Profile Node", + "debugMode": false, + "configuration": { + "persistAlarmRulesState": false, + "fetchAlarmRulesStateOnStart": false } } ], "connections": [ { - "fromIndex": 4, - "toIndex": 0, - "type": "NewTempAlarm" + "fromIndex": 2, + "toIndex": 4, + "type": "Other" }, { - "fromIndex": 4, + "fromIndex": 2, "toIndex": 1, - "type": "ClearTempAlarm" + "type": "Post attributes" }, { - "fromIndex": 4, - "toIndex": 2, - "type": "NewHumidityAlarm" + "fromIndex": 2, + "toIndex": 0, + "type": "Post telemetry" }, { - "fromIndex": 4, + "fromIndex": 2, "toIndex": 3, - "type": "ClearHumidityAlarm" + "type": "RPC Request from Device" }, { - "fromIndex": 5, - "toIndex": 4, + "fromIndex": 2, + "toIndex": 5, + "type": "RPC Request to Device" + }, + { + "fromIndex": 6, + "toIndex": 2, "type": "Success" } ], diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 6c7678f023..ef25eeefdb 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -28,12 +28,21 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +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.TenantProfile; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.device.profile.AlarmCondition; +import org.thingsboard.server.common.data.device.profile.AlarmRule; +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.DeviceProfileAlarm; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -42,19 +51,29 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; -import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.query.BooleanFilterPredicate; +import org.thingsboard.server.common.data.query.DynamicValue; +import org.thingsboard.server.common.data.query.DynamicValueSourceType; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.EntityKeyValueType; +import org.thingsboard.server.common.data.query.FilterPredicateValue; +import org.thingsboard.server.common.data.query.KeyFilter; +import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; @@ -62,6 +81,8 @@ import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; @Service @Profile("install") @@ -96,12 +117,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private CustomerService customerService; - @Autowired - private RelationService relationService; - - @Autowired - private AssetService assetService; - @Autowired private DeviceService deviceService; @@ -114,6 +129,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private DeviceCredentialsService deviceCredentialsService; + @Autowired + private RuleChainService ruleChainService; + @Bean protected BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -245,19 +263,133 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " + "Raspberry Pi GPIO control sample application"); - Asset thermostatAlarms = new Asset(); - thermostatAlarms.setTenantId(demoTenant.getId()); - thermostatAlarms.setName("Thermostat Alarms"); - thermostatAlarms.setType("AlarmPropagationAsset"); - thermostatAlarms = assetService.saveAsset(thermostatAlarms); - - DeviceProfile thermostatDeviceProfile = this.deviceProfileService.findOrCreateDeviceProfile(demoTenant.getId(), "thermostat"); - - DeviceId t1Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); - DeviceId t2Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); - - relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t1Id, "ToAlarmPropagationAsset")); - relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t2Id, "ToAlarmPropagationAsset")); + DeviceProfile thermostatDeviceProfile = new DeviceProfile(); + thermostatDeviceProfile.setTenantId(demoTenant.getId()); + thermostatDeviceProfile.setDefault(false); + thermostatDeviceProfile.setName("thermostat"); + thermostatDeviceProfile.setType(DeviceProfileType.DEFAULT); + thermostatDeviceProfile.setTransportType(DeviceTransportType.DEFAULT); + thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); + thermostatDeviceProfile.setDescription("Thermostat device profile"); + thermostatDeviceProfile.setDefaultRuleChainId(ruleChainService.findTenantRuleChains( + demoTenant.getId(), new PageLink(1, 0, "Thermostat Alarms")).getData().get(0).getId()); + + DeviceProfileData deviceProfileData = new DeviceProfileData(); + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); + DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration(); + DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null); + deviceProfileData.setConfiguration(configuration); + deviceProfileData.setTransportConfiguration(transportConfiguration); + deviceProfileData.setProvisionConfiguration(provisionConfiguration); + thermostatDeviceProfile.setProfileData(deviceProfileData); + + DeviceProfileAlarm highTemperature = new DeviceProfileAlarm(); + highTemperature.setId("highTemperatureAlarmID"); + highTemperature.setAlarmType("High Temperature"); + AlarmRule temperatureRule = new AlarmRule(); + AlarmCondition temperatureCondition = new AlarmCondition(); + temperatureCondition.setSpec(new SimpleAlarmConditionSpec()); + + KeyFilter alarmTemperatureAttributeFilter = new KeyFilter(); + alarmTemperatureAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmTemperature")); + alarmTemperatureAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); + BooleanFilterPredicate alarmTemperatureAttributePredicate = new BooleanFilterPredicate(); + alarmTemperatureAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); + alarmTemperatureAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); + alarmTemperatureAttributeFilter.setPredicate(alarmTemperatureAttributePredicate); + + KeyFilter temperatureTimeseriesFilter = new KeyFilter(); + temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); + temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate(); + temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + FilterPredicateValue temperatureTimeseriesPredicateValue = + new FilterPredicateValue<>(25.0, null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature")); + temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue); + temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate); + temperatureCondition.setCondition(Arrays.asList(alarmTemperatureAttributeFilter, temperatureTimeseriesFilter)); + temperatureRule.setAlarmDetails("Current temperature = ${temperature}"); + temperatureRule.setCondition(temperatureCondition); + highTemperature.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule))); + + AlarmRule clearTemperatureRule = new AlarmRule(); + AlarmCondition clearTemperatureCondition = new AlarmCondition(); + clearTemperatureCondition.setSpec(new SimpleAlarmConditionSpec()); + + KeyFilter clearTemperatureTimeseriesFilter = new KeyFilter(); + clearTemperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); + clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate(); + clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL); + FilterPredicateValue clearTemperatureTimeseriesPredicateValue = + new FilterPredicateValue<>(25.0, null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature")); + + clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue); + clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate); + clearTemperatureCondition.setCondition(Collections.singletonList(clearTemperatureTimeseriesFilter)); + clearTemperatureRule.setCondition(clearTemperatureCondition); + clearTemperatureRule.setAlarmDetails("Current temperature = ${temperature}"); + highTemperature.setClearRule(clearTemperatureRule); + + DeviceProfileAlarm lowHumidity = new DeviceProfileAlarm(); + lowHumidity.setId("lowHumidityAlarmID"); + lowHumidity.setAlarmType("Low Humidity"); + AlarmRule humidityRule = new AlarmRule(); + AlarmCondition humidityCondition = new AlarmCondition(); + humidityCondition.setSpec(new SimpleAlarmConditionSpec()); + + KeyFilter alarmHumidityAttributeFilter = new KeyFilter(); + alarmHumidityAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmHumidity")); + alarmHumidityAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); + BooleanFilterPredicate alarmHumidityAttributePredicate = new BooleanFilterPredicate(); + alarmHumidityAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); + alarmHumidityAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); + alarmHumidityAttributeFilter.setPredicate(alarmHumidityAttributePredicate); + + KeyFilter humidityTimeseriesFilter = new KeyFilter(); + humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); + humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate(); + humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); + FilterPredicateValue humidityTimeseriesPredicateValue = + new FilterPredicateValue<>(60.0, null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity")); + humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue); + humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate); + humidityCondition.setCondition(Arrays.asList(alarmHumidityAttributeFilter, humidityTimeseriesFilter)); + + humidityRule.setCondition(humidityCondition); + humidityRule.setAlarmDetails("Current humidity = ${humidity}"); + lowHumidity.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule))); + + AlarmRule clearHumidityRule = new AlarmRule(); + AlarmCondition clearHumidityCondition = new AlarmCondition(); + clearHumidityCondition.setSpec(new SimpleAlarmConditionSpec()); + + KeyFilter clearHumidityTimeseriesFilter = new KeyFilter(); + clearHumidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); + clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate(); + clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL); + FilterPredicateValue clearHumidityTimeseriesPredicateValue = + new FilterPredicateValue<>(60.0, null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity")); + + clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue); + clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate); + clearHumidityCondition.setCondition(Collections.singletonList(clearHumidityTimeseriesFilter)); + clearHumidityRule.setCondition(clearHumidityCondition); + clearHumidityRule.setAlarmDetails("Current humidity = ${humidity}"); + lowHumidity.setClearRule(clearHumidityRule); + + deviceProfileData.setAlarms(Arrays.asList(highTemperature, lowHumidity)); + + DeviceProfile savedThermostatDeviceProfile = deviceProfileService.saveDeviceProfile(thermostatDeviceProfile); + + DeviceId t1Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); + DeviceId t2Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE, Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)), From eecc6aa1a69a9b558c76dd59399df68ebbc49d39 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 19:08:55 +0200 Subject: [PATCH 36/84] fix dynamic schemas, tests --- .../mqtt/AbstractMqttIntegrationTest.java | 28 ++++++-- .../server/mqtt/MqttSqlTestSuite.java | 12 ++-- ...tMqttAttributesRequestIntegrationTest.java | 2 +- ...AttributesRequestProtoIntegrationTest.java | 30 ++++++++- ...AbstractMqttAttributesIntegrationTest.java | 14 ++-- ...actMqttAttributesProtoIntegrationTest.java | 19 +++++- ...actMqttTimeseriesProtoIntegrationTest.java | 66 +++++++++++++++---- .../ProtoTransportPayloadConfiguration.java | 22 +++---- 8 files changed, 145 insertions(+), 48 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index 715ffeade3..a1e4bc0e63 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -62,19 +62,28 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest private static final AtomicInteger atomicInteger = new AtomicInteger(2); - protected static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + public static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax =\"proto3\";\n" + "\n" + "package test;\n" + - " \n" + + "\n" + "message PostTelemetry {\n" + " string key1 = 1;\n" + " bool key2 = 2;\n" + " double key3 = 3;\n" + " int32 key4 = 4;\n" + - " string key5 = 5;\n" + + " JsonObject key5 = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + "}"; - protected static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + public static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax =\"proto3\";\n" + "\n" + "package test;\n" + "\n" + @@ -83,7 +92,16 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest " bool key2 = 2;\n" + " double key3 = 3;\n" + " int32 key4 = 4;\n" + - " string key5 = 5;\n" + + " JsonObject key5 = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + "}"; protected Tenant savedTenant; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index ec47db14f1..f71e8f37a2 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -26,13 +26,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ -// "org.thingsboard.server.mqtt.rpc.sql.*Test", -// "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test", -// "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test", -// "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", + "org.thingsboard.server.mqtt.rpc.sql.*Test", + "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test", + "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test", + "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", "org.thingsboard.server.mqtt.attributes.request.sql.*Test", -// "org.thingsboard.server.mqtt.claim.sql.*Test", -// "org.thingsboard.server.mqtt.provision.sql.*Test" + "org.thingsboard.server.mqtt.claim.sql.*Test", + "org.thingsboard.server.mqtt.provision.sql.*Test" }) public class MqttSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java index 2f33b8dcf8..51d38e6312 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java @@ -122,7 +122,7 @@ public abstract class AbstractMqttAttributesRequestIntegrationTest extends Abstr client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage); latch.await(3, TimeUnit.SECONDS); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); - String expectedRequestPayload = "{\"client\":{\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}},\"attribute4\":73,\"attribute1\":\"value1\",\"attribute3\":42.0,\"attribute2\":true},\"shared\":{\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}},\"attribute4\":73,\"attribute1\":\"value1\",\"attribute3\":42.0,\"attribute2\":true}}"; + String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}"; assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8))); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 2df69c5fc4..9e112c98be 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -62,7 +62,16 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends " bool attribute2 = 2;\n" + " double attribute3 = 3;\n" + " int32 attribute4 = 4;\n" + - " string attribute5 = 5;\n" + + " JsonObject attribute5 = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + "}"; @After @@ -93,6 +102,23 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + + DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); assertNotNull(postAttributesMsgDescriptor); @@ -101,7 +127,7 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends .setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true) .setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0) .setField(postAttributesMsgDescriptor.findFieldByName("attribute4"), 73) - .setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") + .setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), jsonObject) .build(); byte[] payload = postAttributesMsg.toByteArray(); client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(payload)); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java index 308983f070..0c8e963d1b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java @@ -163,16 +163,10 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt break; case "key5": assertNotNull(value); - LinkedHashMap valueMap; - if (value instanceof String) { - valueMap = mapper.readValue((String) value, LinkedHashMap.class); - } else { - valueMap = (LinkedHashMap) value; - } - assertEquals(3, valueMap.size()); - assertEquals(42, valueMap.get("someNumber")); - assertEquals(Arrays.asList(1, 2, 3), valueMap.get("someArray")); - LinkedHashMap someNestedObject = (LinkedHashMap) valueMap.get("someNestedObject"); + assertEquals(3, ((LinkedHashMap) value).size()); + assertEquals(42, ((LinkedHashMap) value).get("someNumber")); + assertEquals(Arrays.asList(1, 2, 3), ((LinkedHashMap) value).get("someArray")); + LinkedHashMap someNestedObject = (LinkedHashMap) ((LinkedHashMap) value).get("someNestedObject"); assertEquals("value", someNestedObject.get("key")); break; } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java index 9cc7c66f55..1e14541d23 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java @@ -58,6 +58,23 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + + DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); assertNotNull(postAttributesMsgDescriptor); @@ -66,7 +83,7 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac .setField(postAttributesMsgDescriptor.findFieldByName("key2"), true) .setField(postAttributesMsgDescriptor.findFieldByName("key3"), 3.0) .setField(postAttributesMsgDescriptor.findFieldByName("key4"), 4) - .setField(postAttributesMsgDescriptor.findFieldByName("key5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") + .setField(postAttributesMsgDescriptor.findFieldByName("key5"), jsonObject) .build(); processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, postAttributesMsg.toByteArray()); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index b69b53856f..4cb2f0271c 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -62,6 +62,23 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + + DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); assertNotNull(postTelemetryMsgDescriptor); @@ -70,7 +87,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac .setField(postTelemetryMsgDescriptor.findFieldByName("key2"), true) .setField(postTelemetryMsgDescriptor.findFieldByName("key3"), 3.0) .setField(postTelemetryMsgDescriptor.findFieldByName("key4"), 4) - .setField(postTelemetryMsgDescriptor.findFieldByName("key5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") + .setField(postTelemetryMsgDescriptor.findFieldByName("key5"), jsonObject) .build(); processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, postTelemetryMsg.toByteArray(), false); } @@ -80,19 +97,27 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac String schemaStr = "syntax =\"proto3\";\n" + "\n" + "package test;\n" + - " \n" + - "message PostTelemetry {\n" + - "\n" + - " message Values {\n" + - " string key1 = 1;\n" + - " bool key2 = 2;\n" + - " double key3 = 3;\n" + - " int32 key4 = 4;\n" + - " string key5 = 5;\n" + - " }\n" + "\n" + + "message PostTelemetry {\n" + " int64 ts = 1;\n" + " Values values = 2;\n" + + " \n" + + " message Values {\n" + + " string key1 = 3;\n" + + " bool key2 = 4;\n" + + " double key3 = 5;\n" + + " int32 key4 = 6;\n" + + " JsonObject key5 = 7;\n" + + " }\n" + + " \n" + + " message JsonObject {\n" + + " int32 someNumber = 8;\n" + + " repeated int32 someArray = 9;\n" + + " NestedJsonObject someNestedObject = 10;\n" + + " message NestedJsonObject {\n" + + " string key = 11;\n" + + " }\n" + + " }\n" + "}"; super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); @@ -105,6 +130,23 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + + DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.Values"); Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType(); assertNotNull(valuesDescriptor); @@ -114,7 +156,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac .setField(valuesDescriptor.findFieldByName("key2"), true) .setField(valuesDescriptor.findFieldByName("key3"), 3.0) .setField(valuesDescriptor.findFieldByName("key4"), 4) - .setField(valuesDescriptor.findFieldByName("key5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") + .setField(valuesDescriptor.findFieldByName("key5"), jsonObject) .build(); DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java index 30bc7b9237..08c846d905 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java @@ -128,19 +128,8 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC List messageDefinitions = new ArrayList<>(); messageElementsList.forEach(messageElement -> { MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); - List messageElementFields = messageElement.getFields(); - List oneOfs = messageElement.getOneOfs(); List nestedTypes = messageElement.getNestedTypes(); - if (!messageElementFields.isEmpty()) { - addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); - } - if (!oneOfs.isEmpty()) { - for (OneOfElement oneOfelement : oneOfs) { - MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); - addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); - } - } if (!nestedTypes.isEmpty()) { List nestedEnumTypes = getEnumElements(nestedTypes); if (!nestedEnumTypes.isEmpty()) { @@ -153,6 +142,17 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC List nestedMessageDefinitions = getMessageDefinitions(nestedMessageTypes); nestedMessageDefinitions.forEach(messageDefinitionBuilder::addMessageDefinition); } + List messageElementFields = messageElement.getFields(); + List oneOfs = messageElement.getOneOfs(); + if (!oneOfs.isEmpty()) { + for (OneOfElement oneOfelement : oneOfs) { + MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); + addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); + } + } + if (!messageElementFields.isEmpty()) { + addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); + } messageDefinitions.add(messageDefinitionBuilder.build()); }); return messageDefinitions; From be094fc62a7b0df444344d3c5796f61b57c1caf9 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 19:15:34 +0200 Subject: [PATCH 37/84] fix license --- .../server/dao/device/DeviceProfileServiceImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 6b54c7c60a..51de9a1bc9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. From 8046f7ef45365a5bf0703dcc62d11f3687e614bd Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 13 Nov 2020 16:04:15 +0200 Subject: [PATCH 38/84] Changed device provisioning response and added ability to provision devices without deviceName --- .../device/DeviceProvisionServiceImpl.java | 6 +++ .../transport/DefaultTransportApiService.java | 38 ++++++++++--------- .../AbstractMqttProvisionJsonDeviceTest.java | 27 ++++--------- .../AbstractMqttProvisionProtoDeviceTest.java | 35 ++++++----------- common/queue/src/main/proto/queue.proto | 5 ++- .../transport/adaptor/JsonConverter.java | 18 ++++----- 6 files changed, 57 insertions(+), 72 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index d372b878f7..583139f05c 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -188,6 +189,11 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); try { if (device == null) { + if (StringUtils.isEmpty(provisionRequest.getDeviceName())) { + String newDeviceName = RandomStringUtils.randomAlphanumeric(20); + log.info("Device name not found in provision request. Generated name is: {}", newDeviceName); + provisionRequest.setDeviceName(newDeviceName); + } Device savedDevice = deviceService.saveDevice(provisionRequest, profile); deviceStateService.onDeviceAdded(savedDevice); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 5ceb9b3cd6..6006e2269e 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -54,12 +54,15 @@ import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceCredentialsProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsgOrBuilder; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; @@ -292,30 +295,29 @@ public class DefaultTransportApiService implements TransportApiService { requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret())))); } catch (ProvisionFailedException e) { return Futures.immediateFuture(getTransportApiResponseMsg( - TransportProtos.DeviceCredentialsProto.getDefaultInstance(), + new DeviceCredentials(), TransportProtos.ProvisionResponseStatus.valueOf(e.getMessage()))); } - return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg( - getDeviceCredentials(provisionResponse.getDeviceCredentials()), TransportProtos.ProvisionResponseStatus.SUCCESS), + return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg(provisionResponse.getDeviceCredentials(), TransportProtos.ProvisionResponseStatus.SUCCESS), dbCallbackExecutorService); } - private TransportApiResponseMsg getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto deviceCredentials, TransportProtos.ProvisionResponseStatus status) { - return TransportApiResponseMsg.newBuilder() - .setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder() - .setDeviceCredentials(deviceCredentials) - .setProvisionResponseStatus(status) - .build()) - .build(); - } + private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ProvisionResponseStatus status) { + TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder() + .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name())) + .setStatus(status); + switch (deviceCredentials.getCredentialsType()){ + case ACCESS_TOKEN: + provisionResponse.setCredentialsValue(deviceCredentials.getCredentialsId()); + break; + case MQTT_BASIC: + case X509_CERTIFICATE: + provisionResponse.setCredentialsValue(deviceCredentials.getCredentialsValue()); + break; + } - private TransportProtos.DeviceCredentialsProto getDeviceCredentials(DeviceCredentials deviceCredentials) { - return TransportProtos.DeviceCredentialsProto.newBuilder() - .setDeviceIdMSB(deviceCredentials.getDeviceId().getId().getMostSignificantBits()) - .setDeviceIdLSB(deviceCredentials.getDeviceId().getId().getLeastSignificantBits()) - .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name())) - .setCredentialsId(deviceCredentials.getCredentialsId()) - .setCredentialsValue(deviceCredentials.getCredentialsValue() != null ? deviceCredentials.getCredentialsValue() : "") + return TransportApiResponseMsg.newBuilder() + .setProvisionDeviceResponseMsg(provisionResponse.build()) .build(); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java index 9f69df0307..a964ca29e3 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java @@ -98,7 +98,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").getAsString()); } @@ -110,13 +110,11 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } @@ -129,15 +127,13 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN"); - Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsValue(), "test_token"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } @@ -150,12 +146,10 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE"); String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); @@ -164,7 +158,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } @@ -177,12 +171,10 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "MQTT_BASIC"); Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username")); @@ -192,21 +184,18 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn mqttCredentials.setPassword("test_password"); Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials)); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - Assert.assertEquals(savedDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } protected void processTestProvisioningWithBadKeyDevice() throws Exception { @@ -214,7 +203,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").getAsString()); } protected TestMqttCallback createMqttClientAndPublish() throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index 76b27ffd23..9f74946cad 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -105,7 +105,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.DISABLED, null, null); ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertNotNull(result); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { @@ -115,13 +115,11 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { @@ -133,15 +131,13 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.ACCESS_TOKEN); Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { @@ -153,12 +149,10 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE); String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); @@ -167,7 +161,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { @@ -185,12 +179,10 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.MQTT_BASIC); Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username")); @@ -200,26 +192,23 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI mqttCredentials.setPassword("test_password"); Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials)); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); - Assert.assertEquals(savedDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningWithBadKeyDevice() throws Exception { super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString()); } protected TestMqttCallback createMqttClientAndPublish() throws Exception { diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 4b5a6028e6..325a517988 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -269,8 +269,9 @@ message ProvisionDeviceCredentialsMsg { } message ProvisionDeviceResponseMsg { - DeviceCredentialsProto deviceCredentials = 1; - ProvisionResponseStatus provisionResponseStatus = 2; + ProvisionResponseStatus status = 1; + CredentialsType credentialsType = 2; + string credentialsValue = 3; } enum ProvisionResponseStatus { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 63c7277ac2..2fa3fd6a88 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -408,22 +408,20 @@ public class JsonConverter { private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) { JsonObject result = new JsonObject(); - if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { + if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { result.addProperty("errorMsg", "Provision data was not found!"); - result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.NOT_FOUND.name()); - } else if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { + result.addProperty("status", ProvisionResponseStatus.NOT_FOUND.name()); + } else if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { result.addProperty("errorMsg", "Failed to provision device!"); - result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.FAILURE.name()); + result.addProperty("status", ProvisionResponseStatus.FAILURE.name()); } else { if (toGateway) { result.addProperty("id", requestId); } - result.addProperty("deviceId", new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB()).toString()); - result.addProperty("credentialsType", payload.getDeviceCredentials().getCredentialsType().name()); - result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId()); + result.addProperty("credentialsType", payload.getCredentialsType().name()); result.addProperty("credentialsValue", - StringUtils.isEmpty(payload.getDeviceCredentials().getCredentialsValue()) ? null : payload.getDeviceCredentials().getCredentialsValue()); - result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.SUCCESS.name()); + StringUtils.isEmpty(payload.getCredentialsValue()) ? null : payload.getCredentialsValue()); + result.addProperty("status", ProvisionResponseStatus.SUCCESS.name()); } return result; } @@ -551,7 +549,7 @@ public class JsonConverter { private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) { return TransportProtos.ProvisionDeviceRequestMsg.newBuilder() - .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true)) + .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, false)) .setCredentialsType(jo.get(DataConstants.CREDENTIALS_TYPE) != null ? TransportProtos.CredentialsType.valueOf(getStrValue(jo, DataConstants.CREDENTIALS_TYPE, false)) : CredentialsType.ACCESS_TOKEN) .setCredentialsDataProto(TransportProtos.CredentialsDataProto.newBuilder() .setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken(getStrValue(jo, DataConstants.TOKEN, false)).build()) From 16e1340c642afd6bccecbcfc1fd76ee3fc6f94dc Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 13 Nov 2020 16:21:46 +0200 Subject: [PATCH 39/84] Added correct response when provisioning failed --- .../server/service/transport/DefaultTransportApiService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 6006e2269e..d446b32e41 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -63,6 +63,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResp import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsgOrBuilder; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; @@ -303,6 +304,9 @@ public class DefaultTransportApiService implements TransportApiService { } private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ProvisionResponseStatus status) { + if (!status.equals(ProvisionResponseStatus.SUCCESS)) { + return TransportApiResponseMsg.newBuilder().setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder().setStatus(status).build()).build(); + } TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder() .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name())) .setStatus(status); From 6a6bd6117b572083b5f9950c413d570c8e1c32e4 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 13 Nov 2020 16:49:17 +0200 Subject: [PATCH 40/84] Fix for test --- .../mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java index a964ca29e3..6f96ae1034 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java @@ -132,7 +132,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN"); - Assert.assertEquals(deviceCredentials.getCredentialsValue(), "test_token"); + Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } From a2afde6261083bbecd6901b87b7ee01d3a5e848b Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 17 Nov 2020 10:57:02 +0200 Subject: [PATCH 41/84] Changed credentials value for device mqtt basic auth --- .../server/common/transport/adaptor/JsonConverter.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 2fa3fd6a88..912514d0fc 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -418,9 +418,14 @@ public class JsonConverter { if (toGateway) { result.addProperty("id", requestId); } + switch (payload.getCredentialsType()) { + case ACCESS_TOKEN: + case X509_CERTIFICATE: + result.addProperty("credentialsValue", payload.getCredentialsValue()); + case MQTT_BASIC: + result.add("credentialsValue", JSON_PARSER.parse(payload.getCredentialsValue()).getAsJsonObject()); + } result.addProperty("credentialsType", payload.getCredentialsType().name()); - result.addProperty("credentialsValue", - StringUtils.isEmpty(payload.getCredentialsValue()) ? null : payload.getCredentialsValue()); result.addProperty("status", ProvisionResponseStatus.SUCCESS.name()); } return result; From 6758a29c127005de70a62352374a8540946da9eb Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 17 Nov 2020 11:03:42 +0200 Subject: [PATCH 42/84] Refactoring --- .../server/common/transport/adaptor/JsonConverter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 912514d0fc..a20f697bba 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -422,8 +422,10 @@ public class JsonConverter { case ACCESS_TOKEN: case X509_CERTIFICATE: result.addProperty("credentialsValue", payload.getCredentialsValue()); + break; case MQTT_BASIC: result.add("credentialsValue", JSON_PARSER.parse(payload.getCredentialsValue()).getAsJsonObject()); + break; } result.addProperty("credentialsType", payload.getCredentialsType().name()); result.addProperty("status", ProvisionResponseStatus.SUCCESS.name()); From 71324536a30b11a1cdf46af48300686345107ca1 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 17 Nov 2020 15:01:40 +0200 Subject: [PATCH 43/84] Additional Kafka settings and logging of time consuming messages --- .../queue/DefaultTbCoreConsumerService.java | 74 ++++++++++++------- .../src/main/resources/thingsboard.yml | 1 + .../queue/kafka/TbKafkaConsumerTemplate.java | 7 +- .../server/queue/kafka/TbKafkaSettings.java | 5 ++ 4 files changed, 55 insertions(+), 32 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index f56e08b10f..4406ff9ade 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.service.queue; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; @@ -76,6 +78,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; @@ -175,39 +178,48 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> ctx = new TbPackProcessingContext<>( processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>()); - pendingMap.forEach((id, msg) -> { - log.trace("[{}] Creating main callback for message: {}", id, msg.getValue()); - TbCallback callback = new TbPackCallback<>(id, ctx); - try { - ToCoreMsg toCoreMsg = msg.getValue(); - if (toCoreMsg.hasToSubscriptionMgrMsg()) { - log.trace("[{}] Forwarding message to subscription manager service {}", id, toCoreMsg.getToSubscriptionMgrMsg()); - forwardToSubMgrService(toCoreMsg.getToSubscriptionMgrMsg(), callback); - } else if (toCoreMsg.hasToDeviceActorMsg()) { - log.trace("[{}] Forwarding message to device actor {}", id, toCoreMsg.getToDeviceActorMsg()); - forwardToDeviceActor(toCoreMsg.getToDeviceActorMsg(), callback); - } else if (toCoreMsg.hasDeviceStateServiceMsg()) { - log.trace("[{}] Forwarding message to state service {}", id, toCoreMsg.getDeviceStateServiceMsg()); - forwardToStateService(toCoreMsg.getDeviceStateServiceMsg(), callback); - } else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) { - Optional actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); - if (actorMsg.isPresent()) { - TbActorMsg tbActorMsg = actorMsg.get(); - if (tbActorMsg.getMsgType().equals(MsgType.DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG)) { - tbCoreDeviceRpcService.forwardRpcRequestToDeviceActor((ToDeviceRpcRequestActorMsg) tbActorMsg); - } else { - log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); - actorContext.tell(actorMsg.get()); + PendingMsgHolder pendingMsgHolder = new PendingMsgHolder(); + Future packSubmitFuture = consumersExecutor.submit(() -> { + pendingMap.forEach((id, msg) -> { + log.trace("[{}] Creating main callback for message: {}", id, msg.getValue()); + TbCallback callback = new TbPackCallback<>(id, ctx); + try { + ToCoreMsg toCoreMsg = msg.getValue(); + pendingMsgHolder.setToCoreMsg(toCoreMsg); + if (toCoreMsg.hasToSubscriptionMgrMsg()) { + log.trace("[{}] Forwarding message to subscription manager service {}", id, toCoreMsg.getToSubscriptionMgrMsg()); + forwardToSubMgrService(toCoreMsg.getToSubscriptionMgrMsg(), callback); + } else if (toCoreMsg.hasToDeviceActorMsg()) { + log.trace("[{}] Forwarding message to device actor {}", id, toCoreMsg.getToDeviceActorMsg()); + forwardToDeviceActor(toCoreMsg.getToDeviceActorMsg(), callback); + } else if (toCoreMsg.hasDeviceStateServiceMsg()) { + log.trace("[{}] Forwarding message to state service {}", id, toCoreMsg.getDeviceStateServiceMsg()); + forwardToStateService(toCoreMsg.getDeviceStateServiceMsg(), callback); + } else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) { + Optional actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); + if (actorMsg.isPresent()) { + TbActorMsg tbActorMsg = actorMsg.get(); + if (tbActorMsg.getMsgType().equals(MsgType.DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG)) { + tbCoreDeviceRpcService.forwardRpcRequestToDeviceActor((ToDeviceRpcRequestActorMsg) tbActorMsg); + } else { + log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); + actorContext.tell(actorMsg.get()); + } } + callback.onSuccess(); } - callback.onSuccess(); + } catch (Throwable e) { + log.warn("[{}] Failed to process message: {}", id, msg, e); + callback.onFailure(e); } - } catch (Throwable e) { - log.warn("[{}] Failed to process message: {}", id, msg, e); - callback.onFailure(e); - } + }); }); if (!processingTimeoutLatch.await(packProcessingTimeout, TimeUnit.MILLISECONDS)) { + if (!packSubmitFuture.isDone()) { + packSubmitFuture.cancel(true); + ToCoreMsg lastSubmitMsg = pendingMsgHolder.getToCoreMsg(); + log.info("Timeout to process message: {}", lastSubmitMsg); + } ctx.getAckMap().forEach((id, msg) -> log.debug("[{}] Timeout to process message: {}", id, msg.getValue())); ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue())); } @@ -227,6 +239,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService extends AbstractTbQue private TbKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder decoder, String clientId, String groupId, String topic, boolean autoCommit, int autoCommitIntervalMs, - int maxPollRecords, TbQueueAdmin admin) { super(topic); Properties props = settings.toProps(); @@ -54,6 +53,9 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue if (groupId != null) { props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); } + if (settings.getMaxPollIntervalMs() > 0) { + props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, settings.getMaxPollIntervalMs()); + } props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, settings.getMaxPollRecords()); props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, settings.getMaxPartitionFetchBytes()); props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, settings.getFetchMaxBytes()); @@ -61,9 +63,6 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitIntervalMs); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArrayDeserializer"); - if (maxPollRecords > 0) { - props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords); - } this.admin = admin; this.consumer = new KafkaConsumer<>(props); this.decoder = decoder; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java index 37e978c07d..15057d64d3 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java @@ -63,6 +63,10 @@ public class TbKafkaSettings { @Getter private int maxPollRecords; + @Value("${queue.kafka.max_poll_interval_ms:0}") + @Getter + private int maxPollIntervalMs; + @Value("${queue.kafka.max_partition_fetch_bytes:16777216}") @Getter private int maxPartitionFetchBytes; @@ -111,4 +115,5 @@ public class TbKafkaSettings { } return props; } + } From d0c39416b66b0f4ee68313abd6bad44a13f30232 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 17 Nov 2020 17:49:32 +0200 Subject: [PATCH 44/84] UI: Added copied button device provision parameters in device profile --- ...ile-provision-configuration.component.html | 16 ++++++++++++++++ ...ofile-provision-configuration.component.ts | 19 ++++++++++++++++++- .../assets/locale/locale.constant-en_US.json | 4 ++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index 2e9299f0cf..9af62c421d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -31,6 +31,14 @@ device-profile.provision-device-key + {{ 'device-profile.provision-device-key-required' | translate }} @@ -38,6 +46,14 @@ device-profile.provision-device-secret + {{ 'device-profile.provision-device-secret-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index e25c07e005..393aadb80c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -33,6 +33,10 @@ import { deviceProvisionTypeTranslationMap } from '@shared/models/device.models'; import { generateSecret, isDefinedAndNotNull } from '@core/utils'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'tb-device-profile-provision-configuration', @@ -73,7 +77,9 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu private propagateChange = (v: any) => { }; - constructor(private fb: FormBuilder) { + constructor(protected store: Store, + private fb: FormBuilder, + private translate: TranslateService) { } ngOnInit(): void { @@ -149,4 +155,15 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu } this.propagateChange(deviceProvisionConfiguration); } + + onProvisionCopied(isKey: boolean) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant(isKey ? 'device-profile.provision-key-copied-message' : 'device-profile.provision-secret-copied-message'), + type: 'success', + duration: 1200, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index ad79c56968..cddc73415e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -950,8 +950,12 @@ "provision-strategy-check-pre-provisioned": "Check for pre-provisioned devices", "provision-device-key": "Provision device key", "provision-device-key-required": "Provision device key is required.", + "copy-provision-key": "Copy provision key", + "provision-key-copied-message": "Provision key has been copied to clipboard", "provision-device-secret": "Provision device secret", "provision-device-secret-required": "Provision device secret is required.", + "copy-provision-secret": "Copy provision secret", + "provision-secret-copied-message": "Provision secret has been copied to clipboard", "condition": "Condition", "condition-type": "Condition type", "condition-type-simple": "Simple", From abb3c472396b5e1d4387384033ea4b9c76361ba0 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 17 Nov 2020 19:06:38 +0200 Subject: [PATCH 45/84] UI: Fixed didn't show alarm detail key --- .../home/components/widget/lib/table-widget.models.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts index 1ab9ea6fc4..53a9b17e4e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -184,10 +184,13 @@ export function getEntityValue(entity: any, key: DataKey): any { export function getAlarmValue(alarm: AlarmDataInfo, key: EntityColumn) { let alarmField = null; if (key.type === DataKeyType.alarm) { - alarmField = alarmFields[key.name]; + alarmField = alarmFields[key.name]?.value; + if (!alarmField && key.name.startsWith('details.')) { + alarmField = key.name; + } } if (alarmField) { - return getDescendantProp(alarm, alarmField.value); + return getDescendantProp(alarm, alarmField); } else { return getDescendantProp(alarm, key.label); } From 08693a407de00e3583a96edbad1754b942df92be Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 18 Nov 2020 13:36:34 +0200 Subject: [PATCH 46/84] UI: Refactoring device-profile payload type protobuf; Refactoring view --- .../profile/device-profile.component.ts | 4 +- ...ile-transport-configuration.component.html | 134 +++++++++--------- ...ofile-transport-configuration.component.ts | 96 +++++++------ .../device-profile-tabs.component.html | 6 +- 4 files changed, 123 insertions(+), 117 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index 8c521dcd77..862b0cff2f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -88,7 +88,7 @@ export class DeviceProfileComponent extends EntityComponent { this.displayTransportConfiguration = entity && entity.transportType && deviceTransportTypeConfigurationInfoMap.get(entity.transportType).hasProfileConfiguration; const deviceProvisionConfiguration: DeviceProvisionConfiguration = { - type: entity?.provisionType ? entity?.provisionType : DeviceProvisionType.DISABLED, + type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret }; @@ -162,7 +162,7 @@ export class DeviceProfileComponent extends EntityComponent { this.displayTransportConfiguration = entity.transportType && deviceTransportTypeConfigurationInfoMap.get(entity.transportType).hasProfileConfiguration; const deviceProvisionConfiguration: DeviceProvisionConfiguration = { - type: entity?.provisionType ? entity?.provisionType : DeviceProvisionType.DISABLED, + type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret }; diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index c855cfda97..a52826bc4f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -16,86 +16,80 @@ -->

-
+
+ device-profile.mqtt-device-topic-filters +
+
+ + device-profile.telemetry-topic-filter + + + {{ 'device-profile.telemetry-topic-filter-required' | translate}} + + + {{ 'device-profile.not-valid-single-character' | translate}} + + + {{ 'device-profile.not-valid-multi-character' | translate}} + + + + device-profile.attributes-topic-filter + + + {{ 'device-profile.attributes-topic-filter-required' | translate}} + + + {{ 'device-profile.not-valid-single-character' | translate}} + + + {{ 'device-profile.not-valid-multi-character' | translate}} + + +
+ + {{ 'device-profile.mqtt-device-topic-filters-unique' | translate }} + +
+
+
+
+
+
- device-profile.mqtt-device-topic-filters + device-profile.mqtt-device-payload-type
-
+ + + + {{mqttTransportPayloadTypeTranslations.get(type) | translate}} + + + + {{ 'device-profile.mqtt-payload-type-required' | translate }} + + +
- device-profile.telemetry-topic-filter - - - {{ 'device-profile.telemetry-topic-filter-required' | translate}} - - - {{ 'device-profile.not-valid-single-character' | translate}} - - - {{ 'device-profile.not-valid-multi-character' | translate}} + device-profile.telemetry-proto-schema + + + {{ 'device-profile.telemetry-proto-schema-required' | translate}} - device-profile.attributes-topic-filter - - - {{ 'device-profile.attributes-topic-filter-required' | translate}} - - - {{ 'device-profile.not-valid-single-character' | translate}} - - - {{ 'device-profile.not-valid-multi-character' | translate}} + device-profile.attributes-proto-schema + + + {{ 'device-profile.attributes-proto-schema-required' | translate}}
- - {{ 'device-profile.mqtt-device-topic-filters-unique' | translate }} - -
-
-
-
-
- device-profile.mqtt-device-payload-type -
- - - - {{mqttTransportPayloadTypeTranslations.get(type) | translate}} - - - - {{ 'device-profile.mqtt-payload-type-required' | translate }} - - -
- - device-profile.telemetry-proto-schema - - - {{ 'device-profile.telemetry-proto-schema-required' | translate}} - - - - device-profile.attributes-proto-schema - - - {{ 'device-profile.attributes-proto-schema-required' | translate}} - - -
-
-
-
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index a5d4ced861..5d26b9f8c8 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -54,6 +54,34 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control mqttDeviceProfileTransportConfigurationFormGroup: FormGroup; + private defaultTelemetrySchema = + 'syntax ="proto3";\n' + + 'package telemetry;\n' + + '\n' + + 'message SensorDataReading {\n' + + '\n' + + ' double temperature = 1;\n' + + ' double humidity = 2;\n' + + ' InnerObject innerObject = 3;\n' + + '\n' + + ' message InnerObject {\n' + + ' string key1 = 1;\n' + + ' bool key2 = 2;\n' + + ' double key3 = 3;\n' + + ' int32 key4 = 4;\n' + + ' string key5 = 5;\n' + + ' }\n' + + '}\n'; + + private defaultAttributesSchema = + 'syntax ="proto3";\n' + + 'package attributes;\n' + + '\n' + + 'message SensorConfiguration {\n' + + ' string firmwareVersion = 1;\n' + + ' string serialNumber = 2;\n' + + '}'; + private requiredValue: boolean; get required(): boolean { @@ -83,17 +111,18 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control ngOnInit() { this.mqttDeviceProfileTransportConfigurationFormGroup = this.fb.group({ - configuration: this.fb.group({ deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], transportPayloadTypeConfiguration: this.fb.group({ - transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] + transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required], + deviceTelemetryProtoSchema: [this.defaultTelemetrySchema, Validators.required], + deviceAttributesProtoSchema: [this.defaultAttributesSchema, Validators.required] }) - }, {validator: this.uniqueDeviceTopicValidator}) - }); - this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').valueChanges.subscribe(payloadType => { - this.updateTransportPayloadBasedControls(payloadType); - this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); + }, {validator: this.uniqueDeviceTopicValidator} + ); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType') + .valueChanges.subscribe(payloadType => { + this.updateTransportPayloadBasedControls(payloadType, true); }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); @@ -110,58 +139,41 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control } get protoPayloadType(): boolean { - let transportPayloadType = this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').value; + const transportPayloadType = this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType').value; return transportPayloadType === MqttTransportPayloadType.PROTOBUF; } writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { - this.updateTransportPayloadBasedControls(value.transportPayloadTypeConfiguration.transportPayloadType); - this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); + this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue(value, {emitEvent: false}); + this.updateTransportPayloadBasedControls(value.transportPayloadTypeConfiguration?.transportPayloadType); } } private updateModel() { let configuration: DeviceProfileTransportConfiguration = null; if (this.mqttDeviceProfileTransportConfigurationFormGroup.valid) { - configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; + configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.value; configuration.type = DeviceTransportType.MQTT; } this.propagateChange(configuration); } - private updateTransportPayloadBasedControls(type: MqttTransportPayloadType) { - const transportPayloadTypeConfigurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration') as FormGroup; - if (type === MqttTransportPayloadType.PROTOBUF) { - const defaultTelemetrySchema = "syntax =\"proto3\";\n" + - "package telemetry;\n" + - "\n" + - "message SensorDataReading {\n" + - "\n" + - " double temperature = 1;\n" + - " double humidity = 2;\n" + - " InnerObject innerObject = 3;\n" + - "\n" + - " message InnerObject {\n" + - " string key1 = 1;\n" + - " bool key2 = 2;\n" + - " double key3 = 3;\n" + - " int32 key4 = 4;\n" + - " string key5 = 5;\n" + - " }\n" + - "}\n"; - const defaultAttributesSchema = "syntax =\"proto3\";\n" + - "package attributes;\n" + - "\n" + - "message SensorConfiguration {\n" + - " string firmwareVersion = 1;\n" + - " string serialNumber = 2;\n" + - "}"; - transportPayloadTypeConfigurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(defaultTelemetrySchema, Validators.required)); - transportPayloadTypeConfigurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(defaultAttributesSchema, Validators.required)); + private updateTransportPayloadBasedControls(type: MqttTransportPayloadType, forceUpdated = false) { + const transportPayloadTypeForm = this.mqttDeviceProfileTransportConfigurationFormGroup + .get('transportPayloadTypeConfiguration') as FormGroup; + if (forceUpdated) { + transportPayloadTypeForm.patchValue({ + deviceTelemetryProtoSchema: this.defaultTelemetrySchema, + deviceAttributesProtoSchema: this.defaultAttributesSchema + }, {emitEvent: false}); + } + if (type === MqttTransportPayloadType.PROTOBUF && !this.disabled) { + transportPayloadTypeForm.get('deviceTelemetryProtoSchema').enable({emitEvent: false}); + transportPayloadTypeForm.get('deviceAttributesProtoSchema').enable({emitEvent: false}); } else { - transportPayloadTypeConfigurationFormGroup.removeControl('deviceTelemetryProtoSchema'); - transportPayloadTypeConfigurationFormGroup.removeControl('deviceAttributesProtoSchema'); + transportPayloadTypeForm.get('deviceTelemetryProtoSchema').disable({emitEvent: false}); + transportPayloadTypeForm.get('deviceAttributesProtoSchema').disable({emitEvent: false}); } } diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html index a803d35b1d..a16cee1d3a 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html @@ -41,9 +41,9 @@ + label="{{'device-profile.alarm-rules-with-count' | translate: + {count: entity.profileData?.alarms?.length ? entity.profileData.alarms.length : 0} + }}" #alarmRules="matTab">
From 986cfe5cc16aff47ec8a5fe1d3a51a2b34a41e17 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 18 Nov 2020 15:59:11 +0200 Subject: [PATCH 47/84] Improve mqtt timeseries integration tests --- ...AbstractMqttTimeseriesIntegrationTest.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java index 9076edc546..64ba73c374 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java @@ -127,7 +127,34 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt } else { getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + String.join(",", actualKeySet); } - Map>> values = doGetAsync(getTelemetryValuesUrl, Map.class); + start = System.currentTimeMillis(); + end = System.currentTimeMillis() + 5000; + Map>> values = null; + while (start <= end) { + values = doGetAsync(getTelemetryValuesUrl, Map.class); + boolean valid = values.size() == expectedKeys.size(); + if (valid) { + for (String key : expectedKeys) { + List> tsValues = values.get(key); + if (tsValues != null && tsValues.size() > 0) { + Object ts = tsValues.get(0).get("ts"); + if (ts == null) { + valid = false; + break; + } + } else { + valid = false; + break; + } + } + } + if (valid) { + break; + } + Thread.sleep(100); + start += 100; + } + assertNotNull(values); if (withTs) { assertTs(values, expectedKeys, 10000, 0); From 0fff85121052c4b38fe1d01cc3b0d850062b87b9 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 19 Nov 2020 11:26:51 +0200 Subject: [PATCH 48/84] Alarm Query fix for propagated alarms with entities assigned to customer --- .../query/DefaultAlarmQueryRepository.java | 40 ++++++++++--------- .../sql/query/DefaultQueryLogComponent.java | 4 +- .../rule/engine/profile/AlarmState.java | 6 ++- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index e3aa614bfe..2c0d67513f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -130,7 +130,6 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { AlarmDataPageLink pageLink = query.getPageLink(); QueryContext ctx = new QueryContext(new QuerySecurityContext(tenantId, customerId, EntityType.ALARM)); ctx.addUuidListParameter("entity_ids", orderedEntityIds.stream().map(EntityId::getId).collect(Collectors.toList())); - StringBuilder selectPart = new StringBuilder(FIELDS_SELECTION); StringBuilder fromPart = new StringBuilder(" from alarm a "); StringBuilder wherePart = new StringBuilder(" where "); @@ -282,24 +281,27 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { StringBuilder permissionsQuery = new StringBuilder(); ctx.addUuidParameter("permissions_tenant_id", tenantId.getId()); permissionsQuery.append(" a.tenant_id = :permissions_tenant_id "); - if (customerId != null && !customerId.isNullUid()) { - ctx.addUuidParameter("permissions_customer_id", customerId.getId()); - ctx.addUuidParameter("permissions_device_customer_id", customerId.getId()); - ctx.addUuidParameter("permissions_asset_customer_id", customerId.getId()); - ctx.addUuidParameter("permissions_user_customer_id", customerId.getId()); - ctx.addUuidParameter("permissions_entity_view_customer_id", customerId.getId()); - permissionsQuery.append(" and ("); - permissionsQuery.append("(a.originator_type = '").append(EntityType.DEVICE.ordinal()).append("' and exists (select 1 from device cd where cd.id = a.originator_id and cd.customer_id = :permissions_device_customer_id))"); - permissionsQuery.append(" or "); - permissionsQuery.append("(a.originator_type = '").append(EntityType.ASSET.ordinal()).append("' and exists (select 1 from asset ca where ca.id = a.originator_id and ca.customer_id = :permissions_device_customer_id))"); - permissionsQuery.append(" or "); - permissionsQuery.append("(a.originator_type = '").append(EntityType.CUSTOMER.ordinal()).append("' and exists (select 1 from customer cc where cc.id = a.originator_id and cc.id = :permissions_customer_id))"); - permissionsQuery.append(" or "); - permissionsQuery.append("(a.originator_type = '").append(EntityType.USER.ordinal()).append("' and exists (select 1 from tb_user cu where cu.id = a.originator_id and cu.customer_id = :permissions_user_customer_id))"); - permissionsQuery.append(" or "); - permissionsQuery.append("(a.originator_type = '").append(EntityType.ENTITY_VIEW.ordinal()).append("' and exists (select 1 from entity_view cv where cv.id = a.originator_id and cv.customer_id = :permissions_entity_view_customer_id))"); - permissionsQuery.append(")"); - } +/* + No need to check the customer id, because we already use entity id list that passed security check when we were evaluating the data query. + */ +// if (customerId != null && !customerId.isNullUid()) { +// ctx.addUuidParameter("permissions_customer_id", customerId.getId()); +// ctx.addUuidParameter("permissions_device_customer_id", customerId.getId()); +// ctx.addUuidParameter("permissions_asset_customer_id", customerId.getId()); +// ctx.addUuidParameter("permissions_user_customer_id", customerId.getId()); +// ctx.addUuidParameter("permissions_entity_view_customer_id", customerId.getId()); +// permissionsQuery.append(" and ("); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.DEVICE.ordinal()).append("' and exists (select 1 from device cd where cd.id = a.originator_id and cd.customer_id = :permissions_device_customer_id))"); +// permissionsQuery.append(" or "); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.ASSET.ordinal()).append("' and exists (select 1 from asset ca where ca.id = a.originator_id and ca.customer_id = :permissions_device_customer_id))"); +// permissionsQuery.append(" or "); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.CUSTOMER.ordinal()).append("' and exists (select 1 from customer cc where cc.id = a.originator_id and cc.id = :permissions_customer_id))"); +// permissionsQuery.append(" or "); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.USER.ordinal()).append("' and exists (select 1 from tb_user cu where cu.id = a.originator_id and cu.customer_id = :permissions_user_customer_id))"); +// permissionsQuery.append(" or "); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.ENTITY_VIEW.ordinal()).append("' and exists (select 1 from entity_view cv where cv.id = a.originator_id and cv.customer_id = :permissions_entity_view_customer_id))"); +// permissionsQuery.append(")"); +// } return permissionsQuery.toString(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java index 7cece0cf3a..62c8aed686 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java @@ -32,9 +32,9 @@ public class DefaultQueryLogComponent implements QueryLogComponent { @Override public void logQuery(QueryContext ctx, String query, long duration) { - if (logSqlQueries && duration > logQueriesThreshold) { +// if (logSqlQueries && duration > logQueriesThreshold) { log.info("QUERY: {} took {}ms", query, duration); Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {} -> {}", param, ctx.getValue(param))); - } +// } } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 81622b6b93..0a501a0890 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -123,8 +123,10 @@ class AlarmState { } public boolean clearAlarmState(boolean stateUpdate, AlarmRuleState state) { - state.clear(); - stateUpdate |= state.checkUpdate(); + if (state != null) { + state.clear(); + stateUpdate |= state.checkUpdate(); + } return stateUpdate; } From 5f8e5925956f145025a824f07edd2dd084987cda Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 19 Nov 2020 11:33:51 +0200 Subject: [PATCH 49/84] Reverted changes to the logging from previous commit --- .../server/dao/sql/query/DefaultQueryLogComponent.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java index 62c8aed686..7cece0cf3a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java @@ -32,9 +32,9 @@ public class DefaultQueryLogComponent implements QueryLogComponent { @Override public void logQuery(QueryContext ctx, String query, long duration) { -// if (logSqlQueries && duration > logQueriesThreshold) { + if (logSqlQueries && duration > logQueriesThreshold) { log.info("QUERY: {} took {}ms", query, duration); Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {} -> {}", param, ctx.getValue(param))); -// } + } } } From 0fb1147ab2efbfe6ca2d6382b81a964a70b870bf Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 19 Nov 2020 12:04:32 +0200 Subject: [PATCH 50/84] Alarm Query Tests improved --- .../dao/service/BaseAlarmServiceTest.java | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java index 15bd4eb905..16e895a869 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -45,6 +46,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.dao.alarm.AlarmOperationResult; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import java.util.Arrays; import java.util.Collections; @@ -265,7 +267,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { PageData tenantAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, new CustomerId(CustomerId.NULL_UUID), toQuery(pageLink), Arrays.asList(tenantDevice.getId(), customerDevice.getId())); Assert.assertEquals(2, tenantAlarms.getData().size()); - PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, customer.getId(), toQuery(pageLink), Arrays.asList(tenantDevice.getId(), customerDevice.getId())); + PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, customer.getId(), toQuery(pageLink), Collections.singletonList(customerDevice.getId())); Assert.assertEquals(1, customerAlarms.getData().size()); Assert.assertEquals(deviceAlarm, customerAlarms.getData().get(0)); @@ -280,6 +282,70 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { Assert.assertEquals(tenantAlarm, alarms.getData().get(0)); } + @Test + public void testFindPopagatedCustomerAlarm() throws ExecutionException, InterruptedException { + Customer customer = new Customer(); + customer.setTitle("TestCustomer"); + customer.setTenantId(tenantId); + customer = customerService.saveCustomer(customer); + + Device tenantDevice = new Device(); + tenantDevice.setName("TestTenantDevice"); + tenantDevice.setType("default"); + tenantDevice.setTenantId(tenantId); + tenantDevice = deviceService.saveDevice(tenantDevice); + + Asset customerAsset = new Asset(); + customerAsset.setName("TestCustomerDevice"); + customerAsset.setType("default"); + customerAsset.setTenantId(tenantId); + customerAsset.setCustomerId(customer.getId()); + customerAsset = assetService.saveAsset(customerAsset); + + EntityRelation relation = new EntityRelation(); + relation.setFrom(customerAsset.getId()); + relation.setTo(tenantDevice.getId()); + relation.setAdditionalInfo(JacksonUtil.newObjectNode()); + relation.setType("Contains"); + relation.setTypeGroup(RelationTypeGroup.COMMON); + relationService.saveRelation(tenantId, relation); + + long ts = System.currentTimeMillis(); + Alarm tenantAlarm = Alarm.builder().tenantId(tenantId) + .originator(tenantDevice.getId()) + .type("Not Propagated") + .propagate(false) + .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) + .startTs(ts).build(); + AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm); + tenantAlarm = result.getAlarm(); + + Alarm customerAlarm = Alarm.builder().tenantId(tenantId) + .originator(tenantDevice.getId()) + .type("Propagated") + .propagate(true) + .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) + .startTs(ts).build(); + result = alarmService.createOrUpdateAlarm(customerAlarm); + customerAlarm = result.getAlarm(); + + AlarmDataPageLink pageLink = new AlarmDataPageLink(); + pageLink.setPage(0); + pageLink.setPageSize(10); + pageLink.setSortOrder(new EntityDataSortOrder(new EntityKey(EntityKeyType.ALARM_FIELD, "createdTime"))); + + pageLink.setStartTs(0L); + pageLink.setEndTs(System.currentTimeMillis()); + pageLink.setSearchPropagatedAlarms(true); + pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); + pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); + + //TEST that propagated alarms are visible on the asset level. + PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, customer.getId(), toQuery(pageLink), Collections.singletonList(customerAsset.getId())); + Assert.assertEquals(1, customerAlarms.getData().size()); + Assert.assertEquals(customerAlarm, customerAlarms.getData().get(0)); + } + private AlarmDataQuery toQuery(AlarmDataPageLink pageLink){ return toQuery(pageLink, Collections.EMPTY_LIST); } From 39eafde85b0199acbf840be63b756920e7017d40 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 19 Nov 2020 11:40:44 +0200 Subject: [PATCH 51/84] fix MQTT x509 auth --- .../server/service/transport/DefaultTransportApiService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index d446b32e41..98f5708bad 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -178,6 +178,8 @@ public class DefaultTransportApiService implements TransportApiService { if (!checkMqttCredentials(mqtt, credentials)) { credentials = null; } + } else { + return getEmptyTransportApiResponseFuture(); } } if (credentials == null) { From 3cabec954e9d3f5305f95b4e66867334cee3449e Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 19 Nov 2020 17:06:54 +0200 Subject: [PATCH 52/84] Critical bug fix --- .../server/dao/sql/query/DefaultAlarmQueryRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index 2c0d67513f..e9a5372894 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -156,7 +156,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { wherePart.append(" a.originator_id in (:entity_ids)"); } } else { - fromPart.append(" left join (select * from (VALUES"); + fromPart.append(" inner join (select * from (VALUES"); int entityIdIdx = 0; int lastEntityIdIdx = orderedEntityIds.size() - 1; for (EntityId entityId : orderedEntityIds) { From 1e1e3ec6a30f784da596768d9cb44836e39155f1 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 13:08:20 +0200 Subject: [PATCH 53/84] Introduce SMS Service. Add Send SMS Rule Node --- application/pom.xml | 8 ++ .../server/actors/ActorSystemContext.java | 19 +++ .../actors/ruleChain/DefaultTbContext.java | 21 +++ .../server/controller/AdminController.java | 18 +++ .../server/service/sms/AbstractSmsSender.java | 54 ++++++++ .../service/sms/DefaultSmsSenderFactory.java | 42 ++++++ .../server/service/sms/DefaultSmsService.java | 127 ++++++++++++++++++ .../service/sms/SmsExecutorService.java | 33 +++++ .../server/service/sms/aws/AwsSmsSender.java | 73 ++++++++++ .../service/sms/twilio/TwilioSmsSender.java | 56 ++++++++ .../src/main/resources/thingsboard.yml | 4 + pom.xml | 29 +++- .../rule/engine/api/SmsService.java | 31 +++++ .../rule/engine/api/TbContext.java | 7 + .../rule/engine/api/sms/SmsSender.java | 26 ++++ .../rule/engine/api/sms/SmsSenderFactory.java | 24 ++++ .../AwsSnsSmsProviderConfiguration.java | 32 +++++ .../sms/config/SmsProviderConfiguration.java | 36 +++++ .../api/sms/config/SmsProviderType.java | 21 +++ .../engine/api/sms/config/TestSmsRequest.java | 27 ++++ .../TwilioSmsProviderConfiguration.java | 32 +++++ .../api/sms/exception/SmsException.java | 28 ++++ .../api/sms/exception/SmsParseException.java | 28 ++++ .../api/sms/exception/SmsSendException.java | 27 ++++ .../rule/engine/sms/TbSendSmsNode.java | 97 +++++++++++++ .../sms/TbSendSmsNodeConfiguration.java | 38 ++++++ ui-ngx/src/app/core/http/admin.service.ts | 13 +- ui-ngx/src/app/core/services/menu.service.ts | 14 +- .../home/components/home-components.module.ts | 78 ++++++----- ...-sns-provider-configuration.component.html | 40 ++++++ ...ws-sns-provider-configuration.component.ts | 99 ++++++++++++++ .../sms-provider-configuration.component.html | 44 ++++++ .../sms-provider-configuration.component.ts | 123 +++++++++++++++++ ...-sms-provider-configuration.component.html | 44 ++++++ ...io-sms-provider-configuration.component.ts | 101 ++++++++++++++ .../home/pages/admin/admin-routing.module.ts | 14 ++ .../modules/home/pages/admin/admin.module.ts | 4 + .../admin/send-test-sms-dialog.component.html | 70 ++++++++++ .../admin/send-test-sms-dialog.component.ts | 87 ++++++++++++ .../pages/admin/sms-provider.component.html | 50 +++++++ .../pages/admin/sms-provider.component.scss | 18 +++ .../pages/admin/sms-provider.component.ts | 95 +++++++++++++ ...ake-dashboard-public-dialog.component.html | 2 +- .../activation-link-dialog.component.html | 2 +- ui-ngx/src/app/shared/models/constants.ts | 1 + .../src/app/shared/models/settings.models.ts | 65 +++++++++ .../assets/locale/locale.constant-en_US.json | 27 ++++ 47 files changed, 1888 insertions(+), 41 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java create mode 100644 application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java create mode 100644 application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSender.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsException.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsParseException.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsSendException.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java create mode 100644 ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts diff --git a/application/pom.xml b/application/pom.xml index 1b29ef3243..f62b1c9f5e 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -193,6 +193,14 @@ com.sun.mail javax.mail + + com.twilio.sdk + twilio + + + com.amazonaws + aws-java-sdk-sns + org.apache.curator curator-recipes diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 52dbb38c9c..49119ed667 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -32,6 +32,8 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.common.data.DataConstants; @@ -80,6 +82,7 @@ import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; import org.thingsboard.server.service.script.JsExecutorService; import org.thingsboard.server.service.script.JsInvokeService; import org.thingsboard.server.service.session.DeviceSessionCacheService; +import org.thingsboard.server.service.sms.SmsExecutorService; import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; @@ -228,6 +231,10 @@ public class ActorSystemContext { @Getter private MailExecutorService mailExecutor; + @Autowired + @Getter + private SmsExecutorService smsExecutor; + @Autowired @Getter private DbCallbackExecutorService dbCallbackExecutor; @@ -244,6 +251,14 @@ public class ActorSystemContext { @Getter private MailService mailService; + @Autowired + @Getter + private SmsService smsService; + + @Autowired + @Getter + private SmsSenderFactory smsSenderFactory; + @Autowired @Getter private ClaimDevicesService claimDevicesService; @@ -325,6 +340,10 @@ public class ActorSystemContext { @Getter private boolean allowSystemMailService; + @Value("${actors.rule.allow_system_sms_service}") + @Getter + private boolean allowSystemSmsService; + @Value("${transport.sessions.inactivity_timeout}") @Getter private long sessionInactivityTimeout; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 6749ed69ed..b1d214069f 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -28,8 +28,10 @@ import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; import org.thingsboard.rule.engine.api.RuleEngineRpcService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.ScriptEngine; +import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.common.data.Customer; @@ -302,6 +304,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getMailExecutor(); } + @Override + public ListeningExecutor getSmsExecutor() { + return mainCtx.getSmsExecutor(); + } + @Override public ListeningExecutor getDbCallbackExecutor() { return mainCtx.getDbCallbackExecutor(); @@ -427,6 +434,20 @@ class DefaultTbContext implements TbContext { } } + @Override + public SmsService getSmsService() { + if (mainCtx.isAllowSystemSmsService()) { + return mainCtx.getSmsService(); + } else { + throw new RuntimeException("Access to System SMS Service is forbidden!"); + } + } + + @Override + public SmsSenderFactory getSmsSenderFactory() { + return mainCtx.getSmsSenderFactory(); + } + @Override public RuleEngineRpcService getRpcService() { return mainCtx.getTbRuleEngineDeviceRpcService(); diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index 134171c8b1..c980bff796 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -25,6 +25,8 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -45,6 +47,9 @@ public class AdminController extends BaseController { @Autowired private MailService mailService; + @Autowired + private SmsService smsService; + @Autowired private AdminSettingsService adminSettingsService; @@ -80,6 +85,8 @@ public class AdminController extends BaseController { if (adminSettings.getKey().equals("mail")) { mailService.updateMailConfiguration(); ((ObjectNode) adminSettings.getJsonValue()).put("password", ""); + } else if (adminSettings.getKey().equals("sms")) { + smsService.updateSmsConfiguration(); } return adminSettings; } catch (Exception e) { @@ -127,6 +134,17 @@ public class AdminController extends BaseController { } } + @PreAuthorize("hasAuthority('SYS_ADMIN')") + @RequestMapping(value = "/settings/testSms", method = RequestMethod.POST) + public void sendTestSms(@RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); + smsService.sendTestSms(testSmsRequest); + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/updates", method = RequestMethod.GET) @ResponseBody diff --git a/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java new file mode 100644 index 0000000000..1add9cd79b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java @@ -0,0 +1,54 @@ +/** + * 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.service.sms; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.sms.SmsSender; +import org.thingsboard.rule.engine.api.sms.exception.SmsParseException; +import sun.misc.Regexp; + +import java.util.regex.Pattern; + +@Slf4j +public abstract class AbstractSmsSender implements SmsSender { + + private static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$"); + + private static final int MAX_SMS_MESSAGE_LENGTH = 1600; + private static final int MAX_SMS_SEGMENT_LENGTH = 70; + + protected String validatePhoneNumber(String phoneNumber) throws SmsParseException { + phoneNumber = phoneNumber.trim(); + if (!E_164_PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches()) { + throw new SmsParseException("Invalid phone number format. Phone number must be in E.164 format."); + } + return phoneNumber; + } + + protected String prepareMessage(String message) { + message = message.replaceAll("^\"|\"$", "").replaceAll("\\\\n", "\n"); + if (message.length() > MAX_SMS_MESSAGE_LENGTH) { + log.warn("SMS message exceeds maximum symbols length and will be truncated"); + message = message.substring(0, MAX_SMS_MESSAGE_LENGTH); + } + return message; + } + + protected int countMessageSegments(String message) { + return (int)Math.ceil((double) message.length() / (double) MAX_SMS_SEGMENT_LENGTH); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java new file mode 100644 index 0000000000..3452bcf1ac --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java @@ -0,0 +1,42 @@ +/** + * 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.service.sms; + +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.sms.SmsSender; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; +import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; +import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; +import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; +import org.thingsboard.server.service.sms.aws.AwsSmsSender; +import org.thingsboard.server.service.sms.twilio.TwilioSmsSender; + +@Component +public class DefaultSmsSenderFactory implements SmsSenderFactory { + + @Override + public SmsSender createSmsSender(SmsProviderConfiguration config) { + switch (config.getType()) { + case AWS_SNS: + return new AwsSmsSender((AwsSnsSmsProviderConfiguration)config); + case TWILIO: + return new TwilioSmsSender((TwilioSmsProviderConfiguration)config); + default: + throw new RuntimeException("Unknown SMS provider type " + config.getType()); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java new file mode 100644 index 0000000000..05df30deae --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java @@ -0,0 +1,127 @@ +/** + * 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.service.sms; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.NestedRuntimeException; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.rule.engine.api.sms.SmsSender; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; +import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; +import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; +import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +@Service +@Slf4j +public class DefaultSmsService implements SmsService { + + @Autowired + private SmsSenderFactory smsSenderFactory; + + @Autowired + private AdminSettingsService adminSettingsService; + + private SmsSender smsSender; + + @PostConstruct + private void init() { + updateSmsConfiguration(); + } + + @PreDestroy + private void destroy() { + if (this.smsSender != null) { + this.smsSender.destroy(); + } + } + + @Override + public void updateSmsConfiguration() { + AdminSettings settings = adminSettingsService.findAdminSettingsByKey(new TenantId(EntityId.NULL_UUID), "sms"); + if (settings != null) { + try { + JsonNode jsonConfig = settings.getJsonValue(); + SmsProviderConfiguration configuration = JacksonUtil.convertValue(jsonConfig, SmsProviderConfiguration.class); + SmsSender newSmsSender = this.smsSenderFactory.createSmsSender(configuration); + if (this.smsSender != null) { + this.smsSender.destroy(); + } + this.smsSender = newSmsSender; + } catch (Exception e) { + log.error("Failed to create SMS sender", e); + } + } + } + + @Override + public void sendSms(String numberTo, String message) throws ThingsboardException { + if (this.smsSender == null) { + throw new ThingsboardException("Unable to send SMS: no SMS provider configured!", ThingsboardErrorCode.GENERAL); + } + this.sendSms(this.smsSender, numberTo, message); + } + + @Override + public void sendSms(String[] numbersTo, String message) throws ThingsboardException { + for (String numberTo : numbersTo) { + this.sendSms(numberTo, message); + } + } + + @Override + public void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException { + SmsSender testSmsSender; + try { + testSmsSender = this.smsSenderFactory.createSmsSender(testSmsRequest.getProviderConfiguration()); + } catch (Exception e) { + throw handleException(e); + } + this.sendSms(testSmsSender, testSmsRequest.getNumberTo(), testSmsRequest.getMessage()); + testSmsSender.destroy(); + } + + private int sendSms(SmsSender smsSender, String numberTo, String message) throws ThingsboardException { + try { + return smsSender.sendSms(numberTo, message); + } catch (Exception e) { + throw handleException(e); + } + } + + private ThingsboardException handleException(Exception exception) { + String message; + if (exception instanceof NestedRuntimeException) { + message = ((NestedRuntimeException) exception).getMostSpecificCause().getMessage(); + } else { + message = exception.getMessage(); + } + log.warn("Unable to send SMS: {}", message); + return new ThingsboardException(String.format("Unable to send SMS: %s", message), + ThingsboardErrorCode.GENERAL); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java b/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java new file mode 100644 index 0000000000..48aa2d5dc1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java @@ -0,0 +1,33 @@ +/** + * 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.service.sms; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.AbstractListeningExecutor; + +@Component +public class SmsExecutorService extends AbstractListeningExecutor { + + @Value("${actors.rule.sms_thread_pool_size}") + private int smsExecutorThreadPoolSize; + + @Override + protected int getThreadPollSize() { + return smsExecutorThreadPoolSize; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java new file mode 100644 index 0000000000..98a8556a5d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java @@ -0,0 +1,73 @@ +/** + * 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.service.sms.aws; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.AmazonSNSClient; +import com.amazonaws.services.sns.model.PublishRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; +import org.thingsboard.rule.engine.api.sms.exception.SmsException; +import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; +import org.thingsboard.server.service.sms.AbstractSmsSender; + +@Slf4j +public class AwsSmsSender extends AbstractSmsSender { + + private AmazonSNS snsClient; + + public AwsSmsSender(AwsSnsSmsProviderConfiguration config) { + if (StringUtils.isEmpty(config.getAccessKeyId()) || StringUtils.isEmpty(config.getSecretAccessKey()) || StringUtils.isEmpty(config.getRegion())) { + throw new IllegalArgumentException("Invalid AWS sms provider configuration: aws accessKeyId, aws secretAccessKey and aws region should be specified!"); + } + AWSCredentials awsCredentials = new BasicAWSCredentials(config.getAccessKeyId(), config.getSecretAccessKey()); + AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials); + this.snsClient = AmazonSNSClient.builder() + .withCredentials(credProvider) + .withRegion(config.getRegion()) + .build(); + } + + @Override + public int sendSms(String numberTo, String message) throws SmsException { + numberTo = this.validatePhoneNumber(numberTo); + message = this.prepareMessage(message); + try { + PublishRequest publishRequest = new PublishRequest() + .withPhoneNumber(numberTo) + .withMessage(message); + this.snsClient.publish(publishRequest); + return this.countMessageSegments(message); + } catch (Exception e) { + throw new SmsSendException("Failed to send SMS message - " + e.getMessage(), e); + } + } + + @Override + public void destroy() { + if (this.snsClient != null) { + try { + this.snsClient.shutdown(); + } catch (Exception e) { + log.error("Failed to shutdown SNS client during destroy()", e); + } + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java new file mode 100644 index 0000000000..072b2b2ff7 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java @@ -0,0 +1,56 @@ +/** + * 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.service.sms.twilio; + +import com.twilio.http.TwilioRestClient; +import com.twilio.rest.api.v2010.account.Message; +import com.twilio.type.PhoneNumber; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; +import org.thingsboard.rule.engine.api.sms.exception.SmsException; +import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; +import org.thingsboard.server.service.sms.AbstractSmsSender; + +public class TwilioSmsSender extends AbstractSmsSender { + + private TwilioRestClient twilioRestClient; + private String numberFrom; + + public TwilioSmsSender(TwilioSmsProviderConfiguration config) { + if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) { + throw new IllegalArgumentException("Invalid twilio sms provider configuration: accountSid, accountToken and numberFrom should be specified!"); + } + this.numberFrom = this.validatePhoneNumber(config.getNumberFrom()); + this.twilioRestClient = new TwilioRestClient.Builder(config.getAccountSid(), config.getAccountToken()).build(); + } + + @Override + public int sendSms(String numberTo, String message) throws SmsException { + numberTo = this.validatePhoneNumber(numberTo); + message = this.prepareMessage(message); + try { + String numSegments = Message.creator(new PhoneNumber(numberTo), new PhoneNumber(this.numberFrom), message).create(this.twilioRestClient).getNumSegments(); + return Integer.valueOf(numSegments); + } catch (Exception e) { + throw new SmsSendException("Failed to send SMS message - " + e.getMessage(), e); + } + } + + @Override + public void destroy() { + + } +} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 73b36b2104..06661d9b58 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -281,8 +281,12 @@ actors: js_thread_pool_size: "${ACTORS_RULE_JS_THREAD_POOL_SIZE:50}" # Specify thread pool size for mail sender executor service mail_thread_pool_size: "${ACTORS_RULE_MAIL_THREAD_POOL_SIZE:50}" + # Specify thread pool size for sms sender executor service + sms_thread_pool_size: "${ACTORS_RULE_SMS_THREAD_POOL_SIZE:50}" # Whether to allow usage of system mail service for rules allow_system_mail_service: "${ACTORS_RULE_ALLOW_SYSTEM_MAIL_SERVICE:true}" + # Whether to allow usage of system sms service for rules + allow_system_sms_service: "${ACTORS_RULE_ALLOW_SYSTEM_SMS_SERVICE:true}" # Specify thread pool size for external call service external_call_thread_pool_size: "${ACTORS_RULE_EXTERNAL_CALL_THREAD_POOL_SIZE:50}" chain: diff --git a/pom.xml b/pom.xml index 45dbb3be87..660b36f621 100755 --- a/pom.xml +++ b/pom.xml @@ -97,7 +97,7 @@ 2.57 2.7.7 1.27 - 1.11.747 + 1.11.747 1.105.0 3.2.0 1.5.0 @@ -108,6 +108,7 @@ 1.5.2 1.0.2TB 3.4.0 + 7.54.2 @@ -1318,7 +1319,12 @@ com.amazonaws aws-java-sdk-sqs - ${amazonaws.sqs.version} + ${aws.sdk.version} + + + com.amazonaws + aws-java-sdk-sns + ${aws.sdk.version} com.google.cloud @@ -1381,6 +1387,25 @@ wire-schema ${wire-schema.version} + + com.twilio.sdk + twilio + ${twilio.version} + + + io.jsonwebtoken + jjwt-api + + + io.jsonwebtoken + jjwt-jackson + + + io.jsonwebtoken + jjwt-impl + + + diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java new file mode 100644 index 0000000000..a041ad9390 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java @@ -0,0 +1,31 @@ +/** + * 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.rule.engine.api; + +import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; +import org.thingsboard.server.common.data.exception.ThingsboardException; + +public interface SmsService { + + void updateSmsConfiguration(); + + void sendSms(String numberTo, String message) throws ThingsboardException; + + void sendSms(String[] numbersTo, String message) throws ThingsboardException;; + + void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException; + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 081a84eef0..b104c6f07b 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.api; import io.netty.channel.EventLoopGroup; import org.springframework.data.redis.core.RedisTemplate; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -194,12 +195,18 @@ public interface TbContext { ListeningExecutor getMailExecutor(); + ListeningExecutor getSmsExecutor(); + ListeningExecutor getDbCallbackExecutor(); ListeningExecutor getExternalCallExecutor(); MailService getMailService(); + SmsService getSmsService(); + + SmsSenderFactory getSmsSenderFactory(); + ScriptEngine createJsScriptEngine(String script, String... argNames); void logJsEvalRequest(); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSender.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSender.java new file mode 100644 index 0000000000..672992df64 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSender.java @@ -0,0 +1,26 @@ +/** + * 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.rule.engine.api.sms; + +import org.thingsboard.rule.engine.api.sms.exception.SmsException; + +public interface SmsSender { + + int sendSms(String numberTo, String message) throws SmsException; + + void destroy(); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java new file mode 100644 index 0000000000..39daa48983 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java @@ -0,0 +1,24 @@ +/** + * 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.rule.engine.api.sms; + +import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; + +public interface SmsSenderFactory { + + SmsSender createSmsSender(SmsProviderConfiguration config); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java new file mode 100644 index 0000000000..74cd5486a1 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java @@ -0,0 +1,32 @@ +/** + * 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.rule.engine.api.sms.config; + +import lombok.Data; + +@Data +public class AwsSnsSmsProviderConfiguration implements SmsProviderConfiguration { + + private String accessKeyId; + private String secretAccessKey; + private String region; + + @Override + public SmsProviderType getType() { + return SmsProviderType.AWS_SNS; + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java new file mode 100644 index 0000000000..8feec6ff40 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java @@ -0,0 +1,36 @@ +/** + * 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.rule.engine.api.sms.config; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = AwsSnsSmsProviderConfiguration.class, name = "AWS_SNS"), + @JsonSubTypes.Type(value = TwilioSmsProviderConfiguration.class, name = "TWILIO")}) +public interface SmsProviderConfiguration { + + @JsonIgnore + SmsProviderType getType(); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java new file mode 100644 index 0000000000..e698dd4783 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java @@ -0,0 +1,21 @@ +/** + * 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.rule.engine.api.sms.config; + +public enum SmsProviderType { + AWS_SNS, + TWILIO +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java new file mode 100644 index 0000000000..3d00f9e580 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java @@ -0,0 +1,27 @@ +/** + * 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.rule.engine.api.sms.config; + +import lombok.Data; + +@Data +public class TestSmsRequest { + + private SmsProviderConfiguration providerConfiguration; + private String numberTo; + private String message; + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java new file mode 100644 index 0000000000..2e0585e466 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java @@ -0,0 +1,32 @@ +/** + * 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.rule.engine.api.sms.config; + +import lombok.Data; + +@Data +public class TwilioSmsProviderConfiguration implements SmsProviderConfiguration { + + private String accountSid; + private String accountToken; + private String numberFrom; + + @Override + public SmsProviderType getType() { + return SmsProviderType.TWILIO; + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsException.java new file mode 100644 index 0000000000..dce599404c --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsException.java @@ -0,0 +1,28 @@ +/** + * 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.rule.engine.api.sms.exception; + +public abstract class SmsException extends RuntimeException { + + public SmsException(String msg) { + super(msg); + } + + public SmsException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsParseException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsParseException.java new file mode 100644 index 0000000000..de3c41029e --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsParseException.java @@ -0,0 +1,28 @@ +/** + * 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.rule.engine.api.sms.exception; + +public class SmsParseException extends SmsException { + + public SmsParseException(String msg) { + super(msg); + } + + public SmsParseException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsSendException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsSendException.java new file mode 100644 index 0000000000..f5254bf6d6 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsSendException.java @@ -0,0 +1,27 @@ +/** + * 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.rule.engine.api.sms.exception; + +public class SmsSendException extends SmsException { + + public SmsSendException(String msg) { + super(msg); + } + + public SmsSendException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java new file mode 100644 index 0000000000..f2c6ad3725 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java @@ -0,0 +1,97 @@ +/** + * 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.rule.engine.sms; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.RuleNode; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.sms.SmsSender; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; + +import static org.thingsboard.common.util.DonAsynchron.withCallback; + +@Slf4j +@RuleNode( + type = ComponentType.EXTERNAL, + name = "send sms", + configClazz = TbSendSmsNodeConfiguration.class, + nodeDescription = "Sends SMS message via SMS provider.", + nodeDetails = "Will send SMS message by populating target phone numbers and sms message fields using values derived from message metadata.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbActionNodeSendSmsConfig", + icon = "sms" +) +public class TbSendSmsNode implements TbNode { + + private TbSendSmsNodeConfiguration config; + private SmsSender smsSender; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + try { + this.config = TbNodeUtils.convert(configuration, TbSendSmsNodeConfiguration.class); + if (!this.config.isUseSystemSmsSettings()) { + smsSender = createSmsSender(ctx); + } + } catch (Exception e) { + throw new TbNodeException(e); + } + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + try { + withCallback(ctx.getSmsExecutor().executeAsync(() -> { + sendSms(ctx, msg); + return null; + }), + ok -> ctx.tellSuccess(msg), + fail -> ctx.tellFailure(msg, fail)); + } catch (Exception ex) { + ctx.tellFailure(msg, ex); + } + } + + private void sendSms(TbContext ctx, TbMsg msg) throws Exception { + String numbersTo = TbNodeUtils.processPattern(this.config.getNumbersToTemplate(), msg.getMetaData()); + String message = TbNodeUtils.processPattern(this.config.getSmsMessageTemplate(), msg.getMetaData()); + String[] numbersToList = numbersTo.split(","); + if (this.config.isUseSystemSmsSettings()) { + ctx.getSmsService().sendSms(numbersToList, message); + } else { + for (String numberTo : numbersToList) { + this.smsSender.sendSms(numberTo, message); + } + } + } + + @Override + public void destroy() { + if (this.smsSender != null) { + this.smsSender.destroy(); + } + } + + private SmsSender createSmsSender(TbContext ctx) { + return ctx.getSmsSenderFactory().createSmsSender(this.config.getSmsProviderConfiguration()); + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java new file mode 100644 index 0000000000..14720aa342 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java @@ -0,0 +1,38 @@ +/** + * 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.rule.engine.sms; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; + +@Data +public class TbSendSmsNodeConfiguration implements NodeConfiguration { + + private String numbersToTemplate; + private String smsMessageTemplate; + private boolean useSystemSmsSettings; + private SmsProviderConfiguration smsProviderConfiguration; + + @Override + public NodeConfiguration defaultConfiguration() { + TbSendSmsNodeConfiguration configuration = new TbSendSmsNodeConfiguration(); + configuration.numbersToTemplate = "${userPhone}"; + configuration.smsMessageTemplate = "Device ${deviceName} has high temperature ${temp}"; + configuration.setUseSystemSmsSettings(true); + return configuration; + } +} diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index f575f56906..a228234c3d 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -18,7 +18,13 @@ import { Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models'; +import { + AdminSettings, + MailServerSettings, + SecuritySettings, + TestSmsRequest, + UpdateMessage +} from '@shared/models/settings.models'; @Injectable({ providedIn: 'root' @@ -43,6 +49,11 @@ export class AdminService { return this.http.post('/api/admin/settings/testMail', adminSettings, defaultHttpOptionsFromConfig(config)); } + public sendTestSms(testSmsRequest: TestSmsRequest, + config?: RequestConfig): Observable { + return this.http.post('/api/admin/settings/testSms', testSmsRequest, defaultHttpOptionsFromConfig(config)); + } + public getSecuritySettings(config?: RequestConfig): Observable { return this.http.get(`/api/admin/securitySettings`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 4e98f375c2..e30d993d92 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -108,7 +108,7 @@ export class MenuService { name: 'admin.system-settings', type: 'toggle', path: '/settings', - height: '160px', + height: '200px', icon: 'settings', pages: [ { @@ -125,6 +125,13 @@ export class MenuService { path: '/settings/outgoing-mail', icon: 'mail' }, + { + id: guid(), + name: 'admin.sms-provider', + type: 'link', + path: '/settings/sms-provider', + icon: 'sms' + }, { id: guid(), name: 'admin.security-settings', @@ -187,6 +194,11 @@ export class MenuService { icon: 'mail', path: '/settings/outgoing-mail' }, + { + name: 'admin.sms-provider', + icon: 'sms', + path: '/settings/sms-provider' + }, { name: 'admin.security-settings', icon: 'security', diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index ec7ccea660..a50cef697d 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -77,44 +77,47 @@ import { ComplexFilterPredicateDialogComponent } from '@home/components/filter/c import { KeyFilterDialogComponent } from '@home/components/filter/key-filter-dialog.component'; import { FiltersDialogComponent } from '@home/components/filter/filters-dialog.component'; import { FilterDialogComponent } from '@home/components/filter/filter-dialog.component'; -import { FilterSelectComponent } from './filter/filter-select.component'; +import { FilterSelectComponent } from '@home/components/filter/filter-select.component'; import { FiltersEditComponent } from '@home/components/filter/filters-edit.component'; import { FiltersEditPanelComponent } from '@home/components/filter/filters-edit-panel.component'; import { UserFilterDialogComponent } from '@home/components/filter/user-filter-dialog.component'; -import { FilterUserInfoComponent } from './filter/filter-user-info.component'; -import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.component'; -import { FilterPredicateValueComponent } from './filter/filter-predicate-value.component'; -import { TenantProfileAutocompleteComponent } from './profile/tenant-profile-autocomplete.component'; -import { TenantProfileComponent } from './profile/tenant-profile.component'; -import { TenantProfileDialogComponent } from './profile/tenant-profile-dialog.component'; -import { TenantProfileDataComponent } from './profile/tenant-profile-data.component'; -import { DefaultDeviceProfileConfigurationComponent } from './profile/device/default-device-profile-configuration.component'; -import { DeviceProfileConfigurationComponent } from './profile/device/device-profile-configuration.component'; -import { DeviceProfileComponent } from './profile/device-profile.component'; -import { DefaultDeviceProfileTransportConfigurationComponent } from './profile/device/default-device-profile-transport-configuration.component'; -import { DeviceProfileTransportConfigurationComponent } from './profile/device/device-profile-transport-configuration.component'; -import { DeviceProfileDialogComponent } from './profile/device-profile-dialog.component'; -import { DeviceProfileAutocompleteComponent } from './profile/device-profile-autocomplete.component'; -import { MqttDeviceProfileTransportConfigurationComponent } from './profile/device/mqtt-device-profile-transport-configuration.component'; -import { Lwm2mDeviceProfileTransportConfigurationComponent } from './profile/device/lwm2m-device-profile-transport-configuration.component'; -import { DeviceProfileAlarmsComponent } from './profile/alarm/device-profile-alarms.component'; -import { DeviceProfileAlarmComponent } from './profile/alarm/device-profile-alarm.component'; -import { CreateAlarmRulesComponent } from './profile/alarm/create-alarm-rules.component'; -import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component'; -import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component'; -import { FilterTextComponent } from './filter/filter-text.component'; -import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; -import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; -import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component"; -import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; -import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; -import { DeviceCredentialsComponent } from './device/device-credentials.component'; -import { AlarmScheduleInfoComponent } from './profile/alarm/alarm-schedule-info.component'; +import { FilterUserInfoComponent } from '@home/components/filter/filter-user-info.component'; +import { FilterUserInfoDialogComponent } from '@home/components/filter/filter-user-info-dialog.component'; +import { FilterPredicateValueComponent } from '@home/components/filter/filter-predicate-value.component'; +import { TenantProfileAutocompleteComponent } from '@home/components/profile/tenant-profile-autocomplete.component'; +import { TenantProfileComponent } from '@home/components/profile/tenant-profile.component'; +import { TenantProfileDialogComponent } from '@home/components/profile/tenant-profile-dialog.component'; +import { TenantProfileDataComponent } from '@home/components/profile/tenant-profile-data.component'; +import { DefaultDeviceProfileConfigurationComponent } from '@home/components/profile/device/default-device-profile-configuration.component'; +import { DeviceProfileConfigurationComponent } from '@home/components/profile/device/device-profile-configuration.component'; +import { DeviceProfileComponent } from '@home/components/profile/device-profile.component'; +import { DefaultDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/default-device-profile-transport-configuration.component'; +import { DeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/device-profile-transport-configuration.component'; +import { DeviceProfileDialogComponent } from '@home/components/profile/device-profile-dialog.component'; +import { DeviceProfileAutocompleteComponent } from '@home/components/profile/device-profile-autocomplete.component'; +import { MqttDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/mqtt-device-profile-transport-configuration.component'; +import { Lwm2mDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/lwm2m-device-profile-transport-configuration.component'; +import { DeviceProfileAlarmsComponent } from '@home/components/profile/alarm/device-profile-alarms.component'; +import { DeviceProfileAlarmComponent } from '@home/components/profile/alarm/device-profile-alarm.component'; +import { CreateAlarmRulesComponent } from '@home/components/profile/alarm/create-alarm-rules.component'; +import { AlarmRuleComponent } from '@home/components/profile/alarm/alarm-rule.component'; +import { AlarmRuleConditionComponent } from '@home/components/profile/alarm/alarm-rule-condition.component'; +import { FilterTextComponent } from '@home/components/filter/filter-text.component'; +import { AddDeviceProfileDialogComponent } from '@home/components/profile/add-device-profile-dialog.component'; +import { RuleChainAutocompleteComponent } from '@home/components/rule-chain/rule-chain-autocomplete.component'; +import { DeviceProfileProvisionConfigurationComponent } from '@home/components/profile/device-profile-provision-configuration.component'; +import { AlarmScheduleComponent } from '@home/components/profile/alarm/alarm-schedule.component'; +import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component'; +import { DeviceCredentialsComponent } from '@home/components/device/device-credentials.component'; +import { AlarmScheduleInfoComponent } from '@home/components/profile/alarm/alarm-schedule-info.component'; import { AlarmScheduleDialogComponent } from '@home/components/profile/alarm/alarm-schedule-dialog.component'; -import { EditAlarmDetailsDialogComponent } from './profile/alarm/edit-alarm-details-dialog.component'; +import { EditAlarmDetailsDialogComponent } from '@home/components/profile/alarm/edit-alarm-details-dialog.component'; import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; -import { DefaultTenantProfileConfigurationComponent } from './profile/tenant/default-tenant-profile-configuration.component'; -import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-profile-configuration.component'; +import { DefaultTenantProfileConfigurationComponent } from '@home/components/profile/tenant/default-tenant-profile-configuration.component'; +import { TenantProfileConfigurationComponent } from '@home/components/profile/tenant/tenant-profile-configuration.component'; +import { SmsProviderConfigurationComponent } from '@home/components/sms/sms-provider-configuration.component'; +import { AwsSnsProviderConfigurationComponent } from '@home/components/sms/aws-sns-provider-configuration.component'; +import { TwilioSmsProviderConfigurationComponent } from '@home/components/sms/twilio-sms-provider-configuration.component'; @NgModule({ declarations: @@ -212,7 +215,10 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro DeviceWizardDialogComponent, DeviceCredentialsComponent, AlarmScheduleDialogComponent, - EditAlarmDetailsDialogComponent + EditAlarmDetailsDialogComponent, + SmsProviderConfigurationComponent, + AwsSnsProviderConfigurationComponent, + TwilioSmsProviderConfigurationComponent ], imports: [ CommonModule, @@ -298,7 +304,9 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro AlarmScheduleDialogComponent, EditAlarmDetailsDialogComponent, DeviceProfileProvisionConfigurationComponent, - AlarmScheduleComponent + SmsProviderConfigurationComponent, + AwsSnsProviderConfigurationComponent, + TwilioSmsProviderConfigurationComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html new file mode 100644 index 0000000000..60d1ff9a1e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html @@ -0,0 +1,40 @@ + +
+ + admin.aws-access-key-id + + + {{ 'admin.aws-access-key-id-required' | translate }} + + + + admin.aws-secret-access-key + + + {{ 'admin.aws-secret-access-key-required' | translate }} + + + + admin.aws-region + + + {{ 'admin.aws-region-required' | translate }} + + +
diff --git a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts new file mode 100644 index 0000000000..6b4a2e23ad --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts @@ -0,0 +1,99 @@ +/// +/// 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. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { isDefinedAndNotNull } from '@core/utils'; +import { AwsSnsSmsProviderConfiguration } from '@shared/models/settings.models'; + +@Component({ + selector: 'tb-aws-sns-provider-configuration', + templateUrl: './aws-sns-provider-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AwsSnsProviderConfigurationComponent), + multi: true + }] +}) +export class AwsSnsProviderConfigurationComponent implements ControlValueAccessor, OnInit { + + awsSnsProviderConfigurationFormGroup: FormGroup; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.awsSnsProviderConfigurationFormGroup = this.fb.group({ + accessKeyId: [null, [Validators.required]], + secretAccessKey: [null, [Validators.required]], + region: [null, [Validators.required]] + }); + this.awsSnsProviderConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.awsSnsProviderConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.awsSnsProviderConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: AwsSnsSmsProviderConfiguration | null): void { + if (isDefinedAndNotNull(value)) { + this.awsSnsProviderConfigurationFormGroup.patchValue(value, {emitEvent: false}); + } + } + + private updateModel() { + let configuration: AwsSnsSmsProviderConfiguration = null; + if (this.awsSnsProviderConfigurationFormGroup.valid) { + configuration = this.awsSnsProviderConfigurationFormGroup.value; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html new file mode 100644 index 0000000000..e852b82314 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html @@ -0,0 +1,44 @@ + +
+ + admin.sms-provider-type + + + {{smsProviderTypeTranslations.get(type) | translate}} + + + + {{ 'admin.sms-provider-type-required' | translate }} + + +
+ + + + + + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts new file mode 100644 index 0000000000..4aafff9369 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts @@ -0,0 +1,123 @@ +/// +/// 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. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + DeviceProfileTransportConfiguration, + DeviceTransportType, + deviceTransportTypeTranslationMap +} from '@shared/models/device.models'; +import { deepClone } from '@core/utils'; +import { + createSmsProviderConfiguration, + SmsProviderConfiguration, + SmsProviderType, + smsProviderTypeTranslationMap +} from '@shared/models/settings.models'; + +@Component({ + selector: 'tb-sms-provider-configuration', + templateUrl: './sms-provider-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SmsProviderConfigurationComponent), + multi: true + }] +}) +export class SmsProviderConfigurationComponent implements ControlValueAccessor, OnInit { + + smsProviderType = SmsProviderType; + smsProviderTypes = Object.keys(SmsProviderType); + smsProviderTypeTranslations = smsProviderTypeTranslationMap; + + smsProviderConfigurationFormGroup: FormGroup; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.smsProviderConfigurationFormGroup = this.fb.group({ + type: [null, Validators.required], + configuration: [null, Validators.required] + }); + this.smsProviderConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + this.smsProviderConfigurationFormGroup.get('type').valueChanges.subscribe(() => { + this.smsProviderTypeChanged(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.smsProviderConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.smsProviderConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: SmsProviderConfiguration | null): void { + const configuration = deepClone(value); + const type = configuration?.type; + if (configuration) { + delete configuration.type; + } + this.smsProviderConfigurationFormGroup.patchValue({type}, {emitEvent: false}); + this.smsProviderConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); + } + + private smsProviderTypeChanged() { + const type: SmsProviderType = this.smsProviderConfigurationFormGroup.get('type').value; + this.smsProviderConfigurationFormGroup.patchValue({configuration: createSmsProviderConfiguration(type)}, {emitEvent: false}); + } + + private updateModel() { + let configuration: SmsProviderConfiguration = null; + if (this.smsProviderConfigurationFormGroup.valid) { + configuration = this.smsProviderConfigurationFormGroup.getRawValue().configuration; + configuration.type = this.smsProviderConfigurationFormGroup.getRawValue().type; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html new file mode 100644 index 0000000000..449140a18e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html @@ -0,0 +1,44 @@ + +
+ + admin.number-from + + + {{ 'admin.number-from-required' | translate }} + + + {{ 'admin.phone-number-pattern' | translate }} + + + + + admin.twilio-account-sid + + + {{ 'admin.twilio-account-sid-required' | translate }} + + + + admin.twilio-account-token + + + {{ 'admin.twilio-account-token-required' | translate }} + + +
diff --git a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts new file mode 100644 index 0000000000..9d2c1a154b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts @@ -0,0 +1,101 @@ +/// +/// 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. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { isDefinedAndNotNull } from '@core/utils'; +import { phoneNumberPattern, TwilioSmsProviderConfiguration } from '@shared/models/settings.models'; + +@Component({ + selector: 'tb-twilio-sms-provider-configuration', + templateUrl: './twilio-sms-provider-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TwilioSmsProviderConfigurationComponent), + multi: true + }] +}) +export class TwilioSmsProviderConfigurationComponent implements ControlValueAccessor, OnInit { + + twilioSmsProviderConfigurationFormGroup: FormGroup; + + phoneNumberPattern = phoneNumberPattern; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.twilioSmsProviderConfigurationFormGroup = this.fb.group({ + numberFrom: [null, [Validators.required, Validators.pattern(phoneNumberPattern)]], + accountSid: [null, [Validators.required]], + accountToken: [null, [Validators.required]] + }); + this.twilioSmsProviderConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.twilioSmsProviderConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.twilioSmsProviderConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: TwilioSmsProviderConfiguration | null): void { + if (isDefinedAndNotNull(value)) { + this.twilioSmsProviderConfigurationFormGroup.patchValue(value, {emitEvent: false}); + } + } + + private updateModel() { + let configuration: TwilioSmsProviderConfiguration = null; + if (this.twilioSmsProviderConfigurationFormGroup.valid) { + configuration = this.twilioSmsProviderConfigurationFormGroup.value; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 7fefbfb521..36413da3cd 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -31,6 +31,7 @@ import { Observable } from 'rxjs'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { OAuth2Service } from '@core/http/oauth2.service'; import { UserProfileResolver } from '@home/pages/profile/profile-routing.module'; +import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; @Injectable() export class OAuth2LoginProcessingUrlResolver implements Resolve { @@ -85,6 +86,19 @@ const routes: Routes = [ } } }, + { + path: 'sms-provider', + component: SmsProviderComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.sms-provider-settings', + breadcrumb: { + label: 'admin.sms-provider', + icon: 'sms' + } + } + }, { path: 'security-settings', component: SecuritySettingsComponent, diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index 73fef6a32f..b3dd8d4531 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -24,12 +24,16 @@ import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-sett import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component'; +import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; +import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component'; @NgModule({ declarations: [ GeneralSettingsComponent, MailServerComponent, + SmsProviderComponent, + SendTestSmsDialogComponent, SecuritySettingsComponent, OAuth2SettingsComponent ], diff --git a/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.html b/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.html new file mode 100644 index 0000000000..7b2b460294 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.html @@ -0,0 +1,70 @@ + +
+ +

admin.send-test-sms

+ + +
+ + +
+
+
+ + admin.number-to + + + {{ 'admin.number-to-required' | translate }} + + + {{ 'admin.phone-number-pattern' | translate }} + + + + + admin.sms-message + + + {{ 'admin.sms-message-required' | translate }} + + + {{ 'admin.sms-message-max-length' | translate }} + + +
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.ts new file mode 100644 index 0000000000..b29f48e15b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.ts @@ -0,0 +1,87 @@ +/// +/// 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. +/// + +import { Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { phoneNumberPattern, SmsProviderConfiguration, TestSmsRequest } from '@shared/models/settings.models'; +import { AdminService } from '@core/http/admin.service'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; + +export interface SendTestSmsDialogData { + smsProviderConfiguration: SmsProviderConfiguration; +} + +@Component({ + selector: 'tb-send-test-sms-dialog', + templateUrl: './send-test-sms-dialog.component.html', + styleUrls: [] +}) +export class SendTestSmsDialogComponent extends + DialogComponent implements OnInit { + + phoneNumberPattern = phoneNumberPattern; + + sendTestSmsFormGroup: FormGroup; + + smsProviderConfiguration = this.data.smsProviderConfiguration; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: SendTestSmsDialogData, + private adminService: AdminService, + private translate: TranslateService, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store, router, dialogRef); + } + + ngOnInit(): void { + this.sendTestSmsFormGroup = this.fb.group({ + numberTo: [null, [Validators.required, Validators.pattern(phoneNumberPattern)]], + message: [null, [Validators.required, Validators.maxLength(1600)]] + }); + } + + close(): void { + this.dialogRef.close(); + } + + sendTestSms(): void { + const request: TestSmsRequest = { + providerConfiguration: this.smsProviderConfiguration, + numberTo: this.sendTestSmsFormGroup.value.numberTo, + message: this.sendTestSmsFormGroup.value.message + }; + this.adminService.sendTestSms(request).subscribe( + () => { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('admin.test-sms-sent'), + target: 'sendTestSmsDialogContent', + verticalPosition: 'bottom', + horizontalPosition: 'left', + type: 'success' + })); + } + ); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html new file mode 100644 index 0000000000..426c3eda25 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html @@ -0,0 +1,50 @@ + +
+ + +
+ admin.sms-provider-settings + +
+
+
+ + +
+ +
+
+ + +
+ + +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss new file mode 100644 index 0000000000..bb3718c2da --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss @@ -0,0 +1,18 @@ +/** + * 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. + */ +:host { + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts new file mode 100644 index 0000000000..69510bd683 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts @@ -0,0 +1,95 @@ +/// +/// 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. +/// + +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import { Router } from '@angular/router'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { AdminSettings, SmsProviderConfiguration } from '@shared/models/settings.models'; +import { AdminService } from '@core/http/admin.service'; +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; +import { MatDialog } from '@angular/material/dialog'; +import { SendTestSmsDialogComponent, SendTestSmsDialogData } from '@home/pages/admin/send-test-sms-dialog.component'; + +@Component({ + selector: 'tb-sms-provider', + templateUrl: './sms-provider.component.html', + styleUrls: ['./sms-provider.component.scss', './settings-card.scss'] +}) +export class SmsProviderComponent extends PageComponent implements OnInit, HasConfirmForm { + + smsProvider: FormGroup; + adminSettings: AdminSettings; + + constructor(protected store: Store, + private router: Router, + private adminService: AdminService, + private dialog: MatDialog, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.buildSmsProviderForm(); + this.adminService.getAdminSettings('sms', {ignoreErrors: true}).subscribe( + (adminSettings) => { + this.adminSettings = adminSettings; + this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); + }, + () => { + this.adminSettings = { + key: 'sms', + jsonValue: null + }; + this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); + } + ); + } + + buildSmsProviderForm() { + this.smsProvider = this.fb.group({ + configuration: [null, [Validators.required]] + }); + this.registerDisableOnLoadFormControl(this.smsProvider.get('configuration')); + } + + sendTestSms(): void { + this.dialog.open(SendTestSmsDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + smsProviderConfiguration: this.smsProvider.value.configuration + } + }); + } + + save(): void { + this.adminSettings.jsonValue = this.smsProvider.value.configuration; + this.adminService.saveAdminSettings(this.adminSettings).subscribe( + (adminSettings) => { + this.adminSettings = adminSettings; + this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); + } + ); + } + + confirmForm(): FormGroup { + return this.smsProvider; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html index 3097e0e303..2c1f2c603a 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

{{ 'dashboard.public-dashboard-title' | translate }}

diff --git a/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html b/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html index 52d221c55f..0221d412c8 100644 --- a/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

user.activation-link

diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 71dcb16a37..049ba03270 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -59,6 +59,7 @@ const helpBaseUrl = 'https://thingsboard.io'; export const HelpLinks = { linksMap: { outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings', + smsProviderSettings: helpBaseUrl + '/docs/user-guide/ui/sms-provider-settings', securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', oauth2Settings: helpBaseUrl + '/docs/user-guide/oauth-2-support/', ruleEngine: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/overview/', diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index f71ac1bb2e..313b92d603 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -14,6 +14,8 @@ /// limitations under the License. /// +import { DeviceTransportType } from '@shared/models/device.models'; + export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; export interface AdminSettings { @@ -60,3 +62,66 @@ export interface UpdateMessage { message: string; updateAvailable: boolean; } + +export const phoneNumberPattern = /^\+[1-9]\d{1,14}$/; + +export enum SmsProviderType { + AWS_SNS = 'AWS_SNS', + TWILIO = 'TWILIO' +} + +export const smsProviderTypeTranslationMap = new Map( + [ + [SmsProviderType.AWS_SNS, 'admin.sms-provider-type-aws-sns'], + [SmsProviderType.TWILIO, 'admin.sms-provider-type-twilio'] + ] +); + +export interface AwsSnsSmsProviderConfiguration { + accessKeyId?: string; + secretAccessKey?: string; + region?: string; +} + +export interface TwilioSmsProviderConfiguration { + accountSid?: string; + accountToken?: string; + numberFrom?: string; +} + +export type SmsProviderConfigurations = AwsSnsSmsProviderConfiguration & TwilioSmsProviderConfiguration; + +export interface SmsProviderConfiguration extends SmsProviderConfigurations { + type: SmsProviderType; +} + +export interface TestSmsRequest { + providerConfiguration: SmsProviderConfiguration; + numberTo: string; + message: string; +} + +export function createSmsProviderConfiguration(type: SmsProviderType): SmsProviderConfiguration { + let smsProviderConfiguration: SmsProviderConfiguration; + if (type) { + switch (type) { + case SmsProviderType.AWS_SNS: + const awsSnsSmsProviderConfiguration: AwsSnsSmsProviderConfiguration = { + accessKeyId: '', + secretAccessKey: '', + region: 'us-east-1' + }; + smsProviderConfiguration = {...awsSnsSmsProviderConfiguration, type: SmsProviderType.AWS_SNS}; + break; + case SmsProviderType.TWILIO: + const twilioSmsProviderConfiguration: TwilioSmsProviderConfiguration = { + numberFrom: '', + accountSid: '', + accountToken: '' + }; + smsProviderConfiguration = {...twilioSmsProviderConfiguration, type: SmsProviderType.TWILIO}; + break; + } + } + return smsProviderConfiguration; +} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index cddc73415e..ca3717eeff 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -104,6 +104,33 @@ "proxy-user": "Proxy user", "proxy-password": "Proxy password", "send-test-mail": "Send test mail", + "sms-provider": "SMS provider", + "sms-provider-settings": "SMS provider settings", + "sms-provider-type": "SMS provider type", + "sms-provider-type-required": "SMS provider type is required.", + "sms-provider-type-aws-sns": "Amazon SNS", + "sms-provider-type-twilio": "Twilio", + "aws-access-key-id": "AWS Access Key ID", + "aws-access-key-id-required": "AWS Access Key ID is required", + "aws-secret-access-key": "AWS Secret Access Key", + "aws-secret-access-key-required": "AWS Secret Access Key is required", + "aws-region": "AWS Region", + "aws-region-required": "AWS Region is required", + "number-from": "Phone Number From", + "number-from-required": "Phone Number From is required.", + "number-to": "Phone Number To", + "number-to-required": "Phone Number To is required.", + "phone-number-hint": "Phone Number in E.164 format, ex. +19995550123", + "phone-number-pattern": "Invalid phone number. Should be in E.164 format, ex. +19995550123.", + "sms-message": "SMS message", + "sms-message-required": "SMS message is required.", + "sms-message-max-length": "SMS message can't be longer 1600 characters", + "twilio-account-sid": "Twilio Account SID", + "twilio-account-sid-required": "Twilio Account SID is required", + "twilio-account-token": "Twilio Account Token", + "twilio-account-token-required": "Twilio Account Token is required", + "send-test-sms": "Send test SMS", + "test-sms-sent": "Test SMS was successfully sent!", "security-settings": "Security settings", "password-policy": "Password policy", "minimum-password-length": "Minimum password length", From ffdf56222e843d80c2f27dbcd5ecc54c1ca2583b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 14:58:24 +0200 Subject: [PATCH 54/84] Add send sms rule node config ui --- .../org/thingsboard/server/service/sms/AbstractSmsSender.java | 1 - .../resources/public/static/rulenode/rulenode-core-config.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java index 1add9cd79b..98c96c3564 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.sms; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.sms.SmsSender; import org.thingsboard.rule.engine.api.sms.exception.SmsParseException; -import sun.misc.Regexp; import java.util.regex.Pattern; diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index a8bd79d68b..0926cb41cb 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,4 +1,4 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@home/components/public-api"),require("@angular/cdk/coercion"),require("@angular/material/chips"),require("@angular/material/autocomplete"),require("rxjs"),require("rxjs/operators")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@core/public-api","@angular/cdk/keycodes","@home/components/public-api","@angular/cdk/coercion","@angular/material/chips","@angular/material/autocomplete","rxjs","rxjs/operators"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.core,e.ng.cdk.keycodes,e.publicApi$2,e.ng.cdk.coercion,e.ng.material.chips,e.ng.material.autocomplete,e.rxjs,e.rxjs.operators)}(this,(function(e,t,r,n,a,o,i,l,s,m,u,d,p,c,f){"use strict"; +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@home/components/public-api"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@angular/cdk/coercion"),require("@angular/material/chips"),require("@angular/material/autocomplete"),require("rxjs"),require("rxjs/operators")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@home/components/public-api","@core/public-api","@angular/cdk/keycodes","@angular/cdk/coercion","@angular/material/chips","@angular/material/autocomplete","rxjs","rxjs/operators"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.publicApi$1,e.core,e.ng.cdk.keycodes,e.ng.cdk.coercion,e.ng.material.chips,e.ng.material.autocomplete,e.rxjs,e.rxjs.operators)}(this,(function(e,t,r,n,a,o,i,l,s,m,u,d,p,c,f){"use strict"; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. @@ -12,5 +12,5 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - ***************************************************************************** */var g=function(e,t){return(g=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(e,t)};function y(e,t){function r(){this.constructor=e}g(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function b(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function C(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}Object.create;var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]],notifyDevice:[!e||e.scope,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var U,j,H,G=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(U||(U={})),function(e){e.ASC="ASC",e.DESC="DESC"}(j||(j={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(H||(H={}));var z,$=new Map([[H.STANDARD,"tb.rulenode.sqs-queue-standard"],[H.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[ne,ae,oe,ie]})],e)}(),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=H,n.sqsQueueTypes=Object.keys(H),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=Q,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce],imports:[r.CommonModule,a.SharedModule,le],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce]})],e)}(),Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ve=function(){function e(){}return e=b([t.NgModule({declarations:[Fe,xe,Te,qe,Se,Ie,ke,Ne],imports:[r.CommonModule,a.SharedModule,le],exports:[Fe,xe,Te,qe,Se,Ie,ke,Ne]})],e)}(),Ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ae=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=G,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(G.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(G.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=U,n.fetchModes=Object.keys(U),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===U.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(){function e(){}return e=b([t.NgModule({declarations:[Ee,Ae,Le,Me,Pe,Re,we,Oe],imports:[r.CommonModule,a.SharedModule,le],exports:[Ee,Ae,Le,Me,Pe,Re,we,Oe]})],e)}(),Ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Be=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),je=function(){function e(){}return e=b([t.NgModule({declarations:[Ke,Be,Ue],imports:[r.CommonModule,a.SharedModule,le],exports:[Ke,Be,Ue]})],e)}(),He=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[ve,Ve,De,je,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=He,e.ɵa=F,e.ɵb=ve,e.ɵba=be,e.ɵbb=he,e.ɵbc=Ce,e.ɵbd=le,e.ɵbe=ne,e.ɵbf=ae,e.ɵbg=oe,e.ɵbh=ie,e.ɵbi=Ve,e.ɵbj=Fe,e.ɵbk=xe,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=De,e.ɵbs=Ee,e.ɵbt=Ae,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=je,e.ɵcb=Ke,e.ɵcc=Be,e.ɵcd=Ue,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=se,e.ɵs=me,e.ɵt=ue,e.ɵu=de,e.ɵv=pe,e.ɵw=ce,e.ɵx=fe,e.ɵy=ge,e.ɵz=ye,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var g=function(e,t){return(g=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(e,t)};function y(e,t){function r(){this.constructor=e}g(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function b(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function C(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}Object.create;var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]],notifyDevice:[!e||e.scope,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var U,j,H,G=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(U||(U={})),function(e){e.ASC="ASC",e.DESC="DESC"}(j||(j={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(H||(H={}));var z,$=new Map([[H.STANDARD,"tb.rulenode.sqs-queue-standard"],[H.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.truncate=n,s.fb=o,s.placeholder="tb.rulenode.message-type",s.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],s.messageTypes=[],s.messageTypesList=[],s.searchText="",s.propagateChange=function(e){},s.messageTypeConfigForm=s.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;s.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return s}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule],exports:[ne,ae,oe,ie]})],e)}(),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=H,n.sqsQueueTypes=Object.keys(H),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=Q,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.sendSmsConfigForm},r.prototype.onConfigurationSet=function(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[i.Validators.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[i.Validators.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmsSettings"]},r.prototype.updateValidators=function(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([i.Validators.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-sms-config",template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Fe=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce,ve],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule,le],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce,ve]})],e)}(),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ve=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ee=function(){function e(){}return e=b([t.NgModule({declarations:[xe,Te,qe,Se,Ie,ke,Ne,Ve],imports:[r.CommonModule,a.SharedModule,le],exports:[xe,Te,qe,Se,Ie,ke,Ne,Ve]})],e)}(),Ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=G,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(G.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(G.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n.fetchMode=U,n.fetchModes=Object.keys(U),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===U.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ke=function(){function e(){}return e=b([t.NgModule({declarations:[Ae,Le,Me,Pe,Re,we,Oe,De],imports:[r.CommonModule,a.SharedModule,le],exports:[Ae,Le,Me,Pe,Re,we,Oe,De]})],e)}(),Be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),je=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),He=function(){function e(){}return e=b([t.NgModule({declarations:[Be,Ue,je],imports:[r.CommonModule,a.SharedModule,le],exports:[Be,Ue,je]})],e)}(),Ge=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":"Comma separated Phone Numbers, use ${metaKeyName} to substitute variables from metadata","sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","sms-message-template-hint":"SMS message template, use ${metaKeyName} to substitute variables from metadata","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[Fe,Ee,Ke,He,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=Ge,e.ɵa=F,e.ɵb=Fe,e.ɵba=be,e.ɵbb=he,e.ɵbc=Ce,e.ɵbd=ve,e.ɵbe=le,e.ɵbf=ne,e.ɵbg=ae,e.ɵbh=oe,e.ɵbi=ie,e.ɵbj=Ee,e.ɵbk=xe,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=Ve,e.ɵbs=Ke,e.ɵbt=Ae,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=De,e.ɵcb=He,e.ɵcc=Be,e.ɵcd=Ue,e.ɵce=je,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=se,e.ɵs=me,e.ɵt=ue,e.ɵu=de,e.ɵv=pe,e.ɵw=ce,e.ɵx=fe,e.ɵy=ge,e.ɵz=ye,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file From 449cb5941d06ce496bc8104eeb03f18bea2be708 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 15:17:31 +0200 Subject: [PATCH 55/84] Fix Api Usage State alias --- .../home/components/entity/entity-filter.component.ts | 5 ++++- ui-ngx/src/app/shared/models/alias.models.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts index 64100eacfc..2555b3d336 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts @@ -147,6 +147,9 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit { entityViewNameFilter: [filter ? filter.entityViewNameFilter : '', []], }); break; + case AliasFilterType.apiUsageState: + this.filterFormGroup = this.fb.group({}); + break; case AliasFilterType.relationsQuery: case AliasFilterType.assetSearchQuery: case AliasFilterType.deviceSearchQuery: @@ -190,7 +193,7 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit { private filterTypeChanged(type: AliasFilterType) { let resolveMultiple = true; - if (type === AliasFilterType.singleEntity || type === AliasFilterType.stateEntity) { + if (type === AliasFilterType.singleEntity || type === AliasFilterType.stateEntity || type === AliasFilterType.apiUsageState) { resolveMultiple = false; } if (this.resolveMultiple !== resolveMultiple) { diff --git a/ui-ngx/src/app/shared/models/alias.models.ts b/ui-ngx/src/app/shared/models/alias.models.ts index f437931f27..74662cfca2 100644 --- a/ui-ngx/src/app/shared/models/alias.models.ts +++ b/ui-ngx/src/app/shared/models/alias.models.ts @@ -108,6 +108,7 @@ export interface EntitySearchQueryFilter { fetchLastLevelOnly?: boolean; } +// tslint:disable-next-line:no-empty-interface export interface ApiUsageStateFilter { } From 1c79480b332c20151c9d99acef82f3059522617a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 17 Nov 2020 17:32:47 +0200 Subject: [PATCH 56/84] created sendApiFeatureStateEmail --- .../DefaultTbApiUsageStateService.java | 37 +++-- .../service/mail/DefaultMailService.java | 29 ++++ .../main/resources/i18n/messages.properties | 3 +- .../resources/templates/state.disabled.ftl | 126 ++++++++++++++++++ .../resources/templates/state.enabled.ftl | 126 ++++++++++++++++++ .../resources/templates/state.warning.ftl | 126 ++++++++++++++++++ .../common/data/ApiUsageStateMailMessage.java | 25 ++++ .../rule/engine/api/MailService.java | 5 + 8 files changed, 467 insertions(+), 10 deletions(-) create mode 100644 application/src/main/resources/templates/state.disabled.ftl create mode 100644 application/src/main/resources/templates/state.enabled.ftl create mode 100644 application/src/main/resources/templates/state.warning.ftl create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index c7206712c3..9b45ffdaed 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -17,21 +17,21 @@ package org.thingsboard.server.service.apiusage; import com.google.common.util.concurrent.FutureCallback; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Lazy; -import org.springframework.context.event.EventListener; -import org.springframework.core.annotation.Order; -import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; @@ -54,14 +55,11 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.scheduler.SchedulerComponent; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; import javax.annotation.PostConstruct; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -94,6 +92,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private final ApiUsageStateService apiUsageStateService; private final SchedulerComponent scheduler; private final TbTenantProfileCache tenantProfileCache; + private final MailService mailService; @Lazy @Autowired @@ -118,7 +117,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TimeseriesService tsService, ApiUsageStateService apiUsageStateService, SchedulerComponent scheduler, - TbTenantProfileCache tenantProfileCache) { + TbTenantProfileCache tenantProfileCache, MailService mailService) { this.clusterService = clusterService; this.partitionService = partitionService; this.tenantService = tenantService; @@ -126,6 +125,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { this.apiUsageStateService = apiUsageStateService; this.scheduler = scheduler; this.tenantProfileCache = tenantProfileCache; + this.mailService = mailService; } @PostConstruct @@ -286,7 +286,26 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { List stateTelemetry = new ArrayList<>(); result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))))); tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); - //TODO: notify tenant admin via email! + + String email = tenantService.findTenantById(state.getTenantId()).getEmail(); + + if (StringUtils.isNotEmpty(email)) { + result.forEach((apiFeature, stateValue) -> { + ApiUsageRecordKey[] keys = ApiUsageRecordKey.getKeys(apiFeature); + ApiUsageStateMailMessage[] msgs = new ApiUsageStateMailMessage[keys.length]; + for (int i = 0; i < keys.length; i++) { + ApiUsageRecordKey key = keys[i]; + msgs[i] = new ApiUsageStateMailMessage(key, state.getProfileThreshold(key), state.get(key)); + } + try { + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs); + } catch (ThingsboardException e) { + log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); + } + }); + } else { + log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId()); + } } private void checkStartOfNextCycle() { diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index bcf541880c..1b85c73196 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -29,6 +29,9 @@ import org.springframework.stereotype.Service; import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.ApiFeature; +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; @@ -246,6 +249,32 @@ public class DefaultMailService implements MailService { sendMail(mailSender, mailFrom, email, subject, message); } + @Override + public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage[] stateMailMessages) throws ThingsboardException { + String subject = messages.getMessage("api.usage.state", null, Locale.US); + + Map model = new HashMap<>(); + model.put("apiFeature", apiFeature.getApiStateKey()); + model.put("apiUsageStateMailMessages", stateMailMessages); + + model.put(TARGET_EMAIL, email); + + String message = null; + + switch (stateValue) { + case ENABLED: + message = mergeTemplateIntoString("state.enabled.ftl", model); + break; + case WARNING: + message = mergeTemplateIntoString("state.warning.ftl", model); + break; + case DISABLED: + message = mergeTemplateIntoString("state.disabled.ftl", model); + break; + } + sendMail(mailSender, mailFrom, email, subject, message); + } + private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, String subject, String message) throws ThingsboardException { diff --git a/application/src/main/resources/i18n/messages.properties b/application/src/main/resources/i18n/messages.properties index a34582e132..9de9c4b789 100644 --- a/application/src/main/resources/i18n/messages.properties +++ b/application/src/main/resources/i18n/messages.properties @@ -3,4 +3,5 @@ activation.subject=Your account activation on Thingsboard account.activated.subject=Thingsboard - your account has been activated reset.password.subject=Thingsboard - Password reset has been requested password.was.reset.subject=Thingsboard - your account password has been reset -account.lockout.subject=Thingsboard - User account has been lockout \ No newline at end of file +account.lockout.subject=Thingsboard - User account has been lockout +api.usage.state=Thingsboard - Api Usage State for tenant has been updated \ No newline at end of file diff --git a/application/src/main/resources/templates/state.disabled.ftl b/application/src/main/resources/templates/state.disabled.ftl new file mode 100644 index 0000000000..c1f81da181 --- /dev/null +++ b/application/src/main/resources/templates/state.disabled.ftl @@ -0,0 +1,126 @@ +<#-- + + 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. + +--> + + + + + +Thingsboard - Api Usage State + + + + + + + + + + + +
+
+ + +
+ + + + + + + + <#list apiUsageStateMailMessages as msg> + + + + + + + + + +
+

Thingsboard Api Usage State for tenant has been updated

+
+ Thingsboard Usage state ${apiFeature} was updated to status DISABLED. +
+ ${msg.key.apiLimitKey} = ${msg.threshold} +
+ ${msg.key.apiCountKey} = ${msg.value} +
+ — The Thingsboard +
+ +
+
+ + diff --git a/application/src/main/resources/templates/state.enabled.ftl b/application/src/main/resources/templates/state.enabled.ftl new file mode 100644 index 0000000000..497b3cd780 --- /dev/null +++ b/application/src/main/resources/templates/state.enabled.ftl @@ -0,0 +1,126 @@ +<#-- + + 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. + +--> + + + + + +Thingsboard - Api Usage State + + + + + + + + + + + +
+
+ + +
+ + + + + + + + <#list apiUsageStateMailMessages as msg> + + + + + + + + + +
+

Thingsboard Api Usage State for tenant has been updated

+
+ Thingsboard Usage state ${apiFeature} was updated to status ENABLED. +
+ ${msg.key.apiLimitKey} = ${msg.threshold} +
+ ${msg.key.apiCountKey} = ${msg.value} +
+ — The Thingsboard +
+ +
+
+ + diff --git a/application/src/main/resources/templates/state.warning.ftl b/application/src/main/resources/templates/state.warning.ftl new file mode 100644 index 0000000000..bd4424a0b9 --- /dev/null +++ b/application/src/main/resources/templates/state.warning.ftl @@ -0,0 +1,126 @@ +<#-- + + 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. + +--> + + + + + +Thingsboard - Api Usage State + + + + + + + + + + + +
+
+ + +
+ + + + + + + + <#list apiUsageStateMailMessages as msg> + + + + + + + + + +
+

Thingsboard Api Usage State for tenant has been updated

+
+ Thingsboard Usage state ${apiFeature} was updated to status WARNING. +
+ ${msg.key.apiLimitKey} = ${msg.threshold} +
+ ${msg.key.apiCountKey} = ${msg.value} +
+ — The Thingsboard +
+ +
+
+ + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java new file mode 100644 index 0000000000..cab3789fdc --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java @@ -0,0 +1,25 @@ +/** + * 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; + +import lombok.Data; + +@Data +public class ApiUsageStateMailMessage { + private final ApiUsageRecordKey key; + private final long threshold; + private final long value; +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java index a36089107d..59b96a2e67 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java @@ -16,6 +16,9 @@ package org.thingsboard.rule.engine.api; import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.server.common.data.ApiFeature; +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardException; import javax.mail.MessagingException; @@ -39,4 +42,6 @@ public interface MailService { void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException; void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; + + void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage[] stateMailMessages) throws ThingsboardException; } From ee3abe59eabc8463ae0be84cb65b7731ae9d1f35 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 17 Nov 2020 18:06:08 +0200 Subject: [PATCH 57/84] added mail executor to DefaultTbApiUsageStateService --- .../DefaultTbApiUsageStateService.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 9b45ffdaed..84dfd7310b 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -59,6 +59,7 @@ import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -67,6 +68,8 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -111,6 +114,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private final Lock updateLock = new ReentrantLock(); + private final ExecutorService mailExecutor; + public DefaultTbApiUsageStateService(TbClusterService clusterService, PartitionService partitionService, TenantService tenantService, @@ -126,6 +131,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { this.scheduler = scheduler; this.tenantProfileCache = tenantProfileCache; this.mailService = mailService; + this.mailExecutor = Executors.newSingleThreadExecutor(); } @PostConstruct @@ -297,11 +303,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { ApiUsageRecordKey key = keys[i]; msgs[i] = new ApiUsageStateMailMessage(key, state.getProfileThreshold(key), state.get(key)); } - try { - mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs); - } catch (ThingsboardException e) { - log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); - } + mailExecutor.submit(() -> { + try { + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs); + } catch (ThingsboardException e) { + log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); + } + }); }); } else { log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId()); @@ -386,4 +394,10 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + @PreDestroy + private void destroy() { + if (mailExecutor != null) { + mailExecutor.shutdownNow(); + } + } } From 3feb32c0f6fae02c3a51ba99457a7da1fb8e2fb0 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 17 Nov 2020 19:00:38 +0200 Subject: [PATCH 58/84] Improvements to the templates --- .../DefaultTbApiUsageStateService.java | 2 +- .../service/mail/DefaultMailService.java | 37 ++- .../resources/templates/state.disabled.ftl | 242 ++++++++++-------- .../server/common/data/ApiFeature.java | 13 +- .../rule/engine/api/MailService.java | 2 +- 5 files changed, 182 insertions(+), 114 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 84dfd7310b..59b63d7200 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -305,7 +305,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } mailExecutor.submit(() -> { try { - mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs); + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs[0]); } catch (ThingsboardException e) { log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); } diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 1b85c73196..19509243ef 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -250,13 +250,11 @@ public class DefaultMailService implements MailService { } @Override - public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage[] stateMailMessages) throws ThingsboardException { + public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException { String subject = messages.getMessage("api.usage.state", null, Locale.US); Map model = new HashMap<>(); - model.put("apiFeature", apiFeature.getApiStateKey()); - model.put("apiUsageStateMailMessages", stateMailMessages); - + model.put("apiFeature", apiFeature.getLabel()); model.put(TARGET_EMAIL, email); String message = null; @@ -269,12 +267,43 @@ public class DefaultMailService implements MailService { message = mergeTemplateIntoString("state.warning.ftl", model); break; case DISABLED: + model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg)); message = mergeTemplateIntoString("state.disabled.ftl", model); break; } sendMail(mailSender, mailFrom, email, subject, message); } + private String toDisabledValueLabel(ApiFeature apiFeature) { + switch (apiFeature) { + case DB: + return "saved"; + case TRANSPORT: + return "received"; + case JS: + case RE: + return "invoked"; + default: + throw new RuntimeException("Not implemented!"); + } + } + + private String toDisabledValueLabel(ApiUsageStateMailMessage msg) { + switch (msg.getKey()) { + case STORAGE_DP_COUNT: + case TRANSPORT_DP_COUNT: + return (msg.getThreshold() / 1000000) + "M data points"; + case TRANSPORT_MSG_COUNT: + return (msg.getThreshold() / 1000000) + "M messages"; + case JS_EXEC_COUNT: + return (msg.getThreshold() / 1000000) + "M JavaScript functions"; + case RE_EXEC_COUNT: + return (msg.getThreshold() / 1000000) + "M Rule Engine nodes"; + default: + throw new RuntimeException("Not implemented!"); + } + } + private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, String subject, String message) throws ThingsboardException { diff --git a/application/src/main/resources/templates/state.disabled.ftl b/application/src/main/resources/templates/state.disabled.ftl index c1f81da181..33a368cceb 100644 --- a/application/src/main/resources/templates/state.disabled.ftl +++ b/application/src/main/resources/templates/state.disabled.ftl @@ -15,112 +15,148 @@ limitations under the License. --> - - + + - - -Thingsboard - Api Usage State - - - + + + Thingsboard - Api Usage State + + + - - - - - - + + +
-
- - -
- - - - - - - - <#list apiUsageStateMailMessages as msg> - - - - - - - - - -
-

Thingsboard Api Usage State for tenant has been updated

-
- Thingsboard Usage state ${apiFeature} was updated to status DISABLED. -
- ${msg.key.apiLimitKey} = ${msg.threshold} -
- ${msg.key.apiCountKey} = ${msg.value} -
- — The Thingsboard -
- -
-
+ + + + + +
+ + + + + + + + + + + + + + + +
+

Your ThingsBoard account feature was disabled

+
We have disabled the ${apiFeature} for your account because ThingsBoard has already  ${apiLimitValueLabel} + . +
Please contact your system administrator to resolve the issue. +
— The ThingsBoard +
+
+ + + + + +
This email was sent to ${targetEmail} by ThingsBoard. +
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java index e12cb5fb14..fe923cefaa 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java @@ -18,16 +18,19 @@ package org.thingsboard.server.common.data; import lombok.Getter; public enum ApiFeature { - TRANSPORT("transportApiState"), - DB("dbApiState"), - RE("ruleEngineApiState"), - JS("jsExecutionApiState"); + TRANSPORT("transportApiState", "Device API"), + DB("dbApiState", "Telemetry persistence"), + RE("ruleEngineApiState", "Rule engine execution"), + JS("jsExecutionApiState", "JavaScript functions execution"); @Getter private final String apiStateKey; + @Getter + private final String label; - ApiFeature(String apiStateKey) { + ApiFeature(String apiStateKey, String label) { this.apiStateKey = apiStateKey; + this.label = label; } } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java index 59b96a2e67..4a4228731c 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java @@ -43,5 +43,5 @@ public interface MailService { void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; - void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage[] stateMailMessages) throws ThingsboardException; + void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException; } From fc7b42c3d6738a63905b0c02b42ee2f7681a9d1e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 18 Nov 2020 15:28:16 +0200 Subject: [PATCH 59/84] Improvements to the templates --- .../DefaultTbApiUsageStateService.java | 35 ++- .../service/mail/DefaultMailService.java | 32 ++- .../resources/templates/state.enabled.ftl | 231 +++++++++-------- .../resources/templates/state.warning.ftl | 238 ++++++++++-------- 4 files changed, 320 insertions(+), 216 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 59b63d7200..330ecc23f5 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -297,15 +297,9 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { if (StringUtils.isNotEmpty(email)) { result.forEach((apiFeature, stateValue) -> { - ApiUsageRecordKey[] keys = ApiUsageRecordKey.getKeys(apiFeature); - ApiUsageStateMailMessage[] msgs = new ApiUsageStateMailMessage[keys.length]; - for (int i = 0; i < keys.length; i++) { - ApiUsageRecordKey key = keys[i]; - msgs[i] = new ApiUsageStateMailMessage(key, state.getProfileThreshold(key), state.get(key)); - } mailExecutor.submit(() -> { try { - mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs[0]); + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, createStateMailMessage(state, apiFeature, stateValue)); } catch (ThingsboardException e) { log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); } @@ -316,6 +310,33 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + private ApiUsageStateMailMessage createStateMailMessage(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) { + StateChecker checker = getStateChecker(stateValue); + for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) { + long threshold = state.getProfileThreshold(apiUsageRecordKey); + long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey); + long value = state.get(apiUsageRecordKey); + if (checker.check(threshold, warnThreshold, value)) { + return new ApiUsageStateMailMessage(apiUsageRecordKey, threshold, value); + } + } + return null; + } + + private StateChecker getStateChecker(ApiUsageStateValue stateValue) { + if (ApiUsageStateValue.ENABLED.equals(stateValue)) { + return (t, wt, v) -> true; + } else if (ApiUsageStateValue.WARNING.equals(stateValue)) { + return (t, wt, v) -> v < t && v >= wt; + } else { + return (t, wt, v) -> v >= t; + } + } + + private interface StateChecker { + boolean check(long threshold, long warnThreshold, long value); + } + private void checkStartOfNextCycle() { updateLock.lock(); try { diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 19509243ef..3045fe0cce 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -30,6 +30,7 @@ import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.ApiFeature; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -261,19 +262,36 @@ public class DefaultMailService implements MailService { switch (stateValue) { case ENABLED: + model.put("apiLabel", toEnabledValueLabel(apiFeature)); message = mergeTemplateIntoString("state.enabled.ftl", model); break; case WARNING: + model.put("apiLimitValueLabel", toDisabledValueLabel(msg.getKey(), msg.getThreshold())); + model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getValue())); message = mergeTemplateIntoString("state.warning.ftl", model); break; case DISABLED: - model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg)); + model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getThreshold())); message = mergeTemplateIntoString("state.disabled.ftl", model); break; } sendMail(mailSender, mailFrom, email, subject, message); } + private String toEnabledValueLabel(ApiFeature apiFeature) { + switch (apiFeature) { + case DB: + return "save"; + case TRANSPORT: + return "receive"; + case JS: + case RE: + return "invoke"; + default: + throw new RuntimeException("Not implemented!"); + } + } + private String toDisabledValueLabel(ApiFeature apiFeature) { switch (apiFeature) { case DB: @@ -288,17 +306,17 @@ public class DefaultMailService implements MailService { } } - private String toDisabledValueLabel(ApiUsageStateMailMessage msg) { - switch (msg.getKey()) { + private String toDisabledValueLabel(ApiUsageRecordKey key, long value) { + switch (key) { case STORAGE_DP_COUNT: case TRANSPORT_DP_COUNT: - return (msg.getThreshold() / 1000000) + "M data points"; + return (value / 1000000) + "M data points"; case TRANSPORT_MSG_COUNT: - return (msg.getThreshold() / 1000000) + "M messages"; + return (value / 1000000) + "M messages"; case JS_EXEC_COUNT: - return (msg.getThreshold() / 1000000) + "M JavaScript functions"; + return (value / 1000000) + "M JavaScript functions"; case RE_EXEC_COUNT: - return (msg.getThreshold() / 1000000) + "M Rule Engine nodes"; + return (value / 1000000) + "M Rule Engine nodes"; default: throw new RuntimeException("Not implemented!"); } diff --git a/application/src/main/resources/templates/state.enabled.ftl b/application/src/main/resources/templates/state.enabled.ftl index 497b3cd780..52db9683b7 100644 --- a/application/src/main/resources/templates/state.enabled.ftl +++ b/application/src/main/resources/templates/state.enabled.ftl @@ -15,112 +15,141 @@ limitations under the License. --> - - + + - - -Thingsboard - Api Usage State - - - + + + Thingsboard - Api Usage State + + + - - - -
-
- - -
- - - - - - - - <#list apiUsageStateMailMessages as msg> - - - - - - - - - -
-

Thingsboard Api Usage State for tenant has been updated

-
- Thingsboard Usage state ${apiFeature} was updated to status ENABLED. -
- ${msg.key.apiLimitKey} = ${msg.threshold} -
- ${msg.key.apiCountKey} = ${msg.value} -
- — The Thingsboard -
- -
+ + + + + + + + +
+ + + + + + + + + + + + +
+

Your ThingsBoard account feature was enabled

+
We have enabled the ${apiFeature} for your account and ThingsBoard already able to ${apiLabel} messages. +
— The ThingsBoard +
+
+ + + + - +
This email was sent to ${targetEmail} by ThingsBoard.
diff --git a/application/src/main/resources/templates/state.warning.ftl b/application/src/main/resources/templates/state.warning.ftl index bd4424a0b9..1a2787a9f5 100644 --- a/application/src/main/resources/templates/state.warning.ftl +++ b/application/src/main/resources/templates/state.warning.ftl @@ -15,112 +15,148 @@ limitations under the License. --> - - + + - - -Thingsboard - Api Usage State - - - + + + Thingsboard - Api Usage State + + + - - - - diff --git a/application/src/main/resources/templates/state.enabled.ftl b/application/src/main/resources/templates/state.enabled.ftl index 52db9683b7..0a7841f871 100644 --- a/application/src/main/resources/templates/state.enabled.ftl +++ b/application/src/main/resources/templates/state.enabled.ftl @@ -123,7 +123,7 @@ diff --git a/application/src/main/resources/templates/state.warning.ftl b/application/src/main/resources/templates/state.warning.ftl index 1a2787a9f5..1ce29100a7 100644 --- a/application/src/main/resources/templates/state.warning.ftl +++ b/application/src/main/resources/templates/state.warning.ftl @@ -117,14 +117,14 @@ diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java index fe923cefaa..1f693a51b9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java @@ -20,7 +20,7 @@ import lombok.Getter; public enum ApiFeature { TRANSPORT("transportApiState", "Device API"), DB("dbApiState", "Telemetry persistence"), - RE("ruleEngineApiState", "Rule engine execution"), + RE("ruleEngineApiState", "Rule Engine execution"), JS("jsExecutionApiState", "JavaScript functions execution"); @Getter From eb8975d504be5d02d52d7693c6afaba01da7982d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 20 Nov 2020 18:02:09 +0200 Subject: [PATCH 61/84] SMS Email Limits --- .../actors/ruleChain/DefaultTbContext.java | 1 + .../service/mail/DefaultMailService.java | 57 ++++++++++++------- .../server/service/sms/DefaultSmsService.java | 40 +++++++++---- .../server/common/data/ApiFeature.java | 4 +- .../server/common/data/ApiUsageRecordKey.java | 10 +++- .../server/common/data/ApiUsageState.java | 16 ++++++ .../DefaultTenantProfileConfiguration.java | 6 ++ .../server/dao/model/ModelConstants.java | 2 + .../dao/model/sql/ApiUsageStateEntity.java | 10 ++++ .../usagerecord/ApiUsageStateServiceImpl.java | 2 + .../resources/sql/schema-entities-hsql.sql | 2 + .../main/resources/sql/schema-entities.sql | 2 + .../rule/engine/api/MailService.java | 5 +- .../rule/engine/api/SmsService.java | 5 +- .../rule/engine/api/TbContext.java | 1 + .../rule/engine/mail/TbSendEmailNode.java | 3 +- .../rule/engine/sms/TbSendSmsNode.java | 3 +- 17 files changed, 126 insertions(+), 43 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index b1d214069f..e645339e0b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -34,6 +34,7 @@ import org.thingsboard.rule.engine.api.TbRelationTypes; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorRef; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index a26363c5ff..5f4ea6ad9d 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -21,7 +21,6 @@ import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.core.NestedRuntimeException; import org.springframework.mail.javamail.JavaMailSenderImpl; @@ -40,6 +39,8 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; import javax.mail.MessagingException; @@ -58,18 +59,24 @@ public class DefaultMailService implements MailService { public static final String UTF_8 = "UTF-8"; public static final int _10K = 10000; public static final int _1M = 1000000; - @Autowired - private MessageSource messages; - @Autowired - private Configuration freemarkerConfig; + private final MessageSource messages; + private final Configuration freemarkerConfig; + private final AdminSettingsService adminSettingsService; + private final TbApiUsageStateService apiUsageStateService; + private final TbApiUsageClient apiUsageClient; private JavaMailSenderImpl mailSender; private String mailFrom; - @Autowired - private AdminSettingsService adminSettingsService; + public DefaultMailService(MessageSource messages, Configuration freemarkerConfig, AdminSettingsService adminSettingsService, TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { + this.messages = messages; + this.freemarkerConfig = freemarkerConfig; + this.adminSettingsService = adminSettingsService; + this.apiUsageStateService = apiUsageStateService; + this.apiUsageClient = apiUsageClient; + } @PostConstruct private void init() { @@ -148,8 +155,11 @@ public class DefaultMailService implements MailService { } @Override - public void sendEmail(String email, String subject, String message) throws ThingsboardException { - sendMail(mailSender, mailFrom, email, subject, message); + public void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException { + if (apiUsageStateService.getApiUsageState(tenantId).isEmailSendEnabled()) { + sendMail(mailSender, mailFrom, email, subject, message); + apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); + } } @Override @@ -223,20 +233,23 @@ public class DefaultMailService implements MailService { } @Override - public void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException { - MimeMessage mailMsg = mailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8"); - helper.setFrom(StringUtils.isBlank(from) ? mailFrom : from); - helper.setTo(to.split("\\s*,\\s*")); - if (!StringUtils.isBlank(cc)) { - helper.setCc(cc.split("\\s*,\\s*")); - } - if (!StringUtils.isBlank(bcc)) { - helper.setBcc(bcc.split("\\s*,\\s*")); + public void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body) throws MessagingException { + if (apiUsageStateService.getApiUsageState(tenantId).isEmailSendEnabled()) { + MimeMessage mailMsg = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8"); + helper.setFrom(StringUtils.isBlank(from) ? mailFrom : from); + helper.setTo(to.split("\\s*,\\s*")); + if (!StringUtils.isBlank(cc)) { + helper.setCc(cc.split("\\s*,\\s*")); + } + if (!StringUtils.isBlank(bcc)) { + helper.setBcc(bcc.split("\\s*,\\s*")); + } + helper.setSubject(subject); + helper.setText(body); + mailSender.send(helper.getMimeMessage()); + apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); } - helper.setSubject(subject); - helper.setText(body); - mailSender.send(helper.getMimeMessage()); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java index 05df30deae..203fc1127d 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java @@ -17,7 +17,6 @@ package org.thingsboard.server.service.sms; import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.NestedRuntimeException; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.SmsService; @@ -26,12 +25,15 @@ import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.util.mapping.JacksonUtil; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -40,14 +42,20 @@ import javax.annotation.PreDestroy; @Slf4j public class DefaultSmsService implements SmsService { - @Autowired - private SmsSenderFactory smsSenderFactory; - - @Autowired - private AdminSettingsService adminSettingsService; + private final SmsSenderFactory smsSenderFactory; + private final AdminSettingsService adminSettingsService; + private final TbApiUsageStateService apiUsageStateService; + private final TbApiUsageClient apiUsageClient; private SmsSender smsSender; + public DefaultSmsService(SmsSenderFactory smsSenderFactory, AdminSettingsService adminSettingsService, TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { + this.smsSenderFactory = smsSenderFactory; + this.adminSettingsService = adminSettingsService; + this.apiUsageStateService = apiUsageStateService; + this.apiUsageClient = apiUsageClient; + } + @PostConstruct private void init() { updateSmsConfiguration(); @@ -78,18 +86,26 @@ public class DefaultSmsService implements SmsService { } } - @Override - public void sendSms(String numberTo, String message) throws ThingsboardException { + private int sendSms(String numberTo, String message) throws ThingsboardException { if (this.smsSender == null) { throw new ThingsboardException("Unable to send SMS: no SMS provider configured!", ThingsboardErrorCode.GENERAL); } - this.sendSms(this.smsSender, numberTo, message); + return this.sendSms(this.smsSender, numberTo, message); } @Override - public void sendSms(String[] numbersTo, String message) throws ThingsboardException { - for (String numberTo : numbersTo) { - this.sendSms(numberTo, message); + public void sendSms(TenantId tenantId, String[] numbersTo, String message) throws ThingsboardException { + if (apiUsageStateService.getApiUsageState(tenantId).isSmsSendEnabled()) { + int smsCount = 0; + try { + for (String numberTo : numbersTo) { + smsCount += this.sendSms(numberTo, message); + } + } finally { + if (smsCount > 0) { + apiUsageClient.report(tenantId, ApiUsageRecordKey.SMS_EXEC_COUNT, smsCount); + } + } } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java index 1f693a51b9..10ae01a699 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java @@ -21,7 +21,9 @@ public enum ApiFeature { TRANSPORT("transportApiState", "Device API"), DB("dbApiState", "Telemetry persistence"), RE("ruleEngineApiState", "Rule Engine execution"), - JS("jsExecutionApiState", "JavaScript functions execution"); + JS("jsExecutionApiState", "JavaScript functions execution"), + EMAIL("emailApiState", "Email messages"), + SMS("smsApiState", "SMS messages"); @Getter private final String apiStateKey; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index dc8556a755..70b2902256 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -23,11 +23,15 @@ public enum ApiUsageRecordKey { TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit"), STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit"), RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"), - JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"); + JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"), + EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit"), + SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit"); private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT}; private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT}; private static final ApiUsageRecordKey[] DB_RECORD_KEYS = {STORAGE_DP_COUNT}; private static final ApiUsageRecordKey[] TRANSPORT_RECORD_KEYS = {TRANSPORT_MSG_COUNT, TRANSPORT_DP_COUNT}; + private static final ApiUsageRecordKey[] EMAIL_RECORD_KEYS = {EMAIL_EXEC_COUNT}; + private static final ApiUsageRecordKey[] SMS_RECORD_KEYS = {SMS_EXEC_COUNT}; @Getter private final ApiFeature apiFeature; @@ -52,6 +56,10 @@ public enum ApiUsageRecordKey { return RE_RECORD_KEYS; case JS: return JS_RECORD_KEYS; + case EMAIL: + return EMAIL_RECORD_KEYS; + case SMS: + return SMS_RECORD_KEYS; default: return new ApiUsageRecordKey[]{}; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java index 52797baa3b..8f13fa8164 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java @@ -47,6 +47,12 @@ public class ApiUsageState extends BaseData implements HasTenan @Getter @Setter private ApiUsageStateValue jsExecState; + @Getter + @Setter + private ApiUsageStateValue emailExecState; + @Getter + @Setter + private ApiUsageStateValue smsExecState; public ApiUsageState() { super(); @@ -64,6 +70,8 @@ public class ApiUsageState extends BaseData implements HasTenan this.dbStorageState = ur.getDbStorageState(); this.reExecState = ur.getReExecState(); this.jsExecState = ur.getJsExecState(); + this.emailExecState = ur.getEmailExecState(); + this.smsExecState = ur.getSmsExecState(); } public boolean isTransportEnabled() { @@ -81,4 +89,12 @@ public class ApiUsageState extends BaseData implements HasTenan public boolean isJsExecEnabled() { return !ApiUsageStateValue.DISABLED.equals(jsExecState); } + + public boolean isEmailSendEnabled(){ + return !ApiUsageStateValue.DISABLED.equals(emailExecState); + } + + public boolean isSmsSendEnabled(){ + return !ApiUsageStateValue.DISABLED.equals(smsExecState); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 10df66ece5..28ef5cae13 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -42,6 +42,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxJSExecutions; private long maxDPStorageDays; private int maxRuleNodeExecutionsPerMessage; + private long maxEmails; + private long maxSms; private double warnThreshold; @@ -58,6 +60,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura return maxREExecutions; case STORAGE_DP_COUNT: return maxDPStorageDays; + case EMAIL_EXEC_COUNT: + return maxEmails; + case SMS_EXEC_COUNT: + return maxSms; } return 0L; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index e863de1edc..e55f55eef2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -450,6 +450,8 @@ public class ModelConstants { public static final String API_USAGE_STATE_DB_STORAGE_COLUMN = "db_storage"; public static final String API_USAGE_STATE_RE_EXEC_COLUMN = "re_exec"; public static final String API_USAGE_STATE_JS_EXEC_COLUMN = "js_exec"; + public static final String API_USAGE_STATE_EMAIL_EXEC_COLUMN = "email_exec"; + public static final String API_USAGE_STATE_SMS_EXEC_COLUMN = "sms_exec"; /** * Cassandra attributes and timeseries constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java index bc4aed6398..4b07d02321 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java @@ -63,6 +63,12 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements @Enumerated(EnumType.STRING) @Column(name = ModelConstants.API_USAGE_STATE_JS_EXEC_COLUMN) private ApiUsageStateValue jsExecState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_EMAIL_EXEC_COLUMN) + private ApiUsageStateValue emailExecState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_SMS_EXEC_COLUMN) + private ApiUsageStateValue smsExecState = ApiUsageStateValue.ENABLED; public ApiUsageStateEntity() { } @@ -83,6 +89,8 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements this.dbStorageState = ur.getDbStorageState(); this.reExecState = ur.getReExecState(); this.jsExecState = ur.getJsExecState(); + this.emailExecState = ur.getEmailExecState(); + this.smsExecState = ur.getSmsExecState(); } @Override @@ -99,6 +107,8 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements ur.setDbStorageState(dbStorageState); ur.setReExecState(reExecState); ur.setJsExecState(jsExecState); + ur.setEmailExecState(emailExecState); + ur.setSmsExecState(smsExecState); return ur; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 7836154c62..5fc1c293af 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -78,6 +78,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A apiUsageState.setReExecState(ApiUsageStateValue.ENABLED); apiUsageState.setJsExecState(ApiUsageStateValue.ENABLED); apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED); + apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 0e8f8a7791..998e268bdc 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -416,5 +416,7 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( db_storage varchar(32), re_exec varchar(32), js_exec varchar(32), + email_exec varchar(32), + sms_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index db0362a877..329b486aff 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -442,6 +442,8 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( db_storage varchar(32), re_exec varchar(32), js_exec varchar(32), + email_exec varchar(32), + sms_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java index 4a4228731c..2add686349 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; import javax.mail.MessagingException; @@ -27,7 +28,7 @@ public interface MailService { void updateMailConfiguration(); - void sendEmail(String email, String subject, String message) throws ThingsboardException; + void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException; void sendTestMail(JsonNode config, String email) throws ThingsboardException; @@ -39,7 +40,7 @@ public interface MailService { void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException; - void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException; + void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body) throws MessagingException; void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java index a041ad9390..fb34383a9d 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java @@ -17,14 +17,13 @@ package org.thingsboard.rule.engine.api; import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; public interface SmsService { void updateSmsConfiguration(); - void sendSms(String numberTo, String message) throws ThingsboardException; - - void sendSms(String[] numbersTo, String message) throws ThingsboardException;; + void sendSms(TenantId tenantId, String[] numbersTo, String message) throws ThingsboardException;; void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index b104c6f07b..eb494af569 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -19,6 +19,7 @@ import io.netty.channel.EventLoopGroup; import org.springframework.data.redis.core.RedisTemplate; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java index 3ca2d5dbab..7c1bc67e21 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -87,7 +88,7 @@ public class TbSendEmailNode implements TbNode { private void sendEmail(TbContext ctx, EmailPojo email) throws Exception { if (this.config.isUseSystemSmtpSettings()) { - ctx.getMailService().send(email.getFrom(), email.getTo(), email.getCc(), + ctx.getMailService().send(ctx.getTenantId(), email.getFrom(), email.getTo(), email.getCc(), email.getBcc(), email.getSubject(), email.getBody()); } else { MimeMessage mailMsg = mailSender.createMimeMessage(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java index f2c6ad3725..d06ecdd3fe 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java @@ -23,6 +23,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.sms.SmsSender; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -75,7 +76,7 @@ public class TbSendSmsNode implements TbNode { String message = TbNodeUtils.processPattern(this.config.getSmsMessageTemplate(), msg.getMetaData()); String[] numbersToList = numbersTo.split(","); if (this.config.isUseSystemSmsSettings()) { - ctx.getSmsService().sendSms(numbersToList, message); + ctx.getSmsService().sendSms(ctx.getTenantId(), numbersToList, message); } else { for (String numberTo : numbersToList) { this.smsSender.sendSms(numberTo, message); From d5cf27cdbf0359f3ade857b1a9b6ec0118fe3e7b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 18:19:53 +0200 Subject: [PATCH 62/84] Sms provider configuration component improvements --- ui-ngx/src/app/core/utils.ts | 4 +++ ...ws-sns-provider-configuration.component.ts | 7 +++- .../sms-provider-configuration.component.ts | 4 +-- ...io-sms-provider-configuration.component.ts | 7 +++- .../src/app/shared/models/settings.models.ts | 32 +++++++++++++++++++ 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 4650caca77..bf58d8c2c8 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -92,6 +92,10 @@ export function isEmptyStr(value: any): boolean { return value === ''; } +export function isNotEmptyStr(value: any): boolean { + return value !== null && typeof value === 'string' && value.trim().length > 0; +} + export function isFunction(value: any): boolean { return typeof value === 'function'; } diff --git a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts index 6b4a2e23ad..a223df4bb1 100644 --- a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts @@ -20,7 +20,11 @@ import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { isDefinedAndNotNull } from '@core/utils'; -import { AwsSnsSmsProviderConfiguration } from '@shared/models/settings.models'; +import { + AwsSnsSmsProviderConfiguration, + SmsProviderConfiguration, + SmsProviderType +} from '@shared/models/settings.models'; @Component({ selector: 'tb-aws-sns-provider-configuration', @@ -93,6 +97,7 @@ export class AwsSnsProviderConfigurationComponent implements ControlValueAccesso let configuration: AwsSnsSmsProviderConfiguration = null; if (this.awsSnsProviderConfigurationFormGroup.valid) { configuration = this.awsSnsProviderConfigurationFormGroup.value; + (configuration as SmsProviderConfiguration).type = SmsProviderType.AWS_SNS; } this.propagateChange(configuration); } diff --git a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts index 4aafff9369..feea8b9939 100644 --- a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts @@ -27,7 +27,7 @@ import { import { deepClone } from '@core/utils'; import { createSmsProviderConfiguration, - SmsProviderConfiguration, + SmsProviderConfiguration, smsProviderConfigurationValidator, SmsProviderType, smsProviderTypeTranslationMap } from '@shared/models/settings.models'; @@ -78,7 +78,7 @@ export class SmsProviderConfigurationComponent implements ControlValueAccessor, ngOnInit() { this.smsProviderConfigurationFormGroup = this.fb.group({ type: [null, Validators.required], - configuration: [null, Validators.required] + configuration: [null, smsProviderConfigurationValidator(true)] }); this.smsProviderConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); diff --git a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts index 9d2c1a154b..5577a16a00 100644 --- a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts @@ -20,7 +20,11 @@ import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { isDefinedAndNotNull } from '@core/utils'; -import { phoneNumberPattern, TwilioSmsProviderConfiguration } from '@shared/models/settings.models'; +import { + phoneNumberPattern, + SmsProviderConfiguration, SmsProviderType, + TwilioSmsProviderConfiguration +} from '@shared/models/settings.models'; @Component({ selector: 'tb-twilio-sms-provider-configuration', @@ -95,6 +99,7 @@ export class TwilioSmsProviderConfigurationComponent implements ControlValueAcce let configuration: TwilioSmsProviderConfiguration = null; if (this.twilioSmsProviderConfigurationFormGroup.valid) { configuration = this.twilioSmsProviderConfigurationFormGroup.value; + (configuration as SmsProviderConfiguration).type = SmsProviderType.TWILIO; } this.propagateChange(configuration); } diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 313b92d603..582b077cac 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -15,6 +15,8 @@ /// import { DeviceTransportType } from '@shared/models/device.models'; +import { ValidatorFn } from '@angular/forms'; +import { isNotEmptyStr } from '@core/utils'; export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; @@ -95,6 +97,36 @@ export interface SmsProviderConfiguration extends SmsProviderConfigurations { type: SmsProviderType; } +export function smsProviderConfigurationValidator(required: boolean): ValidatorFn { + return control => { + const configuration: SmsProviderConfiguration = control.value; + let errors = null; + if (required) { + let valid = false; + if (configuration && configuration.type) { + switch (configuration.type) { + case SmsProviderType.AWS_SNS: + const awsSnsConfiguration: AwsSnsSmsProviderConfiguration = configuration; + valid = isNotEmptyStr(awsSnsConfiguration.accessKeyId) && isNotEmptyStr(awsSnsConfiguration.secretAccessKey) + && isNotEmptyStr(awsSnsConfiguration.region); + break; + case SmsProviderType.TWILIO: + const twilioConfiguration: TwilioSmsProviderConfiguration = configuration; + valid = isNotEmptyStr(twilioConfiguration.numberFrom) && isNotEmptyStr(twilioConfiguration.accountSid) + && isNotEmptyStr(twilioConfiguration.accountToken); + break; + } + } + if (!valid) { + errors = { + invalid: true + }; + } + } + return errors; + }; +} + export interface TestSmsRequest { providerConfiguration: SmsProviderConfiguration; numberTo: string; From 6f780b8fa540068807382ce3aa7386c5577c91ba Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 18:36:02 +0200 Subject: [PATCH 63/84] Minor improvements --- ui-ngx/src/app/shared/models/settings.models.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 582b077cac..64c5dda85c 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -14,7 +14,6 @@ /// limitations under the License. /// -import { DeviceTransportType } from '@shared/models/device.models'; import { ValidatorFn } from '@angular/forms'; import { isNotEmptyStr } from '@core/utils'; From d2f63c762f0ce0327d9179b1dd2a784511c386b8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 19:01:02 +0200 Subject: [PATCH 64/84] Fix circular dependency --- .../server/service/mail/DefaultMailService.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 5f4ea6ad9d..057eb098e4 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -21,7 +21,9 @@ import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Lazy; import org.springframework.core.NestedRuntimeException; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; @@ -63,18 +65,20 @@ public class DefaultMailService implements MailService { private final MessageSource messages; private final Configuration freemarkerConfig; private final AdminSettingsService adminSettingsService; - private final TbApiUsageStateService apiUsageStateService; private final TbApiUsageClient apiUsageClient; + @Lazy + @Autowired + private TbApiUsageStateService apiUsageStateService; + private JavaMailSenderImpl mailSender; private String mailFrom; - public DefaultMailService(MessageSource messages, Configuration freemarkerConfig, AdminSettingsService adminSettingsService, TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { + public DefaultMailService(MessageSource messages, Configuration freemarkerConfig, AdminSettingsService adminSettingsService, TbApiUsageClient apiUsageClient) { this.messages = messages; this.freemarkerConfig = freemarkerConfig; this.adminSettingsService = adminSettingsService; - this.apiUsageStateService = apiUsageStateService; this.apiUsageClient = apiUsageClient; } From eb4f4baafb174067b8e68f39a3636b5cc4151431 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 19:09:42 +0200 Subject: [PATCH 65/84] UI: Update Tenant Profile - add max emails and sms --- ...enant-profile-configuration.component.html | 24 +++++++++++++++++++ ...-tenant-profile-configuration.component.ts | 4 +++- ui-ngx/src/app/shared/models/tenant.model.ts | 6 ++++- .../assets/locale/locale.constant-en_US.json | 8 ++++++- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index ae9fd8ea9c..fa0029b46f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -160,6 +160,30 @@ {{ 'tenant-profile.max-rule-node-executions-per-message-range' | translate}} + + tenant-profile.max-emails + + + {{ 'tenant-profile.max-emails-required' | translate}} + + + {{ 'tenant-profile.max-emails-range' | translate}} + + + + tenant-profile.max-sms + + + {{ 'tenant-profile.max-sms-required' | translate}} + + + {{ 'tenant-profile.max-sms-range' | translate}} + + tenant-profile.transport-tenant-msg-rate-limit diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index e88537736b..641f14d52c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -70,7 +70,9 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA maxREExecutions: [null, [Validators.required, Validators.min(0)]], maxJSExecutions: [null, [Validators.required, Validators.min(0)]], maxDPStorageDays: [null, [Validators.required, Validators.min(0)]], - maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]] + maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]], + maxEmails: [null, [Validators.required, Validators.min(0)]], + maxSms: [null, [Validators.required, Validators.min(0)]] }); this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index ce22c27fac..f682bf1d39 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -44,6 +44,8 @@ export interface DefaultTenantProfileConfiguration { maxJSExecutions: number; maxDPStorageDays: number; maxRuleNodeExecutionsPerMessage: number; + maxEmails: number; + maxSms: number; } export type TenantProfileConfigurations = DefaultTenantProfileConfiguration; @@ -69,7 +71,9 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan maxREExecutions: 0, maxJSExecutions: 0, maxDPStorageDays: 0, - maxRuleNodeExecutionsPerMessage: 0 + maxRuleNodeExecutionsPerMessage: 0, + maxEmails: 0, + maxSms: 0 }; configuration = {...defaultConfiguration, type: TenantProfileType.DEFAULT}; break; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index ca3717eeff..41243f885e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2017,7 +2017,13 @@ "max-d-p-storage-days-range": "Minimum number of data points storage days can't be negative", "max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)", "max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.", - "max-rule-node-executions-per-message-range": "Minimum number of rule node executions per message can't be negative" + "max-rule-node-executions-per-message-range": "Minimum number of rule node executions per message can't be negative", + "max-emails": "Maximum number of emails sent (0 - unlimited)", + "max-emails-required": "Maximum number of emails sent is required.", + "max-emails-range": "Maximum number of emails sent can't be negative", + "max-sms": "Maximum number of SMS sent (0 - unlimited)", + "max-sms-required": "Maximum number of SMS sent is required.", + "max-sms-range": "Maximum number of SMS sent can't be negative" }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", From 9d359a8047daace17d037cd0af526771ae680bab Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Mon, 23 Nov 2020 11:37:56 +0200 Subject: [PATCH 66/84] Fixed inactivitiy timeout for gw sessions --- .gitignore | 2 ++ .../common/transport/service/DefaultTransportService.java | 4 ++-- pom.xml | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index da8569baa3..ae3b49af21 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ pom.xml.versionsBackup **/.env .instance_id rebuild-docker.sh +.run/** +.run \ No newline at end of file diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 6fbc5d8e8e..b4d768f397 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -418,8 +418,8 @@ public class DefaultTransportService implements TransportService { sessions.forEach((uuid, sessionMD) -> { long lastActivityTime = sessionMD.getLastActivityTime(); TransportProtos.SessionInfoProto sessionInfo = sessionMD.getSessionInfo(); - if (sessionInfo.getGwSessionIdMSB() > 0 && - sessionInfo.getGwSessionIdLSB() > 0) { + if (sessionInfo.getGwSessionIdMSB() != 0 && + sessionInfo.getGwSessionIdLSB() != 0) { SessionMetaData gwMetaData = sessions.get(new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB())); if (gwMetaData != null) { lastActivityTime = Math.max(gwMetaData.getLastActivityTime(), lastActivityTime); diff --git a/pom.xml b/pom.xml index 03b815635d..6974497db2 100755 --- a/pom.xml +++ b/pom.xml @@ -725,6 +725,7 @@ **/*.proto.js docker/haproxy/** docker/tb-node/** + .run/** JAVADOC_STYLE From 205be4e5cea74108564a6f4fd38973d1cb588713 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 23 Nov 2020 12:13:05 +0200 Subject: [PATCH 67/84] UI: Added new component copy-device-credentials --- .../copy-device-credentials.component.html | 26 ++++ .../copy-device-credentials.component.ts | 128 ++++++++++++++++++ .../home/components/home-components.module.ts | 5 +- .../home/pages/device/device.component.html | 13 +- .../home/pages/device/device.component.ts | 30 +--- .../device/devices-table-config.resolver.ts | 9 +- .../assets/locale/locale.constant-en_US.json | 2 + 7 files changed, 179 insertions(+), 34 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.html create mode 100644 ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts diff --git a/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.html b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.html new file mode 100644 index 0000000000..77c3d10c4a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.html @@ -0,0 +1,26 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts new file mode 100644 index 0000000000..99b4a82960 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts @@ -0,0 +1,128 @@ +/// +/// 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. +/// + +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { EntityId } from '@shared/models/id/entity-id'; +import { DeviceService } from '@core/http/device.service'; +import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; +import { isDefinedAndNotNull, isEqual } from '@core/utils'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { distinctUntilChanged, filter, mergeMap, tap } from 'rxjs/operators'; +import { EntityType } from '@shared/models/entity-type.models'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'tb-copy-device-credentials', + templateUrl: './copy-device-credentials.component.html', + styleUrls: [] +}) +export class CopyDeviceCredentialsComponent implements OnDestroy { + + private deviceId$ = new BehaviorSubject(null); + + private credentials$ = new Subject(); + + private tooltipMessage: string; + + public hideButton = true; + + public credential: string; + + public loading = false; + + public buttonLabel: string; + + @Input() + set deviceId(deviceId: EntityId) { + this.deviceId$.next(deviceId); + } + + @Input() disabled: boolean; + + @Input() + set credentials(credential: DeviceCredentials) { + this.credentials$.next(credential); + } + + constructor(private store: Store, + private translate: TranslateService, + private deviceService: DeviceService + ) { + this.deviceId$.pipe( + filter(device => isDefinedAndNotNull(device) && device.entityType === EntityType.DEVICE), + distinctUntilChanged((prev, curr) => prev.id === curr.id), + tap(() => this.loading = true), + mergeMap(device => this.deviceService.getDeviceCredentials(device.id)) + ).subscribe(deviceCredentials => { + this.processingValue(deviceCredentials); + this.loading = false; + }); + + this.credentials$.pipe( + filter(credential => isDefinedAndNotNull(credential)), + distinctUntilChanged((prev, curr) => isEqual(prev, curr)) + ).subscribe(deviceCredentials => { + console.warn(deviceCredentials); + this.processingValue(deviceCredentials); + }); + } + + ngOnDestroy(): void { + this.deviceId$.unsubscribe(); + this.credentials$.unsubscribe(); + } + + private processingValue(credential: DeviceCredentials): void { + switch (credential.credentialsType) { + case DeviceCredentialsType.ACCESS_TOKEN: + this.hideButton = false; + this.credential = credential.credentialsId; + this.buttonLabel = this.translate.instant('device.copyAccessToken'); + this.tooltipMessage = this.translate.instant('device.accessTokenCopiedMessage'); + break; + case DeviceCredentialsType.MQTT_BASIC: + this.hideButton = false; + this.credential = this.convertObjectToString(JSON.parse(credential.credentialsValue)); + this.buttonLabel = this.translate.instant('device.copy-mqtt-authentication'); + this.tooltipMessage = this.translate.instant('device.mqtt-authentication-copied-message'); + break; + default: + this.hideButton = true; + this.credential = null; + this.buttonLabel = ''; + this.tooltipMessage = ''; + } + } + + private convertObjectToString(obj: object): string { + Object.keys(obj).forEach(k => (!obj[k] && obj[k] !== undefined) && delete obj[k]); + return JSON.stringify(obj).replace(/"([^"]+)":/g, '$1:'); + } + + onCopyCredential() { + this.store.dispatch(new ActionNotificationShow( + { + message: this.tooltipMessage, + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index ec7ccea660..54d15c1ee1 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -105,7 +105,7 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio import { FilterTextComponent } from './filter/filter-text.component'; import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; -import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component"; +import { DeviceProfileProvisionConfigurationComponent } from './profile/device-profile-provision-configuration.component'; import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; import { DeviceCredentialsComponent } from './device/device-credentials.component'; @@ -115,6 +115,7 @@ import { EditAlarmDetailsDialogComponent } from './profile/alarm/edit-alarm-deta import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; import { DefaultTenantProfileConfigurationComponent } from './profile/tenant/default-tenant-profile-configuration.component'; import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-profile-configuration.component'; +import { CopyDeviceCredentialsComponent } from './device/copy-device-credentials.component'; @NgModule({ declarations: @@ -211,6 +212,7 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro AlarmScheduleComponent, DeviceWizardDialogComponent, DeviceCredentialsComponent, + CopyDeviceCredentialsComponent, AlarmScheduleDialogComponent, EditAlarmDetailsDialogComponent ], @@ -293,6 +295,7 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro RuleChainAutocompleteComponent, DeviceWizardDialogComponent, DeviceCredentialsComponent, + CopyDeviceCredentialsComponent, AlarmScheduleInfoComponent, AlarmScheduleComponent, AlarmScheduleDialogComponent, diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.html b/ui-ngx/src/app/modules/home/pages/device/device.component.html index e9371b4708..d781aa1373 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.html @@ -51,16 +51,17 @@ ngxClipboard (cbOnSuccess)="onDeviceIdCopied($event)" [cbContent]="entity?.id?.id" + [disabled]="(isLoading$ | async)" [fxShow]="!isEdit"> device.copyId - + +
diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.ts b/ui-ngx/src/app/modules/home/pages/device/device.component.ts index 833919eb78..74503bfddb 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.ts @@ -21,10 +21,9 @@ import { EntityComponent } from '../../components/entity/entity.component'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { createDeviceConfiguration, - createDeviceProfileConfiguration, createDeviceTransportConfiguration, + createDeviceTransportConfiguration, DeviceData, DeviceInfo, - DeviceProfileData, DeviceProfileInfo, DeviceProfileType, DeviceTransportType @@ -33,8 +32,6 @@ import { EntityType } from '@shared/models/entity-type.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; -import { DeviceService } from '@core/http/device.service'; -import { ClipboardService } from 'ngx-clipboard'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @Component({ @@ -46,12 +43,12 @@ export class DeviceComponent extends EntityComponent { entityType = EntityType; + componentsData: any; + deviceScope: 'tenant' | 'customer' | 'customer_user'; constructor(protected store: Store, protected translate: TranslateService, - private deviceService: DeviceService, - private clipboardService: ClipboardService, @Inject('entity') protected entityValue: DeviceInfo, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, public fb: FormBuilder) { @@ -60,6 +57,7 @@ export class DeviceComponent extends EntityComponent { ngOnInit() { this.deviceScope = this.entitiesTableConfig.componentsData.deviceScope; + this.componentsData = this.entitiesTableConfigValue.componentsData; super.ngOnInit(); } @@ -114,26 +112,6 @@ export class DeviceComponent extends EntityComponent { })); } - copyAccessToken($event) { - if (this.entity.id) { - this.deviceService.getDeviceCredentials(this.entity.id.id, true).subscribe( - (deviceCredentials) => { - const credentialsId = deviceCredentials.credentialsId; - if (this.clipboardService.copyFromContent(credentialsId)) { - this.store.dispatch(new ActionNotificationShow( - { - message: this.translate.instant('device.accessTokenCopiedMessage'), - type: 'success', - duration: 750, - verticalPosition: 'bottom', - horizontalPosition: 'right' - })); - } - } - ); - } - } - onDeviceProfileUpdated() { this.entitiesTableConfig.table.updateData(false); } diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 0aaf8e62e7..60dffcc5fe 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -63,6 +63,7 @@ import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; import { HomeDialogsService } from '@home/dialogs/home-dialogs.service'; import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component'; import { BaseData, HasId } from '@shared/models/base-data'; +import { isDefinedAndNotNull } from '@core/utils'; @Injectable() export class DevicesTableConfigResolver implements Resolve> { @@ -115,7 +116,8 @@ export class DevicesTableConfigResolver implements Resolve { + if (isDefinedAndNotNull(deviceCredential)) { + this.config.table.onEntityUpdated(device); + this.config.componentsData.deviceCredential = deviceCredential; + } }); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index cddc73415e..6c51d65424 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -814,8 +814,10 @@ "details": "Details", "copyId": "Copy device Id", "copyAccessToken": "Copy access token", + "copy-mqtt-authentication": "Copy MQTT authentication", "idCopiedMessage": "Device Id has been copied to clipboard", "accessTokenCopiedMessage": "Device access token has been copied to clipboard", + "mqtt-authentication-copied-message": "Device MQTT authentication has been copied to clipboard", "assignedToCustomer": "Assigned to customer", "unable-delete-device-alias-title": "Unable to delete device alias", "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", From e1b9124ee48de2d9ef3be9dcf9494ffa4af939fe Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 23 Nov 2020 12:17:20 +0200 Subject: [PATCH 68/84] Refactoring --- .../modules/home/pages/device/devices-table-config.resolver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 60dffcc5fe..1aefffa184 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -483,7 +483,6 @@ export class DevicesTableConfigResolver implements Resolve { if (isDefinedAndNotNull(deviceCredential)) { - this.config.table.onEntityUpdated(device); this.config.componentsData.deviceCredential = deviceCredential; } }); From 7f9bbb24047095d65ba769905d7e0debd397eebb Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 23 Nov 2020 12:19:34 +0200 Subject: [PATCH 69/84] Revert change after merge --- .../home/components/home-components.module.ts | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 27992d981a..113f4ae4bb 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -81,35 +81,35 @@ import { FilterSelectComponent } from '@home/components/filter/filter-select.com import { FiltersEditComponent } from '@home/components/filter/filters-edit.component'; import { FiltersEditPanelComponent } from '@home/components/filter/filters-edit-panel.component'; import { UserFilterDialogComponent } from '@home/components/filter/user-filter-dialog.component'; -import { FilterUserInfoComponent } from './filter/filter-user-info.component'; -import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.component'; -import { FilterPredicateValueComponent } from './filter/filter-predicate-value.component'; -import { TenantProfileAutocompleteComponent } from './profile/tenant-profile-autocomplete.component'; -import { TenantProfileComponent } from './profile/tenant-profile.component'; -import { TenantProfileDialogComponent } from './profile/tenant-profile-dialog.component'; -import { TenantProfileDataComponent } from './profile/tenant-profile-data.component'; -import { DefaultDeviceProfileConfigurationComponent } from './profile/device/default-device-profile-configuration.component'; -import { DeviceProfileConfigurationComponent } from './profile/device/device-profile-configuration.component'; -import { DeviceProfileComponent } from './profile/device-profile.component'; -import { DefaultDeviceProfileTransportConfigurationComponent } from './profile/device/default-device-profile-transport-configuration.component'; -import { DeviceProfileTransportConfigurationComponent } from './profile/device/device-profile-transport-configuration.component'; -import { DeviceProfileDialogComponent } from './profile/device-profile-dialog.component'; -import { DeviceProfileAutocompleteComponent } from './profile/device-profile-autocomplete.component'; -import { MqttDeviceProfileTransportConfigurationComponent } from './profile/device/mqtt-device-profile-transport-configuration.component'; -import { Lwm2mDeviceProfileTransportConfigurationComponent } from './profile/device/lwm2m-device-profile-transport-configuration.component'; -import { DeviceProfileAlarmsComponent } from './profile/alarm/device-profile-alarms.component'; -import { DeviceProfileAlarmComponent } from './profile/alarm/device-profile-alarm.component'; -import { CreateAlarmRulesComponent } from './profile/alarm/create-alarm-rules.component'; -import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component'; -import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component'; -import { FilterTextComponent } from './filter/filter-text.component'; -import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; -import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; -import { DeviceProfileProvisionConfigurationComponent } from './profile/device-profile-provision-configuration.component'; -import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; -import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; -import { DeviceCredentialsComponent } from './device/device-credentials.component'; -import { AlarmScheduleInfoComponent } from './profile/alarm/alarm-schedule-info.component'; +import { FilterUserInfoComponent } from '@home/components/filter/filter-user-info.component'; +import { FilterUserInfoDialogComponent } from '@home/components/filter/filter-user-info-dialog.component'; +import { FilterPredicateValueComponent } from '@home/components/filter/filter-predicate-value.component'; +import { TenantProfileAutocompleteComponent } from '@home/components/profile/tenant-profile-autocomplete.component'; +import { TenantProfileComponent } from '@home/components/profile/tenant-profile.component'; +import { TenantProfileDialogComponent } from '@home/components/profile/tenant-profile-dialog.component'; +import { TenantProfileDataComponent } from '@home/components/profile/tenant-profile-data.component'; +import { DefaultDeviceProfileConfigurationComponent } from '@home/components/profile/device/default-device-profile-configuration.component'; +import { DeviceProfileConfigurationComponent } from '@home/components/profile/device/device-profile-configuration.component'; +import { DeviceProfileComponent } from '@home/components/profile/device-profile.component'; +import { DefaultDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/default-device-profile-transport-configuration.component'; +import { DeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/device-profile-transport-configuration.component'; +import { DeviceProfileDialogComponent } from '@home/components/profile/device-profile-dialog.component'; +import { DeviceProfileAutocompleteComponent } from '@home/components/profile/device-profile-autocomplete.component'; +import { MqttDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/mqtt-device-profile-transport-configuration.component'; +import { Lwm2mDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/lwm2m-device-profile-transport-configuration.component'; +import { DeviceProfileAlarmsComponent } from '@home/components/profile/alarm/device-profile-alarms.component'; +import { DeviceProfileAlarmComponent } from '@home/components/profile/alarm/device-profile-alarm.component'; +import { CreateAlarmRulesComponent } from '@home/components/profile/alarm/create-alarm-rules.component'; +import { AlarmRuleComponent } from '@home/components/profile/alarm/alarm-rule.component'; +import { AlarmRuleConditionComponent } from '@home/components/profile/alarm/alarm-rule-condition.component'; +import { FilterTextComponent } from '@home/components/filter/filter-text.component'; +import { AddDeviceProfileDialogComponent } from '@home/components/profile/add-device-profile-dialog.component'; +import { RuleChainAutocompleteComponent } from '@home/components/rule-chain/rule-chain-autocomplete.component'; +import { DeviceProfileProvisionConfigurationComponent } from '@home/components/profile/device-profile-provision-configuration.component'; +import { AlarmScheduleComponent } from '@home/components/profile/alarm/alarm-schedule.component'; +import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component'; +import { DeviceCredentialsComponent } from '@home/components/device/device-credentials.component'; +import { AlarmScheduleInfoComponent } from '@home/components/profile/alarm/alarm-schedule-info.component'; import { AlarmScheduleDialogComponent } from '@home/components/profile/alarm/alarm-schedule-dialog.component'; import { EditAlarmDetailsDialogComponent } from '@home/components/profile/alarm/edit-alarm-details-dialog.component'; import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; From 5436316d1e075252bbf2c8136c4f4c0c94c4e1d5 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 23 Nov 2020 13:02:10 +0200 Subject: [PATCH 70/84] Fixed updated button --- .../modules/home/pages/device/devices-table-config.resolver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 1aefffa184..b04bf13d44 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -484,6 +484,7 @@ export class DevicesTableConfigResolver implements Resolve { if (isDefinedAndNotNull(deviceCredential)) { this.config.componentsData.deviceCredential = deviceCredential; + this.config.table.onEntityUpdated(device); } }); } From 70fdb9061888471fbea2c52df51a690040cf135e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Nov 2020 16:01:23 +0200 Subject: [PATCH 71/84] Remove api usage state for system sendEmail method --- .../thingsboard/server/service/mail/DefaultMailService.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 057eb098e4..a0ff937e27 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -160,10 +160,8 @@ public class DefaultMailService implements MailService { @Override public void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException { - if (apiUsageStateService.getApiUsageState(tenantId).isEmailSendEnabled()) { - sendMail(mailSender, mailFrom, email, subject, message); - apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); - } + sendMail(mailSender, mailFrom, email, subject, message); + apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); } @Override From b681abfe42c46c88afd3274a4ad4dcdadd8d8d44 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Nov 2020 16:02:40 +0200 Subject: [PATCH 72/84] Remove api usage state for system sendEmail method --- .../org/thingsboard/server/service/mail/DefaultMailService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index a0ff937e27..37910bddd1 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -161,7 +161,6 @@ public class DefaultMailService implements MailService { @Override public void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException { sendMail(mailSender, mailFrom, email, subject, message); - apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); } @Override From 4f0253a33a4ca37c673f3550b5d0d6903291b789 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 23 Nov 2020 16:24:07 +0200 Subject: [PATCH 73/84] Ack messages that we ignore --- .../transport/mqtt/MqttTransportHandler.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index a8bfc3d938..cdd5f1b3ca 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -232,7 +232,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (topicName.startsWith(MqttTopics.BASE_GATEWAY_API_TOPIC)) { if (gatewaySessionHandler != null) { - handleGatewayPublishMsg(topicName, msgId, mqttMsg); + handleGatewayPublishMsg(ctx, topicName, msgId, mqttMsg); transportService.reportActivity(deviceSessionCtx.getSessionInfo()); } } else { @@ -240,7 +240,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private void handleGatewayPublishMsg(String topicName, int msgId, MqttPublishMessage mqttMsg) { + private void handleGatewayPublishMsg(ChannelHandlerContext ctx, String topicName, int msgId, MqttPublishMessage mqttMsg) { try { switch (topicName) { case MqttTopics.GATEWAY_TELEMETRY_TOPIC: @@ -264,6 +264,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case MqttTopics.GATEWAY_DISCONNECT_TOPIC: gatewaySessionHandler.onDeviceDisconnect(mqttMsg); break; + default: + ack(ctx, msgId); } } catch (RuntimeException | AdaptorException e) { log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); @@ -293,6 +295,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); } else { transportService.reportActivity(deviceSessionCtx.getSessionInfo()); + ack(ctx, msgId); } } catch (AdaptorException e) { log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); @@ -301,15 +304,18 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } + private void ack(ChannelHandlerContext ctx, int msgId) { + if (msgId > 0) { + ctx.writeAndFlush(createMqttPubAckMsg(msgId)); + } + } private TransportServiceCallback getPubAckCallback(final ChannelHandlerContext ctx, final int msgId, final T msg) { return new TransportServiceCallback() { @Override public void onSuccess(Void dummy) { log.trace("[{}] Published msg: {}", sessionId, msg); - if (msgId > 0) { - ctx.writeAndFlush(createMqttPubAckMsg(msgId)); - } + ack(ctx, msgId); } @Override @@ -334,9 +340,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) { log.trace("[{}] Published msg: {}", sessionId, msg); - if (msgId > 0) { - ctx.writeAndFlush(createMqttPubAckMsg(msgId)); - } + ack(ctx, msgId); try { if (deviceSessionCtx.getProvisionPayloadType().equals(TransportPayloadType.JSON)) { deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); From d6c6a2e343c9e9116354afa47ae7fa744b1b4081 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Nov 2020 18:13:25 +0200 Subject: [PATCH 74/84] Add missing columns of api_usage_state table to DB upgrade script. --- .../server/service/install/SqlDatabaseUpgradeService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 19727684a3..20c00dcb09 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -367,6 +367,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService " db_storage varchar(32)," + " re_exec varchar(32)," + " js_exec varchar(32)," + + " email_exec varchar(32)," + + " sms_exec varchar(32)," + " CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)\n" + ");"); } catch (Exception e) { From 04bb1e5f4f396efb62985bb274d7577620d82d4c Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Nov 2020 18:28:02 +0200 Subject: [PATCH 75/84] Add missing cases for email and sms api limits --- .../service/apiusage/TenantApiUsageState.java | 10 ++++++++++ .../server/service/mail/DefaultMailService.java | 14 ++++++++++++++ .../dao/usagerecord/ApiUsageStateServiceImpl.java | 4 ++++ 3 files changed, 28 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index a43afcd2c5..fd4ef96122 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -125,6 +125,10 @@ public class TenantApiUsageState { return apiUsageState.getDbStorageState(); case JS: return apiUsageState.getJsExecState(); + case EMAIL: + return apiUsageState.getEmailExecState(); + case SMS: + return apiUsageState.getSmsExecState(); default: return ApiUsageStateValue.ENABLED; } @@ -145,6 +149,12 @@ public class TenantApiUsageState { case JS: apiUsageState.setJsExecState(value); break; + case EMAIL: + apiUsageState.setEmailExecState(value); + break; + case SMS: + apiUsageState.setSmsExecState(value); + break; } return !currentValue.equals(value); } diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 37910bddd1..7865321e67 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -304,6 +304,9 @@ public class DefaultMailService implements MailService { return "invoke"; case RE: return "process"; + case EMAIL: + case SMS: + return "send"; default: throw new RuntimeException("Not implemented!"); } @@ -319,6 +322,9 @@ public class DefaultMailService implements MailService { return "invoked"; case RE: return "processed"; + case EMAIL: + case SMS: + return "sent"; default: throw new RuntimeException("Not implemented!"); } @@ -337,6 +343,10 @@ public class DefaultMailService implements MailService { return valueInM + " out of " + thresholdInM + " allowed JavaScript functions"; case RE_EXEC_COUNT: return valueInM + " out of " + thresholdInM + " allowed Rule Engine messages"; + case EMAIL_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed Email messages"; + case SMS_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed SMS messages"; default: throw new RuntimeException("Not implemented!"); } @@ -353,6 +363,10 @@ public class DefaultMailService implements MailService { return "JavaScript functions " + getValueAsString(value) + " times"; case RE_EXEC_COUNT: return getValueAsString(value) + " Rule Engine messages"; + case EMAIL_EXEC_COUNT: + return getValueAsString(value) + " Email messages"; + case SMS_EXEC_COUNT: + return getValueAsString(value) + " SMS messages"; default: throw new RuntimeException("Not implemented!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 5fc1c293af..5677b0d73b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -96,6 +96,10 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), + new StringDataEntry(ApiFeature.EMAIL.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), + new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); List profileThresholds = new ArrayList<>(); From 241a45cce41ec5d4cc22c6af21250e8cb15b5baf Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 23 Nov 2020 19:22:28 +0200 Subject: [PATCH 76/84] Fix sort by originator type --- .../server/dao/sql/query/DefaultAlarmQueryRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index e9a5372894..c1a419381a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -72,8 +72,8 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { alarmFieldColumnMap.put("status", ModelConstants.ALARM_STATUS_PROPERTY); alarmFieldColumnMap.put("type", ModelConstants.ALARM_TYPE_PROPERTY); alarmFieldColumnMap.put("severity", ModelConstants.ALARM_SEVERITY_PROPERTY); - alarmFieldColumnMap.put("originator_id", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY); - alarmFieldColumnMap.put("originator_type", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY); + alarmFieldColumnMap.put("originatorId", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY); + alarmFieldColumnMap.put("originatorType", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY); alarmFieldColumnMap.put("originator", "originator_name"); } From aa816d61ab93b06305b28dba421ff73a4ee8c61b Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Fri, 20 Nov 2020 18:27:06 +0200 Subject: [PATCH 77/84] Handle device update or delete event in device's session --- .../server/controller/DeviceController.java | 4 ++++ .../queue/DefaultTbClusterService.java | 12 ++++++++++- .../service/queue/TbClusterService.java | 5 +++++ .../common/msg/session/SessionContext.java | 3 +++ .../common/transport/SessionMsgListener.java | 3 +++ .../service/DefaultTransportService.java | 20 +++++++++++++++++++ .../session/DeviceAwareSessionContext.java | 7 ++++++- 7 files changed, 52 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 03ccc9fb93..da5d705ef8 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -118,6 +118,7 @@ public class DeviceController extends BaseController { Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); + tbClusterService.onDeviceChange(savedDevice, null); tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(), savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null); tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(), @@ -150,6 +151,9 @@ public class DeviceController extends BaseController { Device device = checkDeviceId(deviceId, Operation.DELETE); deviceService.deleteDevice(getCurrentUser().getTenantId(), deviceId); + tbClusterService.onDeviceDeleted(device, null); + tbClusterService.onEntityStateChange(device.getTenantId(), deviceId, ComponentLifecycleEvent.DELETED); + logEntityAction(deviceId, device, device.getCustomerId(), ActionType.DELETED, null, strDeviceId); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index edd53c6727..a8a29f09e0 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -22,6 +22,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; @@ -32,7 +33,6 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; @@ -237,6 +237,16 @@ public class DefaultTbClusterService implements TbClusterService { onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); } + @Override + public void onDeviceChange(Device entity, TbQueueCallback callback) { + onEntityChange(entity.getTenantId(), entity.getId(), entity, callback); + } + + @Override + public void onDeviceDeleted(Device entity, TbQueueCallback callback) { + onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); + } + public void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName(); log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java index b36af16716..1547a5e3f0 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.queue; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; @@ -66,4 +67,8 @@ public interface TbClusterService { void onTenantDelete(Tenant tenant, TbQueueCallback callback); void onApiStateChange(ApiUsageState apiUsageState, TbQueueCallback callback); + + void onDeviceChange(Device device, TbQueueCallback callback); + + void onDeviceDeleted(Device device, TbQueueCallback callback); } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java index d124fd48b0..a8500b8e09 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.msg.session; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import java.util.UUID; @@ -26,4 +27,6 @@ public interface SessionContext { int nextMsgId(); void onProfileUpdate(DeviceProfile deviceProfile); + + void onDeviceUpdate(Device device); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java index ccd63ca430..c7f1a254f7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.transport; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; @@ -40,4 +41,6 @@ public interface SessionMsgListener { default void onProfileUpdate(DeviceProfile deviceProfile) { } + default void onDeviceUpdate(Device device) { + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 1afcfb5ebf..5bef9ddeb5 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -27,6 +27,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; @@ -643,6 +644,9 @@ public class DefaultTransportService implements TransportService { rateLimitService.update(apiUsageState.getTenantId(), apiUsageState.isTransportEnabled()); //TODO: if transport is disabled, we should close all sessions and not to check credentials. } + } else if (EntityType.DEVICE.equals(entityType)) { + Optional deviceOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); + deviceOpt.ifPresent(this::onDeviceUpdate); } } else if (toSessionMsg.hasEntityDeleteMsg()) { TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); @@ -675,6 +679,22 @@ public class DefaultTransportService implements TransportService { }); } + private void onDeviceUpdate(Device device) { + long deviceIdMSB = device.getId().getId().getMostSignificantBits(); + long deviceIdLSB = device.getId().getId().getLeastSignificantBits(); + sessions.forEach((id, md) -> { + if (md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB + && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB) { + long deviceProfileIdMSB = device.getDeviceProfileId().getId().getMostSignificantBits(); + long deviceProfileIdLSB = device.getDeviceProfileId().getId().getLeastSignificantBits(); + if (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB + && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB) { + transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(device)); + } + } + }); + } + protected UUID toSessionId(TransportProtos.SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index 2f7f2d69e6..46c7ca67c7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -18,12 +18,12 @@ package org.thingsboard.server.common.transport.session; import lombok.Data; import lombok.Getter; import lombok.Setter; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.msg.session.SessionContext; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import java.util.UUID; @@ -65,6 +65,11 @@ public abstract class DeviceAwareSessionContext implements SessionContext { this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo).setDeviceType(deviceProfile.getName()).build(); } + @Override + public void onDeviceUpdate(Device device) { + this.deviceInfo.setDeviceProfileId(device.getDeviceProfileId()); + } + public boolean isConnected() { return connected; } From 379ba80b2ca9513917249dd436bcb06d8011cea3 Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Fri, 20 Nov 2020 20:05:52 +0200 Subject: [PATCH 78/84] Get device profile id before the foreach --- .../service/DefaultTransportService.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 5bef9ddeb5..c9336862a2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -682,15 +682,14 @@ public class DefaultTransportService implements TransportService { private void onDeviceUpdate(Device device) { long deviceIdMSB = device.getId().getId().getMostSignificantBits(); long deviceIdLSB = device.getId().getId().getLeastSignificantBits(); + long deviceProfileIdMSB = device.getDeviceProfileId().getId().getMostSignificantBits(); + long deviceProfileIdLSB = device.getDeviceProfileId().getId().getLeastSignificantBits(); sessions.forEach((id, md) -> { - if (md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB - && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB) { - long deviceProfileIdMSB = device.getDeviceProfileId().getId().getMostSignificantBits(); - long deviceProfileIdLSB = device.getDeviceProfileId().getId().getLeastSignificantBits(); - if (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB - && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB) { - transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(device)); - } + if ((md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB + && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB) + && (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB + && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB)) { + transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(device)); } }); } From c36fc46e2ce9fa18fb078eaa1e7565f8237f25d7 Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Sat, 21 Nov 2020 16:01:04 +0200 Subject: [PATCH 79/84] Override default implementation onDeviceUpdate() --- .../server/transport/mqtt/MqttTransportHandler.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index cdd5f1b3ca..eadfab729e 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -40,6 +40,7 @@ import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -703,4 +704,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement public void onProfileUpdate(DeviceProfile deviceProfile) { deviceSessionCtx.onProfileUpdate(deviceProfile); } + + @Override + public void onDeviceUpdate(Device device) { + deviceSessionCtx.onDeviceUpdate(device); + } } From 52d7d790f6f5c2b2e54af45dd99190157ef0a443 Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Sat, 21 Nov 2020 17:18:19 +0200 Subject: [PATCH 80/84] Update device and session infos in context --- .../server/common/msg/session/SessionContext.java | 2 +- .../server/transport/mqtt/MqttTransportHandler.java | 4 ++-- .../transport/mqtt/session/DeviceSessionCtx.java | 5 +++++ .../server/common/transport/SessionMsgListener.java | 2 +- .../transport/service/DefaultTransportService.java | 2 +- .../transport/session/DeviceAwareSessionContext.java | 10 ++++++++-- 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java index a8500b8e09..47f2af1c84 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java @@ -28,5 +28,5 @@ public interface SessionContext { void onProfileUpdate(DeviceProfile deviceProfile); - void onDeviceUpdate(Device device); + void onDeviceProfileUpdate(Device device); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index eadfab729e..335158af32 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -706,7 +706,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } @Override - public void onDeviceUpdate(Device device) { - deviceSessionCtx.onDeviceUpdate(device); + public void onDeviceProfileUpdate(Device device) { + deviceSessionCtx.onDeviceProfileUpdate(device); } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index b45dc5add5..4451045899 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -20,6 +20,7 @@ import io.netty.channel.ChannelHandlerContext; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -113,6 +114,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { updateTopicFilters(deviceProfile); } + @Override + public void onDeviceProfileUpdate(Device device) { + super.onDeviceProfileUpdate(device); + } private void updateTopicFilters(DeviceProfile deviceProfile) { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java index c7f1a254f7..28a9d6d727 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java @@ -41,6 +41,6 @@ public interface SessionMsgListener { default void onProfileUpdate(DeviceProfile deviceProfile) { } - default void onDeviceUpdate(Device device) { + default void onDeviceProfileUpdate(Device device) { } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index c9336862a2..68040b3d67 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -689,7 +689,7 @@ public class DefaultTransportService implements TransportService { && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB) && (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB)) { - transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(device)); + transportCallbackExecutor.submit(() -> md.getListener().onDeviceProfileUpdate(device)); } }); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index 46c7ca67c7..73b6694666 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.transport.session; import lombok.Data; import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceId; @@ -65,9 +66,14 @@ public abstract class DeviceAwareSessionContext implements SessionContext { this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo).setDeviceType(deviceProfile.getName()).build(); } - @Override - public void onDeviceUpdate(Device device) { + public void onDeviceProfileUpdate(Device device) { this.deviceInfo.setDeviceProfileId(device.getDeviceProfileId()); + this.deviceInfo.setDeviceType(device.getType()); + this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo) + .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) + .setDeviceType(device.getType()) + .build(); } public boolean isConnected() { From 7834b9221771a7cca346e5870d800f817604f27d Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Sat, 21 Nov 2020 17:19:56 +0200 Subject: [PATCH 81/84] Remove unused import --- .../common/transport/session/DeviceAwareSessionContext.java | 1 - 1 file changed, 1 deletion(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index 73b6694666..db798e5b7f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -18,7 +18,6 @@ package org.thingsboard.server.common.transport.session; import lombok.Data; import lombok.Getter; import lombok.Setter; -import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceId; From 63406b010f4c462fa0ef333cf76e3b5de9139fc2 Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Mon, 23 Nov 2020 13:08:45 +0200 Subject: [PATCH 82/84] Use the same device session info in transport service and in context --- .../transport/mqtt/MqttTransportHandler.java | 4 ++-- .../mqtt/session/DeviceSessionCtx.java | 6 ------ .../common/transport/SessionMsgListener.java | 3 ++- .../common/transport/TransportAdaptor.java | 2 +- .../service/DefaultTransportService.java | 20 ++++++++++++++++--- .../session/DeviceAwareSessionContext.java | 9 ++------- .../transport}/session/SessionContext.java | 5 +++-- 7 files changed, 27 insertions(+), 22 deletions(-) rename common/{message/src/main/java/org/thingsboard/server/common/msg => transport/transport-api/src/main/java/org/thingsboard/server/common/transport}/session/SessionContext.java (80%) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 335158af32..091c656462 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -706,7 +706,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } @Override - public void onDeviceProfileUpdate(Device device) { - deviceSessionCtx.onDeviceProfileUpdate(device); + public void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo) { + deviceSessionCtx.onDeviceProfileUpdate(device, sessionInfo); } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 4451045899..c2c2d258fc 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -20,7 +20,6 @@ import io.netty.channel.ChannelHandlerContext; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -114,11 +113,6 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { updateTopicFilters(deviceProfile); } - @Override - public void onDeviceProfileUpdate(Device device) { - super.onDeviceProfileUpdate(device); - } - private void updateTopicFilters(DeviceProfile deviceProfile) { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); if (transportConfiguration.getType().equals(DeviceTransportType.MQTT) && diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java index 28a9d6d727..85c162930d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java @@ -17,6 +17,7 @@ package org.thingsboard.server.common.transport; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; @@ -41,6 +42,6 @@ public interface SessionMsgListener { default void onProfileUpdate(DeviceProfile deviceProfile) { } - default void onDeviceProfileUpdate(Device device) { + default void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo) { } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java index de37856985..5ffc5abc7d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.transport; -import org.thingsboard.server.common.msg.session.SessionContext; +import org.thingsboard.server.common.transport.session.SessionContext; public interface TransportAdaptor { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 68040b3d67..70a4817079 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -684,14 +684,28 @@ public class DefaultTransportService implements TransportService { long deviceIdLSB = device.getId().getId().getLeastSignificantBits(); long deviceProfileIdMSB = device.getDeviceProfileId().getId().getMostSignificantBits(); long deviceProfileIdLSB = device.getDeviceProfileId().getId().getLeastSignificantBits(); - sessions.forEach((id, md) -> { + for (Map.Entry entry : sessions.entrySet()) { + SessionMetaData md = entry.getValue(); if ((md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB) && (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB)) { - transportCallbackExecutor.submit(() -> md.getListener().onDeviceProfileUpdate(device)); + updateSessionMetadata(device, entry, md); } - }); + } + } + + private void updateSessionMetadata(Device device, Map.Entry entry, SessionMetaData md) { + TransportProtos.SessionInfoProto newSessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .mergeFrom(md.getSessionInfo()) + .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) + .setDeviceType(device.getType()) + .build(); + SessionMetaData newSessionMetaData = new SessionMetaData(newSessionInfo, md.getSessionType(), md.getListener()); + entry.setValue(newSessionMetaData); + transportCallbackExecutor.submit(() -> newSessionMetaData.getListener().onDeviceProfileUpdate(device, + newSessionMetaData.getSessionInfo())); } protected UUID toSessionId(TransportProtos.SessionInfoProto sessionInfo) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index db798e5b7f..5d33dccff6 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -21,7 +21,6 @@ import lombok.Setter; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.msg.session.SessionContext; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.gen.transport.TransportProtos; @@ -65,14 +64,10 @@ public abstract class DeviceAwareSessionContext implements SessionContext { this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo).setDeviceType(deviceProfile.getName()).build(); } - public void onDeviceProfileUpdate(Device device) { + public void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo) { this.deviceInfo.setDeviceProfileId(device.getDeviceProfileId()); this.deviceInfo.setDeviceType(device.getType()); - this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo) - .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) - .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) - .setDeviceType(device.getType()) - .build(); + this.sessionInfo = sessionInfo; } public boolean isConnected() { diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java similarity index 80% rename from common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java index 47f2af1c84..9b5870cace 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg.session; +package org.thingsboard.server.common.transport.session; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.gen.transport.TransportProtos; import java.util.UUID; @@ -28,5 +29,5 @@ public interface SessionContext { void onProfileUpdate(DeviceProfile deviceProfile); - void onDeviceProfileUpdate(Device device); + void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo); } From 97ef261ad2609f6364d014683a4075b1d8dd336b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 24 Nov 2020 10:57:59 +0200 Subject: [PATCH 83/84] UI: fixed show alarm fields in data keys widget with unresolved alias --- .../components/widget/widget-config.component.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index ce37c46326..ba8cdaf5d2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -813,7 +813,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont return dataKeys; } ), - catchError(val => of([])) + catchError(() => of([])) )); } return forkJoin(fetchEntityTasks).pipe( @@ -825,11 +825,23 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont return result; } )); + } else if (dataKeyTypes.includes(DataKeyType.alarm)) { + return this.entityService.getEntityKeys(null, query, DataKeyType.alarm).pipe( + map((keys) => { + const dataKeys: Array = []; + for (const key of keys) { + dataKeys.push({name: key, type: DataKeyType.alarm}); + } + return dataKeys; + } + ), + catchError(() => of([])) + ); } else { return of([]); } }), - catchError(val => of([] as Array)) + catchError(() => of([] as Array)) ); } From 529608e60fffd83484685cc18b1e641701d89b99 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 24 Nov 2020 12:11:29 +0200 Subject: [PATCH 84/84] Improved transport cache invalidation for device updates --- .../transport/mqtt/MqttTransportHandler.java | 9 ++-- .../mqtt/session/DeviceSessionCtx.java | 5 +- .../common/transport/SessionMsgListener.java | 6 ++- .../service/DefaultTransportService.java | 51 +++++++++++-------- .../transport/service/SessionMetaData.java | 19 ++++--- .../session/DeviceAwareSessionContext.java | 12 +++-- .../transport/session/SessionContext.java | 5 +- 7 files changed, 61 insertions(+), 46 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 091c656462..cabc61cab6 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -72,6 +72,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -701,12 +702,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } @Override - public void onProfileUpdate(DeviceProfile deviceProfile) { - deviceSessionCtx.onProfileUpdate(deviceProfile); + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { + deviceSessionCtx.onDeviceProfileUpdate(sessionInfo, deviceProfile); } @Override - public void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo) { - deviceSessionCtx.onDeviceProfileUpdate(device, sessionInfo); + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { + deviceSessionCtx.onDeviceUpdate(sessionInfo, device, deviceProfileOpt); } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index c2c2d258fc..487aa8a207 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportC 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.TransportProtos; import org.thingsboard.server.transport.mqtt.MqttTransportContext; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter; @@ -108,8 +109,8 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { } @Override - public void onProfileUpdate(DeviceProfile deviceProfile) { - super.onProfileUpdate(deviceProfile); + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { + super.onDeviceProfileUpdate(sessionInfo, deviceProfile); updateTopicFilters(deviceProfile); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java index 85c162930d..9eb0fbcd6c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponse import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; +import java.util.Optional; + /** * Created by ashvayka on 04.10.18. */ @@ -39,9 +41,9 @@ public interface SessionMsgListener { void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse); - default void onProfileUpdate(DeviceProfile deviceProfile) { + default void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) { } - default void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo) { + default void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 70a4817079..3e619f8d6a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -672,9 +672,17 @@ public class DefaultTransportService implements TransportService { long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits(); long deviceProfileIdLSB = deviceProfile.getId().getId().getLeastSignificantBits(); sessions.forEach((id, md) -> { + //TODO: if transport types are different - we should close the session. if (md.getSessionInfo().getDeviceProfileIdMSB() == deviceProfileIdMSB && md.getSessionInfo().getDeviceProfileIdLSB() == deviceProfileIdLSB) { - transportCallbackExecutor.submit(() -> md.getListener().onProfileUpdate(deviceProfile)); + TransportProtos.SessionInfoProto newSessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .mergeFrom(md.getSessionInfo()) + .setDeviceProfileIdMSB(deviceProfileIdMSB) + .setDeviceProfileIdLSB(deviceProfileIdLSB) + .setDeviceType(deviceProfile.getName()) + .build(); + md.setSessionInfo(newSessionInfo); + transportCallbackExecutor.submit(() -> md.getListener().onDeviceProfileUpdate(newSessionInfo, deviceProfile)); } }); } @@ -684,28 +692,27 @@ public class DefaultTransportService implements TransportService { long deviceIdLSB = device.getId().getId().getLeastSignificantBits(); long deviceProfileIdMSB = device.getDeviceProfileId().getId().getMostSignificantBits(); long deviceProfileIdLSB = device.getDeviceProfileId().getId().getLeastSignificantBits(); - for (Map.Entry entry : sessions.entrySet()) { - SessionMetaData md = entry.getValue(); - if ((md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB - && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB) - && (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB - && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB)) { - updateSessionMetadata(device, entry, md); + sessions.forEach((id, md) -> { + if ((md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB)) { + DeviceProfile newDeviceProfile; + if (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB + && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB) { + //TODO: if transport types are different - we should close the session. + newDeviceProfile = deviceProfileCache.get(new DeviceProfileId(new UUID(deviceProfileIdMSB, deviceProfileIdLSB))); + } else { + newDeviceProfile = null; + } + TransportProtos.SessionInfoProto newSessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .mergeFrom(md.getSessionInfo()) + .setDeviceProfileIdMSB(deviceProfileIdMSB) + .setDeviceProfileIdLSB(deviceProfileIdLSB) + .setDeviceName(device.getName()) + .setDeviceType(device.getType()) + .build(); + md.setSessionInfo(newSessionInfo); + transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(newSessionInfo, device, Optional.ofNullable(newDeviceProfile))); } - } - } - - private void updateSessionMetadata(Device device, Map.Entry entry, SessionMetaData md) { - TransportProtos.SessionInfoProto newSessionInfo = TransportProtos.SessionInfoProto.newBuilder() - .mergeFrom(md.getSessionInfo()) - .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) - .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) - .setDeviceType(device.getType()) - .build(); - SessionMetaData newSessionMetaData = new SessionMetaData(newSessionInfo, md.getSessionType(), md.getListener()); - entry.setValue(newSessionMetaData); - transportCallbackExecutor.submit(() -> newSessionMetaData.getListener().onDeviceProfileUpdate(device, - newSessionMetaData.getSessionInfo())); + }); } protected UUID toSessionId(TransportProtos.SessionInfoProto sessionInfo) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java index 5181d8e781..ac293307a9 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java @@ -27,22 +27,17 @@ import java.util.concurrent.ScheduledFuture; @Data class SessionMetaData { - private final TransportProtos.SessionInfoProto sessionInfo; + private volatile TransportProtos.SessionInfoProto sessionInfo; private final TransportProtos.SessionType sessionType; private final SessionMsgListener listener; - private ScheduledFuture scheduledFuture; - + private volatile ScheduledFuture scheduledFuture; private volatile long lastActivityTime; private volatile long lastReportedActivityTime; private volatile boolean subscribedToAttributes; private volatile boolean subscribedToRPC; - SessionMetaData( - TransportProtos.SessionInfoProto sessionInfo, - TransportProtos.SessionType sessionType, - SessionMsgListener listener - ) { + SessionMetaData(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionType sessionType, SessionMsgListener listener) { this.sessionInfo = sessionInfo; this.sessionType = sessionType; this.listener = listener; @@ -54,11 +49,15 @@ class SessionMetaData { this.lastActivityTime = System.currentTimeMillis(); } - void setScheduledFuture(ScheduledFuture scheduledFuture) { this.scheduledFuture = scheduledFuture; } + void setScheduledFuture(ScheduledFuture scheduledFuture) { + this.scheduledFuture = scheduledFuture; + } public ScheduledFuture getScheduledFuture() { return scheduledFuture; } - public boolean hasScheduledFuture() { return null != this.scheduledFuture; } + public boolean hasScheduledFuture() { + return null != this.scheduledFuture; + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index 5d33dccff6..168a20ba67 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.gen.transport.TransportProtos; +import java.util.Optional; import java.util.UUID; /** @@ -58,16 +59,19 @@ public abstract class DeviceAwareSessionContext implements SessionContext { } @Override - public void onProfileUpdate(DeviceProfile deviceProfile) { + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { + this.sessionInfo = sessionInfo; this.deviceProfile = deviceProfile; this.deviceInfo.setDeviceType(deviceProfile.getName()); - this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo).setDeviceType(deviceProfile.getName()).build(); + } - public void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo) { + @Override + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { + this.sessionInfo = sessionInfo; this.deviceInfo.setDeviceProfileId(device.getDeviceProfileId()); this.deviceInfo.setDeviceType(device.getType()); - this.sessionInfo = sessionInfo; + deviceProfileOpt.ifPresent(profile -> this.deviceProfile = profile); } public boolean isConnected() { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java index 9b5870cace..fc82974764 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java @@ -19,6 +19,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.gen.transport.TransportProtos; +import java.util.Optional; import java.util.UUID; public interface SessionContext { @@ -27,7 +28,7 @@ public interface SessionContext { int nextMsgId(); - void onProfileUpdate(DeviceProfile deviceProfile); + void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile); - void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo); + void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt); }
-
- - -
- - - - - - - - <#list apiUsageStateMailMessages as msg> - - - - - - - - - -
-

Thingsboard Api Usage State for tenant has been updated

-
- Thingsboard Usage state ${apiFeature} was updated to status WARNING. -
- ${msg.key.apiLimitKey} = ${msg.threshold} -
- ${msg.key.apiCountKey} = ${msg.value} -
- — The Thingsboard -
- -
+ + + + + + + + +
+ + + + + + + + + + + + + + + +
+

Your ThingsBoard account feature may be disabled

+
+ Your ${apiFeature} limit (${apiLimitValueLabel}) is almost exhausted.
ThingsBoard has already ${apiValueLabel}.
${apiFeature} will be disabled for your account when limit will be reached. +
Please contact your system administrator to resolve the issue. +
— The ThingsBoard +
+
+ + + + - +
This email was sent to ${targetEmail} by ThingsBoard.
From 031f579bd6cd4986dcd9bddd42771b2368842dd7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 20 Nov 2020 15:44:42 +0200 Subject: [PATCH 60/84] Email notifications for API state --- .../service/mail/DefaultMailService.java | 49 ++++++++++++++++--- .../resources/templates/state.disabled.ftl | 3 +- .../resources/templates/state.enabled.ftl | 2 +- .../resources/templates/state.warning.ftl | 4 +- .../server/common/data/ApiFeature.java | 2 +- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 3045fe0cce..a26363c5ff 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -20,6 +20,7 @@ import freemarker.template.Configuration; import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.core.NestedRuntimeException; @@ -55,6 +56,8 @@ public class DefaultMailService implements MailService { public static final String MAIL_PROP = "mail."; public static final String TARGET_EMAIL = "targetEmail"; public static final String UTF_8 = "UTF-8"; + public static final int _10K = 10000; + public static final int _1M = 1000000; @Autowired private MessageSource messages; @@ -266,8 +269,7 @@ public class DefaultMailService implements MailService { message = mergeTemplateIntoString("state.enabled.ftl", model); break; case WARNING: - model.put("apiLimitValueLabel", toDisabledValueLabel(msg.getKey(), msg.getThreshold())); - model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getValue())); + model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(msg.getKey(), msg.getValue(), msg.getThreshold())); message = mergeTemplateIntoString("state.warning.ftl", model); break; case DISABLED: @@ -285,8 +287,9 @@ public class DefaultMailService implements MailService { case TRANSPORT: return "receive"; case JS: - case RE: return "invoke"; + case RE: + return "process"; default: throw new RuntimeException("Not implemented!"); } @@ -299,8 +302,27 @@ public class DefaultMailService implements MailService { case TRANSPORT: return "received"; case JS: - case RE: return "invoked"; + case RE: + return "processed"; + default: + throw new RuntimeException("Not implemented!"); + } + } + + private String toWarningValueLabel(ApiUsageRecordKey key, long value, long threshold) { + String valueInM = getValueAsString(value); + String thresholdInM = getValueAsString(threshold); + switch (key) { + case STORAGE_DP_COUNT: + case TRANSPORT_DP_COUNT: + return valueInM + " out of " + thresholdInM + " allowed data points"; + case TRANSPORT_MSG_COUNT: + return valueInM + " out of " + thresholdInM + " allowed messages"; + case JS_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed JavaScript functions"; + case RE_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed Rule Engine messages"; default: throw new RuntimeException("Not implemented!"); } @@ -310,18 +332,29 @@ public class DefaultMailService implements MailService { switch (key) { case STORAGE_DP_COUNT: case TRANSPORT_DP_COUNT: - return (value / 1000000) + "M data points"; + return getValueAsString(value) + " data points"; case TRANSPORT_MSG_COUNT: - return (value / 1000000) + "M messages"; + return getValueAsString(value) + " messages"; case JS_EXEC_COUNT: - return (value / 1000000) + "M JavaScript functions"; + return "JavaScript functions " + getValueAsString(value) + " times"; case RE_EXEC_COUNT: - return (value / 1000000) + "M Rule Engine nodes"; + return getValueAsString(value) + " Rule Engine messages"; default: throw new RuntimeException("Not implemented!"); } } + @NotNull + private String getValueAsString(long value) { + if (value > _1M && value % _1M < _10K) { + return value / _1M + "M"; + } else if (value > _10K) { + return String.format("%.2fM", ((double) value) / 1000000); + } else { + return value + ""; + } + } + private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, String subject, String message) throws ThingsboardException { diff --git a/application/src/main/resources/templates/state.disabled.ftl b/application/src/main/resources/templates/state.disabled.ftl index 33a368cceb..f14bdd2235 100644 --- a/application/src/main/resources/templates/state.disabled.ftl +++ b/application/src/main/resources/templates/state.disabled.ftl @@ -123,8 +123,7 @@
We have disabled the ${apiFeature} for your account because ThingsBoard has already  ${apiLimitValueLabel} - . + valign="top">We have disabled the ${apiFeature} for your account because ThingsBoard has already ${apiLimitValueLabel}.
We have enabled the ${apiFeature} for your account and ThingsBoard already able to ${apiLabel} messages. + valign="top">We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages.
-

Your ThingsBoard account feature may be disabled

+

Your ThingsBoard account feature may be disabled soon

- Your ${apiFeature} limit (${apiLimitValueLabel}) is almost exhausted.
ThingsBoard has already ${apiValueLabel}.
${apiFeature} will be disabled for your account when limit will be reached. + ThingsBoard has already ${apiValueLabel}.
${apiFeature} will be disabled for your account once the limit will be reached.